diff --git a/CHANGELOG.md b/CHANGELOG.md
index ca053cd..f5261c0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,15 +5,62 @@ All notable changes to this project are documented here. The format follows
[Semantic Versioning](https://semver.org/spec/v2.0.0.html): only a major version changes the
public API, a minor version adds to it, and a patch version fixes it.
-## [Unreleased]
+## [1.1.0] - 2026-11-01
+
+Generated and tested against the contract of Kaiten 26.10.8,
+[`kaitencloud/kaiten@f685ceb8`](https://github.com/kaitencloud/kaiten/commit/f685ceb86bfeb5f6110663662fb955336ce1a1a4):
+use it with Kaiten 26.10.8 or later. Nothing 1.0.0 published was removed or renamed: methods
+only gained optional arguments.
### Added
+- `client.billing`, a namespace per billing resource: the organization's settings, capabilities,
+ health, provider sync and period close; `subscriptions` -- subscribe, change the provider and
+ terms, cancel, reactivate, schedule and drop a plan change, the upcoming invoice, an instance's
+ invoices; `invoices` -- list, export, void, recompose, mark paid, write off, release a hold,
+ retry a push, sync, and a line's usage reports; the accounting `handoff`, claimed and
+ acknowledged; `license_prices` and the invoice preview; `addons`, with their families, grants,
+ prices and the license families they fit; `instance_addons`; `vouchers`, their redemptions and
+ lookup, and `instance_vouchers`; `customers` -- a customer's billing, payment method and
+ provider portal; `publishable_keys`, `customer_sessions` and `checkout_sessions`.
+- `client.billing.public` and `client.billing.session`: what a publishable key (`pk_...`), an
+ anonymous checkout's token (`ksc_...`) and a customer session (`kst_...`) reach. A route under
+ `/public` takes one of them and refuses any other; the SDK refuses the mix-up before sending,
+ and sends a publishable key in `X-Kaiten-Publishable-Key`, never as a bearer token.
+- `instances.report_usage_detailed()`: a usage report under an idempotency key,
+ `transaction_id`, which Kaiten applies at most once. It returns a `UsageReportResult`: the
+ usage, whether the report was a replay, and whether its metadata was too large to store. A key
+ spent on another report raises `TransactionIdReusedError`.
+- The usage history: `instances.list_usage_reports()`, `instances.export_usage_reports()` and
+ `instances.export_organization_usage_reports()`.
+- Entitlement exceptions: `instances.list_entitlement_exceptions()`,
+ `instances.create_entitlement_exception()` and `instances.revoke_entitlement_exception()`.
+- `license_families.update()`: whether a family is public, its fallback family, and how its
+ trials end.
+- A license version's commercial terms on `licenses.create()` and `licenses.update()`:
+ `pricing_type`, `trial_period_days`, `requires_payment_method` and `self_serve_cta_url`. A
+ customer's `billing_email` on `customers.create()` and `customers.update()`.
+ `metadata_fields.list(include_archived=True)`.
+- `platform.billing.close_periods()`, the Platform API's eleventh operation.
+- Models for every new response, and for the 62 webhook events published since 1.0.0: all 117
+ are typed. An event carries the delivery's trace `headers`, and its `data` who wrote the change
+ (`via`, `checkout_session_id`, `customer_session_id`); that `data` is still an instance of the
+ class 1.0.0 typed it with.
- `feature_flags.targeting_context()`, `feature_flags.lint_rule()` and `feature_flags.test_rule()`:
the facts a targeting rule can read, the check the API runs before accepting one, and a dry run
of a rule against a customer or an instance.
- `feature_flags.update(..., slug=...)` renames a flag.
+### Changed
+
+- The models require what Kaiten 26.10.8 always sends and older deployments do not:
+ `EntitlementUsage.source`, `AuditTrail.actor`, `ConnectorSettings.configured`, the trial and
+ catalog settings of `LicenseFamilyView`, and the period rollover event's `reason`. From an older
+ Kaiten, those reads raise `APIResponseValidationError`, and that event `WebhookPayloadError`.
+- `ResetAnchor` gains `BILLING_PERIOD`, and `Scope` the `read:` and `write:` scopes of `addons`,
+ `billing`, `customer_sessions`, `profile`, `publishable_keys`, `users`, `voucher_redemptions` and
+ `vouchers`.
+
### Fixed
- `feature_flags.update` sends the flag's slug. Without it, Kaiten blanked the slug and the flag
@@ -66,4 +113,5 @@ The first release of the Kaiten Python SDK, generated and tested against the con
- Flag evaluation. Kaiten implements OFREP, so flags are evaluated with an OpenFeature SDK and
its generic OFREP provider (`openfeature-provider-ofrep`); the README shows how.
+[1.1.0]: https://github.com/kaitencloud/sdk-python/releases/tag/v1.1.0
[1.0.0]: https://github.com/kaitencloud/sdk-python/releases/tag/v1.0.0
diff --git a/README.md b/README.md
index 57b58b6..ef17b82 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@
# Kaiten Python SDK
**The official Python client for [Kaiten](https://kaiten.sh), the control plane of your SaaS:
-customers, instances, licenses, entitlements, usage metering, feature flags and releases.**
+customers, instances, licenses, entitlements, usage metering, billing, feature flags and releases.**
[](https://pypi.org/project/kaitencloud/)
[](https://pypi.org/project/kaitencloud/)
@@ -42,10 +42,10 @@ client.instances.report_usage("acme-production", "seats", 1)
problem: `code`, `detail`, `error_id`.
- **Both APIs.** The Core API your product talks to, and the Platform API that provisions
organizations and mints their tokens.
-- **Webhooks included.** Svix signatures verified, all 55 event types parsed into models.
+- **Webhooks included.** Svix signatures verified, all 117 event types parsed into models.
- **Flags through OpenFeature.** Kaiten speaks OFREP, so flags are defined here and
evaluated with the [OpenFeature SDK](#feature-flags) — a vendor-neutral API, not ours.
-- **Contract-tested.** All 111 operations the SDK wraps are exercised against the contract, and
+- **Contract-tested.** All 221 operations the SDK wraps are exercised against the contract, and
every request body is validated against the API's own schema.
## Contents
@@ -54,7 +54,7 @@ client.instances.report_usage("acme-production", "seats", 1)
- [Quick start](#quick-start)
- [Configuration](#configuration)
- [Guide](#guide)
- - [Catalog and licenses](#catalog-and-licenses) · [Customers and instances](#customers-and-instances) · [Usage metering](#usage-metering)
+ - [Catalog and licenses](#catalog-and-licenses) · [Customers and instances](#customers-and-instances) · [Usage metering](#usage-metering) · [Billing](#billing)
- [Feature flags](#feature-flags) · [Releases and deployment zones](#releases-and-deployment-zones) · [Webhooks](#webhooks)
- [The Platform API](#the-platform-api)
- [The playbook](#the-playbook)
@@ -131,6 +131,7 @@ Organization you: a SaaS vendor using Kaiten
├── License client.licenses the contract it runs under, granting values
│ client.entitlements ...of the entitlements in your catalog
├── Usage client.instances.report_usage() what it consumed
+ ├── Subscription client.billing.subscriptions how it is billed, invoice after invoice
└── Zone client.deployment_zones where it runs, and the release deployed there
client.releases → client.components
```
@@ -160,8 +161,15 @@ anything when one ends up in the wrong place.
| Organization token | `ksh_...` | `KaitenClient` |
| Identity-provider JWT | `eyJ...` | `KaitenClient` |
| Platform credential | `ksm_...` | `KaitenPlatformClient` |
+| Publishable key | `pk_...` | `KaitenClient`, for `client.billing.public` |
+| Customer session | `kst_...` | `KaitenClient`, for `client.billing.session` |
+| Anonymous checkout session | `ksc_...` | `KaitenClient`, to read or confirm that checkout |
| Webhook signing secret | `whsec_...` | `kaitencloud.webhooks` |
+The routes under `/public` — a pricing page's, a customer portal's — each take one of the three
+public credentials and refuse any other, so the SDK refuses the mix-up before sending. It sends a
+publishable key in `X-Kaiten-Publishable-Key`, as the API requires, never as a bearer token.
+
A credential that rotates can be supplied as a callable — it is read before every request, so
there is no client to rebuild:
@@ -264,6 +272,29 @@ except ThresholdExceededError as error:
`behavior="append"` (the default) folds the value into the total through the entitlement's
aggregation method; `behavior="set"` replaces the total, which is also how to correct it.
+A report retried after a lost response would count twice, so the SDK never retries one. To make
+resending safe — after a timeout, or from a queue that delivers at least once — report under
+the id of the business event it counts. Kaiten applies a report at most once per key, instance
+and entitlement within its idempotency window (35 days by default):
+
+```python
+result = client.instances.report_usage_detailed(
+ "acme-production", "tokens", 1200, transaction_id="llm-call-9f2c:tokens"
+)
+if result.replayed:
+ print("Counted already: nothing was added")
+```
+
+The same key with another value raises `TransactionIdReusedError`. Every accepted report is kept
+in the usage history, with the counter before and after it:
+
+```python
+for report in client.instances.list_usage_reports("acme-production", "tokens", limit=100):
+ print(report.report_seq, report.reported_at, report.delta, report.value_after)
+
+csv = client.instances.export_usage_reports("acme-production", "tokens", format="csv")
+```
+
`kaitencloud.usage` computes the same boundaries the server enforces, to render a meter or check
a report before sending it:
@@ -279,6 +310,55 @@ usage.remaining(included, used, grant.limit_cap_exceeded_overage_percent) # lef
usage.percentage_used(included, used) # passes 100 once the allowance is in use
```
+### Billing
+
+`client.billing` composes invoices from what you sell and what your customers used. Prices belong
+to license versions; a subscription pins an instance to one of them, and each period closes into
+an invoice. Most of it requires billing to be enabled for the organization:
+`client.billing.get_capabilities()` says whether it is, and why not.
+
+```python
+price = client.billing.license_prices.create(
+ "growth-v2",
+ billing_model="FLAT_FEE",
+ currency="EUR",
+ unit_amount_decimal="4900", # minor units: 49.00 EUR
+ billing_period="MONTHLY",
+ is_default=True,
+)
+
+client.billing.subscriptions.subscribe("acme-production", base_price_id=price.id)
+client.billing.instance_addons.attach("acme-production", "extra-seats-v1", quantity=2)
+client.billing.instance_vouchers.redeem("acme-production", "WELCOME-2026")
+```
+
+Each subscription says who issues and collects its invoices: `NOOP`, your organization itself, or
+a connected payment provider. Invoices of your own reach your accounting system through the
+handoff queue, claimed in leased batches and acknowledged one by one — a lost acknowledgement is
+claimed again, so book each invoice once by its id:
+
+```python
+claim = client.billing.handoff.claim(limit=25)
+for invoice in claim.invoices:
+ number = book(invoice) # your accounting system
+ client.billing.handoff.ack(invoice.id, lease_id=claim.lease_id, external_reference=number)
+```
+
+A pricing page reads the public catalog with a publishable key, and a customer's own billing page
+acts with a customer session your backend mints for it:
+
+```python
+session = client.billing.customer_sessions.create(
+ customer_slug="acme", instance_slug="acme-production"
+)
+hand_to_the_page(session.token) # kst_..., shown this once
+
+storefront = KaitenClient(token="pk_...")
+catalog = storefront.billing.public.get_catalog()
+```
+
+Amounts are decimal strings in minor units, exactly as Kaiten stores them.
+
### Feature flags
Define flags with the builders in `kaitencloud.targeting`. Targeting rules are CEL expressions,
@@ -493,7 +573,8 @@ Every `list()` returns the **whole** collection: the SDK walks the cursor at the
size the API accepts. If the API ever reports more rows without a usable cursor, `list()` raises
`PaginationError` rather than return a list it knows is partial.
-The audit trail grows without bound, so it takes a ceiling instead:
+Collections that grow without bound — the audit trail, invoices, the usage history — also
+take a ceiling:
```python
entries = client.instances.list_audit_trails(
@@ -516,7 +597,8 @@ KaitenError
│ ├── PermissionDeniedError 403 a scope is missing
│ ├── NotFoundError 404
│ ├── ConflictError 409
-│ │ └── ThresholdExceededError a usage report refused at the cap
+│ │ ├── ThresholdExceededError a usage report refused at the cap
+│ │ └── TransactionIdReusedError a usage report key spent on another report
│ ├── UnprocessableEntityError 422
│ ├── RateLimitError 429
│ └── InternalServerError 5xx
@@ -551,8 +633,8 @@ backoff and jitter, honouring `Retry-After` — but only when repeating it is sa
| Request | Retried |
| --- | --- |
| `GET`, `PUT`, `DELETE` | ✓ |
-| Instance status and lifecycle updates, metadata dry runs and reorders, `organizations.ensure()`, `connectors.register()` | ✓ — they converge |
-| Any other `POST` or `PATCH`: creations, usage reports, upserts | only if the connection never opened |
+| Instance status and lifecycle updates, add-on quantities, metadata dry runs and reorders, invoice previews, voucher lookups and checks, invoice voids and write-offs, handoff acknowledgements, payment-method confirmations, revocations, `organizations.ensure()`, `connectors.register()` | ✓ — they converge |
+| Any other `POST` or `PATCH`: creations, usage reports — keyed or not — handoff claims, upserts | only if the connection never opened |
| A `401` or `403` | never |
Tune it per client, or per call with `with_options()`, which shares the connection pool:
@@ -595,10 +677,158 @@ logging.getLogger("kaitencloud").setLevel(logging.INFO)
## API reference
-The Core API clients cover 101 of its 107 operations. Of the six left out, four are a signed-in
-person's own notification feed and preferences — which an API token has no equivalent of — and
-two are the OFREP evaluation endpoints, which an [OpenFeature provider](#feature-flags) calls. The Platform API clients cover
-all 10 of its operations. The async clients have the same methods.
+The Core API clients cover 210 of its 228 operations. Of the eighteen left out, twelve administer
+the people of an organization and their roles, which the console and the operational CLI do; four
+are a signed-in person's own notification feed and preferences — which an API token has no
+equivalent of — and two are the OFREP evaluation endpoints, which an
+[OpenFeature provider](#feature-flags) calls. The Platform API clients cover all 11 of its
+operations. The async clients have the same methods.
+
+
+client.billing · client.billing.subscriptions · client.billing.invoices · client.billing.handoff
+
+| Method | Operation |
+| --- | --- |
+| `client.billing.get_settings()` | `GET /billing/settings` |
+| `client.billing.get_capabilities()` | `GET /billing/capabilities` |
+| `client.billing.get_health()` | `GET /billing/health` |
+| `client.billing.update_settings()` | `PUT /billing/settings` |
+| `client.billing.sync()` | `POST /billing/sync` |
+| `client.billing.close_periods()` | `POST /billing/close-periods` |
+| `client.billing.subscriptions.get()` | `GET /instances/{instanceSlug}/billing` |
+| `client.billing.subscriptions.subscribe()` | `POST /instances/{instanceSlug}/billing` |
+| `client.billing.subscriptions.update()` | `PATCH /instances/{instanceSlug}/billing` |
+| `client.billing.subscriptions.cancel()` | `POST /instances/{instanceSlug}/billing/cancel` |
+| `client.billing.subscriptions.reactivate()` | `POST /instances/{instanceSlug}/billing/reactivate` |
+| `client.billing.subscriptions.schedule_plan_change()` | `PUT /instances/{instanceSlug}/billing/scheduled-change` |
+| `client.billing.subscriptions.cancel_plan_change()` | `DELETE /instances/{instanceSlug}/billing/scheduled-change` |
+| `client.billing.subscriptions.get_upcoming_invoice()` | `GET /instances/{instanceSlug}/billing/upcoming-invoice` |
+| `client.billing.subscriptions.list_invoices()` | `GET /instances/{instanceSlug}/invoices` |
+| `client.billing.invoices.list()` | `GET /invoices` |
+| `client.billing.invoices.get()` | `GET /invoices/{invoiceId}` |
+| `client.billing.invoices.export()` | `GET /invoices/export` |
+| `client.billing.invoices.void()` | `POST /invoices/{invoiceId}/void` |
+| `client.billing.invoices.recompose()` | `POST /invoices/{invoiceId}/recompose` |
+| `client.billing.invoices.mark_paid()` | `POST /invoices/{invoiceId}/mark-paid` |
+| `client.billing.invoices.write_off()` | `POST /invoices/{invoiceId}/write-off` |
+| `client.billing.invoices.release_hold()` | `POST /invoices/{invoiceId}/release-hold` |
+| `client.billing.invoices.retry_push()` | `POST /invoices/{invoiceId}/retry-push` |
+| `client.billing.invoices.sync()` | `POST /invoices/{invoiceId}/sync` |
+| `client.billing.invoices.list_line_reports()` | `GET /invoices/{invoiceId}/lines/{lineId}/reports` |
+| `client.billing.handoff.list()` | `GET /billing/handoff` |
+| `client.billing.handoff.claim()` | `POST /billing/handoff/claim` |
+| `client.billing.handoff.ack()` | `POST /billing/handoff/{invoiceId}/ack` |
+
+
+
+
+client.billing.license_prices · client.billing.addons
+
+| Method | Operation |
+| --- | --- |
+| `client.billing.license_prices.list()` | `GET /licenses/{licenseSlug}/prices` |
+| `client.billing.license_prices.get()` | `GET /licenses/{licenseSlug}/prices/{priceId}` |
+| `client.billing.license_prices.create()` | `POST /licenses/{licenseSlug}/prices` |
+| `client.billing.license_prices.update()` | `PATCH /licenses/{licenseSlug}/prices/{priceId}` |
+| `client.billing.license_prices.deprecate()` | `POST /licenses/{licenseSlug}/prices/{priceId}/deprecate` |
+| `client.billing.license_prices.preview_invoice()` | `POST /licenses/{licenseSlug}/invoice-preview` |
+| `client.billing.addons.list()` | `GET /addons` |
+| `client.billing.addons.get()` | `GET /addons/{addonSlug}` |
+| `client.billing.addons.create()` | `POST /addons` |
+| `client.billing.addons.update()` | `PUT /addons/{addonSlug}` |
+| `client.billing.addons.delete()` | `DELETE /addons/{addonSlug}` |
+| `client.billing.addons.publish()` | `POST /addons/{addonSlug}/publish` |
+| `client.billing.addons.archive()` | `POST /addons/{addonSlug}/archive` |
+| `client.billing.addons.unarchive()` | `POST /addons/{addonSlug}/unarchive` |
+| `client.billing.addons.list_compatibility()` | `GET /addons/{addonSlug}/compatible-license-families` |
+| `client.billing.addons.set_compatibility()` | `PUT /addons/{addonSlug}/compatible-license-families/{familySlug}` |
+| `client.billing.addons.remove_compatibility()` | `DELETE /addons/{addonSlug}/compatible-license-families/{familySlug}` |
+| `client.billing.addons.list_entitlements()` | `GET /addons/{addonSlug}/entitlements` |
+| `client.billing.addons.get_entitlement()` | `GET /addons/{addonSlug}/entitlements/{entitlementSlug}` |
+| `client.billing.addons.assign_entitlement()` | `POST /addons/{addonSlug}/entitlements` |
+| `client.billing.addons.update_entitlement()` | `PUT /addons/{addonSlug}/entitlements/{entitlementSlug}` |
+| `client.billing.addons.unassign_entitlement()` | `DELETE /addons/{addonSlug}/entitlements/{entitlementSlug}` |
+| `client.billing.addons.list_prices()` | `GET /addons/{addonSlug}/prices` |
+| `client.billing.addons.create_price()` | `POST /addons/{addonSlug}/prices` |
+| `client.billing.addons.deprecate_price()` | `POST /addons/{addonSlug}/prices/{priceId}/deprecate` |
+| `client.billing.addons.list_families()` | `GET /addon-families` |
+| `client.billing.addons.get_family()` | `GET /addon-families/{familySlug}` |
+| `client.billing.addons.update_family()` | `PATCH /addon-families/{familySlug}` |
+
+
+
+
+client.billing.instance_addons · client.billing.instance_vouchers · client.billing.vouchers
+
+| Method | Operation |
+| --- | --- |
+| `client.billing.instance_addons.list()` | `GET /instances/{instanceSlug}/addons` |
+| `client.billing.instance_addons.attach()` | `POST /instances/{instanceSlug}/addons` |
+| `client.billing.instance_addons.set_quantity()` | `PATCH /instances/{instanceSlug}/addons/{addonSlug}` |
+| `client.billing.instance_addons.detach()` | `DELETE /instances/{instanceSlug}/addons/{addonSlug}` |
+| `client.billing.instance_vouchers.list()` | `GET /instances/{instanceSlug}/vouchers` |
+| `client.billing.instance_vouchers.redeem()` | `POST /instances/{instanceSlug}/vouchers/redeem` |
+| `client.billing.instance_vouchers.revoke()` | `POST /instances/{instanceSlug}/vouchers/{instanceVoucherId}/revoke` |
+| `client.billing.vouchers.list()` | `GET /vouchers` |
+| `client.billing.vouchers.get()` | `GET /vouchers/{voucherId}` |
+| `client.billing.vouchers.create()` | `POST /vouchers` |
+| `client.billing.vouchers.update()` | `PUT /vouchers/{voucherId}` |
+| `client.billing.vouchers.delete()` | `DELETE /vouchers/{voucherId}` |
+| `client.billing.vouchers.publish()` | `POST /vouchers/{voucherId}/publish` |
+| `client.billing.vouchers.archive()` | `POST /vouchers/{voucherId}/archive` |
+| `client.billing.vouchers.list_redemptions()` | `GET /vouchers/{voucherId}/redemptions` |
+| `client.billing.vouchers.lookup()` | `POST /vouchers/lookup` |
+| `client.billing.vouchers.validate()` | `POST /vouchers/validate` |
+
+
+
+
+client.billing.customers · client.billing.customer_sessions · client.billing.publishable_keys · client.billing.checkout_sessions
+
+| Method | Operation |
+| --- | --- |
+| `client.billing.customers.get()` | `GET /customers/{customerSlug}/billing` |
+| `client.billing.customers.detach_payment_method()` | `DELETE /customers/{customerSlug}/billing/payment-method` |
+| `client.billing.customers.create_payment_method_session()` | `POST /customers/{customerSlug}/billing/payment-method-session` |
+| `client.billing.customers.complete_payment_method_session()` | `POST /customers/{customerSlug}/billing/payment-method-session/{sessionId}/complete` |
+| `client.billing.customers.create_portal_session()` | `POST /customers/{customerSlug}/billing/portal-session` |
+| `client.billing.customer_sessions.create()` | `POST /customer-sessions` |
+| `client.billing.customer_sessions.revoke()` | `POST /customer-sessions/{sessionId}/revoke` |
+| `client.billing.publishable_keys.list()` | `GET /publishable-keys` |
+| `client.billing.publishable_keys.create()` | `POST /publishable-keys` |
+| `client.billing.publishable_keys.update()` | `PATCH /publishable-keys/{keyId}` |
+| `client.billing.publishable_keys.revoke()` | `POST /publishable-keys/{keyId}/revoke` |
+| `client.billing.checkout_sessions.list()` | `GET /checkout-sessions` |
+| `client.billing.checkout_sessions.get()` | `GET /checkout-sessions/{sessionId}` |
+
+
+
+
+client.billing.public · client.billing.session — with a public credential
+
+`client.billing.public` takes a publishable key (`pk_...`), and an anonymous checkout's own
+session token (`ksc_...`) to read or confirm it; `client.billing.session` takes a customer
+session (`kst_...`). Build the client with that token.
+
+| Method | Operation |
+| --- | --- |
+| `client.billing.public.get_catalog()` | `GET /public/catalog` |
+| `client.billing.public.create_checkout_session()` | `POST /public/checkout-sessions` |
+| `client.billing.public.get_checkout_session()` | `GET /public/checkout-sessions/{sessionId}` |
+| `client.billing.public.confirm_checkout_session()` | `POST /public/checkout-sessions/{sessionId}/confirm` |
+| `client.billing.session.get_portal()` | `GET /public/session/portal` |
+| `client.billing.session.list_invoices()` | `GET /public/session/invoices` |
+| `client.billing.session.checkout()` | `POST /public/session/checkout` |
+| `client.billing.session.cancel()` | `POST /public/session/billing/cancel` |
+| `client.billing.session.reactivate()` | `POST /public/session/billing/reactivate` |
+| `client.billing.session.set_addon_quantity()` | `PUT /public/session/addons/{addonSlug}` |
+| `client.billing.session.create_payment_method_session()` | `POST /public/session/billing/payment-method-session` |
+| `client.billing.session.complete_payment_method_session()` | `POST /public/session/billing/payment-method-session/{sessionId}/complete` |
+| `client.billing.session.create_portal_session()` | `POST /public/session/billing/portal-session` |
+| `client.billing.session.validate_voucher()` | `POST /public/session/vouchers/validate` |
+| `client.billing.session.redeem_voucher()` | `POST /public/session/vouchers/redeem` |
+
+
client.components · client.connectors · client.customers
@@ -690,6 +920,13 @@ all 10 of its operations. The async clients have the same methods.
| `client.instances.list_usage()` | `GET /instances/{instanceSlug}/entitlements/usage` |
| `client.instances.get_usage()` | `GET /instances/{instanceSlug}/entitlements/{entitlementSlug}/usage` |
| `client.instances.report_usage()` | `POST /instances/{instanceSlug}/entitlements/{entitlementSlug}/usage` |
+| `client.instances.report_usage_detailed()` | `POST /instances/{instanceSlug}/entitlements/{entitlementSlug}/usage` |
+| `client.instances.list_usage_reports()` | `GET /instances/{instanceSlug}/entitlements/{entitlementSlug}/usage/reports` |
+| `client.instances.export_usage_reports()` | `GET /instances/{instanceSlug}/entitlements/{entitlementSlug}/usage/reports/export` |
+| `client.instances.export_organization_usage_reports()` | `GET /usage/reports/export` |
+| `client.instances.list_entitlement_exceptions()` | `GET /instances/{instanceSlug}/entitlement-exceptions` |
+| `client.instances.create_entitlement_exception()` | `POST /instances/{instanceSlug}/entitlement-exceptions` |
+| `client.instances.revoke_entitlement_exception()` | `DELETE /instances/{instanceSlug}/entitlement-exceptions/{exceptionId}` |
| `client.instances.get_integration()` | `GET /instances/{instanceSlug}/integrations/{integrationName}` |
| `client.instances.create_integration()` | `POST /instances/{instanceSlug}/integrations/{integrationName}` |
| `client.instances.update_integration()` | `PUT /instances/{instanceSlug}/integrations/{integrationName}` |
@@ -702,12 +939,13 @@ all 10 of its operations. The async clients have the same methods.
-client.licenses · client.metadata_fields
+client.license_families · client.licenses · client.metadata_fields
| Method | Operation |
| --- | --- |
| `client.license_families.list()` | `GET /license-families` |
| `client.license_families.get()` | `GET /license-families/{familySlug}` |
+| `client.license_families.update()` | `PATCH /license-families/{familySlug}` |
| `client.licenses.list()` | `GET /licenses` |
| `client.licenses.get()` | `GET /licenses/{licenseSlug}` |
| `client.licenses.create()` | `POST /licenses` |
@@ -756,6 +994,7 @@ all 10 of its operations. The async clients have the same methods.
| Method | Operation |
| --- | --- |
| `platform.me()` | `GET /platform/me` |
+| `platform.billing.close_periods()` | `POST /platform/organizations/{orgId}/billing/close-periods` |
| `platform.connectors.register()` | `POST /platform/connectors` |
| `platform.organizations.ensure()` | `POST /platform/organizations` |
| `platform.organizations.get()` | `GET /platform/organizations/{orgId}` |
diff --git a/openapi/coverage.yaml b/openapi/coverage.yaml
index 273d5e0..1367a8a 100644
--- a/openapi/coverage.yaml
+++ b/openapi/coverage.yaml
@@ -11,6 +11,132 @@
core:
operations:
+ # billing: settings, capabilities and the payment providers
+ getBillingSettings: billing.get_settings
+ updateBillingSettings: billing.update_settings
+ getBillingCapabilities: billing.get_capabilities
+ getBillingHealth: billing.get_health
+ syncBillingProvider: billing.sync
+ closeBillingPeriods: billing.close_periods
+
+ # billing: add-ons, their families, grants, prices and compatibility
+ listAddons: billing.addons.list
+ getAddon: billing.addons.get
+ createAddon: billing.addons.create
+ updateAddon: billing.addons.update
+ deleteAddon: billing.addons.delete
+ publishAddon: billing.addons.publish
+ archiveAddon: billing.addons.archive
+ unarchiveAddon: billing.addons.unarchive
+ listAddonCompatibility: billing.addons.list_compatibility
+ setAddonCompatibility: billing.addons.set_compatibility
+ removeAddonCompatibility: billing.addons.remove_compatibility
+ listAddonEntitlements: billing.addons.list_entitlements
+ getAddonEntitlement: billing.addons.get_entitlement
+ assignAddonEntitlement: billing.addons.assign_entitlement
+ updateAddonEntitlement: billing.addons.update_entitlement
+ unassignAddonEntitlement: billing.addons.unassign_entitlement
+ listAddonPrices: billing.addons.list_prices
+ createAddonPrice: billing.addons.create_price
+ deprecateAddonPrice: billing.addons.deprecate_price
+ listAddonFamilies: billing.addons.list_families
+ getAddonFamily: billing.addons.get_family
+ updateAddonFamily: billing.addons.update_family
+
+ # billing: anonymous checkouts, customer sessions and publishable keys
+ listCheckoutSessions: billing.checkout_sessions.list
+ getCheckoutSessionAdmin: billing.checkout_sessions.get
+ createCustomerSession: billing.customer_sessions.create
+ revokeCustomerSession: billing.customer_sessions.revoke
+ listPublishableKeys: billing.publishable_keys.list
+ createPublishableKey: billing.publishable_keys.create
+ updatePublishableKey: billing.publishable_keys.update
+ revokePublishableKey: billing.publishable_keys.revoke
+
+ # billing: a customer's payment method and provider portal
+ getCustomerBilling: billing.customers.get
+ detachPaymentMethod: billing.customers.detach_payment_method
+ createPaymentMethodSession: billing.customers.create_payment_method_session
+ completePaymentMethodSession: billing.customers.complete_payment_method_session
+ createPortalSession: billing.customers.create_portal_session
+
+ # billing: the accounting handoff
+ listHandoff: billing.handoff.list
+ claimHandoff: billing.handoff.claim
+ ackHandoff: billing.handoff.ack
+
+ # billing: an instance's add-ons and redeemed vouchers
+ listInstanceAddons: billing.instance_addons.list
+ attachInstanceAddon: billing.instance_addons.attach
+ setInstanceAddonQuantity: billing.instance_addons.set_quantity
+ detachInstanceAddon: billing.instance_addons.detach
+ listInstanceVouchers: billing.instance_vouchers.list
+ redeemVoucher: billing.instance_vouchers.redeem
+ revokeInstanceVoucher: billing.instance_vouchers.revoke
+
+ # billing: invoices
+ listInvoices: billing.invoices.list
+ getInvoice: billing.invoices.get
+ exportInvoices: billing.invoices.export
+ voidInvoice: billing.invoices.void
+ recomposeInvoice: billing.invoices.recompose
+ markInvoicePaid: billing.invoices.mark_paid
+ writeOffInvoice: billing.invoices.write_off
+ releaseInvoiceHold: billing.invoices.release_hold
+ retryInvoicePush: billing.invoices.retry_push
+ syncInvoice: billing.invoices.sync
+ listInvoiceLineReports: billing.invoices.list_line_reports
+
+ # billing: the prices of license versions
+ listLicensePrices: billing.license_prices.list
+ getLicensePrice: billing.license_prices.get
+ createLicensePrice: billing.license_prices.create
+ updateLicensePrice: billing.license_prices.update
+ deprecateLicensePrice: billing.license_prices.deprecate
+ previewLicenseInvoice: billing.license_prices.preview_invoice
+
+ # billing: what a publishable key (pk_) and an anonymous checkout's token (ksc_) reach
+ getPublicCatalog: billing.public.get_catalog
+ createCheckoutSession: billing.public.create_checkout_session
+ getCheckoutSession: billing.public.get_checkout_session
+ confirmCheckoutSession: billing.public.confirm_checkout_session
+
+ # billing: what a customer session (kst_) reaches
+ getSessionPortal: billing.session.get_portal
+ listSessionInvoices: billing.session.list_invoices
+ createSessionCheckout: billing.session.checkout
+ cancelSessionSubscription: billing.session.cancel
+ reactivateSessionSubscription: billing.session.reactivate
+ setSessionAddonQuantity: billing.session.set_addon_quantity
+ createSessionPaymentMethodSession: billing.session.create_payment_method_session
+ completeSessionPaymentMethodSession: billing.session.complete_payment_method_session
+ createSessionPortalSession: billing.session.create_portal_session
+ validateSessionVoucher: billing.session.validate_voucher
+ redeemSessionVoucher: billing.session.redeem_voucher
+
+ # billing: an instance's subscription
+ getInstanceBilling: billing.subscriptions.get
+ subscribeInstance: billing.subscriptions.subscribe
+ updateInstanceBilling: billing.subscriptions.update
+ cancelSubscription: billing.subscriptions.cancel
+ reactivateSubscription: billing.subscriptions.reactivate
+ schedulePlanChange: billing.subscriptions.schedule_plan_change
+ cancelPlanChange: billing.subscriptions.cancel_plan_change
+ getUpcomingInvoice: billing.subscriptions.get_upcoming_invoice
+ listInstanceInvoices: billing.subscriptions.list_invoices
+
+ # billing: vouchers
+ listVouchers: billing.vouchers.list
+ getVoucher: billing.vouchers.get
+ createVoucher: billing.vouchers.create
+ updateVoucher: billing.vouchers.update
+ deleteVoucher: billing.vouchers.delete
+ publishVoucher: billing.vouchers.publish
+ archiveVoucher: billing.vouchers.archive
+ listVoucherRedemptions: billing.vouchers.list_redemptions
+ lookupVoucher: billing.vouchers.lookup
+ validateVoucher: billing.vouchers.validate
+
# components
list-components: components.list
get-component: components.get
@@ -86,7 +212,13 @@ core:
getAuditTrails: instances.list_audit_trails
getEntitlementsUsageMetrics: instances.list_usage
getEntitlementUsageMetrics: instances.get_usage
- reportEntitlementUsageMetric: instances.report_usage
+ reportEntitlementUsageMetric: [instances.report_usage, instances.report_usage_detailed]
+ listUsageReports: instances.list_usage_reports
+ exportUsageReports: instances.export_usage_reports
+ exportOrganizationUsageReports: instances.export_organization_usage_reports
+ listEntitlementExceptions: instances.list_entitlement_exceptions
+ createEntitlementException: instances.create_entitlement_exception
+ revokeEntitlementException: instances.revoke_entitlement_exception
get-instance-integration: instances.get_integration
create-instance-integration: instances.create_integration
update-instance-integration: instances.update_integration
@@ -116,6 +248,7 @@ core:
# license families: the products whose versions the licenses above are
list-license-families: license_families.list
get-license-family: license_families.get
+ updateLicenseFamily: license_families.update
# metadata fields
list-metadata-fields: metadata_fields.list
@@ -163,9 +296,30 @@ core:
every flag at once. (sdk-js does keep a bulk call, but only to feed the licensing
snapshot its React components render; nothing in a Python SDK needs that.)
+ GetMe: >-
+ The signed-in person's own profile and access. A machine client holding an API token has
+ no human profile. kaiten's own operation-coverage test exempts this operation and the
+ eleven below as console authoring, for the same reasons.
+ GetAuthorization: >-
+ Request-time identity and grant introspection, which Kaiten's SaaS middleware consumes;
+ not product automation.
+ ListUsers: The human member directory, administered in the console and the operational CLI.
+ GetUser: An administrator's view of a human member's profile.
+ UpdateUser: An administrator's edit of a human identity, not machine account automation.
+ PutUserAuthorization: >-
+ An administrator's replacement of a person's roles and permissions -- never a service
+ token's.
+ SetUserAssignments: The console's role and direct-scope editor for a person.
+ SetUserEnabled: A person's membership access lifecycle, administered by a human.
+ ListRoles: The role directory of the console and the operational CLI.
+ CreateRole: Scope-group authoring by a human administrator.
+ UpdateRole: Scope-group authoring by a human administrator.
+ DeleteRole: Scope-group lifecycle, administered by a human.
+
platform:
operations:
get-platform-me: me
+ platform-close-billing-periods: billing.close_periods
register-connector: connectors.register
ensure-organization: organizations.ensure
get-organization: organizations.get
diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml
index e09e19c..a28362d 100644
--- a/openapi/openapi.yaml
+++ b/openapi/openapi.yaml
@@ -1,1796 +1,2277 @@
components:
schemas:
- AuditTrail:
+ Addon:
additionalProperties: false
properties:
- eventName:
- description: Event name (e.g. ENTITLEMENT_VALUE_GET)
- type: string
- eventType:
- description: Event type identifier (e.g. com.kaiten.instance.entitlement.v1.value_get)
- type: string
- id:
- description: Unique audit trail entry ID
+ createdAt:
+ format: date-time
+ readOnly: true
type: string
- instanceId:
- description: Instance UUID (present for instance-scoped events)
+ description:
type: string
- instanceSlug:
- description: Instance slug
+ familySlug:
+ description: The family the version belongs to
+ examples:
+ - extra-seats
type: string
- payload:
- description: Event payload (JSON)
- timestamp:
- description: When the event occurred
- format: date-time
+ id:
+ readOnly: true
type: string
- required:
- - id
- - instanceSlug
- - eventName
- - eventType
- - timestamp
- type: object
- Basic:
- additionalProperties: false
- properties:
- type:
+ increaseBilling:
+ description: "The family's: how a mid-period increase or attachment of an ADVANCE add-on is billed. TRUE_UP adds a true-up line for the elapsed period at the next renewal; NEXT_BOUNDARY bills from the next boundary only"
enum:
- - basic
+ - TRUE_UP
+ - NEXT_BOUNDARY
+ readOnly: true
type: string
- value:
+ isDefault:
+ description: The version the family resolves to. Only a PUBLISHED version.
+ type: boolean
+ lifecycleState:
+ description: A DRAFT can be attached to try it; an ARCHIVED version stays on the instances holding it but cannot be attached again.
+ enum:
+ - DRAFT
+ - PUBLISHED
+ - ARCHIVED
type: string
- required:
- - type
- - value
- type: object
- BasicTargeting:
- additionalProperties: false
- properties:
+ maxQuantity:
+ description: The largest quantity an instance can hold; unbounded when null
+ examples:
+ - 10
+ format: int32
+ type:
+ - integer
+ - "null"
name:
examples:
- - Targeting Rule 1
+ - Extra seats
type: string
- rule:
- description: CEL expression for basicTargeting evaluation
+ pricingType:
+ enum:
+ - FREE
+ - PAID
+ - CUSTOM
+ type: string
+ slug:
+ description: The version's slug, unique in the organization
examples:
- - user.group == 'beta'
- maxLength: 10000
+ - extra-seats-v1
type: string
- type:
- enum:
- - basic
+ updatedAt:
+ format: date-time
+ readOnly: true
type: string
- variant:
+ version:
+ description: The version number in its family, assigned on create
+ examples:
+ - 1
+ format: int32
+ readOnly: true
+ type: integer
+ versionName:
+ examples:
+ - Version - 1
type: string
required:
- - type
+ - id
+ - slug
+ - familySlug
- name
- - variant
- - rule
+ - description
+ - version
+ - versionName
+ - isDefault
+ - lifecycleState
+ - pricingType
+ - maxQuantity
+ - increaseBilling
+ - createdAt
+ - updatedAt
type: object
- BooleanEntitlementValue:
+ AddonAmount:
additionalProperties: false
properties:
- type:
- description: Value type discriminator
- enum:
- - boolean
+ addonSlug:
examples:
- - boolean
+ - extra-seats-v1
type: string
- value:
- description: Boolean value
+ quantity:
examples:
- - true
- type: boolean
+ - 3
+ format: int32
+ type: integer
required:
- - type
- - value
+ - addonSlug
+ - quantity
type: object
- BulkEvaluationFailure:
+ AddonChanges:
additionalProperties: false
properties:
- errorCode:
- description: Error code specific to the bulk evaluation error
+ description:
type: string
- errorDetails:
- description: Optional error details description
+ isDefault:
+ type: boolean
+ maxQuantity:
+ format: int32
+ type: integer
+ name:
+ examples:
+ - Extra seats
+ minLength: 1
type: string
- required:
- - errorCode
- type: object
- BulkEvaluationRequest:
- additionalProperties: false
- properties:
- context:
- additionalProperties: {}
- type: object
- type: object
- Command:
- additionalProperties: false
- properties:
- entitlementSlug:
+ versionName:
type: string
required:
- - entitlementSlug
+ - name
+ - description
+ - isDefault
type: object
- Component:
+ AddonDelivered:
additionalProperties: false
properties:
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
+ type: string
createdAt:
- description: Timestamp when the component was created
- examples:
- - "2023-10-01T12:00:00Z"
format: date-time
readOnly: true
type: string
- createdBy:
- $ref: "#/components/schemas/User"
- description: User who created this component
- readOnly: true
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
+ type: string
description:
- description: Description of the component
+ type: string
+ familySlug:
+ description: The family the version belongs to
examples:
- - Main API gateway component
- maxLength: 500
+ - extra-seats
type: string
id:
- description: Unique identifier for the component
- examples:
- - 123e4567-e89b-12d3-a456-426614174000
readOnly: true
type: string
+ increaseBilling:
+ description: "The family's: how a mid-period increase or attachment of an ADVANCE add-on is billed. TRUE_UP adds a true-up line for the elapsed period at the next renewal; NEXT_BOUNDARY bills from the next boundary only"
+ enum:
+ - TRUE_UP
+ - NEXT_BOUNDARY
+ readOnly: true
+ type: string
+ isDefault:
+ description: The version the family resolves to. Only a PUBLISHED version.
+ type: boolean
+ lifecycleState:
+ description: A DRAFT can be attached to try it; an ARCHIVED version stays on the instances holding it but cannot be attached again.
+ enum:
+ - DRAFT
+ - PUBLISHED
+ - ARCHIVED
+ type: string
+ maxQuantity:
+ description: The largest quantity an instance can hold; unbounded when null
+ examples:
+ - 10
+ format: int32
+ type:
+ - integer
+ - "null"
name:
- description: Name of the component
examples:
- - api-gateway
- maxLength: 100
- minLength: 1
+ - Extra seats
type: string
- previousComponentId:
- description: Reference to the previous version of this component
- examples:
- - 123e4567-e89b-12d3-a456-426614174000
+ pricingType:
+ enum:
+ - FREE
+ - PAID
+ - CUSTOM
type: string
slug:
- description: Optional URL-friendly identifier, unique per organization. Auto-generated if not provided.
+ description: The version's slug, unique in the organization
examples:
- - api-gateway-v1-2-3
- maxLength: 100
- minLength: 2
- pattern: ^[a-z0-9][a-z0-9-]*[a-z0-9]$
+ - extra-seats-v1
+ type: string
+ updatedAt:
+ format: date-time
+ readOnly: true
type: string
version:
- description: Version of the component
+ description: The version number in its family, assigned on create
examples:
- - v1.2.3
- maxLength: 100
- minLength: 1
+ - 1
+ format: int32
+ readOnly: true
+ type: integer
+ versionName:
+ examples:
+ - Version - 1
+ type: string
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
type: string
required:
- id
+ - slug
+ - familySlug
- name
+ - description
- version
- - createdBy
+ - versionName
+ - isDefault
+ - lifecycleState
+ - pricingType
+ - maxQuantity
+ - increaseBilling
- createdAt
+ - updatedAt
type: object
- ConfigEntitlementValue:
+ x-kaiten-payload: "#/components/schemas/Addon"
+ AddonEntitlement:
additionalProperties: false
properties:
- type:
- description: Value type discriminator
- enum:
- - object
+ entitlementSlug:
examples:
- - object
+ - seats
+ type: string
+ entitlementType:
+ enum:
+ - BOOLEAN
+ - NUMBER
+ - CONFIG
+ - NUMBER_AI_CREDIT
+ type: string
+ id:
+ readOnly: true
+ type: string
+ limitCapExceededOveragePercent:
+ description: The overage the add-on allows; null inherits the licence grant's
+ format: int32
+ type:
+ - integer
+ - "null"
+ overrideBehavior:
+ description: "How a number grant combines with the licence's: ADD sums value × quantity, OVERRIDE replaces it with the latest attached add-on's, MAX keeps the larger. A boolean always ORs, a config always overrides."
+ enum:
+ - ADD
+ - OVERRIDE
+ - MAX
type: string
value:
additionalProperties: {}
- description: Object configuration value
+ description: "{type, value}, shaped like a licence grant. A number counts once per unit of quantity."
type: object
required:
- - type
+ - id
+ - entitlementSlug
+ - entitlementType
- value
+ - overrideBehavior
+ - limitCapExceededOveragePercent
type: object
- Connector:
+ AddonFamily:
additionalProperties: false
properties:
- created_at:
- description: Timestamp when connector was first registered
- format: date-time
+ currentVersion:
+ description: The default version, else the highest PUBLISHED one; null when none is published
+ oneOf:
+ - $ref: "#/components/schemas/Addon"
+ - enum:
+ - null
+ type: "null"
+ id:
+ readOnly: true
type: string
- entitlement_slug:
- description: Slug of the BOOLEAN entitlement a license must grant for an organization to activate this connector. Absent means the connector is ungated.
- examples:
- - connector-attio
+ increaseBilling:
+ description: How a mid-period increase or attachment of the family's ADVANCE add-ons is billed (V31-70)
+ enum:
+ - TRUE_UP
+ - NEXT_BOUNDARY
type: string
- name:
- description: Stable connector identifier
- examples:
- - kaiten.integration.crm.attio
- type: string
- settings_schema:
- additionalProperties: {}
- description: JSON schema used to validate connector settings payloads
- type: object
- updated_at:
- description: Timestamp when connector registration was last updated
- format: date-time
- type: string
- version:
- description: Registered connector version
+ isPublic:
+ description: Whether the family is listed in the public catalogue
+ type: boolean
+ lastVersion:
+ description: The highest version number the family has assigned
+ format: int32
+ type: integer
+ slug:
examples:
- - 1.0.0
+ - extra-seats
type: string
+ versions:
+ description: Every version, newest first
+ items:
+ $ref: "#/components/schemas/Addon"
+ type:
+ - array
+ - "null"
required:
- - name
- - version
- - settings_schema
- - created_at
- - updated_at
+ - id
+ - slug
+ - isPublic
+ - increaseBilling
+ - lastVersion
+ - currentVersion
+ - versions
type: object
- ConnectorActivation:
+ AddonFamilyVisibility:
additionalProperties: false
properties:
- activatedAt:
- description: When this organization first activated the connector
- format: date-time
- type: string
- connectorName:
- description: Stable connector identifier
- examples:
- - kaiten.integration.crm.attio
+ increaseBilling:
+ description: "How a mid-period increase or attachment of the family's ADVANCE add-ons is billed, from the next composition: TRUE_UP (a true-up line for the elapsed period at the next renewal) or NEXT_BOUNDARY (from the next boundary only); another value answers 422 .InvalidIncreaseBilling"
type: string
- required:
- - connectorName
- - activatedAt
+ isPublic:
+ description: List the family's default PUBLISHED version, with its ACTIVE prices, in the public catalogue
+ type: boolean
type: object
- ConnectorSettings:
+ AddonGrant:
additionalProperties: false
properties:
- connector_name:
- description: Stable connector identifier
- examples:
- - kaiten.integration.crm.attio
- readOnly: true
+ limitCapExceededOveragePercent:
+ description: "The overage the add-on allows: -1 with an unlimited value, 0 or more otherwise. Absent inherits the licence grant's."
+ format: int32
+ type: integer
+ overrideBehavior:
+ default: MAX
+ description: How a number grant combines with the licence's
+ enum:
+ - ADD
+ - OVERRIDE
+ - MAX
type: string
- settings:
+ value:
additionalProperties: {}
- description: Connector settings payload
+ description: "{type, value}, shaped like a licence grant: a number counts once per unit of quantity"
+ examples:
+ - type: number
+ value: 5
type: object
required:
- - connector_name
- - settings
+ - value
type: object
- ConnectorSettingsSchema:
+ AddonGrantUpdateDelivered:
additionalProperties: false
properties:
- connector_name:
- description: Stable connector identifier
+ addonSlug:
+ description: The add-on version the grant belongs to
+ type: string
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
+ type: string
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
+ type: string
+ entitlementSlug:
examples:
- - kaiten.integration.crm.attio
+ - seats
type: string
- schema:
+ entitlementType:
+ enum:
+ - BOOLEAN
+ - NUMBER
+ - CONFIG
+ - NUMBER_AI_CREDIT
+ type: string
+ id:
+ readOnly: true
+ type: string
+ limitCapExceededOveragePercent:
+ description: The overage the add-on allows; null inherits the licence grant's
+ format: int32
+ type:
+ - integer
+ - "null"
+ overrideBehavior:
+ description: "How a number grant combines with the licence's: ADD sums value × quantity, OVERRIDE replaces it with the latest attached add-on's, MAX keeps the larger. A boolean always ORs, a config always overrides."
+ enum:
+ - ADD
+ - OVERRIDE
+ - MAX
+ type: string
+ value:
additionalProperties: {}
- description: JSON schema describing supported connector settings payload
+ description: "{type, value}, shaped like a licence grant. A number counts once per unit of quantity."
type: object
- version:
- description: Registered connector version
- examples:
- - 1.0.0
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
type: string
required:
- - connector_name
- - version
- - schema
+ - addonSlug
+ - id
+ - entitlementSlug
+ - entitlementType
+ - value
+ - overrideBehavior
+ - limitCapExceededOveragePercent
type: object
- ConnectorState:
+ x-kaiten-payload: "#/components/schemas/AddonGrantUpdate"
+ AddonPriceUpdateDelivered:
additionalProperties: false
properties:
- activated:
- description: Whether this organization has turned the connector on
- type: boolean
- activatedAt:
- description: When this organization activated the connector. Absent when it has not.
- format: date-time
+ addonSlug:
+ description: The add-on version the price belongs to
type: string
- available:
- description: Whether this deployment has the connector registered
- type: boolean
- connectorName:
- description: Stable connector identifier
- examples:
- - kaiten.integration.crm.attio
+ billingModel:
+ description: "FLAT_FEE: an amount per billing period. USAGE_BASED: an amount per sale unit of the metered entitlement's usage. OVERAGE: an amount per sale unit of the usage above the licence's limit."
+ enum:
+ - FLAT_FEE
+ - USAGE_BASED
+ - OVERAGE
type: string
- entitled:
- description: Whether this organization's licence includes the connector. True for an ungated connector.
- type: boolean
- version:
- description: Registered connector version. Absent when the connector is not available.
- examples:
- - 1.0.0
+ billingPeriod:
+ description: The period a FLAT_FEE price is charged for. Null on metered prices, which bill on the subscription's period.
+ enum:
+ - MONTHLY
+ - QUARTERLY
+ - SEMI_ANNUAL
+ - ANNUAL
+ - null
+ type:
+ - string
+ - "null"
+ billingTiming:
+ description: ADVANCE bills the period that starts; ARREARS the period that ended. Metered prices are always ARREARS.
+ enum:
+ - ADVANCE
+ - ARREARS
+ type: string
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
type: string
- required:
- - connectorName
- - available
- - entitled
- - activated
- type: object
- Customer:
- additionalProperties: false
- properties:
createdAt:
- description: Timestamp when the customer was created
- examples:
- - "2023-10-01T12:00:00Z"
format: date-time
readOnly: true
type: string
- createdBy:
- $ref: "#/components/schemas/User"
- description: User who created this customer
- readOnly: true
- domain:
- description: Optional customer domain name
+ currency:
+ description: ISO 4217 code, upper case. One currency per licence version.
examples:
- - example.tld
- pattern: ^[a-z0-9_-]+(\.[a-z0-9_-]+)+$
+ - EUR
type: string
- externalCustomerId:
- description: Optional identifier for the customer in an external system
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
+ type: string
+ deprecatedAt:
+ description: When the price was deprecated
+ format: date-time
+ type:
+ - string
+ - "null"
+ displayLabel:
+ description: The invoice line's label; a default is derived when it is null.
examples:
- - external-customer-id-12345
+ - Pro plan
type:
- string
- "null"
- id:
- description: Unique identifier for the customer
+ displayOrder:
+ description: Order among the version's prices
examples:
- - 123e4567-e89b-12d3-a456-426614174000
+ - 0
+ format: int32
+ type: integer
+ id:
+ description: Unique identifier of the price
readOnly: true
type: string
- integrations:
- additionalProperties:
- $ref: "#/components/schemas/CustomerIntegration"
- description: Integrations grouped by adapter name
- type: object
- name:
- description: Name of the customer
- examples:
- - Awesome Customer
- minLength: 1
+ isDefault:
+ description: The FLAT_FEE price picked for its billing period when none is named. At most one per period; only an ACTIVE FLAT_FEE price.
+ type: boolean
+ metered:
+ description: What a metered price measures. Null on FLAT_FEE prices.
+ oneOf:
+ - $ref: "#/components/schemas/PriceMeter"
+ - enum:
+ - null
+ type: "null"
+ status:
+ description: A DEPRECATED price keeps billing what is already pinned to it but is no longer offered.
+ enum:
+ - ACTIVE
+ - DEPRECATED
type: string
- slug:
- description: Optional URL-friendly identifier, unique per organization. Auto-generated if not provided.
+ unitAmount:
+ description: unitAmountDecimal as an integer number of minor units, when it is one; null otherwise.
examples:
- - awesome-customer
- maxLength: 100
- minLength: 2
- pattern: ^[a-z0-9][a-z0-9-]*[a-z0-9]$
+ - 2900
+ format: int64
+ type:
+ - integer
+ - "null"
+ unitAmountDecimal:
+ description: "Amount in the currency's minor units (cents for EUR): per period for FLAT_FEE, per sale unit for metered prices. Up to 12 decimal places."
+ examples:
+ - "2900"
type: string
updatedAt:
- description: Timestamp when the customer was last updated
- examples:
- - "2023-10-01T12:00:00Z"
format: date-time
readOnly: true
type: string
- updatedBy:
- $ref: "#/components/schemas/User"
- description: User who last updated this customer
- readOnly: true
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
+ type: string
required:
+ - addonSlug
- id
- - name
- - createdBy
+ - billingModel
+ - billingTiming
+ - billingPeriod
+ - currency
+ - unitAmount
+ - unitAmountDecimal
+ - metered
+ - displayLabel
+ - displayOrder
+ - isDefault
+ - status
+ - deprecatedAt
- createdAt
- - updatedBy
- updatedAt
type: object
- CustomerBody:
+ x-kaiten-payload: "#/components/schemas/AddonPriceUpdate"
+ AddonQuantity:
additionalProperties: false
properties:
- domain:
- description: Optional customer domain name
+ quantity:
examples:
- - example.tld
- pattern: ^[a-z0-9_-]+(\.[a-z0-9_-]+)+$
+ - 3
+ format: int32
+ minimum: 1
+ type: integer
+ required:
+ - quantity
+ type: object
+ AddonUpdateDelivered:
+ additionalProperties: false
+ properties:
+ changedFields:
+ description: The members the change touched, as the API names them
+ items:
+ type: string
+ type:
+ - array
+ - "null"
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
type: string
- error:
- description: Optional integration error message
+ compatibleLicenseFamilies:
+ description: The licence families the version fits after the change
+ items:
+ type: string
+ type:
+ - array
+ - "null"
+ createdAt:
+ format: date-time
+ readOnly: true
type: string
- integrationMetadata:
- additionalProperties: {}
- description: Adapter-specific integration metadata
- type: object
- name:
- description: Customer name
- examples:
- - Awesome Customer
- minLength: 1
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
type: string
- slug:
- description: Customer slug
- examples:
- - awesome-customer
- maxLength: 100
- minLength: 2
- pattern: ^[a-z0-9][a-z0-9-]*[a-z0-9]$
+ description:
type: string
- webUrl:
- description: Absolute http(s) link to the record in the third-party system
+ familySlug:
+ description: The family the version belongs to
examples:
- - https://app.attio.com/w/acme/record/rec_12345
+ - extra-seats
type: string
- type: object
- CustomerCreationRejected:
- additionalProperties: false
- properties:
- name:
+ id:
+ readOnly: true
type: string
- reason:
+ increaseBilling:
+ description: "The family's: how a mid-period increase or attachment of an ADVANCE add-on is billed. TRUE_UP adds a true-up line for the elapsed period at the next renewal; NEXT_BOUNDARY bills from the next boundary only"
+ enum:
+ - TRUE_UP
+ - NEXT_BOUNDARY
+ readOnly: true
type: string
- slug:
+ isDefault:
+ description: The version the family resolves to. Only a PUBLISHED version.
+ type: boolean
+ lifecycleState:
+ description: A DRAFT can be attached to try it; an ARCHIVED version stays on the instances holding it but cannot be attached again.
+ enum:
+ - DRAFT
+ - PUBLISHED
+ - ARCHIVED
type: string
- required:
- - name
- - slug
- - reason
- type: object
- CustomerIntegration:
- additionalProperties: false
- properties:
- external_id:
- description: External identifier in the third-party adapter
+ maxQuantity:
+ description: The largest quantity an instance can hold; unbounded when null
examples:
- - rec_12345
+ - 10
+ format: int32
+ type:
+ - integer
+ - "null"
+ name:
+ examples:
+ - Extra seats
type: string
- last_error:
- description: Last synchronization error message, if any
+ pricingType:
+ enum:
+ - FREE
+ - PAID
+ - CUSTOM
type: string
- metadata:
- additionalProperties: {}
- description: Adapter-specific integration metadata
- type: object
- synced_at:
- description: Timestamp of the last synchronization with the adapter
+ slug:
+ description: The version's slug, unique in the organization
+ examples:
+ - extra-seats-v1
+ type: string
+ updatedAt:
format: date-time
readOnly: true
type: string
- web_url:
- description: Absolute http(s) link to the record in the third-party system
+ version:
+ description: The version number in its family, assigned on create
examples:
- - https://app.attio.com/w/acme/company/rec_12345
+ - 1
+ format: int32
+ readOnly: true
+ type: integer
+ versionName:
+ examples:
+ - Version - 1
+ type: string
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
type: string
required:
- - external_id
- - synced_at
+ - changedFields
+ - compatibleLicenseFamilies
+ - id
+ - slug
+ - familySlug
+ - name
+ - description
+ - version
+ - versionName
+ - isDefault
+ - lifecycleState
+ - pricingType
+ - maxQuantity
+ - increaseBilling
+ - createdAt
+ - updatedAt
type: object
- CustomerIntegrationResource:
+ x-kaiten-payload: "#/components/schemas/AddonUpdate"
+ AuditActor:
additionalProperties: false
properties:
- domain:
- description: Optional customer domain name
- examples:
- - example.tld
+ id:
type: string
- error:
- description: Last synchronization error for this integration, if any
+ kind:
+ description: "USER: a person. SERVICE: a service account. SYSTEM: Kaiten itself -- jobs, and the anonymous checkouts no one of the organization makes"
+ enum:
+ - USER
+ - SERVICE
+ - SYSTEM
type: string
- integrationMetadata:
- additionalProperties: {}
- description: Adapter-specific customer integration metadata
- type: object
name:
- description: Customer name
- examples:
- - Awesome Customer
- type: string
- slug:
- description: Customer slug
- examples:
- - awesome-customer
- type: string
- webUrl:
- description: Absolute http(s) link to the record in the third-party system
- examples:
- - https://app.attio.com/w/acme/company/rec_12345
+ description: The user's name; "Kaiten (system)" for the work Kaiten does itself
type: string
required:
- - integrationMetadata
+ - id
- name
- - slug
+ - kind
type: object
- DefaultVariant:
- discriminator:
- mapping:
- basic: "#/components/schemas/Basic"
- rollout_date: "#/components/schemas/RolloutDate"
- rollout_percentage: "#/components/schemas/RolloutPercentage"
- propertyName: type
- oneOf:
- - $ref: "#/components/schemas/Basic"
- - $ref: "#/components/schemas/RolloutDate"
- - $ref: "#/components/schemas/RolloutPercentage"
- Deployment:
+ AuditTrail:
additionalProperties: false
properties:
- createdAt:
- description: Timestamp when the deployment was recorded
- examples:
- - "2023-10-01T12:00:00Z"
- format: date-time
+ actor:
+ description: Who the event is recorded under; null for an entry recorded before actors were (E-27), or whose user is gone
+ oneOf:
+ - $ref: "#/components/schemas/AuditActor"
+ - enum:
+ - null
+ type: "null"
+ eventName:
+ description: Event name (e.g. ENTITLEMENT_VALUE_GET)
type: string
- createdBy:
- $ref: "#/components/schemas/User"
- description: User who recorded this deployment
- deploymentZoneId:
- description: Deployment zone the release was deployed to
- examples:
- - 123e4567-e89b-12d3-a456-426614174000
+ eventType:
+ description: Event type identifier (e.g. com.kaiten.instance.entitlement.v1.value_get)
type: string
id:
- description: Unique identifier for this deployment event
- examples:
- - 123e4567-e89b-12d3-a456-426614174000
+ description: Unique audit trail entry ID
type: string
- releaseId:
- description: Release that was deployed
- examples:
- - 123e4567-e89b-12d3-a456-426614174000
+ instanceId:
+ description: Instance UUID (present for instance-scoped events)
+ type: string
+ instanceSlug:
+ description: Instance slug
+ type: string
+ payload:
+ description: Event payload (JSON)
+ timestamp:
+ description: When the event occurred
+ format: date-time
type: string
required:
- id
- - deploymentZoneId
- - releaseId
- - createdBy
- - createdAt
+ - instanceSlug
+ - eventName
+ - eventType
+ - timestamp
+ - actor
type: object
- DeploymentZone:
+ Authorization:
additionalProperties: false
properties:
- createdAt:
- description: Timestamp when the deployment zone was created
- examples:
- - "2023-10-01T12:00:00Z"
- format: date-time
- readOnly: true
- type: string
- createdBy:
- $ref: "#/components/schemas/User"
- description: User who created this deployment zone
- readOnly: true
- description:
- description: Description of the deployment zone
- examples:
- - "2023-10-01T12:00:00Z"
+ externalOrganizationId:
type: string
- id:
- description: Unique identifier for the deployment zone
- examples:
- - 123e4567-e89b-12d3-a456-426614174000
- readOnly: true
+ kind:
type: string
- metadata:
- additionalProperties: {}
- description: Typed metadata for the deployment zone, validated against the org-scoped MetadataField schema
- examples:
- - region: eu-west-1
- type: object
- name:
- description: Name of the deployment zone
- examples:
- - AWS us-west-2
- minLength: 1
+ organizationId:
type: string
- releaseId:
- description: Currently deployed release ID
- examples:
- - 123e4567-e89b-12d3-a456-426614174000
+ scopes:
+ items:
+ type: string
+ type: array
+ subject:
type: string
- slug:
- description: Optional URL-friendly identifier, unique per organization. Auto-generated if not provided.
- examples:
- - aws-us-west-2
- maxLength: 100
- minLength: 2
- pattern: ^[a-z0-9][a-z0-9-]*[a-z0-9]$
+ userId:
type: string
+ required:
+ - kind
+ - userId
+ - organizationId
+ - externalOrganizationId
+ - subject
+ - scopes
+ type: object
+ Basic:
+ additionalProperties: false
+ properties:
type:
- description: "Environment class of the deployment zone. Free-form: any value is stored as sent, but production, staging and development are the ones the console labels, and feature-flag targeting rules match on this field as __kaiten.deploymentZone.type."
- examples:
- - production
+ enum:
+ - basic
type: string
- updatedAt:
- description: Timestamp when the deployment zone was last updated
- examples:
- - "2023-10-01T12:00:00Z"
- format: date-time
- readOnly: true
+ value:
type: string
- updatedBy:
- $ref: "#/components/schemas/User"
- description: User who last updated the deployment zone
- readOnly: true
required:
- - id
- - name
- type
- - description
- - createdBy
- - createdAt
- - updatedBy
- - updatedAt
+ - value
type: object
- Entitlement:
+ BasicTargeting:
additionalProperties: false
properties:
- aggregationMethod:
- description: Method used to fold each reported usage value into the stored total (NUMBER only). Defaults to SUM.
- enum:
- - COUNT
- - SUM
- - AVERAGE
- - MIN
- - MAX
- - LATEST
+ name:
examples:
- - SUM
+ - Targeting Rule 1
type: string
- createdAt:
- description: Timestamp when this entitlement was created
+ rule:
+ description: CEL expression for basicTargeting evaluation
examples:
- - "2023-10-01T12:00:00Z"
- format: date-time
- readOnly: true
+ - user.group == 'beta'
+ maxLength: 10000
type: string
- description:
- description: Description of the entitlement
- examples:
- - Basic subscription plan with limited features
+ type:
+ enum:
+ - basic
+ type: string
+ variant:
+ type: string
+ required:
+ - type
+ - name
+ - variant
+ - rule
+ type: object
+ BillingCapabilities:
+ additionalProperties: false
+ properties:
+ disabledReason:
+ description: "Why they do not: billing is off on this deployment, or the organization's plan does not include it"
+ enum:
+ - DEPLOYMENT_DISABLED
+ - NOT_ENTITLED
+ - null
type:
- string
- "null"
- displayOrder:
- description: Sort order of this entitlement in customer-facing components (ascending). Defaults to 0.
- examples:
- - 10
- format: int32
- minimum: 0
- type: integer
- entitlementGroups:
- description: Groups associated with this entitlement
+ enabled:
+ description: Whether the billing routes answer for this organization
+ type: boolean
+ features:
+ $ref: "#/components/schemas/BillingFeatures"
+ description: Which parts of billing this release ships
+ providers:
+ description: Who can collect invoices
items:
- $ref: "#/components/schemas/EntitlementGroupSummary"
- readOnly: true
+ $ref: "#/components/schemas/BillingProvider"
+ type: array
+ publicSurface:
+ $ref: "#/components/schemas/PublicSurface"
+ usageHistoryRetentionMonths:
+ description: How long usage reports are kept, in months; null when they are kept forever or it cannot be told
+ format: int64
type:
- - array
+ - integer
- "null"
- groupSlugs:
- description: Groups associated with this entitlement, identified by slug
+ usageHistoryRetentionSource:
+ description: "Where the retention comes from: KBK, the organization's licence or an add-on; ENV, the deployment's KAITEN_USAGE_LEDGER_RETENTION_MONTHS; UNKNOWN, the licensing authority could not tell. A null usageHistoryRetentionMonths reads as kept forever with KBK or ENV, and as unknown with UNKNOWN (V31-11)."
+ enum:
+ - KBK
+ - ENV
+ - UNKNOWN
+ type: string
+ usageIdempotencyWindowDays:
+ description: How long a usage report's transactionId is remembered, in days
+ format: int64
+ type: integer
+ required:
+ - enabled
+ - disabledReason
+ - providers
+ - publicSurface
+ - usageHistoryRetentionMonths
+ - usageHistoryRetentionSource
+ - usageIdempotencyWindowDays
+ - features
+ type: object
+ BillingFeatures:
+ additionalProperties: false
+ properties:
+ addons:
+ type: boolean
+ chargeAutomatically:
+ type: boolean
+ lifecycle:
+ description: Cancellation, reactivation, plan changes and overdue tracking
+ type: boolean
+ publicSurface:
+ type: boolean
+ stripe:
+ type: boolean
+ trials:
+ type: boolean
+ vouchers:
+ type: boolean
+ required:
+ - stripe
+ - lifecycle
+ - trials
+ - addons
+ - vouchers
+ - chargeAutomatically
+ - publicSurface
+ type: object
+ BillingHealth:
+ additionalProperties: false
+ properties:
+ closeBacklog:
+ $ref: "#/components/schemas/CloseBacklog"
+ description: Subscriptions whose period ended more than KAITEN_BILLING_CLOSE_GRACE ago and has not closed yet
+ handoff:
+ $ref: "#/components/schemas/HandoffBacklog"
+ heldInvoices:
+ $ref: "#/components/schemas/HeldInvoices"
+ overdueInvoices:
+ format: int64
+ type: integer
+ pastDueSubscriptions:
+ format: int64
+ type: integer
+ paymentMethodsExpiring30d:
+ description: ACTIVE payment methods whose card expires in the next 30 days
+ format: int64
+ type: integer
+ providerSync:
items:
- type: string
+ $ref: "#/components/schemas/ProviderSync"
+ type: array
+ pushFailures:
+ $ref: "#/components/schemas/PushFailures"
+ description: Invoices whose push failed at least as many times as the alert threshold
+ reconciliationMismatches30d:
+ description: Invoices whose provider's amounts differed from Kaiten's, in the last 30 days
+ format: int64
+ type: integer
+ usageLedger:
+ $ref: "#/components/schemas/UsageLedger"
+ required:
+ - closeBacklog
+ - heldInvoices
+ - pushFailures
+ - reconciliationMismatches30d
+ - providerSync
+ - pastDueSubscriptions
+ - overdueInvoices
+ - handoff
+ - paymentMethodsExpiring30d
+ - usageLedger
+ type: object
+ BillingProvider:
+ additionalProperties: false
+ properties:
+ available:
+ description: Whether the organization may connect it on this deployment
+ type: boolean
+ capabilities:
+ $ref: "#/components/schemas/ProviderCapabilities"
+ connected:
+ description: Whether it is set up for the organization; NOOP always is
+ type: boolean
+ kind:
+ enum:
+ - NOOP
+ - STRIPE
+ type: string
+ livemode:
+ description: Whether the connection reaches the provider's live account rather than a test one; null when not connected, and for NOOP
type:
- - array
+ - boolean
- "null"
- writeOnly: true
- icon:
- description: Optional icon, stored as a provider-namespaced token (provider:name). Decoupled from any specific icon library.
- examples:
- - lucide:rocket
- maxLength: 64
- pattern: ^[a-z0-9]+:[a-z0-9]+(-[a-z0-9]+)*$
- type: string
- id:
- description: Unique identifier for the entitlement
+ unavailableReason:
+ description: Why it is not available; null when it is, and when the licensing authority cannot answer
+ enum:
+ - NOT_ENTITLED
+ - VAULT_NOT_CONFIGURED
+ - null
+ type:
+ - string
+ - "null"
+ required:
+ - kind
+ - available
+ - unavailableReason
+ - connected
+ - livemode
+ - capabilities
+ type: object
+ BillingSettings:
+ additionalProperties: false
+ properties:
+ defaultCollectionMethod:
+ description: "How an invoice is collected when its subscription does not say: SEND_INVOICE sends it for payment; CHARGE_AUTOMATICALLY charges the customer's payment method, on a provider that can. A NOOP subscription sends its invoices whatever the default."
+ enum:
+ - SEND_INVOICE
+ - CHARGE_AUTOMATICALLY
examples:
- - 123e4567-e89b-12d3-a456-426614174000
- readOnly: true
+ - SEND_INVOICE
type: string
- name:
- description: Name of the entitlement
+ defaultDaysUntilDue:
+ description: Days an invoice is due after it is issued, when its subscription does not say; 0 to 365.
examples:
- - Basic Plan
- minLength: 1
+ - 30
+ format: int32
+ type: integer
+ handoffStripeInvoices:
+ description: Whether invoices a payment provider issues also enter the handoff queue, for an accounting system that wants every invoice. Invoices without a provider always do.
+ type: boolean
+ required:
+ - defaultCollectionMethod
+ - defaultDaysUntilDue
+ - handoffStripeInvoices
+ type: object
+ BillingStartedDelivered:
+ additionalProperties: false
+ properties:
+ anchorAt:
+ description: The instant its periods are counted from
+ format: date-time
type: string
- resetAnchor:
- description: "Phase of the periodic usage window: CALENDAR (UTC calendar-aligned) or LICENSE_START (phased off the instance's license start date). Required exactly when resetPeriod is set; defaults to CALENDAR when omitted. Immutable once set."
+ basePrice:
+ $ref: "#/components/schemas/Price"
+ description: The FLAT_FEE price the subscription is pinned to, deprecated or not
+ billingPeriod:
enum:
- - CALENDAR
- - LICENSE_START
- examples:
- - CALENDAR
+ - MONTHLY
+ - QUARTERLY
+ - SEMI_ANNUAL
+ - ANNUAL
type: string
- resetPeriod:
- description: Cadence at which usage automatically resets for this NUMBER entitlement. NULL means a lifetime counter (default). Immutable once set -- a full-replace PUT must echo back the stored value.
+ cancelAtPeriodEnd:
+ type: boolean
+ cancelRequestedAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ canceledAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ cancellationReason:
+ type:
+ - string
+ - "null"
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
+ type: string
+ collectionMethod:
+ description: "Effective: the subscription's own, else the organization's default"
enum:
- - HOUR
- - DAY
- - WEEK
- - MONTH
- - YEAR
- examples:
- - MONTH
+ - SEND_INVOICE
+ - CHARGE_AUTOMATICALLY
type: string
- saleUnitFactor:
- description: Number of base units one sale unit represents (1 sale unit = N base units).
- examples:
- - 3
- exclusiveMinimum: 0
- format: double
- type: number
- saleUnitPlural:
- description: Plural label of the unit this entitlement is sold in, when it differs from the base unit.
- examples:
- - packs
- maxLength: 100
- minLength: 1
+ collectionMethodOverride:
+ description: The subscription's own collection method; null when it takes the organization's
+ type:
+ - string
+ - "null"
+ createdAt:
+ format: date-time
type: string
- saleUnitSingular:
- description: Singular label of the unit this entitlement is sold in, when it differs from the base unit. Requires the base unit labels and the full sale unit trio (saleUnitSingular, saleUnitPlural, saleUnitFactor).
+ currency:
examples:
- - pack
- maxLength: 100
- minLength: 1
+ - EUR
type: string
- slug:
- description: Optional URL-friendly identifier, unique per organization. Auto-generated if not provided.
- examples:
- - basic-plan
- maxLength: 100
- minLength: 2
- pattern: ^[a-z0-9][a-z0-9-]*[a-z0-9]$
+ currentPeriodEnd:
+ description: "The next boundary: the period closes, and its invoice is composed, after it"
+ format: date-time
type: string
- type:
- description: Type of the entitlement
- enum:
- - BOOLEAN
- - NUMBER
- - CONFIG
- - NUMBER_AI_CREDIT
- examples:
- - NUMBER
+ currentPeriodStart:
+ format: date-time
type: string
- unitPlural:
- description: Plural label of the base unit this entitlement is measured in (NUMBER type only). Must be provided together with unitSingular.
- examples:
- - seats
- maxLength: 100
- minLength: 1
+ customerName:
type: string
- unitSingular:
- description: Singular label of the base unit this entitlement is measured in (NUMBER type only). Must be provided together with unitPlural.
- examples:
- - seat
- maxLength: 100
- minLength: 1
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
type: string
- updatedAt:
- description: Timestamp when this entitlement was last updated
- examples:
- - "2023-10-02T12:00:00Z"
- format: date-time
- readOnly: true
+ customerSlug:
type: string
- userFacing:
- description: Whether this entitlement is displayed in customer-facing components (e.g. plan and pricing pages). Defaults to false.
- examples:
- - true
- type: boolean
- warningThresholdPercent:
- description: Percentage of the cap at which an early-warning event fires before the cap is reached. 0 disables the warning (default).
- examples:
- - 80
+ daysUntilDue:
+ description: "Effective: the subscription's own, else the organization's default"
format: int32
- maximum: 100
- minimum: 0
type: integer
- required:
- - id
- - name
- - description
- - createdAt
- - updatedAt
- type: object
- EntitlementGroup:
- additionalProperties: false
- properties:
- description:
- description: Description of the entitlement group
- examples:
- - All entitlements related to AI usage quotas
+ daysUntilDueOverride:
+ description: The subscription's own payment terms; null when it takes the organization's
+ format: int32
type:
- - string
+ - integer
- "null"
id:
- description: Unique identifier for the entitlement group
- examples:
- - 123e4567-e89b-12d3-a456-426614174000
- readOnly: true
type: string
- name:
- description: Name of the entitlement group
- examples:
- - AI Quotas
- minLength: 1
+ incompleteExpiresAt:
+ description: "When a checkout waiting for its payment ends unpaid: INCOMPLETE, or a free subscription that carries one"
+ format: date-time
+ type:
+ - string
+ - "null"
+ instanceName:
type: string
- slug:
- description: URL-friendly identifier, unique per organization. Auto-generated if not provided.
- examples:
- - ai-quotas
- maxLength: 100
- minLength: 2
- pattern: ^[a-z0-9][a-z0-9-]*[a-z0-9]$
+ instanceSlug:
+ description: As it was at the subscription's last write
type: string
- required:
- - id
- - name
- - description
- type: object
- EntitlementGroupSummary:
- additionalProperties: false
- properties:
- id:
- description: Unique identifier for the entitlement group
- examples:
- - 123e4567-e89b-12d3-a456-426614174000
- type: string
- name:
- description: Name of the entitlement group
- examples:
- - AI Quotas
- type: string
- slug:
- description: URL-friendly identifier for the entitlement group
- examples:
- - ai-quotas
- type: string
- required:
- - id
- - name
- - slug
- type: object
- EntitlementGroupUsage:
- additionalProperties: false
- properties:
- currentPeriodEnd:
- description: End of the current usage window (exclusive). Null for a lifetime entitlement.
+ pastDueSince:
format: date-time
+ type:
+ - string
+ - "null"
+ pendingLicenseSlug:
+ description: The licence version a checkout waiting for its payment moves the instance to once paid
+ type:
+ - string
+ - "null"
+ providerKind:
+ description: "Who collects its invoices. NOOP: the organization itself, through the handoff queue"
+ enum:
+ - NOOP
+ - STRIPE
type: string
- currentPeriodStart:
- description: Start of the current usage window (inclusive). Null for a lifetime entitlement.
+ reminderAt:
+ description: "When trial_will_end is (or was) emitted: trialEndsAt minus the family's trialReminderDays as it was when the trial started; set with trialEndsAt"
+ format: date-time
+ type:
+ - string
+ - "null"
+ resubscribed:
+ description: Set when a CANCELED subscription was subscribed again
+ type: boolean
+ scheduledChange:
+ description: A plan change waiting for the next boundary
+ oneOf:
+ - $ref: "#/components/schemas/ScheduledChange"
+ - enum:
+ - null
+ type: "null"
+ startedAt:
+ description: "When billing started: usage before it is never billed"
format: date-time
type: string
- entitlementId:
- description: Entitlement ID
- type: string
- entitlementName:
- description: Entitlement name
+ status:
+ enum:
+ - INCOMPLETE
+ - TRIAL
+ - ACTIVE
+ - PAST_DUE
+ - CANCELED
type: string
- entitlementSlug:
- description: Entitlement slug
+ trialEndsAt:
+ description: "When the trial ends, or ended: usage before it is never billed"
+ format: date-time
+ type:
+ - string
+ - "null"
+ updatedAt:
+ format: date-time
type: string
- entitlementType:
- description: Entitlement type
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
type: string
- licenseValue:
- description: License limit value, discriminated by the 'type' field. Null when the license grants no value.
- discriminator:
- mapping:
- boolean: "#/components/schemas/BooleanEntitlementValue"
- number: "#/components/schemas/NumberEntitlementValue"
- object: "#/components/schemas/ConfigEntitlementValue"
- propertyName: type
- oneOf:
- - $ref: "#/components/schemas/NumberEntitlementValue"
- - $ref: "#/components/schemas/BooleanEntitlementValue"
- - $ref: "#/components/schemas/ConfigEntitlementValue"
- usageValue:
- description: Current usage value, discriminated by the 'type' field. Null when nothing has been reported yet.
- discriminator:
- mapping:
- boolean: "#/components/schemas/BooleanEntitlementValue"
- number: "#/components/schemas/NumberEntitlementValue"
- object: "#/components/schemas/ConfigEntitlementValue"
- propertyName: type
- oneOf:
- - $ref: "#/components/schemas/NumberEntitlementValue"
- - $ref: "#/components/schemas/BooleanEntitlementValue"
- - $ref: "#/components/schemas/ConfigEntitlementValue"
required:
- - entitlementId
- - entitlementSlug
- - entitlementName
- - entitlementType
+ - resubscribed
+ - id
+ - instanceSlug
+ - instanceName
+ - customerSlug
+ - customerName
+ - status
+ - providerKind
+ - collectionMethod
+ - collectionMethodOverride
+ - daysUntilDue
+ - daysUntilDueOverride
+ - basePrice
+ - billingPeriod
+ - currency
+ - anchorAt
+ - startedAt
+ - currentPeriodStart
+ - currentPeriodEnd
+ - trialEndsAt
+ - reminderAt
+ - pendingLicenseSlug
+ - incompleteExpiresAt
+ - scheduledChange
+ - cancelAtPeriodEnd
+ - cancelRequestedAt
+ - canceledAt
+ - cancellationReason
+ - pastDueSince
+ - createdAt
+ - updatedAt
type: object
- EntitlementUsage:
+ x-kaiten-payload: "#/components/schemas/BillingStarted"
+ BooleanEntitlementValue:
additionalProperties: false
properties:
- currentPeriodEnd:
- description: End of the current usage window (exclusive). Null for a lifetime entitlement (no configured reset period).
- examples:
- - "2026-04-01T00:00:00Z"
- format: date-time
- type: string
- currentPeriodStart:
- description: Start of the current usage window (inclusive). Null for a lifetime entitlement (no configured reset period).
- examples:
- - "2026-03-01T00:00:00Z"
- format: date-time
- type: string
- entitlementId:
- description: Unique identifier for the entitlement
- examples:
- - 123e4567-e89b-12d3-a456-426614174000
- type: string
- entitlementSlug:
- description: URL-friendly identifier of the entitlement
- examples:
- - premium-support
- type: string
- licenseId:
- description: Unique identifier for the license to which this entitlement usage belongs
- examples:
- - 123e4567-e89b-12d3-a456-426614174001
- type: string
- licenseSlug:
- description: URL-friendly identifier of the license
+ type:
+ description: Value type discriminator
+ enum:
+ - boolean
examples:
- - enterprise-license
+ - boolean
type: string
- limit:
- description: License grant this usage is measured against, discriminated by the 'type' field. Null when the license grants no value; -1 means unlimited for a NUMBER entitlement.
- discriminator:
- mapping:
- boolean: "#/components/schemas/BooleanEntitlementValue"
- number: "#/components/schemas/NumberEntitlementValue"
- object: "#/components/schemas/ConfigEntitlementValue"
- propertyName: type
- oneOf:
- - $ref: "#/components/schemas/NumberEntitlementValue"
- - $ref: "#/components/schemas/BooleanEntitlementValue"
- - $ref: "#/components/schemas/ConfigEntitlementValue"
value:
- description: Resolved entitlement value, discriminated by the 'type' field
- discriminator:
- mapping:
- boolean: "#/components/schemas/BooleanEntitlementValue"
- number: "#/components/schemas/NumberEntitlementValue"
- object: "#/components/schemas/ConfigEntitlementValue"
- propertyName: type
- oneOf:
- - $ref: "#/components/schemas/NumberEntitlementValue"
- - $ref: "#/components/schemas/BooleanEntitlementValue"
- - $ref: "#/components/schemas/ConfigEntitlementValue"
+ description: Boolean value
+ examples:
+ - true
+ type: boolean
required:
- - entitlementId
- - entitlementSlug
- - licenseId
- - licenseSlug
+ - type
- value
type: object
- ErrorDetail:
- additionalProperties: false
- properties:
- location:
- description: Where the error occurred, e.g. 'body.items[3].tags' or 'path.thing-id'
- type: string
- message:
- description: Error message text
- type: string
- value:
- description: The value at the given location
- type: object
- EvaluationFailure:
+ BulkEvaluationFailure:
additionalProperties: false
properties:
errorCode:
- description: OpenFeature compatible error code
+ description: Error code specific to the bulk evaluation error
type: string
errorDetails:
- description: Error description
- type: string
- key:
- description: Feature flag key
+ description: Optional error details description
type: string
- metadata:
- additionalProperties: {}
- description: Arbitrary metadata for the flag
- type: object
required:
- - key
- errorCode
type: object
- EvaluationRequest:
+ BulkEvaluationRequest:
additionalProperties: false
properties:
context:
additionalProperties: {}
type: object
type: object
- EvaluationSuccess:
+ CanceledSubscription:
additionalProperties: false
properties:
- key:
- description: Feature flag key
+ anchorAt:
+ description: The instant its periods are counted from
+ format: date-time
type: string
- metadata:
- additionalProperties: {}
- description: Arbitrary metadata for the flag
- type: object
- reason:
- description: OpenFeature reason for the evaluation
- examples:
- - STATIC
+ basePrice:
+ $ref: "#/components/schemas/Price"
+ description: The FLAT_FEE price the subscription is pinned to, deprecated or not
+ billingPeriod:
+ enum:
+ - MONTHLY
+ - QUARTERLY
+ - SEMI_ANNUAL
+ - ANNUAL
type: string
- value:
- description: Flag evaluation result
- variant:
- description: string of the evaluated flag value
+ cancelAtPeriodEnd:
+ type: boolean
+ cancelRequestedAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ canceledAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ cancellationReason:
+ type:
+ - string
+ - "null"
+ collectionMethod:
+ description: "Effective: the subscription's own, else the organization's default"
+ enum:
+ - SEND_INVOICE
+ - CHARGE_AUTOMATICALLY
type: string
- required:
- - key
- type: object
- FeatureFlag:
- additionalProperties: false
- properties:
- default_variant:
- $ref: "#/components/schemas/DefaultVariant"
- description:
- examples:
- - This is a new feature flag
+ collectionMethodOverride:
+ description: The subscription's own collection method; null when it takes the organization's
type:
- string
- "null"
- enabled:
- examples:
- - true
- type: boolean
- event_name:
- examples:
- - feature_flag_event
+ createdAt:
+ format: date-time
type: string
- id:
+ currency:
examples:
- - 123e4567-e89b-12d3-a456-426614174000
- readOnly: true
+ - EUR
type: string
- metadata:
- additionalProperties: {}
- type: object
- name:
- examples:
- - New Feature
- minLength: 1
+ currentPeriodEnd:
+ description: "The next boundary: the period closes, and its invoice is composed, after it"
+ format: date-time
type: string
- slug:
- examples:
- - new-feature
- maxLength: 100
- minLength: 2
- pattern: ^[a-z0-9][a-z0-9-]*[a-z0-9]$
+ currentPeriodStart:
+ format: date-time
type: string
- targetings:
- $ref: "#/components/schemas/Targetings"
- type:
- enum:
- - boolean
- - string
- - number
- - object
- examples:
- - boolean
+ customerName:
type: string
- variants:
- items:
- $ref: "#/components/schemas/Variant"
+ customerSlug:
+ type: string
+ daysUntilDue:
+ description: "Effective: the subscription's own, else the organization's default"
+ format: int32
+ type: integer
+ daysUntilDueOverride:
+ description: The subscription's own payment terms; null when it takes the organization's
+ format: int32
type:
- - array
+ - integer
- "null"
- required:
- - id
- - type
- - variants
- - targetings
- - name
- - description
- - default_variant
- - metadata
- - enabled
- - event_name
- type: object
- FeatureFlagEvaluated:
- additionalProperties: false
- properties:
- error_code:
- description: Set when the evaluation failed and the default was returned
+ finalInvoice:
+ $ref: "#/components/schemas/InvoiceSummary"
+ description: The FINAL invoice of an immediate cancellation
+ id:
type: string
- error_message:
- description: Human-readable detail accompanying error_code
+ incompleteExpiresAt:
+ description: "When a checkout waiting for its payment ends unpaid: INCOMPLETE, or a free subscription that carries one"
+ format: date-time
+ type:
+ - string
+ - "null"
+ instanceName:
type: string
- flag_id:
- description: Unique identifier of the evaluated flag
+ instanceSlug:
+ description: As it was at the subscription's last write
type: string
- flag_slug:
- description: URL-friendly identifier of the evaluated flag
+ pastDueSince:
+ format: date-time
+ type:
+ - string
+ - "null"
+ pendingLicenseSlug:
+ description: The licence version a checkout waiting for its payment moves the instance to once paid
+ type:
+ - string
+ - "null"
+ providerKind:
+ description: "Who collects its invoices. NOOP: the organization itself, through the handoff queue"
+ enum:
+ - NOOP
+ - STRIPE
type: string
- matched_rule_name:
- description: Name of the targeting rule that matched, when the reason is TARGETING_MATCH
+ reminderAt:
+ description: "When trial_will_end is (or was) emitted: trialEndsAt minus the family's trialReminderDays as it was when the trial started; set with trialEndsAt"
+ format: date-time
+ type:
+ - string
+ - "null"
+ scheduledChange:
+ description: A plan change waiting for the next boundary
+ oneOf:
+ - $ref: "#/components/schemas/ScheduledChange"
+ - enum:
+ - null
+ type: "null"
+ startedAt:
+ description: "When billing started: usage before it is never billed"
+ format: date-time
type: string
- reason:
- description: Why the value resolved as it did
+ status:
+ enum:
+ - INCOMPLETE
+ - TRIAL
+ - ACTIVE
+ - PAST_DUE
+ - CANCELED
type: string
- variant:
- description: Name of the resolved variant, when the flag has one
+ trialEndsAt:
+ description: "When the trial ends, or ended: usage before it is never billed"
+ format: date-time
+ type:
+ - string
+ - "null"
+ updatedAt:
+ format: date-time
type: string
required:
- - flag_slug
- - flag_id
- - reason
+ - id
+ - instanceSlug
+ - instanceName
+ - customerSlug
+ - customerName
+ - status
+ - providerKind
+ - collectionMethod
+ - collectionMethodOverride
+ - daysUntilDue
+ - daysUntilDueOverride
+ - basePrice
+ - billingPeriod
+ - currency
+ - anchorAt
+ - startedAt
+ - currentPeriodStart
+ - currentPeriodEnd
+ - trialEndsAt
+ - reminderAt
+ - pendingLicenseSlug
+ - incompleteExpiresAt
+ - scheduledChange
+ - cancelAtPeriodEnd
+ - cancelRequestedAt
+ - canceledAt
+ - cancellationReason
+ - pastDueSince
+ - createdAt
+ - updatedAt
type: object
- Impact:
+ CancellationChangeDelivered:
additionalProperties: false
properties:
- count:
- description: Number of resources whose stored value for this field would no longer satisfy the candidate schema
- format: int64
- type: integer
- samples:
- description: Up to 5 impacted resources, for display
- items:
- $ref: "#/components/schemas/ImpactSample"
+ anchorAt:
+ description: The instant its periods are counted from
+ format: date-time
+ type: string
+ basePrice:
+ $ref: "#/components/schemas/Price"
+ description: The FLAT_FEE price the subscription is pinned to, deprecated or not
+ billingPeriod:
+ enum:
+ - MONTHLY
+ - QUARTERLY
+ - SEMI_ANNUAL
+ - ANNUAL
+ type: string
+ cancelAtPeriodEnd:
+ type: boolean
+ cancelRequestedAt:
+ format: date-time
type:
- - array
+ - string
- "null"
- required:
- - count
- - samples
- type: object
- ImpactSample:
- additionalProperties: false
- properties:
- name:
- description: Resource display name
+ canceledAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ cancellationReason:
+ type:
+ - string
+ - "null"
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
type: string
- slug:
- description: Resource slug
+ collectionMethod:
+ description: "Effective: the subscription's own, else the organization's default"
+ enum:
+ - SEND_INVOICE
+ - CHARGE_AUTOMATICALLY
type: string
- required:
- - name
- - slug
- type: object
- Instance:
- additionalProperties: false
- properties:
+ collectionMethodOverride:
+ description: The subscription's own collection method; null when it takes the organization's
+ type:
+ - string
+ - "null"
createdAt:
- description: Timestamp when the instance was created
- examples:
- - "2023-10-01T12:00:00Z"
format: date-time
- readOnly: true
type: string
- createdBy:
- $ref: "#/components/schemas/User"
- description: User who created this instance
- readOnly: true
- customerId:
- description: ID of the customer associated with this instance
+ currency:
examples:
- - 123e4567-e89b-12d3-a456-426614174003
+ - EUR
type: string
- customerSlug:
- description: Slug of the customer associated with this instance
- examples:
- - acme-corp
- readOnly: true
+ currentPeriodEnd:
+ description: "The next boundary: the period closes, and its invoice is composed, after it"
+ format: date-time
type: string
- deploymentZoneId:
- description: ID of the deployment zone associated with this instance (optional)
- examples:
- - 123e4567-e89b-12d3-a456-426614174005
+ currentPeriodStart:
+ format: date-time
type: string
- deploymentZoneSlug:
- description: Slug of the deployment zone associated with this instance (optional)
- examples:
- - aws-us-west-2
- readOnly: true
+ customerName:
type: string
- description:
- description: Brief description of the instance
- examples:
- - This is a sample instance description
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
type: string
- endLicenseDate:
- description: End date of the license for this instance
- examples:
- - "2024-10-01T12:00:00Z"
+ customerSlug:
+ type: string
+ daysUntilDue:
+ description: "Effective: the subscription's own, else the organization's default"
+ format: int32
+ type: integer
+ daysUntilDueOverride:
+ description: The subscription's own payment terms; null when it takes the organization's
+ format: int32
+ type:
+ - integer
+ - "null"
+ effectiveAt:
+ description: The boundary the cancellation takes effect at
format: date-time
type: string
id:
- description: Unique identifier for the instance
- examples:
- - 123e4567-e89b-12d3-a2456-426614174000
- readOnly: true
- type: string
- integrations:
- additionalProperties:
- $ref: "#/components/schemas/InstanceIntegration"
- description: Integrations grouped by adapter name
- type: object
- licenseId:
- description: ID of the license version this instance is pinned to. An ARCHIVED version cannot be assigned, on create or by changing the license (CreateInstance.LicenseArchived, UpdateInstance.LicenseArchived); an instance already on a version that is archived later keeps it. A DRAFT version can be assigned, to try it before publishing.
- examples:
- - 123e4567-e89b-12d3-a456-426614174004
- type: string
- licenseSlug:
- description: Slug of the license associated with this instance
- examples:
- - my-license
- readOnly: true
type: string
- lifecycleStage:
- description: "Commercial lifecycle stage of the instance (free-form; suggested: TRIAL, ACTIVE, AT_RISK, CHURNED)"
- examples:
- - ACTIVE
- readOnly: true
+ incompleteExpiresAt:
+ description: "When a checkout waiting for its payment ends unpaid: INCOMPLETE, or a free subscription that carries one"
+ format: date-time
+ type:
+ - string
+ - "null"
+ instanceName:
type: string
- metadata:
- additionalProperties: {}
- description: Metadata for the instance
- examples:
- - key: value
- type: object
- name:
- description: Name of the instance
- examples:
- - My Instance
- minLength: 1
+ instanceSlug:
+ description: As it was at the subscription's last write
type: string
- slug:
- description: Optional URL-friendly identifier, unique per organization. Auto-generated if not provided.
- examples:
- - my-instance
- maxLength: 100
- minLength: 2
- pattern: ^[a-z0-9][a-z0-9-]*[a-z0-9]$
+ pastDueSince:
+ format: date-time
+ type:
+ - string
+ - "null"
+ pendingLicenseSlug:
+ description: The licence version a checkout waiting for its payment moves the instance to once paid
+ type:
+ - string
+ - "null"
+ providerKind:
+ description: "Who collects its invoices. NOOP: the organization itself, through the handoff queue"
+ enum:
+ - NOOP
+ - STRIPE
type: string
- startLicenseDate:
- description: Start date of the license for this instance
- examples:
- - "2023-10-01T12:00:00Z"
+ reminderAt:
+ description: "When trial_will_end is (or was) emitted: trialEndsAt minus the family's trialReminderDays as it was when the trial started; set with trialEndsAt"
+ format: date-time
+ type:
+ - string
+ - "null"
+ scheduledChange:
+ description: A plan change waiting for the next boundary
+ oneOf:
+ - $ref: "#/components/schemas/ScheduledChange"
+ - enum:
+ - null
+ type: "null"
+ startedAt:
+ description: "When billing started: usage before it is never billed"
format: date-time
type: string
status:
- description: Operational status of the instance
enum:
- - HEALTHY
- - DEGRADED
- - INCIDENT
- - MAINTENANCE
- examples:
- - HEALTHY
- readOnly: true
+ - INCOMPLETE
+ - TRIAL
+ - ACTIVE
+ - PAST_DUE
+ - CANCELED
type: string
+ trialEndsAt:
+ description: "When the trial ends, or ended: usage before it is never billed"
+ format: date-time
+ type:
+ - string
+ - "null"
updatedAt:
- description: Timestamp when the instance was last updated
- examples:
- - "2023-10-01T12:00:00Z"
format: date-time
- readOnly: true
type: string
- updatedBy:
- $ref: "#/components/schemas/User"
- description: User who last updated this instance
- readOnly: true
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
+ type: string
required:
+ - effectiveAt
- id
- - createdBy
+ - instanceSlug
+ - instanceName
+ - customerSlug
+ - customerName
+ - status
+ - providerKind
+ - collectionMethod
+ - collectionMethodOverride
+ - daysUntilDue
+ - daysUntilDueOverride
+ - basePrice
+ - billingPeriod
+ - currency
+ - anchorAt
+ - startedAt
+ - currentPeriodStart
+ - currentPeriodEnd
+ - trialEndsAt
+ - reminderAt
+ - pendingLicenseSlug
+ - incompleteExpiresAt
+ - scheduledChange
+ - cancelAtPeriodEnd
+ - cancelRequestedAt
+ - canceledAt
+ - cancellationReason
+ - pastDueSince
- createdAt
- - updatedBy
- updatedAt
- - name
- - description
- - customerId
- - customerSlug
- - licenseId
- - licenseSlug
- - startLicenseDate
- - endLicenseDate
type: object
- InstanceBody:
+ x-kaiten-payload: "#/components/schemas/CancellationChange"
+ CancellationDelivered:
additionalProperties: false
properties:
- customerExternalId:
- description: External identifier of the related customer in the same adapter
- examples:
- - rec_customer_123
- type: string
- deploymentZoneId:
- description: Deployment zone ID
- examples:
- - 123e4567-e89b-12d3-a456-426614174005
+ anchorAt:
+ description: The instant its periods are counted from
+ format: date-time
type: string
- description:
- description: Instance description
- examples:
- - This is a sample instance description
+ basePrice:
+ $ref: "#/components/schemas/Price"
+ description: The FLAT_FEE price the subscription is pinned to, deprecated or not
+ billingPeriod:
+ enum:
+ - MONTHLY
+ - QUARTERLY
+ - SEMI_ANNUAL
+ - ANNUAL
type: string
- endLicenseDate:
- description: License end date
- examples:
- - "2024-10-01T12:00:00Z"
+ cancelAtPeriodEnd:
+ type: boolean
+ cancelRequestedAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ canceledAt:
format: date-time
+ type:
+ - string
+ - "null"
+ cancellationReason:
+ type:
+ - string
+ - "null"
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
type: string
- error:
- description: Optional integration error message
+ collectionMethod:
+ description: "Effective: the subscription's own, else the organization's default"
+ enum:
+ - SEND_INVOICE
+ - CHARGE_AUTOMATICALLY
type: string
- integrationMetadata:
- additionalProperties: {}
- description: Adapter-specific integration metadata
- type: object
- licenseId:
- description: License ID
- examples:
- - 123e4567-e89b-12d3-a456-426614174004
+ collectionMethodOverride:
+ description: The subscription's own collection method; null when it takes the organization's
+ type:
+ - string
+ - "null"
+ createdAt:
+ format: date-time
type: string
- metadata:
- additionalProperties: {}
- description: Instance metadata
- type: object
- name:
- description: Instance name
+ currency:
examples:
- - My Instance
- minLength: 1
+ - EUR
type: string
- slug:
- description: Instance slug
- examples:
- - my-instance
- maxLength: 100
- minLength: 2
- pattern: ^[a-z0-9][a-z0-9-]*[a-z0-9]$
+ currentPeriodEnd:
+ description: "The next boundary: the period closes, and its invoice is composed, after it"
+ format: date-time
type: string
- startLicenseDate:
- description: License start date
- examples:
- - "2023-10-01T12:00:00Z"
+ currentPeriodStart:
format: date-time
type: string
- webUrl:
- description: Absolute http(s) link to the record in the third-party system
- examples:
- - https://app.attio.com/w/acme/record/rec_12345
+ customerName:
type: string
- type: object
- InstanceEntitlementCapExceeded:
- additionalProperties: false
- properties:
- entitlement_slug:
- description: URL-friendly identifier of the entitlement
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
type: string
- overage:
- description: Amount by which usage exceeds the cap
- format: double
- type: number
- threshold:
- description: The entitlement cap
- format: double
- type: number
- value:
- description: Current usage value, including overage
- format: double
- type: number
- required:
- - entitlement_slug
- - threshold
- - value
- - overage
- type: object
- InstanceEntitlementUsagePeriodRolledOver:
- additionalProperties: false
- properties:
- closed_period_end:
+ customerSlug:
+ type: string
+ daysUntilDue:
+ description: "Effective: the subscription's own, else the organization's default"
+ format: int32
+ type: integer
+ daysUntilDueOverride:
+ description: The subscription's own payment terms; null when it takes the organization's
+ format: int32
+ type:
+ - integer
+ - "null"
+ finalInvoiceId:
+ description: The FINAL invoice; null when the cancellation issued none (a trial)
+ type:
+ - string
+ - "null"
+ id:
+ type: string
+ incompleteExpiresAt:
+ description: "When a checkout waiting for its payment ends unpaid: INCOMPLETE, or a free subscription that carries one"
format: date-time
type:
- string
- "null"
- closed_period_start:
+ instanceName:
+ type: string
+ instanceSlug:
+ description: As it was at the subscription's last write
+ type: string
+ mode:
+ description: How it was canceled; a trial is canceled at once in either mode
+ enum:
+ - AT_PERIOD_END
+ - IMMEDIATE
+ type: string
+ pastDueSince:
format: date-time
type:
- string
- "null"
- entitlement_id:
+ pendingLicenseSlug:
+ description: The licence version a checkout waiting for its payment moves the instance to once paid
+ type:
+ - string
+ - "null"
+ providerKind:
+ description: "Who collects its invoices. NOOP: the organization itself, through the handoff queue"
+ enum:
+ - NOOP
+ - STRIPE
type: string
- entitlement_slug:
+ reminderAt:
+ description: "When trial_will_end is (or was) emitted: trialEndsAt minus the family's trialReminderDays as it was when the trial started; set with trialEndsAt"
+ format: date-time
+ type:
+ - string
+ - "null"
+ scheduledChange:
+ description: A plan change waiting for the next boundary
+ oneOf:
+ - $ref: "#/components/schemas/ScheduledChange"
+ - enum:
+ - null
+ type: "null"
+ startedAt:
+ description: "When billing started: usage before it is never billed"
+ format: date-time
type: string
- event_count:
- format: int32
- type: integer
- instance_id:
+ status:
+ enum:
+ - INCOMPLETE
+ - TRIAL
+ - ACTIVE
+ - PAST_DUE
+ - CANCELED
type: string
- is_synthetic:
- type: boolean
- new_period_start:
+ trialEndsAt:
+ description: "When the trial ends, or ended: usage before it is never billed"
+ format: date-time
+ type:
+ - string
+ - "null"
+ updatedAt:
format: date-time
type: string
- value:
- format: double
- type: number
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
+ type: string
required:
- - entitlement_id
- - entitlement_slug
- - instance_id
- - value
- - event_count
- - closed_period_start
- - closed_period_end
- - new_period_start
- - is_synthetic
+ - mode
+ - finalInvoiceId
+ - id
+ - instanceSlug
+ - instanceName
+ - customerSlug
+ - customerName
+ - status
+ - providerKind
+ - collectionMethod
+ - collectionMethodOverride
+ - daysUntilDue
+ - daysUntilDueOverride
+ - basePrice
+ - billingPeriod
+ - currency
+ - anchorAt
+ - startedAt
+ - currentPeriodStart
+ - currentPeriodEnd
+ - trialEndsAt
+ - reminderAt
+ - pendingLicenseSlug
+ - incompleteExpiresAt
+ - scheduledChange
+ - cancelAtPeriodEnd
+ - cancelRequestedAt
+ - canceledAt
+ - cancellationReason
+ - pastDueSince
+ - createdAt
+ - updatedAt
type: object
- InstanceEntitlementUsageReportAccepted:
+ x-kaiten-payload: "#/components/schemas/Cancellation"
+ CheckoutConfirmation:
additionalProperties: false
properties:
- behavior:
- description: "Report behavior: append folds the value into the stored total, set overwrites it"
- enum:
- - append
- - set
+ session:
+ $ref: "#/components/schemas/CheckoutSession"
+ setupExpiresAt:
+ description: requires_payment_method only
+ format: date-time
type: string
- entitlement_slug:
- description: URL-friendly identifier of the entitlement the usage was reported against
+ setupUrl:
+ description: "requires_payment_method only: the hosted page that saves the payment method"
type: string
- event_count:
- description: Number of reports folded into the current usage window, including this one
- format: int32
- type: integer
status:
- description: Whether the report was persisted
enum:
- - ACCEPTED
- - REJECTED
+ - requires_payment_method
+ - awaiting_payment
+ - completed
type: string
- value:
- description: Value carried by the report
- format: double
- type: number
required:
- - entitlement_slug
- - value
- - behavior
- - event_count
- status
+ - session
type: object
- InstanceEntitlementUsageWarningThresholdReached:
+ CheckoutPayment:
additionalProperties: false
properties:
- boundary:
- description: Usage value at which the warning fires (cap * warningThresholdPercent / 100)
- format: double
- type: number
- entitlement_slug:
- description: URL-friendly identifier of the entitlement
+ hostedInvoiceUrl:
+ type:
+ - string
+ - "null"
+ status:
+ enum:
+ - paid
+ - requires_action
+ - failed
+ - processing
+ - not_required
type: string
- threshold:
- description: The entitlement cap
- format: double
- type: number
- value:
- description: Current usage value
- format: double
- type: number
required:
- - entitlement_slug
- - threshold
- - boundary
- - value
+ - status
+ - hostedInvoiceUrl
type: object
- InstanceEntitlementValueGet:
+ CheckoutSession:
additionalProperties: false
properties:
- entitlement_id:
- description: Unique identifier for the entitlement
- type: string
- entitlement_slug:
- description: URL-friendly identifier of the entitlement
- type: string
- instance_id:
- description: Unique identifier for the instance the value was read for
+ activationInvoice:
+ description: Once an ACTIVATION was issued
+ oneOf:
+ - $ref: "#/components/schemas/SessionInvoice"
+ - enum:
+ - null
+ type: "null"
+ addOns:
+ items:
+ $ref: "#/components/schemas/SessionAddonSelection"
+ type: array
+ billingPeriod:
type: string
- license_id:
- description: Unique identifier for the license carrying the entitlement
+ clientReferenceId:
+ type:
+ - string
+ - "null"
+ completedAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ createdAt:
+ format: date-time
type: string
- organization_id:
- description: Unique identifier for the owning organization
+ customerSlug:
+ description: Set by the completion
+ type:
+ - string
+ - "null"
+ dueToday:
+ $ref: "#/components/schemas/DueToday"
+ description: The voucher's discount shows as a line; the code itself is never echoed
+ email:
type: string
- timestamp:
- description: When the value was read
+ expiresAt:
format: date-time
type: string
- type:
- description: Discriminator of the resolved entitlement value
- examples:
- - number
+ instanceSlug:
+ description: Set by the completion
+ type:
+ - string
+ - "null"
+ licenseSlug:
type: string
- value:
- description: Resolved entitlement value at read time
- discriminator:
- mapping:
- boolean: "#/components/schemas/BooleanEntitlementValue"
- number: "#/components/schemas/NumberEntitlementValue"
- object: "#/components/schemas/ConfigEntitlementValue"
- propertyName: type
+ payment:
oneOf:
- - $ref: "#/components/schemas/NumberEntitlementValue"
- - $ref: "#/components/schemas/BooleanEntitlementValue"
- - $ref: "#/components/schemas/ConfigEntitlementValue"
+ - $ref: "#/components/schemas/CheckoutPayment"
+ - enum:
+ - null
+ type: "null"
+ priceId:
+ type: string
+ requiresPaymentMethod:
+ type: boolean
+ sessionId:
+ type: string
+ status:
+ description: A session still OPEN after expiresAt reads ABANDONED
+ enum:
+ - OPEN
+ - AWAITING_PAYMENT
+ - COMPLETED
+ - ABANDONED
+ - EXPIRED
+ type: string
+ trialEndsAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ trialRefusedReason:
+ type:
+ - string
+ - "null"
required:
- - entitlement_id
- - instance_id
- - license_id
- - organization_id
- - type
- - value
- - timestamp
- - entitlement_slug
+ - sessionId
+ - status
+ - email
+ - clientReferenceId
+ - licenseSlug
+ - billingPeriod
+ - priceId
+ - addOns
+ - dueToday
+ - trialEndsAt
+ - trialRefusedReason
+ - requiresPaymentMethod
+ - expiresAt
+ - customerSlug
+ - instanceSlug
+ - activationInvoice
+ - payment
+ - createdAt
+ - completedAt
type: object
- InstanceIntegration:
+ CheckoutSessionAdmin:
additionalProperties: false
properties:
- external_id:
- description: External identifier in the third-party adapter
- examples:
- - rec_12345
+ addOns:
+ items:
+ $ref: "#/components/schemas/SessionAddonSelection"
+ type: array
+ billingPeriod:
type: string
- last_error:
- description: Last synchronization error message, if any
+ clientReferenceId:
+ type:
+ - string
+ - "null"
+ closedAt:
+ description: When it became COMPLETED, ABANDONED or EXPIRED; null while OPEN or AWAITING_PAYMENT
+ format: date-time
+ type:
+ - string
+ - "null"
+ completedAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ createdAt:
+ format: date-time
type: string
- metadata:
- additionalProperties: {}
- description: Adapter-specific integration metadata
- type: object
- synced_at:
- description: Timestamp of the last synchronization with the adapter
+ customerSlug:
+ description: Set by the completion
+ type:
+ - string
+ - "null"
+ dueToday:
+ $ref: "#/components/schemas/DueToday"
+ email:
+ description: As the visitor gave it. Personal data
+ type: string
+ expiresAt:
format: date-time
- readOnly: true
type: string
- web_url:
- description: Absolute http(s) link to the record in the third-party system
- examples:
- - https://app.attio.com/w/acme/workspace/rec_12345
+ instanceSlug:
+ description: Set by the completion
+ type:
+ - string
+ - "null"
+ licenseSlug:
+ type: string
+ priceId:
+ type: string
+ publishableKeyId:
+ description: The key whose page started it
type: string
+ sessionId:
+ type: string
+ status:
+ description: A session still OPEN after expiresAt reads ABANDONED
+ enum:
+ - OPEN
+ - AWAITING_PAYMENT
+ - COMPLETED
+ - ABANDONED
+ - EXPIRED
+ type: string
+ trialEndsAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ trialRefusedReason:
+ type:
+ - string
+ - "null"
required:
- - external_id
- - synced_at
+ - sessionId
+ - publishableKeyId
+ - status
+ - email
+ - clientReferenceId
+ - licenseSlug
+ - billingPeriod
+ - priceId
+ - addOns
+ - dueToday
+ - trialEndsAt
+ - trialRefusedReason
+ - customerSlug
+ - instanceSlug
+ - createdAt
+ - expiresAt
+ - completedAt
+ - closedAt
type: object
- InstanceIntegrationResource:
+ CheckoutSessionDraft:
additionalProperties: false
properties:
- customerExternalId:
- description: External identifier of the related customer in the same adapter
- examples:
- - rec_customer_123
- type: string
- deploymentZoneId:
- description: Deployment zone ID
- examples:
- - 123e4567-e89b-12d3-a456-426614174005
- type: string
- description:
- description: Instance description
- examples:
- - This is a sample instance description
+ addOns:
+ items:
+ $ref: "#/components/schemas/SubscriptionAddon"
+ maxItems: 50
+ type:
+ - array
+ - "null"
+ billingPeriod:
+ description: MONTHLY, QUARTERLY, SEMI_ANNUAL or ANNUAL, with licenseSlug
type: string
- endLicenseDate:
- description: License end date
- examples:
- - "2024-10-01T12:00:00Z"
- format: date-time
+ clientReferenceId:
+ description: The vendor's opaque reference, echoed on the checkout_session events
+ maxLength: 255
+ minLength: 1
type: string
- error:
- description: Last synchronization error for this integration, if any
+ dryRun:
+ description: Compute what would be due today, and write nothing
+ type: boolean
+ email:
+ description: The buyer's e-mail. It becomes the new customer's billing e-mail at completion. Required unless dryRun.
+ maxLength: 254
type: string
- integrationMetadata:
- additionalProperties: {}
- description: Adapter-specific instance integration metadata
- type: object
- licenseId:
- description: License ID
- examples:
- - 123e4567-e89b-12d3-a456-426614174004
+ licenseSlug:
+ description: "A public plan's version, with billingPeriod: its default ACTIVE FLAT_FEE price of that period"
+ maxLength: 255
type: string
- name:
- description: Instance name
- examples:
- - My Instance
+ priceId:
+ description: A FLAT_FEE price of a public plan. Not with licenseSlug and billingPeriod.
type: string
- slug:
- description: Instance slug
- examples:
- - my-instance
+ returnUrl:
+ description: "Where the payment-method setup page sends the visitor back, with kaiten_checkout_session= appended: https, or http on localhost, on an origin the key allows. Required unless dryRun."
+ maxLength: 2048
type: string
- startLicenseDate:
- description: License start date
- examples:
- - "2023-10-01T12:00:00Z"
- format: date-time
+ trial:
+ description: false declines the licence's trial
+ type: boolean
+ turnstileToken:
+ description: "The token the Turnstile widget gave the page. Single-use: a dryRun spends one too."
+ maxLength: 2048
+ minLength: 1
type: string
- webUrl:
- description: Absolute http(s) link to the record in the third-party system
- examples:
- - https://app.attio.com/w/acme/workspace/rec_12345
+ voucherCode:
+ description: "A code to apply, checked against the selection, with no customer yet: a voucher restricted to a customer never applies, and one without a PRICE part only when nothing is due today. Its discount counts in dueToday; the completion, or the payment, records its redemption. Any refusal answers 422 .VoucherInvalid"
+ maxLength: 64
type: string
required:
- - description
- - endLicenseDate
- - licenseId
- - integrationMetadata
- - name
- - slug
- - startLicenseDate
+ - turnstileToken
type: object
- InstanceLifecycleStageChanged:
+ CheckoutSessionStart:
additionalProperties: false
properties:
- changed_by:
- format: uuid
+ dueToday:
+ $ref: "#/components/schemas/DueToday"
+ expiresAt:
+ description: "open only: 24 h after the start"
+ format: date-time
type: string
- instance_id:
- format: uuid
+ requiresPaymentMethod:
+ description: Whether the confirm will send the visitor to a payment-method setup page
+ type: boolean
+ sessionId:
+ description: open only
type: string
- new_lifecycle_stage:
+ status:
+ enum:
+ - preview
+ - open
+ type: string
+ token:
+ description: "open only: the session's ksc_, returned once. Keep it in memory and sessionStorage, never in a URL."
+ type: string
+ trialEndsAt:
+ description: The end of the trial the subscription would start with; null without one
+ format: date-time
type:
- string
- "null"
- previous_lifecycle_stage:
+ trialRefusedReason:
+ description: "ALREADY_CONSUMED: a customer with this e-mail already had a trial of the family, so none is given; null otherwise"
type:
- string
- "null"
required:
- - instance_id
- - previous_lifecycle_stage
- - new_lifecycle_stage
- - changed_by
+ - status
+ - dueToday
+ - trialEndsAt
+ - trialRefusedReason
+ - requiresPaymentMethod
type: object
- InstanceStatusChanged:
+ CloseBacklog:
additionalProperties: false
properties:
- changed_by:
- format: uuid
- type: string
- instance_id:
- format: uuid
- type: string
- new_status:
- enum:
- - HEALTHY
- - DEGRADED
- - INCIDENT
- - MAINTENANCE
- type: string
- previous_status:
- enum:
- - HEALTHY
- - DEGRADED
- - INCIDENT
- - MAINTENANCE
- type: string
+ count:
+ format: int64
+ type: integer
+ oldestDueAt:
+ format: date-time
+ type:
+ - string
+ - "null"
required:
- - instance_id
- - previous_status
- - new_status
- - changed_by
+ - count
+ - oldestDueAt
type: object
- License:
+ ClosePeriodsReport:
additionalProperties: false
properties:
- createdAt:
- description: Timestamp when this license was created
- examples:
- - "2023-10-01T12:00:00Z"
- format: date-time
- readOnly: true
- type: string
- description:
- description: Detailed description of the license capabilities and restrictions
- examples:
- - This license provides access to all enterprise features including advanced analytics, priority support, and unlimited users.
- maxLength: 500
+ closed:
+ description: Periods closed into an invoice
+ format: int64
+ type: integer
+ examined:
+ description: Subscriptions the batch looked at
+ format: int64
+ type: integer
+ hasMore:
+ description: Set when due subscriptions are left beyond the batch
+ type: boolean
+ held:
+ description: Of those, invoices held as DRAFTs for their usage journal
+ format: int64
+ type: integer
+ invoices:
+ items:
+ $ref: "#/components/schemas/ClosedInvoice"
+ type: array
+ skipped:
+ description: "Subscriptions left for later: held by another transaction, their meters' clock not at the boundary, or failed (logged)"
+ format: int64
+ type: integer
+ required:
+ - examined
+ - closed
+ - held
+ - skipped
+ - hasMore
+ - invoices
+ type: object
+ ClosePeriodsScope:
+ additionalProperties: false
+ properties:
+ instanceSlug:
+ description: Close only this instance's subscription
type: string
- familyId:
- description: "Identifier of the license family this license is a version of. Stable across renames and new versions: two licenses sharing it are the same product. Always present in responses. On create, optional: names the family this license is a new version of, as familySlug does by slug; omit both to create a new family. On update, optional and must be the license's own family: a version cannot move between families (UpdateLicense.FamilyNotReassignable)."
+ type: object
+ ClosedInvoice:
+ additionalProperties: false
+ properties:
+ boundaryAt:
+ format: date-time
+ type: string
+ held:
+ description: Set when the invoice is a held DRAFT
+ type: boolean
+ id:
+ type: string
+ instanceSlug:
+ type: string
+ kind:
+ enum:
+ - ACTIVATION
+ - RENEWAL
+ - FINAL
+ type: string
+ status:
+ enum:
+ - DRAFT
+ - PUSHED
+ - PUSH_FAILED
+ - MANUAL
+ - PAID
+ - PAYMENT_FAILED
+ - UNCOLLECTIBLE
+ - VOID
+ type: string
+ required:
+ - id
+ - instanceSlug
+ - kind
+ - boundaryAt
+ - status
+ - held
+ type: object
+ Command:
+ additionalProperties: false
+ properties:
+ entitlementSlug:
+ type: string
+ required:
+ - entitlementSlug
+ type: object
+ CompatibleLicenseFamilies:
+ additionalProperties: false
+ properties:
+ familySlugs:
examples:
- - 123e4567-e89b-12d3-a456-426614174000
+ - - pro
+ items:
+ type: string
+ type: array
+ required:
+ - familySlugs
+ type: object
+ CompletedPaymentMethodSession:
+ additionalProperties: false
+ properties:
+ paymentMethod:
+ $ref: "#/components/schemas/PaymentMethodLabels"
+ required:
+ - paymentMethod
+ type: object
+ Component:
+ additionalProperties: false
+ properties:
+ createdAt:
+ description: Timestamp when the component was created
+ examples:
+ - "2023-10-01T12:00:00Z"
+ format: date-time
+ readOnly: true
type: string
- familySlug:
- description: "Write-only on create: slug of the family this license is a new version of. familyId names the same family by identifier; sent together they must agree. Omit both to create a new family, of which this license becomes version 1. Never returned -- see familyId. Refused on update: a version cannot move between families."
+ createdBy:
+ $ref: "#/components/schemas/User"
+ description: User who created this component
+ readOnly: true
+ description:
+ description: Description of the component
examples:
- - enterprise-license
- maxLength: 100
- minLength: 2
- pattern: ^[a-z0-9][a-z0-9-]*[a-z0-9]$
+ - Main API gateway component
+ maxLength: 500
type: string
- writeOnly: true
id:
- description: Unique identifier for the license
+ description: Unique identifier for the component
examples:
- 123e4567-e89b-12d3-a456-426614174000
readOnly: true
type: string
- isDefault:
- description: "Whether this version is the one its family puts forward: at most one version per family, and only a PUBLISHED one (CreateLicense.DefaultMustBePublished, UpdateLicense.DefaultMustBePublished). Resolution hands it out ahead of the highest-numbered published version, so it keeps being served when a newer version is published. Setting it takes the flag from the family's previous default, which gets a LICENSE_UPDATED event of its own. On update, sending false unsets it; the default cannot be archived until another version takes its place or the flag is unset (ArchiveLicense.DefaultMustBePublished)."
- examples:
- - false
- type: boolean
- lifecycleState:
- description: Whether this version may be served. Always present in responses. Optional on create, where it is DRAFT or PUBLISHED and PUBLISHED when omitted; ARCHIVED is refused (CreateLicense.LifecycleStateNotSettable), since a version is archived by withdrawing it from sale. After that it changes only through publish-license (DRAFT to PUBLISHED), archive-license (PUBLISHED to ARCHIVED) and unarchive-license (ARCHIVED to PUBLISHED); update accepts the stored state and refuses any other (UpdateLicense.LifecycleStateNotSettable). Only a PUBLISHED version can be the family's default. An ARCHIVED version cannot be assigned to an instance (CreateInstance.LicenseArchived, UpdateInstance.LicenseArchived); instances already on it keep it.
- enum:
- - DRAFT
- - PUBLISHED
- - ARCHIVED
- examples:
- - PUBLISHED
- type: string
name:
- description: Display name of the license
+ description: Name of the component
examples:
- - Enterprise License
+ - api-gateway
maxLength: 100
minLength: 1
type: string
+ previousComponentId:
+ description: Reference to the previous version of this component
+ examples:
+ - 123e4567-e89b-12d3-a456-426614174000
+ type: string
slug:
- description: Optional URL-friendly identifier, unique per organization. When omitted, a new version of an existing family takes {familySlug}-v{version}, and any other license gets one generated from its name. A license that opens a new family gives the family its slug.
+ description: Optional URL-friendly identifier, unique per organization. Auto-generated if not provided.
examples:
- - enterprise-license
+ - api-gateway-v1-2-3
maxLength: 100
minLength: 2
pattern: ^[a-z0-9][a-z0-9-]*[a-z0-9]$
type: string
- type:
- description: Type of the license determining its capabilities and restrictions
- enum:
- - DEVELOPMENT
- - TRIAL
- - PAID
- - COMMUNITY
- examples:
- - paid
- type: string
- updatedAt:
- description: Timestamp when this license was last updated
- examples:
- - "2023-10-02T12:00:00Z"
- format: date-time
- readOnly: true
- type: string
version:
- description: "Version of the license within its family, assigned by the server when the version is created and never changed afterwards: the version's derived slug ({familySlug}-v{version}, see slug) is built from it. A number is never reused within a family, even after its version is deleted. Create refuses any value (CreateLicense.VersionNotSettable); update accepts only the stored one, so a representation can be written back as it was read (UpdateLicense.VersionNotSettable)."
- examples:
- - "1"
- readOnly: true
- type: string
- versionName:
- description: Optional human-readable name for the license version. Defaults to "Version - ".
+ description: Version of the component
examples:
- - Winter 2024 Release
- maxLength: 50
+ - v1.2.3
+ maxLength: 100
+ minLength: 1
type: string
required:
- id
- name
- - description
- - type
- version
- - isDefault
+ - createdBy
- createdAt
- - updatedAt
type: object
- LicenseEntitlement:
+ ComponentDelivered:
additionalProperties: false
properties:
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
+ type: string
createdAt:
- description: Timestamp when this entitlement was created
+ description: Timestamp when the component was created
examples:
- "2023-10-01T12:00:00Z"
format: date-time
@@ -1798,1396 +2279,14270 @@ components:
type: string
createdBy:
$ref: "#/components/schemas/User"
- description: User who created this entitlement
- readOnly: true
- entitlementGroups:
- description: Groups associated with this entitlement
- items:
- $ref: "#/components/schemas/EntitlementGroupSummary"
- readOnly: true
- type:
- - array
- - "null"
- entitlementName:
- description: Name of the entitlement feature or service
- examples:
- - Premium Support
+ description: User who created this component
readOnly: true
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
type: string
- entitlementSlug:
- description: URL-friendly identifier for the entitlement
+ description:
+ description: Description of the component
examples:
- - premium-support
- format: string
+ - Main API gateway component
+ maxLength: 500
type: string
- entitlementType:
- description: Type of entitlement (BOOLEAN, NUMBER, or CONFIG)
- enum:
- - BOOLEAN
- - NUMBER
- - CONFIG
+ id:
+ description: Unique identifier for the component
examples:
- - NUMBER
+ - 123e4567-e89b-12d3-a456-426614174000
readOnly: true
type: string
- licenseId:
- description: Unique identifier for the license this entitlement belongs to
+ name:
+ description: Name of the component
examples:
- - a1b2c3d4-e5f6-7g8h-9i0j-k1l2m3n4o5p6
- readOnly: true
+ - api-gateway
+ maxLength: 100
+ minLength: 1
type: string
- licenseSlug:
- description: URL-friendly identifier for the license this entitlement belongs to
+ previousComponentId:
+ description: Reference to the previous version of this component
examples:
- - enterprise-license
- readOnly: true
+ - 123e4567-e89b-12d3-a456-426614174000
type: string
- limitCapExceededOveragePercent:
- description: "Derives this grant's enforcement from its own numeric value: -1 when the value is unlimited (no cap, no overage); otherwise 0 rejects usage above the value (hard limit) and a positive percentage allows usage to exceed it by that much before being rejected (soft limit). Null for non-numeric (BOOLEAN/CONFIG) grants."
+ slug:
+ description: Optional URL-friendly identifier, unique per organization. Auto-generated if not provided.
examples:
- - 10
- format: int32
- type: integer
- updatedAt:
- description: Timestamp when this entitlement was last updated
+ - api-gateway-v1-2-3
+ maxLength: 100
+ minLength: 2
+ pattern: ^[a-z0-9][a-z0-9-]*[a-z0-9]$
+ type: string
+ version:
+ description: Version of the component
examples:
- - "2023-10-02T12:00:00Z"
- format: date-time
- readOnly: true
+ - v1.2.3
+ maxLength: 100
+ minLength: 1
+ type: string
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
type: string
- updatedBy:
- $ref: "#/components/schemas/User"
- description: User who last updated this entitlement
- readOnly: true
- value:
- description: "Typed entitlement value. Supported shapes: {type:number,value:number}, {type:boolean,value:boolean}, {type:object,value:{...}}"
- discriminator:
- mapping:
- boolean: "#/components/schemas/BooleanEntitlementValue"
- number: "#/components/schemas/NumberEntitlementValue"
- object: "#/components/schemas/ConfigEntitlementValue"
- propertyName: type
- oneOf:
- - $ref: "#/components/schemas/NumberEntitlementValue"
- - $ref: "#/components/schemas/BooleanEntitlementValue"
- - $ref: "#/components/schemas/ConfigEntitlementValue"
required:
- - entitlementName
- - licenseId
- - licenseSlug
+ - id
+ - name
+ - version
- createdBy
- createdAt
- - updatedBy
- - updatedAt
+ type: object
+ x-kaiten-payload: "#/components/schemas/Component"
+ ConfigEntitlementValue:
+ additionalProperties: false
+ properties:
+ type:
+ description: Value type discriminator
+ enum:
+ - object
+ examples:
+ - object
+ type: string
+ value:
+ additionalProperties: {}
+ description: Object configuration value
+ type: object
+ required:
+ - type
- value
type: object
- LicenseFamilyView:
+ ConnectionDelivered:
additionalProperties: false
properties:
- createdAt:
- description: Timestamp when this family was created
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
+ type: string
+ connectorName:
+ description: The connector that configures it
examples:
- - "2023-10-01T12:00:00Z"
+ - kaiten.integration.billing.stripe
+ type: string
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
+ type: string
+ livemode:
+ description: Whether the connection reaches the provider's live account rather than a test one
+ type: boolean
+ providerKind:
+ description: The payment provider
+ enum:
+ - STRIPE
+ type: string
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
+ type: string
+ required:
+ - providerKind
+ - connectorName
+ - livemode
+ type: object
+ x-kaiten-payload: "#/components/schemas/Connection"
+ Connector:
+ additionalProperties: false
+ properties:
+ created_at:
+ description: Timestamp when connector was first registered
format: date-time
- readOnly: true
type: string
- currentVersion:
- $ref: "#/components/schemas/License"
- description: "The version this read is about. By default the version the family resolves to: its default version if it has one, otherwise its highest-numbered PUBLISHED version. Under ?version=N it is the version asked for instead, whatever its lifecycle state. Absent when the family has no PUBLISHED version, which only a list or an include=versions read returns."
- id:
- description: Unique identifier for the license family
+ entitlement_slug:
+ description: Slug of the BOOLEAN entitlement a license must grant for an organization to activate this connector. Absent means the connector is ungated.
examples:
- - 123e4567-e89b-12d3-a456-426614174000
- readOnly: true
+ - connector-attio
type: string
- slug:
- description: URL-friendly identifier of the family, unique per organization and stable across renames and new versions. Deleting the family's last version deletes the family and frees the slug.
+ name:
+ description: Stable connector identifier
examples:
- - enterprise-license
+ - kaiten.integration.crm.attio
type: string
- updatedAt:
- description: Timestamp when a version was last added to this family, or when the family was created. Renames, lifecycle moves and default changes update the versions' own updatedAt, not this one.
- examples:
- - "2023-10-02T12:00:00Z"
+ settings_schema:
+ additionalProperties: {}
+ description: JSON schema used to validate connector settings payloads
+ type: object
+ updated_at:
+ description: Timestamp when connector registration was last updated
format: date-time
- readOnly: true
type: string
- versionCount:
- description: Number of versions in this family, whatever their lifecycle state
+ version:
+ description: Registered connector version
examples:
- - 3
- format: int32
- type: integer
- versions:
- description: "Every version of the family, oldest first. Only returned when explicitly requested with include=versions, and then never null: a family always has at least one version."
- items:
- $ref: "#/components/schemas/License"
- type: array
+ - 1.0.0
+ type: string
required:
- - id
- - slug
- - versionCount
- - createdAt
- - updatedAt
+ - name
+ - version
+ - settings_schema
+ - created_at
+ - updated_at
type: object
- List:
+ ConnectorActivation:
additionalProperties: false
properties:
- data:
- description: The page, newest first
- items:
- $ref: "#/components/schemas/Notification"
- type: array
- nextCursor:
- description: Opaque cursor for the next page; null on the last page
- type:
- - string
- - "null"
- unreadCount:
- description: Unread notifications, capped (see MaxUnreadCount)
- format: int64
- type: integer
+ activatedAt:
+ description: When this organization first activated the connector
+ format: date-time
+ type: string
+ connectorName:
+ description: Stable connector identifier
+ examples:
+ - kaiten.integration.crm.attio
+ type: string
required:
- - data
- - nextCursor
- - unreadCount
+ - connectorName
+ - activatedAt
type: object
- ManifestEnvelope:
+ ConnectorSettings:
additionalProperties: false
properties:
- flags:
- items:
- $ref: "#/components/schemas/ManifestFlag"
- type:
- - array
- - "null"
+ configured:
+ description: Whether settings were ever stored for this connector. A connector never configured reads false, with empty settings, rather than 404.
+ readOnly: true
+ type: boolean
+ connector_name:
+ description: Stable connector identifier
+ examples:
+ - kaiten.integration.crm.attio
+ readOnly: true
+ type: string
+ settings:
+ additionalProperties: {}
+ description: Connector settings payload
+ type: object
required:
- - flags
+ - connector_name
+ - settings
+ - configured
type: object
- ManifestFlag:
+ ConnectorSettingsSchema:
additionalProperties: false
properties:
- defaultValue: {}
- description:
+ connector_name:
+ description: Stable connector identifier
+ examples:
+ - kaiten.integration.crm.attio
type: string
- key:
+ schema:
+ additionalProperties: {}
+ description: JSON schema describing supported connector settings payload
+ type: object
+ version:
+ description: Registered connector version
+ examples:
+ - 1.0.0
type: string
- name:
+ required:
+ - connector_name
+ - version
+ - schema
+ type: object
+ ConnectorState:
+ additionalProperties: false
+ properties:
+ activated:
+ description: Whether this organization has turned the connector on
+ type: boolean
+ activatedAt:
+ description: When this organization activated the connector. Absent when it has not.
+ format: date-time
type: string
- type:
+ available:
+ description: Whether this installation has the connector registered
+ type: boolean
+ connectorName:
+ description: Stable connector identifier
+ examples:
+ - kaiten.integration.crm.attio
+ type: string
+ entitled:
+ description: Whether this organization's license includes the connector. True for an ungated connector.
+ type: boolean
+ version:
+ description: Registered connector version. Absent when the connector is not available.
+ examples:
+ - 1.0.0
type: string
required:
- - key
- - type
- - defaultValue
+ - connectorName
+ - available
+ - entitled
+ - activated
type: object
- MarkReadResult:
+ CreateRole:
additionalProperties: false
properties:
- unreadCount:
- description: Unread notifications after the change
- format: int64
- type: integer
- updated:
- description: Notifications newly marked read
- format: int64
- type: integer
+ description:
+ maxLength: 1000
+ type: string
+ name:
+ maxLength: 200
+ minLength: 1
+ type: string
+ permissions:
+ items:
+ type: string
+ type: array
+ slug:
+ maxLength: 64
+ minLength: 1
+ pattern: ^[a-z][a-z0-9]*(-[a-z0-9]+)*$
+ type: string
required:
- - updated
- - unreadCount
+ - slug
+ - name
+ - description
+ - permissions
type: object
- MetadataField:
+ CreatedCustomerSession:
additionalProperties: false
properties:
- archivedAt:
- description: Timestamp when the field was archived (soft delete). Absent if active.
+ customerSlug:
+ type: string
+ expiresAt:
format: date-time
- readOnly: true
type: string
+ id:
+ description: Revoke it with POST /customer-sessions/{id}/revoke
+ type: string
+ instanceSlug:
+ type: string
+ token:
+ description: "The kst_ token: hand it to the customer's browser, which sends it as Authorization: Bearer on /api/public/session routes. Shown once; only its digest is stored."
+ examples:
+ - kst_3q2-7wEXAMPLEtokenBodyOfFortyThreeCharactersX
+ type: string
+ required:
+ - id
+ - token
+ - expiresAt
+ - customerSlug
+ type: object
+ Customer:
+ additionalProperties: false
+ properties:
+ billingEmail:
+ description: "Where invoices sent to this customer are delivered, at most 254 characters (CreateCustomer.InvalidBillingEmail). Absent when there is none. On update, omit it or send null to keep the stored value, or send an empty string to remove it. Personal data: never included in event payloads."
+ examples:
+ - billing@example.tld
+ type:
+ - string
+ - "null"
createdAt:
- description: Timestamp when the field was created
+ description: Timestamp when the customer was created
examples:
- - "2026-05-26T12:00:00Z"
+ - "2023-10-01T12:00:00Z"
format: date-time
readOnly: true
type: string
createdBy:
$ref: "#/components/schemas/User"
- description: User who created this field
+ description: User who created this customer
readOnly: true
- displayOrder:
- description: Sort order in the admin UI; ties are broken by createdAt ASC. Settable only through POST /metadata-fields/reorder; never echoed back on update.
+ domain:
+ description: Optional customer domain name
examples:
- - 0
- format: int32
- type: integer
+ - example.tld
+ pattern: ^[a-z0-9_-]+(\.[a-z0-9_-]+)+$
+ type: string
+ externalCustomerId:
+ description: Optional identifier for the customer in an external system
+ examples:
+ - external-customer-id-12345
+ type:
+ - string
+ - "null"
id:
- description: Unique identifier
+ description: Unique identifier for the customer
examples:
- 123e4567-e89b-12d3-a456-426614174000
readOnly: true
type: string
- jsonSchema:
- additionalProperties: {}
- description: "JSON Schema 2020-12 document describing the value space. Example: {\"type\":\"string\",\"enum\":[\"a\",\"b\"]}"
+ integrations:
+ additionalProperties:
+ $ref: "#/components/schemas/CustomerIntegration"
+ description: Integrations grouped by adapter name
type: object
- key:
- description: Stable identifier, unique per (organization, resource type) on non-archived rows. Immutable after creation; update must echo back the stored value.
+ name:
+ description: Name of the customer
examples:
- - region
- maxLength: 100
+ - Awesome Customer
minLength: 1
type: string
- label:
- description: Human-readable label shown in the admin UI
+ slug:
+ description: Optional URL-friendly identifier, unique per organization. Auto-generated if not provided.
examples:
- - Region
+ - awesome-customer
maxLength: 100
- minLength: 1
- type: string
- resourceType:
- description: Resource type this field applies to. Immutable after creation; update must echo back the stored value.
- enum:
- - DEPLOYMENT_ZONE
- - INSTANCE
+ minLength: 2
+ pattern: ^[a-z0-9][a-z0-9-]*[a-z0-9]$
type: string
updatedAt:
- description: Timestamp when the field was last updated
+ description: Timestamp when the customer was last updated
examples:
- - "2026-05-26T12:00:00Z"
+ - "2023-10-01T12:00:00Z"
format: date-time
readOnly: true
type: string
updatedBy:
$ref: "#/components/schemas/User"
- description: User who last updated this field
+ description: User who last updated this customer
readOnly: true
required:
- id
- - resourceType
- - key
- - label
- - jsonSchema
+ - name
- createdBy
- createdAt
- updatedBy
- updatedAt
type: object
- MetadataFieldReordered:
+ CustomerBilling:
additionalProperties: false
properties:
- ids:
- description: Metadata field ids in their new display order
- items:
- type: string
+ billingEmail:
+ description: Where the customer's invoices are sent. Personal data
type:
- - array
+ - string
- "null"
+ providers:
+ description: The customer in each payment provider; none for NOOP
+ items:
+ $ref: "#/components/schemas/CustomerInProvider"
+ type: array
required:
- - ids
+ - billingEmail
+ - providers
type: object
- MetadataFieldSchemaProposal:
+ CustomerBody:
additionalProperties: false
properties:
- jsonSchema:
+ domain:
+ description: Optional customer domain name
+ examples:
+ - example.tld
+ pattern: ^[a-z0-9_-]+(\.[a-z0-9_-]+)+$
+ type: string
+ error:
+ description: Optional integration error message
+ type: string
+ integrationMetadata:
additionalProperties: {}
- description: Candidate JSON Schema 2020-12 document to evaluate existing values against.
+ description: Adapter-specific integration metadata
type: object
- required:
- - jsonSchema
+ name:
+ description: Customer name
+ examples:
+ - Awesome Customer
+ minLength: 1
+ type: string
+ slug:
+ description: Customer slug
+ examples:
+ - awesome-customer
+ maxLength: 100
+ minLength: 2
+ pattern: ^[a-z0-9][a-z0-9-]*[a-z0-9]$
+ type: string
+ webUrl:
+ description: Absolute http(s) link to the record in the third-party system
+ examples:
+ - https://app.attio.com/w/acme/record/rec_12345
+ type: string
type: object
- Notification:
+ CustomerCreationRejectedDelivered:
additionalProperties: false
properties:
- actionUrl:
- description: Where clicking the notification goes
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
type: string
- body:
- description: Rendered detail line, when the event has one
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
+ type: string
+ name:
+ type: string
+ reason:
+ type: string
+ slug:
+ type: string
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
+ type: string
+ required:
+ - name
+ - slug
+ - reason
+ type: object
+ x-kaiten-payload: "#/components/schemas/CustomerCreationRejected"
+ CustomerEventDelivered:
+ additionalProperties: false
+ properties:
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
type: string
createdAt:
- description: When the event occurred
+ description: Timestamp when the customer was created
+ examples:
+ - "2023-10-01T12:00:00Z"
format: date-time
type: string
- eventName:
- description: Event this notification reports
- examples:
- - INSTANCE_DEPLOYED
+ createdBy:
+ $ref: "#/components/schemas/User"
+ description: User who created this customer
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
type: string
- eventType:
- description: CloudEvents type of the same event -- the key it is published under in this document's webhooks section, so a client can line a notification up with the payload schema and the description already published there
+ domain:
+ description: Customer domain name
examples:
- - com.kaiten.instance.v1.deployed
+ - example.tld
type: string
+ externalCustomerId:
+ description: Identifier for the customer in an external system, null when there is none
+ examples:
+ - external-customer-id-12345
+ type:
+ - string
+ - "null"
id:
- description: Identifier of the underlying audit trail entry
+ description: Unique identifier for the customer
+ examples:
+ - 123e4567-e89b-12d3-a456-426614174000
type: string
- objectType:
- description: What the notification is about -- the kind of page actionUrl opens, and what the list's objectType filter matches
+ integrations:
+ additionalProperties:
+ $ref: "#/components/schemas/CustomerIntegration"
+ description: Integrations grouped by adapter name
+ type: object
+ name:
+ description: Name of the customer
examples:
- - instance
+ - Awesome Customer
type: string
- readAt:
- description: When this user read it; absent means unread
+ slug:
+ description: URL-friendly identifier, unique per organization
+ examples:
+ - awesome-customer
+ type: string
+ updatedAt:
+ description: Timestamp when the customer was last updated
+ examples:
+ - "2023-10-01T12:00:00Z"
format: date-time
type: string
- title:
- description: Rendered headline
+ updatedBy:
+ $ref: "#/components/schemas/User"
+ description: User who last updated this customer
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
type: string
required:
- id
- - eventName
- - eventType
- - objectType
- - title
+ - name
+ - externalCustomerId
+ - createdBy
- createdAt
+ - updatedBy
+ - updatedAt
type: object
- NumberEntitlementValue:
+ x-kaiten-payload: "#/components/schemas/CustomerEvent"
+ CustomerInProvider:
additionalProperties: false
properties:
- event_count:
- description: Number of events accumulated (server-computed)
- examples:
- - 2
- format: int32
- readOnly: true
- type: integer
- type:
- description: Value type discriminator
+ externalCustomerId:
+ description: The customer's id in the provider
+ type: string
+ paymentMethod:
+ description: Its default payment method; null when there is none
+ oneOf:
+ - $ref: "#/components/schemas/PaymentMethodLabels"
+ - enum:
+ - null
+ type: "null"
+ providerKind:
enum:
- - number
- examples:
- - number
+ - STRIPE
type: string
- value:
- description: Numeric value
- examples:
- - 42.5
- format: double
- type: number
+ syncedAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ webUrl:
+ description: The customer's page in the provider's dashboard
+ type:
+ - string
+ - "null"
required:
- - type
- - value
+ - providerKind
+ - externalCustomerId
+ - webUrl
+ - syncedAt
+ - paymentMethod
type: object
- PageAuditTrail:
+ CustomerIntegration:
additionalProperties: false
properties:
- hasMore:
- description: Whether more rows exist beyond this page.
- type: boolean
- items:
- items:
- $ref: "#/components/schemas/AuditTrail"
- type: array
- nextCursor:
- description: Opaque cursor for the next page. Absent when hasMore is false.
+ external_id:
+ description: External identifier in the third-party adapter
+ examples:
+ - rec_12345
type: string
- required:
- - items
- - hasMore
- type: object
- PageComponent:
- additionalProperties: false
- properties:
- hasMore:
- description: Whether more rows exist beyond this page.
- type: boolean
- items:
- items:
- $ref: "#/components/schemas/Component"
- type: array
- nextCursor:
- description: Opaque cursor for the next page. Absent when hasMore is false.
+ last_error:
+ description: Last synchronization error message, if any
type: string
- required:
- - items
- - hasMore
- type: object
- PageCustomer:
- additionalProperties: false
- properties:
- hasMore:
- description: Whether more rows exist beyond this page.
- type: boolean
- items:
- items:
- $ref: "#/components/schemas/Customer"
- type: array
- nextCursor:
- description: Opaque cursor for the next page. Absent when hasMore is false.
+ metadata:
+ additionalProperties: {}
+ description: Adapter-specific integration metadata
+ type: object
+ synced_at:
+ description: Timestamp of the last synchronization with the adapter
+ format: date-time
+ readOnly: true
type: string
- required:
- - items
- - hasMore
- type: object
- PageDeploymentZone:
- additionalProperties: false
- properties:
- hasMore:
- description: Whether more rows exist beyond this page.
- type: boolean
- items:
- items:
- $ref: "#/components/schemas/DeploymentZone"
- type: array
- nextCursor:
- description: Opaque cursor for the next page. Absent when hasMore is false.
+ web_url:
+ description: Absolute http(s) link to the record in the third-party system
+ examples:
+ - https://app.attio.com/w/acme/company/rec_12345
type: string
required:
- - items
- - hasMore
+ - external_id
+ - synced_at
type: object
- PageEntitlement:
+ CustomerIntegrationResource:
additionalProperties: false
properties:
- hasMore:
- description: Whether more rows exist beyond this page.
- type: boolean
- items:
- items:
- $ref: "#/components/schemas/Entitlement"
- type: array
- nextCursor:
- description: Opaque cursor for the next page. Absent when hasMore is false.
+ domain:
+ description: Optional customer domain name
+ examples:
+ - example.tld
type: string
- required:
- - items
- - hasMore
- type: object
- PageEntitlementGroup:
- additionalProperties: false
- properties:
- hasMore:
- description: Whether more rows exist beyond this page.
- type: boolean
- items:
- items:
- $ref: "#/components/schemas/EntitlementGroup"
- type: array
- nextCursor:
- description: Opaque cursor for the next page. Absent when hasMore is false.
+ error:
+ description: Last synchronization error for this integration, if any
type: string
- required:
- - items
- - hasMore
- type: object
- PageFeatureFlag:
- additionalProperties: false
- properties:
- hasMore:
- description: Whether more rows exist beyond this page.
- type: boolean
- items:
- items:
- $ref: "#/components/schemas/FeatureFlag"
- type: array
- nextCursor:
- description: Opaque cursor for the next page. Absent when hasMore is false.
+ integrationMetadata:
+ additionalProperties: {}
+ description: Adapter-specific customer integration metadata
+ type: object
+ name:
+ description: Customer name
+ examples:
+ - Awesome Customer
type: string
- required:
- - items
- - hasMore
- type: object
- PageInstance:
- additionalProperties: false
- properties:
- hasMore:
- description: Whether more rows exist beyond this page.
- type: boolean
- items:
- items:
- $ref: "#/components/schemas/Instance"
- type: array
- nextCursor:
- description: Opaque cursor for the next page. Absent when hasMore is false.
+ slug:
+ description: Customer slug
+ examples:
+ - awesome-customer
type: string
- required:
- - items
- - hasMore
- type: object
- PageLicense:
- additionalProperties: false
- properties:
- hasMore:
- description: Whether more rows exist beyond this page.
- type: boolean
- items:
- items:
- $ref: "#/components/schemas/License"
- type: array
- nextCursor:
- description: Opaque cursor for the next page. Absent when hasMore is false.
+ webUrl:
+ description: Absolute http(s) link to the record in the third-party system
+ examples:
+ - https://app.attio.com/w/acme/company/rec_12345
type: string
required:
- - items
- - hasMore
+ - integrationMetadata
+ - name
+ - slug
type: object
- PageLicenseEntitlement:
+ CustomerSessionDraft:
additionalProperties: false
properties:
- hasMore:
- description: Whether more rows exist beyond this page.
- type: boolean
- items:
- items:
- $ref: "#/components/schemas/LicenseEntitlement"
- type: array
- nextCursor:
- description: Opaque cursor for the next page. Absent when hasMore is false.
+ customerSlug:
+ description: The customer the session acts for
+ examples:
+ - acme
type: string
+ instanceSlug:
+ description: "Binds the session to one of the customer's instances; checkout, cancel and the other subscription routes need it. Omitted: the session reads the whole customer."
+ examples:
+ - acme-prod
+ type: string
+ ttlSeconds:
+ description: "How long the session lives, in seconds: 300 to 3600 (CreateCustomerSession.InvalidTtl otherwise). Default 30 minutes; mint a new one before it ends."
+ examples:
+ - 1800
+ format: int32
+ type: integer
required:
- - items
- - hasMore
+ - customerSlug
type: object
- PageLicenseFamilyView:
+ DefaultVariant:
+ discriminator:
+ mapping:
+ basic: "#/components/schemas/Basic"
+ rollout_date: "#/components/schemas/RolloutDate"
+ rollout_percentage: "#/components/schemas/RolloutPercentage"
+ propertyName: type
+ oneOf:
+ - $ref: "#/components/schemas/Basic"
+ - $ref: "#/components/schemas/RolloutDate"
+ - $ref: "#/components/schemas/RolloutPercentage"
+ DeletedAddonDelivered:
additionalProperties: false
properties:
- hasMore:
- description: Whether more rows exist beyond this page.
- type: boolean
- items:
- items:
- $ref: "#/components/schemas/LicenseFamilyView"
- type: array
- nextCursor:
- description: Opaque cursor for the next page. Absent when hasMore is false.
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
+ type: string
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
+ type: string
+ familySlug:
+ type: string
+ id:
+ type: string
+ slug:
+ type: string
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
type: string
required:
- - items
- - hasMore
+ - id
+ - slug
+ - familySlug
type: object
- PageMetadataField:
+ x-kaiten-payload: "#/components/schemas/DeletedAddon"
+ DeploymentDelivered:
additionalProperties: false
properties:
- hasMore:
- description: Whether more rows exist beyond this page.
- type: boolean
- items:
- items:
- $ref: "#/components/schemas/MetadataField"
- type: array
- nextCursor:
- description: Opaque cursor for the next page. Absent when hasMore is false.
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
+ type: string
+ createdAt:
+ description: Timestamp when the deployment was recorded
+ examples:
+ - "2023-10-01T12:00:00Z"
+ format: date-time
+ type: string
+ createdBy:
+ $ref: "#/components/schemas/User"
+ description: User who recorded this deployment
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
+ type: string
+ deploymentZoneId:
+ description: Deployment zone the release was deployed to
+ examples:
+ - 123e4567-e89b-12d3-a456-426614174000
+ type: string
+ id:
+ description: Unique identifier for this deployment event
+ examples:
+ - 123e4567-e89b-12d3-a456-426614174000
+ type: string
+ releaseId:
+ description: Release that was deployed
+ examples:
+ - 123e4567-e89b-12d3-a456-426614174000
+ type: string
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
type: string
required:
- - items
- - hasMore
+ - id
+ - deploymentZoneId
+ - releaseId
+ - createdBy
+ - createdAt
type: object
- PageRelease:
+ x-kaiten-payload: "#/components/schemas/Deployment"
+ DeploymentZone:
additionalProperties: false
properties:
- hasMore:
- description: Whether more rows exist beyond this page.
- type: boolean
- items:
- items:
- $ref: "#/components/schemas/Release"
- type: array
- nextCursor:
- description: Opaque cursor for the next page. Absent when hasMore is false.
+ createdAt:
+ description: Timestamp when the deployment zone was created
+ examples:
+ - "2023-10-01T12:00:00Z"
+ format: date-time
+ readOnly: true
type: string
- required:
- - items
- - hasMore
- type: object
- PageServiceAccount:
- additionalProperties: false
- properties:
- hasMore:
- description: Whether more rows exist beyond this page.
- type: boolean
- items:
- items:
- $ref: "#/components/schemas/ServiceAccount"
- type: array
- nextCursor:
- description: Opaque cursor for the next page. Absent when hasMore is false.
+ createdBy:
+ $ref: "#/components/schemas/User"
+ description: User who created this deployment zone
+ readOnly: true
+ description:
+ description: Description of the deployment zone
+ examples:
+ - "2023-10-01T12:00:00Z"
type: string
- required:
- - items
- - hasMore
- type: object
- PageToken:
- additionalProperties: false
- properties:
- hasMore:
- description: Whether more rows exist beyond this page.
- type: boolean
- items:
- items:
- $ref: "#/components/schemas/Token"
- type: array
- nextCursor:
- description: Opaque cursor for the next page. Absent when hasMore is false.
+ id:
+ description: Unique identifier for the deployment zone
+ examples:
+ - 123e4567-e89b-12d3-a456-426614174000
+ readOnly: true
type: string
- required:
- - items
- - hasMore
- type: object
- PatchInstanceBody:
- additionalProperties: false
- properties:
- lifecycleStage:
- description: "Commercial lifecycle stage (free-form; suggested: TRIAL, ACTIVE, AT_RISK, CHURNED). When omitted, the current stage is preserved; clearing it back to unset is not supported."
+ metadata:
+ additionalProperties: {}
+ description: Typed metadata for the deployment zone, validated against the org-scoped MetadataField schema
examples:
- - AT_RISK
+ - region: eu-west-1
+ type: object
+ name:
+ description: Name of the deployment zone
+ examples:
+ - AWS us-west-2
minLength: 1
type: string
- status:
- description: Operational status of the instance
- enum:
- - HEALTHY
- - DEGRADED
- - INCIDENT
- - MAINTENANCE
+ releaseId:
+ description: Currently deployed release ID
examples:
- - INCIDENT
+ - 123e4567-e89b-12d3-a456-426614174000
+ type: string
+ slug:
+ description: Optional URL-friendly identifier, unique per organization. Auto-generated if not provided.
+ examples:
+ - aws-us-west-2
+ maxLength: 100
+ minLength: 2
+ pattern: ^[a-z0-9][a-z0-9-]*[a-z0-9]$
+ type: string
+ type:
+ description: "Environment class of the deployment zone. Free-form: any value is stored as sent, but production, staging and development are the ones the console labels, and feature-flag targeting rules match on this field as __kaiten.deploymentZone.type."
+ examples:
+ - production
+ type: string
+ updatedAt:
+ description: Timestamp when the deployment zone was last updated
+ examples:
+ - "2023-10-01T12:00:00Z"
+ format: date-time
+ readOnly: true
type: string
+ updatedBy:
+ $ref: "#/components/schemas/User"
+ description: User who last updated the deployment zone
+ readOnly: true
+ required:
+ - id
+ - name
+ - type
+ - description
+ - createdBy
+ - createdAt
+ - updatedBy
+ - updatedAt
type: object
- PlainToken:
+ DeploymentZoneDelivered:
additionalProperties: false
properties:
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
+ type: string
createdAt:
+ description: Timestamp when the deployment zone was created
+ examples:
+ - "2023-10-01T12:00:00Z"
format: date-time
readOnly: true
type: string
createdBy:
$ref: "#/components/schemas/User"
+ description: User who created this deployment zone
readOnly: true
- expiresAt:
- format: date-time
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
+ type: string
+ description:
+ description: Description of the deployment zone
+ examples:
+ - "2023-10-01T12:00:00Z"
type: string
id:
+ description: Unique identifier for the deployment zone
+ examples:
+ - 123e4567-e89b-12d3-a456-426614174000
readOnly: true
type: string
+ metadata:
+ additionalProperties: {}
+ description: Typed metadata for the deployment zone, validated against the org-scoped MetadataField schema
+ examples:
+ - region: eu-west-1
+ type: object
name:
+ description: Name of the deployment zone
+ examples:
+ - AWS us-west-2
+ minLength: 1
type: string
- revokedAt:
- format: date-time
- readOnly: true
- type: string
- revokedBy:
- $ref: "#/components/schemas/User"
- readOnly: true
- scopes:
- items:
- type: string
- type:
- - array
- - "null"
- serviceAccountId:
- readOnly: true
+ releaseId:
+ description: Currently deployed release ID
+ examples:
+ - 123e4567-e89b-12d3-a456-426614174000
type: string
slug:
- description: Optional URL-friendly identifier, unique per service account. Auto-generated if not provided.
+ description: Optional URL-friendly identifier, unique per organization. Auto-generated if not provided.
+ examples:
+ - aws-us-west-2
maxLength: 100
minLength: 2
pattern: ^[a-z0-9][a-z0-9-]*[a-z0-9]$
type: string
- token:
+ type:
+ description: "Environment class of the deployment zone. Free-form: any value is stored as sent, but production, staging and development are the ones the console labels, and feature-flag targeting rules match on this field as __kaiten.deploymentZone.type."
+ examples:
+ - production
+ type: string
+ updatedAt:
+ description: Timestamp when the deployment zone was last updated
+ examples:
+ - "2023-10-01T12:00:00Z"
+ format: date-time
+ readOnly: true
+ type: string
+ updatedBy:
+ $ref: "#/components/schemas/User"
+ description: User who last updated the deployment zone
readOnly: true
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
type: string
required:
+ - id
- name
- - scopes
- - createdAt
+ - type
+ - description
- createdBy
+ - createdAt
+ - updatedBy
+ - updatedAt
type: object
- PreferenceChoices:
+ x-kaiten-payload: "#/components/schemas/DeploymentZone"
+ DiscountAllocation:
additionalProperties: false
properties:
- events:
- description: Events to set; events left out keep their current value
- items:
- $ref: "#/components/schemas/PreferenceUpdate"
- type:
- - array
- - "null"
+ amount:
+ description: In minor units, positive
+ format: int64
+ type: integer
+ targetSeq:
+ description: The target line
+ format: int64
+ type: integer
required:
- - events
+ - targetSeq
+ - amount
type: object
- PreferenceEvent:
+ DiscountDifference:
additionalProperties: false
properties:
- channels:
- additionalProperties:
- type: boolean
- description: Effective value per channel
- type: object
- eventName:
- examples:
- - INSTANCE_DEPLOYED
+ couponId:
+ description: The provider's discount, empty when none was recorded
type: string
- eventType:
- description: CloudEvents type of the same event; see Notification.eventType
- examples:
- - com.kaiten.instance.v1.deployed
+ kaitenAmount:
+ format: int64
+ type: integer
+ lineId:
+ description: The DISCOUNT line
type: string
- group:
- description: Catalogue group the client renders this under
- examples:
- - deployments
+ providerAmount:
+ description: 0 when the provider did not apply it
+ format: int64
+ type: integer
+ seq:
+ format: int64
+ type: integer
+ targetSeq:
+ format: int64
+ type: integer
+ required:
+ - lineId
+ - seq
+ - targetSeq
+ - kaitenAmount
+ - providerAmount
+ - couponId
+ type: object
+ DueDateDifference:
+ additionalProperties: false
+ properties:
+ kaitenDueAt:
+ format: date-time
type: string
- label:
- description: Human-readable event name
+ providerDueAt:
+ format: date-time
type: string
required:
- - eventName
- - eventType
- - label
- - group
- - channels
+ - kaitenDueAt
+ - providerDueAt
type: object
- PreferenceMatrix:
+ DueToday:
additionalProperties: false
properties:
- channels:
- description: Channels this deployment serves, in display order
+ currency:
+ examples:
+ - EUR
+ type: string
+ discountTotal:
+ description: Minor units
+ format: int64
+ type: integer
+ lines:
items:
- type: string
+ $ref: "#/components/schemas/SessionLine"
type: array
- events:
- description: Every notifiable event
- items:
- $ref: "#/components/schemas/PreferenceEvent"
- type: array
- required:
- - channels
- - events
- type: object
- PreferenceUpdate:
- additionalProperties: false
- properties:
- channels:
- additionalProperties:
- type: boolean
- type: object
- eventName:
- type: string
+ subtotal:
+ description: Minor units
+ format: int64
+ type: integer
+ total:
+ description: Minor units
+ format: int64
+ type: integer
required:
- - eventName
- - channels
+ - currency
+ - subtotal
+ - discountTotal
+ - total
+ - lines
type: object
- Problem:
+ Entitlement:
additionalProperties: false
properties:
- code:
- description: Stable, machine-readable error code.
+ aggregationMethod:
+ description: Method used to fold each reported usage value into the stored total (NUMBER only). Defaults to SUM.
+ enum:
+ - COUNT
+ - SUM
+ - AVERAGE
+ - MIN
+ - MAX
+ - LATEST
examples:
- - License.NotFound
+ - SUM
type: string
- detail:
- description: A human-readable explanation specific to this occurrence of the problem.
+ createdAt:
+ description: Timestamp when this entitlement was created
examples:
- - license not found
+ - "2023-10-01T12:00:00Z"
+ format: date-time
+ readOnly: true
type: string
- errorId:
- description: Correlation id for the server-side log entry describing the withheld cause.
+ description:
+ description: Description of the entitlement
examples:
- - 3f6b1a2c-7c1e-4d0b-9d5f-2b0a1c4e8d31
- type: string
- errors:
- description: Optional list of individual error details
- items:
- $ref: "#/components/schemas/ErrorDetail"
+ - Basic subscription plan with limited features
type:
- - array
+ - string
- "null"
- instance:
- description: A URI reference that identifies the specific occurrence of the problem.
- examples:
- - /api/licenses/enterprise
- type: string
- status:
- description: HTTP status code
+ displayOrder:
+ description: Sort order of this entitlement in customer-facing components (ascending). Defaults to 0.
examples:
- - 404
- format: int64
+ - 10
+ format: int32
+ minimum: 0
type: integer
- title:
- description: A short, human-readable summary of the problem type. This value should not change between occurrences of the error.
- examples:
- - Not Found
- type: string
- type:
- default: about:blank
- description: A URI reference to human-readable documentation for the error.
- examples:
- - https://tools.ietf.org/html/rfc7231#section-6.5.4
- format: uri
- type: string
- type: object
- ReadSelection:
- additionalProperties: false
- properties:
- all:
- description: Mark every notification read, up to now
- type: boolean
- ids:
- description: Notifications to mark read
+ entitlementGroups:
+ description: Groups associated with this entitlement
items:
- type: string
- maxItems: 200
+ $ref: "#/components/schemas/EntitlementGroupSummary"
+ readOnly: true
type:
- array
- "null"
- type: object
- Release:
- additionalProperties: false
- properties:
- componentIds:
- description: List of component IDs to link to this release
- examples:
- - - 123e4567-e89b-12d3-a456-426614174000
+ groupSlugs:
+ description: Groups associated with this entitlement, identified by slug
items:
type: string
type:
- array
- "null"
writeOnly: true
- components:
- description: List of components included in this release, fixed at creation
- items:
- $ref: "#/components/schemas/Component"
- readOnly: true
- type:
- - array
- - "null"
- createdAt:
- description: Timestamp when the release was created
- examples:
- - "2023-10-01T12:00:00Z"
- format: date-time
- readOnly: true
- type: string
- createdBy:
- $ref: "#/components/schemas/User"
- description: User who created this release
- readOnly: true
- description:
- description: Description of the release
+ icon:
+ description: Optional icon, stored as a provider-namespaced token (provider:name). Decoupled from any specific icon library.
examples:
- - "This release fixes everything. CHANGELOG: stuff was done"
- maxLength: 500
+ - lucide:rocket
+ maxLength: 64
+ pattern: ^[a-z0-9]+:[a-z0-9]+(-[a-z0-9]+)*$
type: string
id:
- description: Unique identifier for the release
+ description: Unique identifier for the entitlement
examples:
- 123e4567-e89b-12d3-a456-426614174000
readOnly: true
type: string
+ name:
+ description: Name of the entitlement
+ examples:
+ - Basic Plan
+ minLength: 1
+ type: string
+ resetAnchor:
+ description: "Phase of the periodic usage window: CALENDAR (UTC calendar-aligned), LICENSE_START (phased off the instance's license start date) or BILLING_PERIOD (phased off the instance's live subscription's billing anchor, LICENSE_START without one; with resetPeriod MONTH or YEAR only). Required exactly when resetPeriod is set; defaults to CALENDAR when omitted. Immutable once set."
+ enum:
+ - CALENDAR
+ - LICENSE_START
+ - BILLING_PERIOD
+ examples:
+ - CALENDAR
+ type: string
+ resetPeriod:
+ description: Cadence at which usage automatically resets for this NUMBER entitlement. NULL means a lifetime counter (default). Immutable once set -- a full-replace PUT must echo back the stored value.
+ enum:
+ - HOUR
+ - DAY
+ - WEEK
+ - MONTH
+ - YEAR
+ examples:
+ - MONTH
+ type: string
+ saleUnitFactor:
+ description: Number of base units one sale unit represents (1 sale unit = N base units).
+ examples:
+ - 3
+ exclusiveMinimum: 0
+ format: double
+ type: number
+ saleUnitPlural:
+ description: Plural label of the unit this entitlement is sold in, when it differs from the base unit.
+ examples:
+ - packs
+ maxLength: 100
+ minLength: 1
+ type: string
+ saleUnitSingular:
+ description: Singular label of the unit this entitlement is sold in, when it differs from the base unit. Requires the base unit labels and the full sale unit trio (saleUnitSingular, saleUnitPlural, saleUnitFactor).
+ examples:
+ - pack
+ maxLength: 100
+ minLength: 1
+ type: string
slug:
description: Optional URL-friendly identifier, unique per organization. Auto-generated if not provided.
examples:
- - v1-0-0
+ - basic-plan
maxLength: 100
minLength: 2
pattern: ^[a-z0-9][a-z0-9-]*[a-z0-9]$
type: string
- version:
- description: Version of the release
+ type:
+ description: Type of the entitlement
+ enum:
+ - BOOLEAN
+ - NUMBER
+ - CONFIG
+ - NUMBER_AI_CREDIT
examples:
- - v1.0.0
+ - NUMBER
+ type: string
+ unitPlural:
+ description: Plural label of the base unit this entitlement is measured in (NUMBER type only). Must be provided together with unitSingular.
+ examples:
+ - seats
+ maxLength: 100
+ minLength: 1
+ type: string
+ unitSingular:
+ description: Singular label of the base unit this entitlement is measured in (NUMBER type only). Must be provided together with unitPlural.
+ examples:
+ - seat
maxLength: 100
minLength: 1
type: string
+ updatedAt:
+ description: Timestamp when this entitlement was last updated
+ examples:
+ - "2023-10-02T12:00:00Z"
+ format: date-time
+ readOnly: true
+ type: string
+ userFacing:
+ description: Whether this entitlement is displayed in customer-facing components (e.g. plan and pricing pages). Defaults to false.
+ examples:
+ - true
+ type: boolean
+ warningThresholdPercent:
+ description: Percentage of the cap at which an early-warning event fires before the cap is reached. 0 disables the warning (default).
+ examples:
+ - 80
+ format: int32
+ maximum: 100
+ minimum: 0
+ type: integer
required:
- id
- - version
- - createdBy
+ - name
+ - description
- createdAt
+ - updatedAt
type: object
- ReorderMetadataFieldsInput:
- additionalProperties: false
- properties:
- ids:
- items:
- type: string
- type:
- - array
- - "null"
- required:
- - ids
- type: object
- ReportEntitlementUsageBody:
+ EntitlementDelivered:
additionalProperties: false
properties:
- behavior:
- description: "Report behavior: append folds the value into the stored total through the aggregation method; set overwrites it"
+ aggregationMethod:
+ description: Method used to fold each reported usage value into the stored total (NUMBER only). Defaults to SUM.
enum:
- - append
- - set
- type: string
- metadata:
- additionalProperties: {}
- description: Optional metadata for the usage report
- type: object
- value:
- description: Reported entitlement value, discriminated by the 'type' field. Usage reporting accepts the number variant only.
- discriminator:
- mapping:
- boolean: "#/components/schemas/BooleanEntitlementValue"
- number: "#/components/schemas/NumberEntitlementValue"
- object: "#/components/schemas/ConfigEntitlementValue"
- propertyName: type
- oneOf:
- - $ref: "#/components/schemas/NumberEntitlementValue"
- - $ref: "#/components/schemas/BooleanEntitlementValue"
- - $ref: "#/components/schemas/ConfigEntitlementValue"
- required:
- - value
- type: object
- RolloutDate:
- additionalProperties: false
- properties:
- end:
- $ref: "#/components/schemas/RolloutStep"
- start:
- $ref: "#/components/schemas/RolloutStep"
- type:
- enum:
- - rollout_date
+ - COUNT
+ - SUM
+ - AVERAGE
+ - MIN
+ - MAX
+ - LATEST
+ examples:
+ - SUM
type: string
- required:
- - type
- - start
- - end
- type: object
- RolloutDateTargeting:
- additionalProperties: false
- properties:
- end:
- $ref: "#/components/schemas/RolloutStep"
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
+ type: string
+ createdAt:
+ description: Timestamp when this entitlement was created
examples:
- - date: "2024-01-31T23:59:59Z"
- percentage: 100
- variant: variant_a
- name:
+ - "2023-10-01T12:00:00Z"
+ format: date-time
+ readOnly: true
+ type: string
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
+ type: string
+ description:
+ description: Description of the entitlement
examples:
- - Targeting Rule 1
+ - Basic subscription plan with limited features
+ type:
+ - string
+ - "null"
+ displayOrder:
+ description: Sort order of this entitlement in customer-facing components (ascending). Defaults to 0.
+ examples:
+ - 10
+ format: int32
+ minimum: 0
+ type: integer
+ entitlementGroups:
+ description: Groups associated with this entitlement
+ items:
+ $ref: "#/components/schemas/EntitlementGroupSummary"
+ readOnly: true
+ type:
+ - array
+ - "null"
+ groupSlugs:
+ description: Groups associated with this entitlement, identified by slug
+ items:
+ type: string
+ type:
+ - array
+ - "null"
+ writeOnly: true
+ icon:
+ description: Optional icon, stored as a provider-namespaced token (provider:name). Decoupled from any specific icon library.
+ examples:
+ - lucide:rocket
+ maxLength: 64
+ pattern: ^[a-z0-9]+:[a-z0-9]+(-[a-z0-9]+)*$
type: string
- rule:
- description: CEL expression for basicTargeting evaluation
+ id:
+ description: Unique identifier for the entitlement
examples:
- - user.group == 'beta'
- maxLength: 10000
+ - 123e4567-e89b-12d3-a456-426614174000
+ readOnly: true
type: string
- start:
- $ref: "#/components/schemas/RolloutStep"
+ name:
+ description: Name of the entitlement
examples:
- - date: "2024-01-01T00:00:00Z"
- percentage: 0
- variant: variant_a
- type:
+ - Basic Plan
+ minLength: 1
+ type: string
+ resetAnchor:
+ description: "Phase of the periodic usage window: CALENDAR (UTC calendar-aligned), LICENSE_START (phased off the instance's license start date) or BILLING_PERIOD (phased off the instance's live subscription's billing anchor, LICENSE_START without one; with resetPeriod MONTH or YEAR only). Required exactly when resetPeriod is set; defaults to CALENDAR when omitted. Immutable once set."
enum:
- - rollout_date
+ - CALENDAR
+ - LICENSE_START
+ - BILLING_PERIOD
+ examples:
+ - CALENDAR
type: string
- required:
- - type
- - name
- - rule
- - start
- - end
- type: object
- RolloutPercentage:
- additionalProperties: false
- properties:
- distribution:
- additionalProperties:
- format: int64
- type: integer
- type: object
- type:
+ resetPeriod:
+ description: Cadence at which usage automatically resets for this NUMBER entitlement. NULL means a lifetime counter (default). Immutable once set -- a full-replace PUT must echo back the stored value.
enum:
- - rollout_percentage
+ - HOUR
+ - DAY
+ - WEEK
+ - MONTH
+ - YEAR
+ examples:
+ - MONTH
type: string
- required:
- - type
- - distribution
- type: object
- RolloutPercentageTargeting:
- additionalProperties: false
- properties:
- distribution:
- additionalProperties:
- format: int64
- type: integer
+ saleUnitFactor:
+ description: Number of base units one sale unit represents (1 sale unit = N base units).
examples:
- - variant_a: 70
- variant_b: 30
- type: object
- name:
+ - 3
+ exclusiveMinimum: 0
+ format: double
+ type: number
+ saleUnitPlural:
+ description: Plural label of the unit this entitlement is sold in, when it differs from the base unit.
examples:
- - Targeting Rule 1234
+ - packs
+ maxLength: 100
+ minLength: 1
type: string
- rule:
- description: CEL expression for basicTargeting evaluation
+ saleUnitSingular:
+ description: Singular label of the unit this entitlement is sold in, when it differs from the base unit. Requires the base unit labels and the full sale unit trio (saleUnitSingular, saleUnitPlural, saleUnitFactor).
examples:
- - user.group == 'beta'
- maxLength: 10000
+ - pack
+ maxLength: 100
+ minLength: 1
+ type: string
+ slug:
+ description: Optional URL-friendly identifier, unique per organization. Auto-generated if not provided.
+ examples:
+ - basic-plan
+ maxLength: 100
+ minLength: 2
+ pattern: ^[a-z0-9][a-z0-9-]*[a-z0-9]$
type: string
type:
+ description: Type of the entitlement
enum:
- - rollout_percentage
+ - BOOLEAN
+ - NUMBER
+ - CONFIG
+ - NUMBER_AI_CREDIT
+ examples:
+ - NUMBER
type: string
- required:
- - type
- - name
- - rule
- - distribution
- type: object
- RolloutStep:
- additionalProperties: false
- properties:
- date:
+ unitPlural:
+ description: Plural label of the base unit this entitlement is measured in (NUMBER type only). Must be provided together with unitSingular.
examples:
- - "2024-01-15T10:00:00Z"
- format: date-time
+ - seats
+ maxLength: 100
+ minLength: 1
type: string
- percentage:
+ unitSingular:
+ description: Singular label of the base unit this entitlement is measured in (NUMBER type only). Must be provided together with unitPlural.
examples:
- - 50
- format: int64
- type: integer
- variant:
+ - seat
+ maxLength: 100
+ minLength: 1
+ type: string
+ updatedAt:
+ description: Timestamp when this entitlement was last updated
examples:
- - variant_b
+ - "2023-10-02T12:00:00Z"
+ format: date-time
+ readOnly: true
+ type: string
+ userFacing:
+ description: Whether this entitlement is displayed in customer-facing components (e.g. plan and pricing pages). Defaults to false.
+ examples:
+ - true
+ type: boolean
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
type: string
+ warningThresholdPercent:
+ description: Percentage of the cap at which an early-warning event fires before the cap is reached. 0 disables the warning (default).
+ examples:
+ - 80
+ format: int32
+ maximum: 100
+ minimum: 0
+ type: integer
required:
- - variant
- - percentage
- - date
+ - id
+ - name
+ - description
+ - createdAt
+ - updatedAt
type: object
- ServiceAccount:
+ x-kaiten-payload: "#/components/schemas/Entitlement"
+ EntitlementException:
additionalProperties: false
properties:
createdAt:
format: date-time
- readOnly: true
type: string
- createdBy:
- $ref: "#/components/schemas/User"
- readOnly: true
- externalId:
- readOnly: true
+ endsAt:
+ description: "Null: no end"
+ format: date-time
+ type:
+ - string
+ - "null"
+ entitlementSlug:
+ type: string
+ entitlementType:
+ enum:
+ - NUMBER
+ - NUMBER_AI_CREDIT
+ - BOOLEAN
+ - CONFIG
+ type: string
+ id:
+ type: string
+ instanceSlug:
+ type: string
+ kind:
+ enum:
+ - SET
+ - ADD
+ - UNLIMITED
+ type: string
+ reason:
+ type: string
+ revokedAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ startsAt:
+ format: date-time
+ type: string
+ status:
+ description: "At the read: SCHEDULED before startsAt, ACTIVE in its window, EXPIRED after endsAt, REVOKED once revoked"
+ enum:
+ - SCHEDULED
+ - ACTIVE
+ - EXPIRED
+ - REVOKED
type: string
+ value:
+ anyOf:
+ - type: number
+ - type: boolean
+ - additionalProperties: true
+ type: object
+ - type: "null"
+ description: A number for NUMBER and NUMBER_AI_CREDIT, a boolean for BOOLEAN, the CONFIG document as an object; null for UNLIMITED
+ required:
+ - id
+ - instanceSlug
+ - entitlementSlug
+ - entitlementType
+ - kind
+ - value
+ - startsAt
+ - endsAt
+ - reason
+ - status
+ - createdAt
+ - revokedAt
+ type: object
+ EntitlementGroup:
+ additionalProperties: false
+ properties:
+ description:
+ description: Description of the entitlement group
+ examples:
+ - All entitlements related to AI usage quotas
+ type:
+ - string
+ - "null"
id:
+ description: Unique identifier for the entitlement group
+ examples:
+ - 123e4567-e89b-12d3-a456-426614174000
readOnly: true
type: string
name:
+ description: Name of the entitlement group
+ examples:
+ - AI Quotas
minLength: 1
type: string
slug:
- description: Optional URL-friendly identifier, unique per organization. Auto-generated if not provided.
+ description: URL-friendly identifier, unique per organization. Auto-generated if not provided.
+ examples:
+ - ai-quotas
maxLength: 100
minLength: 2
pattern: ^[a-z0-9][a-z0-9-]*[a-z0-9]$
type: string
- tokens:
- items:
- $ref: "#/components/schemas/Token"
- readOnly: true
- type:
- - array
- - "null"
required:
+ - id
- name
- - tokens
+ - description
type: object
- SystemTokenIssuance:
+ EntitlementGroupDelivered:
additionalProperties: false
properties:
- credentialKind:
- description: Credential class that performed the mint; "platform" is the system:kaiten identity
- examples:
- - platform
- type: string
- expiresAt:
- description: Absent when the minted credential does not expire
- format: date-time
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
type: string
- issuedAt:
- description: When the credential was minted
- format: date-time
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
type: string
- issuedTokenId:
- description: The credential that was minted
+ description:
+ description: Description of the entitlement group
+ examples:
+ - All entitlements related to AI usage quotas
+ type:
+ - string
+ - "null"
+ id:
+ description: Unique identifier for the entitlement group
+ examples:
+ - 123e4567-e89b-12d3-a456-426614174000
+ readOnly: true
type: string
name:
- description: Human-readable label the caller gave the minted credential
+ description: Name of the entitlement group
examples:
- - ci-deploy
- type: string
- platformTokenId:
- description: Platform credential that performed the mint; revoking it revokes everything it issued
+ - AI Quotas
+ minLength: 1
type: string
- scopes:
- description: Scopes granted to the minted credential
- examples:
- - - read:customers
- items:
- type: string
- type:
- - array
- - "null"
slug:
- description: Server-assigned identifier of the minted credential
+ description: URL-friendly identifier, unique per organization. Auto-generated if not provided.
examples:
- - system-kaiten-9f2c1a
+ - ai-quotas
+ maxLength: 100
+ minLength: 2
+ pattern: ^[a-z0-9][a-z0-9-]*[a-z0-9]$
+ type: string
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
type: string
required:
- - credentialKind
- - platformTokenId
- - issuedTokenId
+ - id
- name
- - slug
- - scopes
- - issuedAt
- type: object
- TargetingContext:
- additionalProperties: false
- properties:
- roots:
- description: "Identifiers the server guarantees, and what is reachable below each. Not a closed set: a host may target on its own attributes too"
- items:
- $ref: "#/components/schemas/TargetingContextNode"
- type: array
- required:
- - roots
+ - description
type: object
- TargetingContextNode:
+ x-kaiten-payload: "#/components/schemas/EntitlementGroup"
+ EntitlementGroupSummary:
additionalProperties: false
properties:
- description:
- description: What this value means, shown by the editor
+ id:
+ description: Unique identifier for the entitlement group
+ examples:
+ - 123e4567-e89b-12d3-a456-426614174000
type: string
- fields:
- description: Named children, for an object
- items:
- $ref: "#/components/schemas/TargetingContextNode"
- type: array
- knownKeys:
- description: Keys this map currently has, for completion. Not a closed set
- items:
- type: string
- type: array
name:
- description: Name as it is written in a rule
+ description: Name of the entitlement group
+ examples:
+ - AI Quotas
type: string
- optional:
- description: Whether this value may be absent, in which case has() is worth using
- type: boolean
- type:
- description: Shape of this node's value
- enum:
- - string
- - number
- - boolean
- - object
- - map
- - dyn
+ slug:
+ description: URL-friendly identifier for the entitlement group
+ examples:
+ - ai-quotas
type: string
- values:
- $ref: "#/components/schemas/TargetingContextNode"
- description: Shape shared by every entry, for a map
required:
+ - id
- name
- - type
+ - slug
type: object
- TargetingRuleDraft:
+ EntitlementGroupUsage:
additionalProperties: false
properties:
- rule:
- description: The CEL targeting rule to check. May be blank, which is itself a mistake this reports
- examples:
- - __kaiten.license.familySlug == 'scale'
- maxLength: 10000
+ currentPeriodEnd:
+ description: End of the current usage window (exclusive). Null for a lifetime entitlement.
+ format: date-time
+ type: string
+ currentPeriodStart:
+ description: Start of the current usage window (inclusive). Null for a lifetime entitlement.
+ format: date-time
+ type: string
+ entitlementId:
+ description: Entitlement ID
+ type: string
+ entitlementName:
+ description: Entitlement name
+ type: string
+ entitlementSlug:
+ description: Entitlement slug
type: string
+ entitlementType:
+ description: Entitlement type
+ type: string
+ licenseValue:
+ description: License limit value, discriminated by the 'type' field. Null when the license grants no value.
+ discriminator:
+ mapping:
+ boolean: "#/components/schemas/BooleanEntitlementValue"
+ number: "#/components/schemas/NumberEntitlementValue"
+ object: "#/components/schemas/ConfigEntitlementValue"
+ propertyName: type
+ oneOf:
+ - $ref: "#/components/schemas/NumberEntitlementValue"
+ - $ref: "#/components/schemas/BooleanEntitlementValue"
+ - $ref: "#/components/schemas/ConfigEntitlementValue"
+ usageValue:
+ description: Current usage value, discriminated by the 'type' field. Null when nothing has been reported yet.
+ discriminator:
+ mapping:
+ boolean: "#/components/schemas/BooleanEntitlementValue"
+ number: "#/components/schemas/NumberEntitlementValue"
+ object: "#/components/schemas/ConfigEntitlementValue"
+ propertyName: type
+ oneOf:
+ - $ref: "#/components/schemas/NumberEntitlementValue"
+ - $ref: "#/components/schemas/BooleanEntitlementValue"
+ - $ref: "#/components/schemas/ConfigEntitlementValue"
required:
- - rule
+ - entitlementId
+ - entitlementSlug
+ - entitlementName
+ - entitlementType
type: object
- TargetingRuleIssue:
+ EntitlementUsage:
additionalProperties: false
properties:
- column:
- description: 1-based column the problem starts on, or 0 when it has no position
- format: int64
- type: integer
- endColumn:
- description: 1-based column just past the problem, or 0 when only a point is known
- format: int64
- type: integer
- endLine:
- description: 1-based line the problem ends on, or 0 when only a point is known
- format: int64
+ currentPeriodEnd:
+ description: End of the current usage window (exclusive). Null for a lifetime entitlement (no configured reset period).
+ examples:
+ - "2026-04-01T00:00:00Z"
+ format: date-time
+ type: string
+ currentPeriodStart:
+ description: Start of the current usage window (inclusive). Null for a lifetime entitlement (no configured reset period).
+ examples:
+ - "2026-03-01T00:00:00Z"
+ format: date-time
+ type: string
+ entitlementId:
+ description: Unique identifier for the entitlement
+ examples:
+ - 123e4567-e89b-12d3-a456-426614174000
+ type: string
+ entitlementSlug:
+ description: URL-friendly identifier of the entitlement
+ examples:
+ - premium-support
+ type: string
+ licenseId:
+ description: Unique identifier for the license to which this entitlement usage belongs
+ examples:
+ - 123e4567-e89b-12d3-a456-426614174001
+ type: string
+ licenseSlug:
+ description: URL-friendly identifier of the license
+ examples:
+ - enterprise-license
+ type: string
+ limit:
+ description: The effective value this usage is measured against (the licence's grant, with the instance's add-ons and boosts applied; source and provenance say which), discriminated by the 'type' field. Absent when nothing grants the entitlement a value; -1 means unlimited for a NUMBER entitlement.
+ discriminator:
+ mapping:
+ boolean: "#/components/schemas/BooleanEntitlementValue"
+ number: "#/components/schemas/NumberEntitlementValue"
+ object: "#/components/schemas/ConfigEntitlementValue"
+ propertyName: type
+ oneOf:
+ - $ref: "#/components/schemas/NumberEntitlementValue"
+ - $ref: "#/components/schemas/BooleanEntitlementValue"
+ - $ref: "#/components/schemas/ConfigEntitlementValue"
+ limitCapExceededOveragePercent:
+ description: "How far above limit usage is still accepted, in percent: -1 unlimited, 0 a hard limit. Absent for BOOLEAN and CONFIG."
+ format: int32
type: integer
- line:
- description: 1-based line the problem starts on, or 0 when it has no position
- format: int64
+ provenance:
+ $ref: "#/components/schemas/Provenance"
+ description: What the licence, the add-ons and the boosts each contribute
+ source:
+ description: "license: the licence grants it (add-ons, boosts and an exception may change it); addon: an add-on attached does; exception: only the instance's exception does"
+ enum:
+ - license
+ - addon
+ - exception
+ type: string
+ value:
+ description: Resolved entitlement value, discriminated by the 'type' field
+ discriminator:
+ mapping:
+ boolean: "#/components/schemas/BooleanEntitlementValue"
+ number: "#/components/schemas/NumberEntitlementValue"
+ object: "#/components/schemas/ConfigEntitlementValue"
+ propertyName: type
+ oneOf:
+ - $ref: "#/components/schemas/NumberEntitlementValue"
+ - $ref: "#/components/schemas/BooleanEntitlementValue"
+ - $ref: "#/components/schemas/ConfigEntitlementValue"
+ required:
+ - entitlementId
+ - entitlementSlug
+ - licenseId
+ - licenseSlug
+ - value
+ - source
+ type: object
+ EntitlementUsageDelivered:
+ additionalProperties: false
+ properties:
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
+ type: string
+ currentPeriodEnd:
+ description: End of the current usage window (exclusive). Null for a lifetime entitlement (no configured reset period).
+ examples:
+ - "2026-04-01T00:00:00Z"
+ format: date-time
+ type: string
+ currentPeriodStart:
+ description: Start of the current usage window (inclusive). Null for a lifetime entitlement (no configured reset period).
+ examples:
+ - "2026-03-01T00:00:00Z"
+ format: date-time
+ type: string
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
+ type: string
+ entitlementId:
+ description: Unique identifier for the entitlement
+ examples:
+ - 123e4567-e89b-12d3-a456-426614174000
+ type: string
+ entitlementSlug:
+ description: URL-friendly identifier of the entitlement
+ examples:
+ - premium-support
+ type: string
+ licenseId:
+ description: Unique identifier for the license to which this entitlement usage belongs
+ examples:
+ - 123e4567-e89b-12d3-a456-426614174001
+ type: string
+ licenseSlug:
+ description: URL-friendly identifier of the license
+ examples:
+ - enterprise-license
+ type: string
+ limit:
+ description: The effective value this usage is measured against (the licence's grant, with the instance's add-ons and boosts applied; source and provenance say which), discriminated by the 'type' field. Absent when nothing grants the entitlement a value; -1 means unlimited for a NUMBER entitlement.
+ discriminator:
+ mapping:
+ boolean: "#/components/schemas/BooleanEntitlementValue"
+ number: "#/components/schemas/NumberEntitlementValue"
+ object: "#/components/schemas/ConfigEntitlementValue"
+ propertyName: type
+ oneOf:
+ - $ref: "#/components/schemas/NumberEntitlementValue"
+ - $ref: "#/components/schemas/BooleanEntitlementValue"
+ - $ref: "#/components/schemas/ConfigEntitlementValue"
+ limitCapExceededOveragePercent:
+ description: "How far above limit usage is still accepted, in percent: -1 unlimited, 0 a hard limit. Absent for BOOLEAN and CONFIG."
+ format: int32
type: integer
- message:
- description: What is wrong, in the same words the write path refuses it with
+ provenance:
+ $ref: "#/components/schemas/Provenance"
+ description: What the licence, the add-ons and the boosts each contribute
+ source:
+ description: "license: the licence grants it (add-ons, boosts and an exception may change it); addon: an add-on attached does; exception: only the instance's exception does"
+ enum:
+ - license
+ - addon
+ - exception
+ type: string
+ value:
+ description: Resolved entitlement value, discriminated by the 'type' field
+ discriminator:
+ mapping:
+ boolean: "#/components/schemas/BooleanEntitlementValue"
+ number: "#/components/schemas/NumberEntitlementValue"
+ object: "#/components/schemas/ConfigEntitlementValue"
+ propertyName: type
+ oneOf:
+ - $ref: "#/components/schemas/NumberEntitlementValue"
+ - $ref: "#/components/schemas/BooleanEntitlementValue"
+ - $ref: "#/components/schemas/ConfigEntitlementValue"
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
type: string
required:
- - message
- - line
- - column
+ - entitlementId
+ - entitlementSlug
+ - licenseId
+ - licenseSlug
+ - value
+ - source
type: object
- TargetingRuleRehearsal:
+ x-kaiten-payload: "#/components/schemas/EntitlementUsage"
+ ErrorDetail:
additionalProperties: false
properties:
- evaluationError:
- description: Why the engine could not answer, when it could not — e.g. the rule reads a host attribute this context does not carry. At a real evaluation the rule would simply not match
+ location:
+ description: Where the error occurred, e.g. 'body.items[3].tags' or 'path.thing-id'
type: string
- facts:
+ message:
+ description: Error message text
+ type: string
+ value:
+ description: The value at the given location
+ type: object
+ EvaluationFailure:
+ additionalProperties: false
+ properties:
+ errorCode:
+ description: OpenFeature compatible error code
+ type: string
+ errorDetails:
+ description: Error description
+ type: string
+ key:
+ description: Feature flag key
+ type: string
+ metadata:
additionalProperties: {}
- description: The __kaiten namespace as enriched for this context — what a rule actually reads. Empty sub-roots mean the context resolved nothing for them
+ description: Arbitrary metadata for the flag
type: object
- issues:
- description: Problems the lint found. The rule is only run when there are none
- items:
- $ref: "#/components/schemas/TargetingRuleIssue"
- type: array
- matched:
- description: Whether the rule matched this context. Meaningful only when valid and evaluationError is empty
- type: boolean
- valid:
- description: Whether the rule lints clean
- type: boolean
required:
- - facts
- - issues
- - valid
- - matched
+ - key
+ - errorCode
type: object
- TargetingRuleTrial:
+ EvaluationRequest:
additionalProperties: false
properties:
context:
additionalProperties: {}
- description: The evaluation context to rehearse against, as a client would send it. Anything under __kaiten is discarded, exactly as at a real evaluation
type: object
- rule:
- description: The CEL targeting rule to rehearse
+ type: object
+ EvaluationSuccess:
+ additionalProperties: false
+ properties:
+ key:
+ description: Feature flag key
+ type: string
+ metadata:
+ additionalProperties: {}
+ description: Arbitrary metadata for the flag
+ type: object
+ reason:
+ description: OpenFeature reason for the evaluation
examples:
- - __kaiten.license.familySlug == 'scale'
- maxLength: 10000
+ - STATIC
type: string
- targetingKey:
- description: The key the evaluation would be bucketed on. Resolves license and entitlement facts when it names a customer slug
+ value:
+ description: Flag evaluation result
+ variant:
+ description: string of the evaluated flag value
type: string
required:
- - rule
+ - key
type: object
- TargetingRuleVerdict:
- additionalProperties: false
+ EventHeaders:
+ additionalProperties: true
properties:
- issues:
- description: Every problem found, positioned where it is written. Empty when the rule is accepted
- items:
- $ref: "#/components/schemas/TargetingRuleIssue"
- type: array
- valid:
- description: Whether the write path would accept this rule
- type: boolean
- required:
- - issues
- - valid
+ baggage:
+ description: W3C baggage of that request, when it carried any
+ type: string
+ instance_id:
+ description: The instance the event concerns, set on the events of one instance. The usage events' payloads do not name it.
+ format: uuid
+ type: string
+ traceparent:
+ description: W3C trace context of the request that produced the event
+ type: string
+ tracestate:
+ description: W3C trace state of that request, when it carried one
+ type: string
type: object
- Targetings:
- description: Polymorphic list of targeting rules (Basic, RolloutDate, RolloutPercentage)
- items:
- anyOf:
- - $ref: "#/components/schemas/BasicTargeting"
- - $ref: "#/components/schemas/RolloutDateTargeting"
- - $ref: "#/components/schemas/RolloutPercentageTargeting"
- type:
- - array
- - "null"
- Token:
+ ExceptionEventDelivered:
additionalProperties: false
properties:
- createdAt:
- format: date-time
- readOnly: true
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
type: string
- createdBy:
- $ref: "#/components/schemas/User"
- readOnly: true
- expiresAt:
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
+ type: string
+ endsAt:
+ description: "Null: no end"
format: date-time
+ type:
+ - string
+ - "null"
+ entitlementSlug:
type: string
id:
- readOnly: true
type: string
- name:
+ instanceSlug:
+ type: string
+ kind:
+ enum:
+ - SET
+ - ADD
+ - UNLIMITED
+ type: string
+ reason:
type: string
revokedAt:
format: date-time
- readOnly: true
+ type:
+ - string
+ - "null"
+ startsAt:
+ format: date-time
type: string
- revokedBy:
- $ref: "#/components/schemas/User"
- readOnly: true
- scopes:
- items:
- type: string
+ value:
+ anyOf:
+ - type: number
+ - type: boolean
+ - additionalProperties: true
+ type: object
+ - type: "null"
+ description: A number for NUMBER and NUMBER_AI_CREDIT, a boolean for BOOLEAN, the CONFIG document as an object; null for UNLIMITED
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
+ type: string
+ required:
+ - id
+ - instanceSlug
+ - entitlementSlug
+ - kind
+ - value
+ - startsAt
+ - endsAt
+ - reason
+ - revokedAt
+ type: object
+ x-kaiten-payload: "#/components/schemas/ExceptionEvent"
+ ExhaustedEventDelivered:
+ additionalProperties: false
+ properties:
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
+ type: string
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
+ type: string
+ id:
+ type: string
+ maxRedemptions:
+ format: int32
+ type: integer
+ name:
+ type: string
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
+ type: string
+ required:
+ - id
+ - name
+ type: object
+ x-kaiten-payload: "#/components/schemas/ExhaustedEvent"
+ ExpiredEventDelivered:
+ additionalProperties: false
+ properties:
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
+ type: string
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
+ type: string
+ expiresAt:
+ format: date-time
+ type: string
+ id:
+ type: string
+ name:
+ type: string
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
+ type: string
+ required:
+ - id
+ - name
+ - expiresAt
+ type: object
+ x-kaiten-payload: "#/components/schemas/ExpiredEvent"
+ ExtraDiscount:
+ additionalProperties: false
+ properties:
+ amount:
+ format: int64
+ type: integer
+ discountId:
+ type: string
+ externalLineId:
+ type: string
+ required:
+ - externalLineId
+ - discountId
+ - amount
+ type: object
+ FeatureFlag:
+ additionalProperties: false
+ properties:
+ default_variant:
+ $ref: "#/components/schemas/DefaultVariant"
+ description:
+ examples:
+ - This is a new feature flag
type:
- - array
+ - string
- "null"
- serviceAccountId:
+ enabled:
+ examples:
+ - true
+ type: boolean
+ event_name:
+ examples:
+ - feature_flag_event
+ type: string
+ id:
+ examples:
+ - 123e4567-e89b-12d3-a456-426614174000
readOnly: true
type: string
+ metadata:
+ additionalProperties: {}
+ type: object
+ name:
+ examples:
+ - New Feature
+ minLength: 1
+ type: string
slug:
- description: Optional URL-friendly identifier, unique per service account. Auto-generated if not provided.
+ examples:
+ - new-feature
maxLength: 100
minLength: 2
pattern: ^[a-z0-9][a-z0-9-]*[a-z0-9]$
type: string
+ targetings:
+ $ref: "#/components/schemas/Targetings"
+ type:
+ enum:
+ - boolean
+ - string
+ - number
+ - object
+ examples:
+ - boolean
+ type: string
+ variants:
+ items:
+ $ref: "#/components/schemas/Variant"
+ type:
+ - array
+ - "null"
required:
+ - id
+ - type
+ - variants
+ - targetings
- name
- - scopes
- - createdAt
- - createdBy
+ - description
+ - default_variant
+ - metadata
+ - enabled
+ - event_name
type: object
- User:
+ FeatureFlagDelivered:
additionalProperties: false
properties:
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
+ type: string
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
+ type: string
+ default_variant:
+ $ref: "#/components/schemas/DefaultVariant"
+ description:
+ examples:
+ - This is a new feature flag
+ type:
+ - string
+ - "null"
+ enabled:
+ examples:
+ - true
+ type: boolean
+ event_name:
+ examples:
+ - feature_flag_event
+ type: string
id:
- description: ID of the user
examples:
- 123e4567-e89b-12d3-a456-426614174000
+ readOnly: true
type: string
+ metadata:
+ additionalProperties: {}
+ type: object
name:
- description: Name of the user
examples:
- - John Doe
+ - New Feature
+ minLength: 1
+ type: string
+ slug:
+ examples:
+ - new-feature
+ maxLength: 100
+ minLength: 2
+ pattern: ^[a-z0-9][a-z0-9-]*[a-z0-9]$
+ type: string
+ targetings:
+ $ref: "#/components/schemas/Targetings"
+ type:
+ enum:
+ - boolean
+ - string
+ - number
+ - object
+ examples:
+ - boolean
+ type: string
+ variants:
+ items:
+ $ref: "#/components/schemas/Variant"
+ type:
+ - array
+ - "null"
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
type: string
required:
- id
+ - type
+ - variants
+ - targetings
+ - name
+ - description
+ - default_variant
+ - metadata
+ - enabled
+ - event_name
type: object
- Variant:
+ x-kaiten-payload: "#/components/schemas/FeatureFlag"
+ FeatureFlagEvaluatedDelivered:
additionalProperties: false
properties:
- description:
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
type: string
- name:
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
+ type: string
+ error_code:
+ description: Set when the evaluation failed and the default was returned
+ type: string
+ error_message:
+ description: Human-readable detail accompanying error_code
+ type: string
+ flag_id:
+ description: Unique identifier of the evaluated flag
+ type: string
+ flag_slug:
+ description: URL-friendly identifier of the evaluated flag
+ type: string
+ matched_rule_name:
+ description: Name of the targeting rule that matched, when the reason is TARGETING_MATCH
+ type: string
+ reason:
+ description: Why the value resolved as it did
+ type: string
+ variant:
+ description: Name of the resolved variant, when the flag has one
+ type: string
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
type: string
- value: {}
required:
- - name
- - description
- - value
+ - flag_slug
+ - flag_id
+ - reason
type: object
- securitySchemes:
- bearerAuth:
- bearerFormat: JWT
- description: "A JWT verified by the proxy in front of this API, or a Kaiten access token (`ksh_...`) — send either as `Authorization: Bearer `. The scopes listed on each operation are the ones the token must carry; `write:` implies `read:`, and `read:*` / `write:*` are wildcards over every module. `x-kaiten-scopes` lists every scope such a token can carry, including those enforced by a service in front of this API rather than by one of its operations."
- scheme: bearer
- type: http
- x-kaiten-scopes:
- - read:components
- - read:customers
+ x-kaiten-payload: "#/components/schemas/FeatureFlagEvaluated"
+ FlagGrant:
+ additionalProperties: false
+ properties:
+ flagSlug:
+ examples:
+ - new-dashboard
+ type: string
+ variant:
+ description: The name of one of the flag's variants
+ examples:
+ - "on"
+ maxLength: 255
+ minLength: 1
+ type: string
+ required:
+ - flagSlug
+ - variant
+ type: object
+ Grant:
+ additionalProperties: false
+ properties:
+ entitlementSlug:
+ examples:
+ - tokens
+ type: string
+ modifierType:
+ description: SET replaces the value, ADD adds to it, MULTIPLY multiplies it, UNLIMITED lifts the limit. A BOOLEAN or CONFIG entitlement takes SET only
+ enum:
+ - SET
+ - ADD
+ - MULTIPLY
+ - UNLIMITED
+ type: string
+ modifierValue:
+ description: "A NUMBER grant's decimal: >= 0 for SET, > 0 for ADD and MULTIPLY, null for UNLIMITED; null on a BOOLEAN or CONFIG grant. Optional on write."
+ examples:
+ - "2"
+ type:
+ - string
+ - "null"
+ value:
+ anyOf:
+ - type: boolean
+ - additionalProperties: true
+ type: object
+ - type: "null"
+ description: "What a BOOLEAN or CONFIG SET grant sets: true or false, or the CONFIG document as an object; null on a NUMBER grant. Optional on write."
+ required:
+ - entitlementSlug
+ - modifierType
+ type: object
+ HandoffAcknowledgementDelivered:
+ additionalProperties: false
+ properties:
+ acknowledgedBy:
+ type: string
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
+ type: string
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
+ type: string
+ externalReference:
+ description: The invoice's number in the organization's accounting system
+ type: string
+ invoiceId:
+ type: string
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
+ type: string
+ required:
+ - invoiceId
+ - acknowledgedBy
+ type: object
+ x-kaiten-payload: "#/components/schemas/HandoffAcknowledgement"
+ HandoffBacklog:
+ additionalProperties: false
+ properties:
+ oldestPendingIssuedAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ pending:
+ format: int64
+ type: integer
+ reclaimedLast24h:
+ description: "Invoices claimed again after a lease expired, in the last 24 hours: a consumer that claims and does not acknowledge"
+ format: int64
+ type: integer
+ required:
+ - pending
+ - oldestPendingIssuedAt
+ - reclaimedLast24h
+ type: object
+ HandoffBooking:
+ additionalProperties: false
+ properties:
+ externalReference:
+ description: The invoice's number in the accounting system, 1 to 255 characters
+ type: string
+ leaseId:
+ description: The claim's lease. When given, an invoice another claim took since is refused (AckHandoff.LeaseMismatch)
+ type: string
+ type: object
+ HandoffClaim:
+ additionalProperties: false
+ properties:
+ invoices:
+ description: "The leased invoices, oldest issue first, with their billing e-mail: what the accounting system needs to book them"
+ items:
+ $ref: "#/components/schemas/Invoice"
+ type: array
+ leaseId:
+ description: Pass it back when acknowledging an invoice of this claim
+ type: string
+ leasedUntil:
+ description: When the lease expires and the invoices are claimable again
+ format: date-time
+ type: string
+ required:
+ - leaseId
+ - leasedUntil
+ - invoices
+ type: object
+ HandoffClaimSize:
+ additionalProperties: false
+ properties:
+ leaseSeconds:
+ description: Lease length, 60 to 3600 seconds; 900 when omitted
+ format: int32
+ type: integer
+ limit:
+ description: Invoices to lease, 1 to 100; 25 when omitted
+ format: int32
+ type: integer
+ type: object
+ HeldInvoiceDelivered:
+ additionalProperties: false
+ properties:
+ boundaryAt:
+ description: The boundary the invoice bills. With the kind, it identifies the invoice within its subscription
+ format: date-time
+ type: string
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
+ type: string
+ collectionMethod:
+ enum:
+ - SEND_INVOICE
+ - CHARGE_AUTOMATICALLY
+ type: string
+ createdAt:
+ format: date-time
+ type: string
+ currency:
+ examples:
+ - EUR
+ type: string
+ customerName:
+ type: string
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
+ type: string
+ customerSlug:
+ description: As it was when the invoice was composed
+ type: string
+ daysUntilDue:
+ format: int32
+ type:
+ - integer
+ - "null"
+ discountTotal:
+ description: Sum of the discounts, in minor units
+ format: int64
+ type: integer
+ dueAt:
+ description: issuedAt + daysUntilDue
+ format: date-time
+ type:
+ - string
+ - "null"
+ handoffStatus:
+ description: "PENDING: waiting in the handoff queue for the organization's accounting system"
+ enum:
+ - NOT_REQUIRED
+ - PENDING
+ - ACKNOWLEDGED
+ type: string
+ holdDetail:
+ $ref: "#/components/schemas/HoldDetail"
+ holdReason:
+ description: "Why a DRAFT is held: the usage journal it was measured from failed a consistency check (LEDGER_*), or its provider cannot take that many lines (PROVIDER_ITEM_LIMIT)"
+ enum:
+ - LEDGER_SEQUENCE_GAP
+ - LEDGER_CHAIN_BREAK
+ - LEDGER_COUNTER_MISMATCH
+ - PROVIDER_ITEM_LIMIT
+ - null
+ type:
+ - string
+ - "null"
+ id:
+ description: "The invoice's identifier: the key an accounting system deduplicates on"
+ type: string
+ instanceName:
+ type: string
+ instanceSlug:
+ type: string
+ issuedAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ kind:
+ description: ACTIVATION bills a subscription's first period, RENEWAL the period that ended and the one that starts, FINAL what is left when a subscription ends
+ enum:
+ - ACTIVATION
+ - RENEWAL
+ - FINAL
+ type: string
+ licenseSlug:
+ type: string
+ overdue:
+ description: "Whether the invoice is overdue when read, the grace period of automatic collection included: the predicate of ?overdue=true. Computed by the invoice list and the invoice read; false in the answers of writes and in event payloads."
+ type: boolean
+ paidAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ providerKind:
+ description: "Who collects it. NOOP: the organization itself, through the handoff queue"
+ enum:
+ - NOOP
+ - STRIPE
+ type: string
+ serviceFrom:
+ description: Earliest start of its lines' service periods
+ format: date-time
+ type: string
+ serviceTo:
+ description: Latest end of its lines' service periods
+ format: date-time
+ type: string
+ status:
+ description: "MANUAL: issued, for the organization to collect. PAID, UNCOLLECTIBLE and VOID are final. DRAFT: held, waiting for a release or a recompose"
+ enum:
+ - DRAFT
+ - PUSHED
+ - PUSH_FAILED
+ - MANUAL
+ - PAID
+ - PAYMENT_FAILED
+ - UNCOLLECTIBLE
+ - VOID
+ type: string
+ subtotal:
+ description: Sum of the lines, in minor units
+ format: int64
+ type: integer
+ total:
+ description: subtotal − discountTotal, in minor units
+ format: int64
+ type: integer
+ updatedAt:
+ format: date-time
+ type: string
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
+ type: string
+ required:
+ - holdDetail
+ - id
+ - kind
+ - boundaryAt
+ - serviceFrom
+ - serviceTo
+ - status
+ - holdReason
+ - providerKind
+ - collectionMethod
+ - currency
+ - subtotal
+ - discountTotal
+ - total
+ - issuedAt
+ - daysUntilDue
+ - dueAt
+ - paidAt
+ - customerSlug
+ - customerName
+ - instanceSlug
+ - instanceName
+ - licenseSlug
+ - handoffStatus
+ - overdue
+ - createdAt
+ - updatedAt
+ type: object
+ x-kaiten-payload: "#/components/schemas/HeldInvoice"
+ HeldInvoices:
+ additionalProperties: false
+ properties:
+ byReason:
+ additionalProperties:
+ format: int64
+ type: integer
+ type: object
+ count:
+ format: int64
+ type: integer
+ required:
+ - count
+ - byReason
+ type: object
+ HeldPair:
+ additionalProperties: false
+ properties:
+ counterReportSeq:
+ format: int64
+ type:
+ - integer
+ - "null"
+ detail:
+ type: string
+ entitlementId:
+ type: string
+ expected:
+ type:
+ - string
+ - "null"
+ firstSeq:
+ format: int64
+ type:
+ - integer
+ - "null"
+ found:
+ type:
+ - string
+ - "null"
+ instanceId:
+ type: string
+ invariant:
+ type: string
+ lastSeq:
+ format: int64
+ type:
+ - integer
+ - "null"
+ reportSeq:
+ description: The report the failure is at, when one is
+ format: int64
+ type:
+ - integer
+ - "null"
+ required:
+ - instanceId
+ - entitlementId
+ - invariant
+ - expected
+ - found
+ - detail
+ - reportSeq
+ - firstSeq
+ - lastSeq
+ - counterReportSeq
+ type: object
+ HoldDetail:
+ additionalProperties: false
+ properties:
+ itemLimit:
+ $ref: "#/components/schemas/ProviderLineLimit"
+ description: "PROVIDER_ITEM_LIMIT only: the provider's line limit and how many items the invoice needs"
+ pairs:
+ items:
+ $ref: "#/components/schemas/HeldPair"
+ type: array
+ required:
+ - pairs
+ type: object
+ HoldRecord:
+ additionalProperties: false
+ properties:
+ heldAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ releaseReason:
+ type:
+ - string
+ - "null"
+ releasedAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ releasedBy:
+ description: The user who released it; null when it was released by a later check
+ type:
+ - string
+ - "null"
+ required:
+ - heldAt
+ - releasedAt
+ - releasedBy
+ - releaseReason
+ type: object
+ HoldRelease:
+ additionalProperties: false
+ properties:
+ reason:
+ description: 1 to 500 characters; .ReasonRequired otherwise
+ maxLength: 500
+ minLength: 1
+ type: string
+ required:
+ - reason
+ type: object
+ Impact:
+ additionalProperties: false
+ properties:
+ count:
+ description: Number of resources whose stored value for this field would no longer satisfy the candidate schema
+ format: int64
+ type: integer
+ samples:
+ description: Up to 5 impacted resources, for display
+ items:
+ $ref: "#/components/schemas/ImpactSample"
+ type:
+ - array
+ - "null"
+ required:
+ - count
+ - samples
+ type: object
+ ImpactSample:
+ additionalProperties: false
+ properties:
+ name:
+ description: Resource display name
+ type: string
+ slug:
+ description: Resource slug
+ type: string
+ required:
+ - name
+ - slug
+ type: object
+ Instance:
+ additionalProperties: false
+ properties:
+ createdAt:
+ description: Timestamp when the instance was created
+ examples:
+ - "2023-10-01T12:00:00Z"
+ format: date-time
+ readOnly: true
+ type: string
+ createdBy:
+ $ref: "#/components/schemas/User"
+ description: User who created this instance
+ readOnly: true
+ customerId:
+ description: ID of the customer associated with this instance
+ examples:
+ - 123e4567-e89b-12d3-a456-426614174003
+ type: string
+ customerSlug:
+ description: Slug of the customer associated with this instance
+ examples:
+ - acme-corp
+ readOnly: true
+ type: string
+ deploymentZoneId:
+ description: ID of the deployment zone associated with this instance (optional)
+ examples:
+ - 123e4567-e89b-12d3-a456-426614174005
+ type: string
+ deploymentZoneSlug:
+ description: Slug of the deployment zone associated with this instance (optional)
+ examples:
+ - aws-us-west-2
+ readOnly: true
+ type: string
+ description:
+ description: Brief description of the instance
+ examples:
+ - This is a sample instance description
+ type: string
+ endLicenseDate:
+ description: End date of the license for this instance
+ examples:
+ - "2024-10-01T12:00:00Z"
+ format: date-time
+ type: string
+ id:
+ description: Unique identifier for the instance
+ examples:
+ - 123e4567-e89b-12d3-a2456-426614174000
+ readOnly: true
+ type: string
+ integrations:
+ additionalProperties:
+ $ref: "#/components/schemas/InstanceIntegration"
+ description: Integrations grouped by adapter name
+ type: object
+ licenseId:
+ description: ID of the license version this instance is pinned to. An ARCHIVED version cannot be assigned, on create or by changing the license (CreateInstance.LicenseArchived, UpdateInstance.LicenseArchived); an instance already on a version that is archived later keeps it. A DRAFT version can be assigned, to try it before publishing.
+ examples:
+ - 123e4567-e89b-12d3-a456-426614174004
+ type: string
+ licenseSlug:
+ description: Slug of the license associated with this instance
+ examples:
+ - my-license
+ readOnly: true
+ type: string
+ lifecycleStage:
+ description: "Commercial lifecycle stage of the instance (free-form; suggested: TRIAL, ACTIVE, AT_RISK, CHURNED)"
+ examples:
+ - ACTIVE
+ readOnly: true
+ type: string
+ metadata:
+ additionalProperties: {}
+ description: Metadata for the instance
+ examples:
+ - key: value
+ type: object
+ name:
+ description: Name of the instance
+ examples:
+ - My Instance
+ minLength: 1
+ type: string
+ slug:
+ description: Optional URL-friendly identifier, unique per organization. Auto-generated if not provided.
+ examples:
+ - my-instance
+ maxLength: 100
+ minLength: 2
+ pattern: ^[a-z0-9][a-z0-9-]*[a-z0-9]$
+ type: string
+ startLicenseDate:
+ description: Start date of the license for this instance
+ examples:
+ - "2023-10-01T12:00:00Z"
+ format: date-time
+ type: string
+ status:
+ description: Operational status of the instance
+ enum:
+ - HEALTHY
+ - DEGRADED
+ - INCIDENT
+ - MAINTENANCE
+ examples:
+ - HEALTHY
+ readOnly: true
+ type: string
+ updatedAt:
+ description: Timestamp when the instance was last updated
+ examples:
+ - "2023-10-01T12:00:00Z"
+ format: date-time
+ readOnly: true
+ type: string
+ updatedBy:
+ $ref: "#/components/schemas/User"
+ description: User who last updated this instance
+ readOnly: true
+ required:
+ - id
+ - createdBy
+ - createdAt
+ - updatedBy
+ - updatedAt
+ - name
+ - description
+ - customerId
+ - customerSlug
+ - licenseId
+ - licenseSlug
+ - startLicenseDate
+ - endLicenseDate
+ type: object
+ InstanceAddon:
+ additionalProperties: false
+ properties:
+ addonId:
+ type: string
+ addonSlug:
+ examples:
+ - extra-seats-v1
+ type: string
+ attachedAt:
+ format: date-time
+ type: string
+ familySlug:
+ examples:
+ - extra-seats
+ type: string
+ id:
+ readOnly: true
+ type: string
+ maxQuantity:
+ format: int32
+ type:
+ - integer
+ - "null"
+ name:
+ description: The add-on version's name
+ examples:
+ - Extra seats
+ type: string
+ pendingQuantity:
+ description: A decrease scheduled for the next boundary, 0 for a removal; the current quantity holds until then. Null when nothing is scheduled (V31-67)
+ format: int32
+ type:
+ - integer
+ - "null"
+ prices:
+ description: "What the instance's subscription bills for it: the default FLAT_FEE price of the subscription's period and the ACTIVE metered prices. Empty when the instance is not billed."
+ items:
+ $ref: "#/components/schemas/Price"
+ type: array
+ quantity:
+ examples:
+ - 3
+ format: int32
+ type: integer
+ removedAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ required:
+ - id
+ - addonId
+ - addonSlug
+ - familySlug
+ - name
+ - quantity
+ - maxQuantity
+ - pendingQuantity
+ - attachedAt
+ - removedAt
+ - prices
+ type: object
+ InstanceAddonUpdateDelivered:
+ additionalProperties: false
+ properties:
+ addonId:
+ type: string
+ addonSlug:
+ examples:
+ - extra-seats-v1
+ type: string
+ attachedAt:
+ format: date-time
+ type: string
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
+ type: string
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
+ type: string
+ familySlug:
+ examples:
+ - extra-seats
+ type: string
+ id:
+ readOnly: true
+ type: string
+ instanceSlug:
+ type: string
+ maxQuantity:
+ format: int32
+ type:
+ - integer
+ - "null"
+ name:
+ description: The add-on version's name
+ examples:
+ - Extra seats
+ type: string
+ pendingQuantity:
+ description: A decrease scheduled for the next boundary, 0 for a removal; the current quantity holds until then. Null when nothing is scheduled (V31-67)
+ format: int32
+ type:
+ - integer
+ - "null"
+ previousQuantity:
+ description: The quantity before a quantity change
+ format: int32
+ type: integer
+ prices:
+ description: "What the instance's subscription bills for it: the default FLAT_FEE price of the subscription's period and the ACTIVE metered prices. Empty when the instance is not billed."
+ items:
+ $ref: "#/components/schemas/Price"
+ type: array
+ quantity:
+ examples:
+ - 3
+ format: int32
+ type: integer
+ removedAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
+ type: string
+ required:
+ - instanceSlug
+ - id
+ - addonId
+ - addonSlug
+ - familySlug
+ - name
+ - quantity
+ - maxQuantity
+ - pendingQuantity
+ - attachedAt
+ - removedAt
+ - prices
+ type: object
+ x-kaiten-payload: "#/components/schemas/InstanceAddonUpdate"
+ InstanceBilling:
+ additionalProperties: false
+ properties:
+ anchorAt:
+ description: The instant its periods are counted from
+ format: date-time
+ type: string
+ basePrice:
+ $ref: "#/components/schemas/Price"
+ description: The FLAT_FEE price the subscription is pinned to, deprecated or not
+ billingPeriod:
+ enum:
+ - MONTHLY
+ - QUARTERLY
+ - SEMI_ANNUAL
+ - ANNUAL
+ type: string
+ cancelAtPeriodEnd:
+ type: boolean
+ cancelRequestedAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ canceledAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ cancellationReason:
+ type:
+ - string
+ - "null"
+ collectionMethod:
+ description: "Effective: the subscription's own, else the organization's default"
+ enum:
+ - SEND_INVOICE
+ - CHARGE_AUTOMATICALLY
+ type: string
+ collectionMethodOverride:
+ description: The subscription's own collection method; null when it takes the organization's
+ type:
+ - string
+ - "null"
+ createdAt:
+ format: date-time
+ type: string
+ currency:
+ examples:
+ - EUR
+ type: string
+ currentPeriodEnd:
+ description: "The next boundary: the period closes, and its invoice is composed, after it"
+ format: date-time
+ type: string
+ currentPeriodStart:
+ format: date-time
+ type: string
+ customerName:
+ type: string
+ customerSlug:
+ type: string
+ daysUntilDue:
+ description: "Effective: the subscription's own, else the organization's default"
+ format: int32
+ type: integer
+ daysUntilDueOverride:
+ description: The subscription's own payment terms; null when it takes the organization's
+ format: int32
+ type:
+ - integer
+ - "null"
+ id:
+ type: string
+ incompleteExpiresAt:
+ description: "When a checkout waiting for its payment ends unpaid: INCOMPLETE, or a free subscription that carries one"
+ format: date-time
+ type:
+ - string
+ - "null"
+ instanceName:
+ type: string
+ instanceSlug:
+ description: As it was at the subscription's last write
+ type: string
+ pastDueSince:
+ format: date-time
+ type:
+ - string
+ - "null"
+ pendingLicenseSlug:
+ description: The licence version a checkout waiting for its payment moves the instance to once paid
+ type:
+ - string
+ - "null"
+ providerKind:
+ description: "Who collects its invoices. NOOP: the organization itself, through the handoff queue"
+ enum:
+ - NOOP
+ - STRIPE
+ type: string
+ reminderAt:
+ description: "When trial_will_end is (or was) emitted: trialEndsAt minus the family's trialReminderDays as it was when the trial started; set with trialEndsAt"
+ format: date-time
+ type:
+ - string
+ - "null"
+ scheduledChange:
+ description: A plan change waiting for the next boundary
+ oneOf:
+ - $ref: "#/components/schemas/ScheduledChange"
+ - enum:
+ - null
+ type: "null"
+ startedAt:
+ description: "When billing started: usage before it is never billed"
+ format: date-time
+ type: string
+ status:
+ enum:
+ - INCOMPLETE
+ - TRIAL
+ - ACTIVE
+ - PAST_DUE
+ - CANCELED
+ type: string
+ trialEndsAt:
+ description: "When the trial ends, or ended: usage before it is never billed"
+ format: date-time
+ type:
+ - string
+ - "null"
+ updatedAt:
+ format: date-time
+ type: string
+ required:
+ - id
+ - instanceSlug
+ - instanceName
+ - customerSlug
+ - customerName
+ - status
+ - providerKind
+ - collectionMethod
+ - collectionMethodOverride
+ - daysUntilDue
+ - daysUntilDueOverride
+ - basePrice
+ - billingPeriod
+ - currency
+ - anchorAt
+ - startedAt
+ - currentPeriodStart
+ - currentPeriodEnd
+ - trialEndsAt
+ - reminderAt
+ - pendingLicenseSlug
+ - incompleteExpiresAt
+ - scheduledChange
+ - cancelAtPeriodEnd
+ - cancelRequestedAt
+ - canceledAt
+ - cancellationReason
+ - pastDueSince
+ - createdAt
+ - updatedAt
+ type: object
+ InstanceBody:
+ additionalProperties: false
+ properties:
+ customerExternalId:
+ description: External identifier of the related customer in the same adapter
+ examples:
+ - rec_customer_123
+ type: string
+ deploymentZoneId:
+ description: Deployment zone ID
+ examples:
+ - 123e4567-e89b-12d3-a456-426614174005
+ type: string
+ description:
+ description: Instance description
+ examples:
+ - This is a sample instance description
+ type: string
+ endLicenseDate:
+ description: License end date
+ examples:
+ - "2024-10-01T12:00:00Z"
+ format: date-time
+ type: string
+ error:
+ description: Optional integration error message
+ type: string
+ integrationMetadata:
+ additionalProperties: {}
+ description: Adapter-specific integration metadata
+ type: object
+ licenseId:
+ description: License ID
+ examples:
+ - 123e4567-e89b-12d3-a456-426614174004
+ type: string
+ metadata:
+ additionalProperties: {}
+ description: Instance metadata
+ type: object
+ name:
+ description: Instance name
+ examples:
+ - My Instance
+ minLength: 1
+ type: string
+ slug:
+ description: Instance slug
+ examples:
+ - my-instance
+ maxLength: 100
+ minLength: 2
+ pattern: ^[a-z0-9][a-z0-9-]*[a-z0-9]$
+ type: string
+ startLicenseDate:
+ description: License start date
+ examples:
+ - "2023-10-01T12:00:00Z"
+ format: date-time
+ type: string
+ webUrl:
+ description: Absolute http(s) link to the record in the third-party system
+ examples:
+ - https://app.attio.com/w/acme/record/rec_12345
+ type: string
+ type: object
+ InstanceDelivered:
+ additionalProperties: false
+ properties:
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
+ type: string
+ createdAt:
+ description: Timestamp when the instance was created
+ examples:
+ - "2023-10-01T12:00:00Z"
+ format: date-time
+ readOnly: true
+ type: string
+ createdBy:
+ $ref: "#/components/schemas/User"
+ description: User who created this instance
+ readOnly: true
+ customerId:
+ description: ID of the customer associated with this instance
+ examples:
+ - 123e4567-e89b-12d3-a456-426614174003
+ type: string
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
+ type: string
+ customerSlug:
+ description: Slug of the customer associated with this instance
+ examples:
+ - acme-corp
+ readOnly: true
+ type: string
+ deploymentZoneId:
+ description: ID of the deployment zone associated with this instance (optional)
+ examples:
+ - 123e4567-e89b-12d3-a456-426614174005
+ type: string
+ deploymentZoneSlug:
+ description: Slug of the deployment zone associated with this instance (optional)
+ examples:
+ - aws-us-west-2
+ readOnly: true
+ type: string
+ description:
+ description: Brief description of the instance
+ examples:
+ - This is a sample instance description
+ type: string
+ endLicenseDate:
+ description: End date of the license for this instance
+ examples:
+ - "2024-10-01T12:00:00Z"
+ format: date-time
+ type: string
+ id:
+ description: Unique identifier for the instance
+ examples:
+ - 123e4567-e89b-12d3-a2456-426614174000
+ readOnly: true
+ type: string
+ integrations:
+ additionalProperties:
+ $ref: "#/components/schemas/InstanceIntegration"
+ description: Integrations grouped by adapter name
+ type: object
+ licenseId:
+ description: ID of the license version this instance is pinned to. An ARCHIVED version cannot be assigned, on create or by changing the license (CreateInstance.LicenseArchived, UpdateInstance.LicenseArchived); an instance already on a version that is archived later keeps it. A DRAFT version can be assigned, to try it before publishing.
+ examples:
+ - 123e4567-e89b-12d3-a456-426614174004
+ type: string
+ licenseSlug:
+ description: Slug of the license associated with this instance
+ examples:
+ - my-license
+ readOnly: true
+ type: string
+ lifecycleStage:
+ description: "Commercial lifecycle stage of the instance (free-form; suggested: TRIAL, ACTIVE, AT_RISK, CHURNED)"
+ examples:
+ - ACTIVE
+ readOnly: true
+ type: string
+ metadata:
+ additionalProperties: {}
+ description: Metadata for the instance
+ examples:
+ - key: value
+ type: object
+ name:
+ description: Name of the instance
+ examples:
+ - My Instance
+ minLength: 1
+ type: string
+ slug:
+ description: Optional URL-friendly identifier, unique per organization. Auto-generated if not provided.
+ examples:
+ - my-instance
+ maxLength: 100
+ minLength: 2
+ pattern: ^[a-z0-9][a-z0-9-]*[a-z0-9]$
+ type: string
+ startLicenseDate:
+ description: Start date of the license for this instance
+ examples:
+ - "2023-10-01T12:00:00Z"
+ format: date-time
+ type: string
+ status:
+ description: Operational status of the instance
+ enum:
+ - HEALTHY
+ - DEGRADED
+ - INCIDENT
+ - MAINTENANCE
+ examples:
+ - HEALTHY
+ readOnly: true
+ type: string
+ updatedAt:
+ description: Timestamp when the instance was last updated
+ examples:
+ - "2023-10-01T12:00:00Z"
+ format: date-time
+ readOnly: true
+ type: string
+ updatedBy:
+ $ref: "#/components/schemas/User"
+ description: User who last updated this instance
+ readOnly: true
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
+ type: string
+ required:
+ - id
+ - createdBy
+ - createdAt
+ - updatedBy
+ - updatedAt
+ - name
+ - description
+ - customerId
+ - customerSlug
+ - licenseId
+ - licenseSlug
+ - startLicenseDate
+ - endLicenseDate
+ type: object
+ x-kaiten-payload: "#/components/schemas/Instance"
+ InstanceEntitlementCapExceededDelivered:
+ additionalProperties: false
+ properties:
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
+ type: string
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
+ type: string
+ entitlement_slug:
+ description: URL-friendly identifier of the entitlement
+ type: string
+ overage:
+ description: Amount by which usage exceeds the cap
+ format: double
+ type: number
+ threshold:
+ description: The entitlement cap
+ format: double
+ type: number
+ value:
+ description: Current usage value, including overage
+ format: double
+ type: number
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
+ type: string
+ required:
+ - entitlement_slug
+ - threshold
+ - value
+ - overage
+ type: object
+ x-kaiten-payload: "#/components/schemas/InstanceEntitlementCapExceeded"
+ InstanceEntitlementUsagePeriodRolledOverDelivered:
+ additionalProperties: false
+ properties:
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
+ type: string
+ closed_period_end:
+ format: date-time
+ type:
+ - string
+ - "null"
+ closed_period_start:
+ format: date-time
+ type:
+ - string
+ - "null"
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
+ type: string
+ entitlement_id:
+ type: string
+ entitlement_slug:
+ type: string
+ event_count:
+ format: int32
+ type: integer
+ instance_id:
+ type: string
+ is_synthetic:
+ type: boolean
+ new_period_start:
+ format: date-time
+ type: string
+ reason:
+ enum:
+ - PERIOD_ENDED
+ - ANCHOR_CHANGED
+ type: string
+ value:
+ format: double
+ type: number
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
+ type: string
+ required:
+ - entitlement_id
+ - entitlement_slug
+ - instance_id
+ - value
+ - event_count
+ - closed_period_start
+ - closed_period_end
+ - new_period_start
+ - is_synthetic
+ - reason
+ type: object
+ x-kaiten-payload: "#/components/schemas/InstanceEntitlementUsagePeriodRolledOver"
+ InstanceEntitlementUsageReportAcceptedDelivered:
+ additionalProperties: false
+ properties:
+ behavior:
+ description: "Report behavior: append folds the value into the stored total, set overwrites it"
+ enum:
+ - append
+ - set
+ type: string
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
+ type: string
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
+ type: string
+ entitlement_slug:
+ description: URL-friendly identifier of the entitlement the usage was reported against
+ type: string
+ event_count:
+ description: Number of reports folded into the current usage window, including this one
+ format: int32
+ type: integer
+ status:
+ description: Whether the report was persisted
+ enum:
+ - ACCEPTED
+ - REJECTED
+ type: string
+ value:
+ description: Value carried by the report
+ format: double
+ type: number
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
+ type: string
+ required:
+ - entitlement_slug
+ - value
+ - behavior
+ - event_count
+ - status
+ type: object
+ x-kaiten-payload: "#/components/schemas/InstanceEntitlementUsageReportAccepted"
+ InstanceEntitlementUsageWarningThresholdReachedDelivered:
+ additionalProperties: false
+ properties:
+ boundary:
+ description: Usage value at which the warning fires (cap * warningThresholdPercent / 100)
+ format: double
+ type: number
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
+ type: string
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
+ type: string
+ entitlement_slug:
+ description: URL-friendly identifier of the entitlement
+ type: string
+ threshold:
+ description: The entitlement cap
+ format: double
+ type: number
+ value:
+ description: Current usage value
+ format: double
+ type: number
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
+ type: string
+ required:
+ - entitlement_slug
+ - threshold
+ - boundary
+ - value
+ type: object
+ x-kaiten-payload: "#/components/schemas/InstanceEntitlementUsageWarningThresholdReached"
+ InstanceEntitlementValueGetDelivered:
+ additionalProperties: false
+ properties:
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
+ type: string
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
+ type: string
+ entitlement_id:
+ description: Unique identifier for the entitlement
+ type: string
+ entitlement_slug:
+ description: URL-friendly identifier of the entitlement
+ type: string
+ instance_id:
+ description: Unique identifier for the instance the value was read for
+ type: string
+ license_id:
+ description: Unique identifier for the license carrying the entitlement
+ type: string
+ organization_id:
+ description: Unique identifier for the owning organization
+ type: string
+ timestamp:
+ description: When the value was read
+ format: date-time
+ type: string
+ type:
+ description: Discriminator of the resolved entitlement value
+ examples:
+ - number
+ type: string
+ value:
+ description: Resolved entitlement value at read time
+ discriminator:
+ mapping:
+ boolean: "#/components/schemas/BooleanEntitlementValue"
+ number: "#/components/schemas/NumberEntitlementValue"
+ object: "#/components/schemas/ConfigEntitlementValue"
+ propertyName: type
+ oneOf:
+ - $ref: "#/components/schemas/NumberEntitlementValue"
+ - $ref: "#/components/schemas/BooleanEntitlementValue"
+ - $ref: "#/components/schemas/ConfigEntitlementValue"
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
+ type: string
+ required:
+ - entitlement_id
+ - instance_id
+ - license_id
+ - organization_id
+ - type
+ - value
+ - timestamp
+ - entitlement_slug
+ type: object
+ x-kaiten-payload: "#/components/schemas/InstanceEntitlementValueGet"
+ InstanceIntegration:
+ additionalProperties: false
+ properties:
+ external_id:
+ description: External identifier in the third-party adapter
+ examples:
+ - rec_12345
+ type: string
+ last_error:
+ description: Last synchronization error message, if any
+ type: string
+ metadata:
+ additionalProperties: {}
+ description: Adapter-specific integration metadata
+ type: object
+ synced_at:
+ description: Timestamp of the last synchronization with the adapter
+ format: date-time
+ readOnly: true
+ type: string
+ web_url:
+ description: Absolute http(s) link to the record in the third-party system
+ examples:
+ - https://app.attio.com/w/acme/workspace/rec_12345
+ type: string
+ required:
+ - external_id
+ - synced_at
+ type: object
+ InstanceIntegrationResource:
+ additionalProperties: false
+ properties:
+ customerExternalId:
+ description: External identifier of the related customer in the same adapter
+ examples:
+ - rec_customer_123
+ type: string
+ deploymentZoneId:
+ description: Deployment zone ID
+ examples:
+ - 123e4567-e89b-12d3-a456-426614174005
+ type: string
+ description:
+ description: Instance description
+ examples:
+ - This is a sample instance description
+ type: string
+ endLicenseDate:
+ description: License end date
+ examples:
+ - "2024-10-01T12:00:00Z"
+ format: date-time
+ type: string
+ error:
+ description: Last synchronization error for this integration, if any
+ type: string
+ integrationMetadata:
+ additionalProperties: {}
+ description: Adapter-specific instance integration metadata
+ type: object
+ licenseId:
+ description: License ID
+ examples:
+ - 123e4567-e89b-12d3-a456-426614174004
+ type: string
+ name:
+ description: Instance name
+ examples:
+ - My Instance
+ type: string
+ slug:
+ description: Instance slug
+ examples:
+ - my-instance
+ type: string
+ startLicenseDate:
+ description: License start date
+ examples:
+ - "2023-10-01T12:00:00Z"
+ format: date-time
+ type: string
+ webUrl:
+ description: Absolute http(s) link to the record in the third-party system
+ examples:
+ - https://app.attio.com/w/acme/workspace/rec_12345
+ type: string
+ required:
+ - description
+ - endLicenseDate
+ - licenseId
+ - integrationMetadata
+ - name
+ - slug
+ - startLicenseDate
+ type: object
+ InstanceLifecycleStageChangedDelivered:
+ additionalProperties: false
+ properties:
+ changed_by:
+ format: uuid
+ type: string
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
+ type: string
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
+ type: string
+ instance_id:
+ format: uuid
+ type: string
+ new_lifecycle_stage:
+ type:
+ - string
+ - "null"
+ previous_lifecycle_stage:
+ type:
+ - string
+ - "null"
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
+ type: string
+ required:
+ - instance_id
+ - previous_lifecycle_stage
+ - new_lifecycle_stage
+ - changed_by
+ type: object
+ x-kaiten-payload: "#/components/schemas/InstanceLifecycleStageChanged"
+ InstanceStatusChangedDelivered:
+ additionalProperties: false
+ properties:
+ changed_by:
+ format: uuid
+ type: string
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
+ type: string
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
+ type: string
+ instance_id:
+ format: uuid
+ type: string
+ new_status:
+ enum:
+ - HEALTHY
+ - DEGRADED
+ - INCIDENT
+ - MAINTENANCE
+ type: string
+ previous_status:
+ enum:
+ - HEALTHY
+ - DEGRADED
+ - INCIDENT
+ - MAINTENANCE
+ type: string
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
+ type: string
+ required:
+ - instance_id
+ - previous_status
+ - new_status
+ - changed_by
+ type: object
+ x-kaiten-payload: "#/components/schemas/InstanceStatusChanged"
+ InstanceUpdateDelivered:
+ additionalProperties: false
+ properties:
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
+ type: string
+ createdAt:
+ description: Timestamp when the instance was created
+ examples:
+ - "2023-10-01T12:00:00Z"
+ format: date-time
+ readOnly: true
+ type: string
+ createdBy:
+ $ref: "#/components/schemas/User"
+ description: User who created this instance
+ readOnly: true
+ customerId:
+ description: ID of the customer associated with this instance
+ examples:
+ - 123e4567-e89b-12d3-a456-426614174003
+ type: string
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
+ type: string
+ customerSlug:
+ description: Slug of the customer associated with this instance
+ examples:
+ - acme-corp
+ readOnly: true
+ type: string
+ deploymentZoneId:
+ description: ID of the deployment zone associated with this instance (optional)
+ examples:
+ - 123e4567-e89b-12d3-a456-426614174005
+ type: string
+ deploymentZoneSlug:
+ description: Slug of the deployment zone associated with this instance (optional)
+ examples:
+ - aws-us-west-2
+ readOnly: true
+ type: string
+ description:
+ description: Brief description of the instance
+ examples:
+ - This is a sample instance description
+ type: string
+ endLicenseDate:
+ description: End date of the license for this instance
+ examples:
+ - "2024-10-01T12:00:00Z"
+ format: date-time
+ type: string
+ id:
+ description: Unique identifier for the instance
+ examples:
+ - 123e4567-e89b-12d3-a2456-426614174000
+ readOnly: true
+ type: string
+ integrations:
+ additionalProperties:
+ $ref: "#/components/schemas/InstanceIntegration"
+ description: Integrations grouped by adapter name
+ type: object
+ licenseChangeReason:
+ description: "BILLING_FALLBACK: the subscription ended and the instance moved to its family's fallback (V31-66). Null for any other change, the move a paid checkout applies included"
+ enum:
+ - BILLING_FALLBACK
+ type:
+ - string
+ - "null"
+ licenseId:
+ description: ID of the license version this instance is pinned to. An ARCHIVED version cannot be assigned, on create or by changing the license (CreateInstance.LicenseArchived, UpdateInstance.LicenseArchived); an instance already on a version that is archived later keeps it. A DRAFT version can be assigned, to try it before publishing.
+ examples:
+ - 123e4567-e89b-12d3-a456-426614174004
+ type: string
+ licenseSlug:
+ description: Slug of the license associated with this instance
+ examples:
+ - my-license
+ readOnly: true
+ type: string
+ lifecycleStage:
+ description: "Commercial lifecycle stage of the instance (free-form; suggested: TRIAL, ACTIVE, AT_RISK, CHURNED)"
+ examples:
+ - ACTIVE
+ readOnly: true
+ type: string
+ metadata:
+ additionalProperties: {}
+ description: Metadata for the instance
+ examples:
+ - key: value
+ type: object
+ name:
+ description: Name of the instance
+ examples:
+ - My Instance
+ minLength: 1
+ type: string
+ slug:
+ description: Optional URL-friendly identifier, unique per organization. Auto-generated if not provided.
+ examples:
+ - my-instance
+ maxLength: 100
+ minLength: 2
+ pattern: ^[a-z0-9][a-z0-9-]*[a-z0-9]$
+ type: string
+ startLicenseDate:
+ description: Start date of the license for this instance
+ examples:
+ - "2023-10-01T12:00:00Z"
+ format: date-time
+ type: string
+ status:
+ description: Operational status of the instance
+ enum:
+ - HEALTHY
+ - DEGRADED
+ - INCIDENT
+ - MAINTENANCE
+ examples:
+ - HEALTHY
+ readOnly: true
+ type: string
+ updatedAt:
+ description: Timestamp when the instance was last updated
+ examples:
+ - "2023-10-01T12:00:00Z"
+ format: date-time
+ readOnly: true
+ type: string
+ updatedBy:
+ $ref: "#/components/schemas/User"
+ description: User who last updated this instance
+ readOnly: true
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
+ type: string
+ required:
+ - licenseChangeReason
+ - id
+ - createdBy
+ - createdAt
+ - updatedBy
+ - updatedAt
+ - name
+ - description
+ - customerId
+ - customerSlug
+ - licenseId
+ - licenseSlug
+ - startLicenseDate
+ - endLicenseDate
+ type: object
+ x-kaiten-payload: "#/components/schemas/InstanceUpdate"
+ Invoice:
+ additionalProperties: false
+ properties:
+ billingEmail:
+ description: "The address the invoice is for, as it was when composed. Personal data: it appears in no event"
+ type:
+ - string
+ - "null"
+ boundaryAt:
+ description: The boundary the invoice bills. With the kind, it identifies the invoice within its subscription
+ format: date-time
+ type: string
+ collectionMethod:
+ enum:
+ - SEND_INVOICE
+ - CHARGE_AUTOMATICALLY
+ type: string
+ createdAt:
+ format: date-time
+ type: string
+ currency:
+ examples:
+ - EUR
+ type: string
+ customerName:
+ type: string
+ customerSlug:
+ description: As it was when the invoice was composed
+ type: string
+ daysUntilDue:
+ format: int32
+ type:
+ - integer
+ - "null"
+ discountTotal:
+ description: Sum of the discounts, in minor units
+ format: int64
+ type: integer
+ dueAt:
+ description: issuedAt + daysUntilDue
+ format: date-time
+ type:
+ - string
+ - "null"
+ handoff:
+ $ref: "#/components/schemas/InvoiceHandoff"
+ handoffStatus:
+ description: "PENDING: waiting in the handoff queue for the organization's accounting system"
+ enum:
+ - NOT_REQUIRED
+ - PENDING
+ - ACKNOWLEDGED
+ type: string
+ hold:
+ description: When the invoice was held and released
+ oneOf:
+ - $ref: "#/components/schemas/HoldRecord"
+ - enum:
+ - null
+ type: "null"
+ holdDetail:
+ description: Every meter whose journal failed a check
+ oneOf:
+ - $ref: "#/components/schemas/HoldDetail"
+ - enum:
+ - null
+ type: "null"
+ holdReason:
+ description: "Why a DRAFT is held: the usage journal it was measured from failed a consistency check (LEDGER_*), or its provider cannot take that many lines (PROVIDER_ITEM_LIMIT)"
+ enum:
+ - LEDGER_SEQUENCE_GAP
+ - LEDGER_CHAIN_BREAK
+ - LEDGER_COUNTER_MISMATCH
+ - PROVIDER_ITEM_LIMIT
+ - null
+ type:
+ - string
+ - "null"
+ id:
+ description: "The invoice's identifier: the key an accounting system deduplicates on"
+ type: string
+ instanceName:
+ type: string
+ instanceSlug:
+ type: string
+ issuedAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ kind:
+ description: ACTIVATION bills a subscription's first period, RENEWAL the period that ended and the one that starts, FINAL what is left when a subscription ends
+ enum:
+ - ACTIVATION
+ - RENEWAL
+ - FINAL
+ type: string
+ licenseId:
+ type: string
+ licenseSlug:
+ type: string
+ lines:
+ items:
+ $ref: "#/components/schemas/InvoiceLine"
+ type: array
+ overdue:
+ description: "Whether the invoice is overdue when read, the grace period of automatic collection included: the predicate of ?overdue=true. Computed by the invoice list and the invoice read; false in the answers of writes and in event payloads."
+ type: boolean
+ paidAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ provider:
+ description: The invoice in its payment provider; null for NOOP
+ oneOf:
+ - $ref: "#/components/schemas/ProviderRecord"
+ - enum:
+ - null
+ type: "null"
+ providerKind:
+ description: "Who collects it. NOOP: the organization itself, through the handoff queue"
+ enum:
+ - NOOP
+ - STRIPE
+ type: string
+ replacedByInvoiceId:
+ description: The invoice recomposed from this VOID one
+ type:
+ - string
+ - "null"
+ replacesInvoiceId:
+ description: The VOID invoice this one was recomposed from
+ type:
+ - string
+ - "null"
+ serviceFrom:
+ description: Earliest start of its lines' service periods
+ format: date-time
+ type: string
+ serviceTo:
+ description: Latest end of its lines' service periods
+ format: date-time
+ type: string
+ status:
+ description: "MANUAL: issued, for the organization to collect. PAID, UNCOLLECTIBLE and VOID are final. DRAFT: held, waiting for a release or a recompose"
+ enum:
+ - DRAFT
+ - PUSHED
+ - PUSH_FAILED
+ - MANUAL
+ - PAID
+ - PAYMENT_FAILED
+ - UNCOLLECTIBLE
+ - VOID
+ type: string
+ subtotal:
+ description: Sum of the lines, in minor units
+ format: int64
+ type: integer
+ total:
+ description: subtotal − discountTotal, in minor units
+ format: int64
+ type: integer
+ uncollectibleAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ updatedAt:
+ format: date-time
+ type: string
+ voidReason:
+ type:
+ - string
+ - "null"
+ voidedAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ required:
+ - licenseId
+ - lines
+ - billingEmail
+ - holdDetail
+ - hold
+ - handoff
+ - uncollectibleAt
+ - voidedAt
+ - voidReason
+ - replacesInvoiceId
+ - replacedByInvoiceId
+ - provider
+ - id
+ - kind
+ - boundaryAt
+ - serviceFrom
+ - serviceTo
+ - status
+ - holdReason
+ - providerKind
+ - collectionMethod
+ - currency
+ - subtotal
+ - discountTotal
+ - total
+ - issuedAt
+ - daysUntilDue
+ - dueAt
+ - paidAt
+ - customerSlug
+ - customerName
+ - instanceSlug
+ - instanceName
+ - licenseSlug
+ - handoffStatus
+ - overdue
+ - createdAt
+ - updatedAt
+ type: object
+ InvoiceHandoff:
+ additionalProperties: false
+ properties:
+ acknowledgedAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ claimCount:
+ format: int32
+ type: integer
+ externalReference:
+ description: The invoice's number in the organization's accounting system
+ type:
+ - string
+ - "null"
+ leaseId:
+ description: The claim currently holding it
+ type:
+ - string
+ - "null"
+ leasedUntil:
+ format: date-time
+ type:
+ - string
+ - "null"
+ status:
+ enum:
+ - NOT_REQUIRED
+ - PENDING
+ - ACKNOWLEDGED
+ type: string
+ required:
+ - status
+ - leaseId
+ - leasedUntil
+ - claimCount
+ - acknowledgedAt
+ - externalReference
+ type: object
+ InvoiceHold:
+ additionalProperties: false
+ properties:
+ entitlementId:
+ type: string
+ invariant:
+ description: The check that failed
+ type: string
+ required:
+ - entitlementId
+ - invariant
+ type: object
+ InvoiceLine:
+ additionalProperties: true
+ properties:
+ addonId:
+ description: The add-on version an ADDON line bills
+ type:
+ - string
+ - "null"
+ addonPriceId:
+ description: The add-on price an ADDON line bills
+ type:
+ - string
+ - "null"
+ amount:
+ description: round_half_up(quantity × unitAmountDecimal), in minor units; negative on a DISCOUNT line
+ examples:
+ - 2440
+ format: int64
+ type: integer
+ billingModel:
+ description: The price's billing model; absent on a DISCOUNT line
+ enum:
+ - FLAT_FEE
+ - USAGE_BASED
+ - OVERAGE
+ - null
+ type:
+ - string
+ - "null"
+ billingTiming:
+ description: ADVANCE lines bill the period that starts at the boundary, ARREARS lines the one that ends there; absent on a DISCOUNT line
+ enum:
+ - ADVANCE
+ - ARREARS
+ - null
+ type:
+ - string
+ - "null"
+ capped:
+ description: "Set on a preview line whose sample exceeded what the licence accepts: reports above that are rejected, so the excess is not billed"
+ type: boolean
+ description:
+ description: The arithmetic of the line
+ examples:
+ - 3.05 × 8.00 EUR (per 10k tokens); 130,500 used; 30,500 above the applied limit (100,000)
+ type: string
+ discount:
+ description: How a DISCOUNT line was computed
+ oneOf:
+ - $ref: "#/components/schemas/InvoiceLineDiscount"
+ - enum:
+ - null
+ type: "null"
+ entitlementId:
+ description: The metered entitlement, on USAGE and OVERAGE lines
+ type:
+ - string
+ - "null"
+ entitlementSlug:
+ description: Its slug, as it was when the line was composed
+ type:
+ - string
+ - "null"
+ id:
+ description: The line's identifier, stable for the invoice's life; absent on a preview
+ type: string
+ instanceAddonId:
+ description: The instance's attachment an ADDON line bills
+ type:
+ - string
+ - "null"
+ instanceVoucherId:
+ description: The redemption a DISCOUNT line applies
+ type:
+ - string
+ - "null"
+ label:
+ description: The price's display label, else a derived one
+ examples:
+ - Tokens — overage
+ type: string
+ licensePriceId:
+ description: The licence price the line bills; null on an ADDON line
+ type:
+ - string
+ - "null"
+ metering:
+ description: How a USAGE or OVERAGE line's quantity was measured
+ oneOf:
+ - $ref: "#/components/schemas/InvoiceLineMetering"
+ - enum:
+ - null
+ type: "null"
+ overage:
+ description: The arithmetic of an OVERAGE line
+ oneOf:
+ - $ref: "#/components/schemas/InvoiceLineOverage"
+ - enum:
+ - null
+ type: "null"
+ provider:
+ description: The line in the payment provider, once pushed
+ oneOf:
+ - $ref: "#/components/schemas/InvoiceLineProvider"
+ - enum:
+ - null
+ type: "null"
+ quantity:
+ description: "In sale units, a decimal string: 1 on a BASE line, the quantity held on an ADDON line"
+ examples:
+ - "3.05"
+ type: string
+ seq:
+ description: Position on the invoice, from 1
+ format: int64
+ type: integer
+ serviceFrom:
+ description: Start of the period the line bills (inclusive)
+ format: date-time
+ type: string
+ serviceTo:
+ description: End of the period the line bills (exclusive)
+ format: date-time
+ type: string
+ trueUp:
+ description: "On the true-up line of an ADVANCE add-on of a TRUE_UP family: the highest quantity held in the elapsed period and the quantity billed in advance for it (V31-70)"
+ oneOf:
+ - $ref: "#/components/schemas/InvoiceLineTrueUp"
+ - enum:
+ - null
+ type: "null"
+ type:
+ description: "BASE: the subscription's FLAT_FEE price. ADDON: an add-on's FLAT_FEE price times the quantity held at the boundary. USAGE: a USAGE_BASED price's metered usage. OVERAGE: an OVERAGE price's usage above the licence's limit. DISCOUNT: a PRICE voucher's discount, negative."
+ enum:
+ - BASE
+ - ADDON
+ - USAGE
+ - OVERAGE
+ - DISCOUNT
+ type: string
+ unitAmountDecimal:
+ description: The price's unit amount in minor units; absent on a DISCOUNT line
+ examples:
+ - "800"
+ type:
+ - string
+ - "null"
+ voucherId:
+ description: The voucher a DISCOUNT line applies
+ type:
+ - string
+ - "null"
+ required:
+ - seq
+ - type
+ - billingModel
+ - billingTiming
+ - licensePriceId
+ - addonPriceId
+ - addonId
+ - instanceAddonId
+ - voucherId
+ - instanceVoucherId
+ - entitlementId
+ - entitlementSlug
+ - label
+ - description
+ - serviceFrom
+ - serviceTo
+ - quantity
+ - unitAmountDecimal
+ - amount
+ - metering
+ - overage
+ - trueUp
+ - discount
+ - provider
+ type: object
+ InvoiceLineDiscount:
+ additionalProperties: false
+ properties:
+ allocations:
+ description: "The part of the line each target bears, by ascending targetSeq: they sum to the line's magnitude, and no target is discounted below 0. A payment provider applies each one to its target line"
+ items:
+ $ref: "#/components/schemas/DiscountAllocation"
+ readOnly: true
+ type: array
+ application:
+ description: Which invoice of the redemption this is, from 1
+ format: int32
+ type: integer
+ applicationsMax:
+ description: How many invoices the redemption discounts; null for FOREVER
+ format: int32
+ type:
+ - integer
+ - "null"
+ appliesTo:
+ enum:
+ - LICENSE_BASE
+ - ADDONS
+ - BOTH
+ - SELECTED_PRICES
+ type: string
+ base:
+ description: What its targets still amounted to before it, exact, in minor units
+ type: string
+ currency:
+ type:
+ - string
+ - "null"
+ discountType:
+ enum:
+ - PERCENTAGE
+ - FIXED_AMOUNT
+ type: string
+ discountValue:
+ description: The percentage, or the amount in minor units
+ type: string
+ targetSeqs:
+ description: The lines it discounts
+ items:
+ format: int64
+ type: integer
+ type:
+ - array
+ - "null"
+ required:
+ - discountType
+ - discountValue
+ - currency
+ - appliesTo
+ - targetSeqs
+ - base
+ - application
+ - applicationsMax
+ - allocations
+ type: object
+ InvoiceLineLedger:
+ additionalProperties: false
+ properties:
+ firstSeq:
+ description: First report of the period, null when it has none
+ format: int64
+ type:
+ - integer
+ - "null"
+ instanceId:
+ description: "The instance whose reports these are: with the line's entitlement, the pair the journal is kept by. It outlives the instance"
+ type:
+ - string
+ - "null"
+ lastSeq:
+ description: Last report of the period, null when it has none
+ format: int64
+ type:
+ - integer
+ - "null"
+ rows:
+ description: Reports in the period
+ format: int64
+ type: integer
+ sumDelta:
+ description: Sum of the reports' movements, before any floor
+ type: string
+ sumOverage:
+ description: Sum of their movements above the limit, before any floor
+ type:
+ - string
+ - "null"
+ required:
+ - instanceId
+ - firstSeq
+ - lastSeq
+ - rows
+ - sumDelta
+ - sumOverage
+ type: object
+ InvoiceLineMetering:
+ additionalProperties: false
+ properties:
+ ledger:
+ description: The usage journal rows the line was measured from
+ oneOf:
+ - $ref: "#/components/schemas/InvoiceLineLedger"
+ - enum:
+ - null
+ type: "null"
+ measuredQuantity:
+ description: "In measured units: the usage for a USAGE line, the overage for an OVERAGE line"
+ examples:
+ - "30500"
+ type: string
+ negativeSegmentsFloored:
+ description: Windows whose net movement was negative and counted as 0
+ examples:
+ - 0
+ format: int64
+ type: integer
+ saleUnitFactor:
+ description: Measured units in one sale unit
+ examples:
+ - "10000"
+ type: string
+ windows:
+ description: Reset windows the period spans
+ examples:
+ - 1
+ format: int64
+ type: integer
+ required:
+ - saleUnitFactor
+ - measuredQuantity
+ - windows
+ - negativeSegmentsFloored
+ - ledger
+ type: object
+ InvoiceLineOverage:
+ additionalProperties: false
+ properties:
+ limits:
+ description: The limits applied, in the order they first applied
+ items:
+ $ref: "#/components/schemas/OverageLimit"
+ type: array
+ overageMeasured:
+ description: Usage above the applied limit
+ examples:
+ - "30500"
+ type: string
+ usageMeasured:
+ description: Usage in measured units
+ examples:
+ - "130500"
+ type: string
+ required:
+ - usageMeasured
+ - overageMeasured
+ - limits
+ type: object
+ InvoiceLineProvider:
+ additionalProperties: false
+ properties:
+ amount:
+ description: The provider's amount, read back for reconciliation
+ format: int64
+ type: integer
+ couponIds:
+ description: "On a DISCOUNT line: the provider's discount (a Stripe coupon) of each allocation, in the allocations' order"
+ items:
+ type: string
+ type:
+ - array
+ - "null"
+ externalLineId:
+ description: The provider's id of the line; absent on a DISCOUNT line, which the provider holds as discounts on its targets
+ type: string
+ type: object
+ InvoiceLineTrueUp:
+ additionalProperties: false
+ properties:
+ billedQuantity:
+ description: The quantity its ADVANCE line billed at the previous boundary; 0 when it started after it
+ format: int32
+ type: integer
+ maxQuantity:
+ description: The highest quantity the attachment held in the elapsed period
+ format: int32
+ type: integer
+ required:
+ - maxQuantity
+ - billedQuantity
+ type: object
+ InvoicePayment:
+ additionalProperties: false
+ properties:
+ externalReference:
+ description: The invoice's number in the organization's accounting system, 1 to 255 characters. It also acknowledges an invoice still waiting in the handoff queue
+ type: string
+ note:
+ description: A note, such as the transfer's reference, kept only in the paid event
+ maxLength: 1000
+ type: string
+ paidAt:
+ description: When it was paid, now or earlier; now when omitted
+ format: date-time
+ type: string
+ type: object
+ InvoicePreview:
+ additionalProperties: false
+ properties:
+ asOf:
+ description: When the preview was composed
+ format: date-time
+ type: string
+ boundaryAt:
+ description: The boundary the invoice bills
+ format: date-time
+ type: string
+ currency:
+ description: ISO 4217 code
+ examples:
+ - EUR
+ type: string
+ discountTotal:
+ description: Sum of the discounts, in minor units
+ format: int64
+ type: integer
+ kind:
+ description: The boundary previewed
+ enum:
+ - ACTIVATION
+ - RENEWAL
+ - FINAL
+ type: string
+ licenseSlug:
+ description: The licence version previewed
+ type: string
+ lines:
+ items:
+ $ref: "#/components/schemas/InvoiceLine"
+ type: array
+ serviceFrom:
+ description: Earliest start of the lines' service periods; absent without lines
+ format: date-time
+ type: string
+ serviceTo:
+ description: Latest end of the lines' service periods; absent without lines
+ format: date-time
+ type: string
+ status:
+ description: Always PREVIEW
+ enum:
+ - PREVIEW
+ type: string
+ subtotal:
+ description: Sum of the lines, in minor units
+ format: int64
+ type: integer
+ total:
+ description: subtotal − discountTotal, in minor units
+ format: int64
+ type: integer
+ wouldHold:
+ description: Meters whose usage journal fails a consistency check, which would hold the invoice. Always empty for a sample.
+ items:
+ $ref: "#/components/schemas/InvoiceHold"
+ type: array
+ required:
+ - status
+ - kind
+ - asOf
+ - boundaryAt
+ - licenseSlug
+ - currency
+ - subtotal
+ - discountTotal
+ - total
+ - lines
+ - wouldHold
+ type: object
+ InvoicePreviewScenario:
+ additionalProperties: false
+ properties:
+ addOns:
+ description: "Add-on versions priced in: their fees for the base's period and their metered prices, and their grants for the sample (PreviewLicenseInvoice.AddonNotFound; .InvalidAddOns for a repeated one or a quantity below 1)."
+ items:
+ $ref: "#/components/schemas/AddonAmount"
+ type:
+ - array
+ - "null"
+ basePriceId:
+ description: The FLAT_FEE price billed as the base (PreviewLicenseInvoice.PriceNotFound). Defaults to the version's default FLAT_FEE price (PreviewLicenseInvoice.NoBasePrice when it has none).
+ type: string
+ instanceSlug:
+ description: "Rate this instance's own usage, read from its journal over [P0, now): P0 of its live subscription, else one billing period back (PreviewLicenseInvoice.InstanceNotFound; .OutsideRetention when P0 is before the usage history). Not with sampleUsage."
+ type: string
+ sampleUsage:
+ description: Usage of the metered entitlements over the period that ends, one entry per entitlement, rated against the version's grant and the listed add-ons'. An entitlement left out used nothing.
+ items:
+ $ref: "#/components/schemas/SampleUsageAmount"
+ type:
+ - array
+ - "null"
+ voucherCode:
+ description: A PRICE voucher's discount, applied as a redemption would, consuming nothing (PreviewLicenseInvoice.VoucherNotFound; .VoucherInvalid when it is not an ACTIVE PRICE voucher, nor an ACTIVE COMPOSITE with a PRICE part).
+ type: string
+ type: object
+ InvoiceSummary:
+ additionalProperties: false
+ properties:
+ boundaryAt:
+ description: The boundary the invoice bills. With the kind, it identifies the invoice within its subscription
+ format: date-time
+ type: string
+ collectionMethod:
+ enum:
+ - SEND_INVOICE
+ - CHARGE_AUTOMATICALLY
+ type: string
+ createdAt:
+ format: date-time
+ type: string
+ currency:
+ examples:
+ - EUR
+ type: string
+ customerName:
+ type: string
+ customerSlug:
+ description: As it was when the invoice was composed
+ type: string
+ daysUntilDue:
+ format: int32
+ type:
+ - integer
+ - "null"
+ discountTotal:
+ description: Sum of the discounts, in minor units
+ format: int64
+ type: integer
+ dueAt:
+ description: issuedAt + daysUntilDue
+ format: date-time
+ type:
+ - string
+ - "null"
+ handoffStatus:
+ description: "PENDING: waiting in the handoff queue for the organization's accounting system"
+ enum:
+ - NOT_REQUIRED
+ - PENDING
+ - ACKNOWLEDGED
+ type: string
+ holdReason:
+ description: "Why a DRAFT is held: the usage journal it was measured from failed a consistency check (LEDGER_*), or its provider cannot take that many lines (PROVIDER_ITEM_LIMIT)"
+ enum:
+ - LEDGER_SEQUENCE_GAP
+ - LEDGER_CHAIN_BREAK
+ - LEDGER_COUNTER_MISMATCH
+ - PROVIDER_ITEM_LIMIT
+ - null
+ type:
+ - string
+ - "null"
+ id:
+ description: "The invoice's identifier: the key an accounting system deduplicates on"
+ type: string
+ instanceName:
+ type: string
+ instanceSlug:
+ type: string
+ issuedAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ kind:
+ description: ACTIVATION bills a subscription's first period, RENEWAL the period that ended and the one that starts, FINAL what is left when a subscription ends
+ enum:
+ - ACTIVATION
+ - RENEWAL
+ - FINAL
+ type: string
+ licenseSlug:
+ type: string
+ overdue:
+ description: "Whether the invoice is overdue when read, the grace period of automatic collection included: the predicate of ?overdue=true. Computed by the invoice list and the invoice read; false in the answers of writes and in event payloads."
+ type: boolean
+ paidAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ providerKind:
+ description: "Who collects it. NOOP: the organization itself, through the handoff queue"
+ enum:
+ - NOOP
+ - STRIPE
+ type: string
+ serviceFrom:
+ description: Earliest start of its lines' service periods
+ format: date-time
+ type: string
+ serviceTo:
+ description: Latest end of its lines' service periods
+ format: date-time
+ type: string
+ status:
+ description: "MANUAL: issued, for the organization to collect. PAID, UNCOLLECTIBLE and VOID are final. DRAFT: held, waiting for a release or a recompose"
+ enum:
+ - DRAFT
+ - PUSHED
+ - PUSH_FAILED
+ - MANUAL
+ - PAID
+ - PAYMENT_FAILED
+ - UNCOLLECTIBLE
+ - VOID
+ type: string
+ subtotal:
+ description: Sum of the lines, in minor units
+ format: int64
+ type: integer
+ total:
+ description: subtotal − discountTotal, in minor units
+ format: int64
+ type: integer
+ updatedAt:
+ format: date-time
+ type: string
+ required:
+ - id
+ - kind
+ - boundaryAt
+ - serviceFrom
+ - serviceTo
+ - status
+ - holdReason
+ - providerKind
+ - collectionMethod
+ - currency
+ - subtotal
+ - discountTotal
+ - total
+ - issuedAt
+ - daysUntilDue
+ - dueAt
+ - paidAt
+ - customerSlug
+ - customerName
+ - instanceSlug
+ - instanceName
+ - licenseSlug
+ - handoffStatus
+ - overdue
+ - createdAt
+ - updatedAt
+ type: object
+ InvoiceVoid:
+ additionalProperties: false
+ properties:
+ reason:
+ description: 1 to 500 characters; .ReasonRequired otherwise
+ maxLength: 500
+ minLength: 1
+ type: string
+ required:
+ - reason
+ type: object
+ InvoiceWriteOff:
+ additionalProperties: false
+ properties:
+ reason:
+ description: 1 to 500 characters; .ReasonRequired otherwise
+ maxLength: 500
+ minLength: 1
+ type: string
+ required:
+ - reason
+ type: object
+ IssuedInvoiceDelivered:
+ additionalProperties: false
+ properties:
+ boundaryAt:
+ description: The boundary the invoice bills. With the kind, it identifies the invoice within its subscription
+ format: date-time
+ type: string
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
+ type: string
+ collectionMethod:
+ enum:
+ - SEND_INVOICE
+ - CHARGE_AUTOMATICALLY
+ type: string
+ createdAt:
+ format: date-time
+ type: string
+ currency:
+ examples:
+ - EUR
+ type: string
+ customerName:
+ type: string
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
+ type: string
+ customerSlug:
+ description: As it was when the invoice was composed
+ type: string
+ daysUntilDue:
+ format: int32
+ type:
+ - integer
+ - "null"
+ discountTotal:
+ description: Sum of the discounts, in minor units
+ format: int64
+ type: integer
+ dueAt:
+ description: issuedAt + daysUntilDue
+ format: date-time
+ type:
+ - string
+ - "null"
+ handoffStatus:
+ description: "PENDING: waiting in the handoff queue for the organization's accounting system"
+ enum:
+ - NOT_REQUIRED
+ - PENDING
+ - ACKNOWLEDGED
+ type: string
+ holdReason:
+ description: "Why a DRAFT is held: the usage journal it was measured from failed a consistency check (LEDGER_*), or its provider cannot take that many lines (PROVIDER_ITEM_LIMIT)"
+ enum:
+ - LEDGER_SEQUENCE_GAP
+ - LEDGER_CHAIN_BREAK
+ - LEDGER_COUNTER_MISMATCH
+ - PROVIDER_ITEM_LIMIT
+ - null
+ type:
+ - string
+ - "null"
+ id:
+ description: "The invoice's identifier: the key an accounting system deduplicates on"
+ type: string
+ instanceName:
+ type: string
+ instanceSlug:
+ type: string
+ issuedAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ kind:
+ description: ACTIVATION bills a subscription's first period, RENEWAL the period that ended and the one that starts, FINAL what is left when a subscription ends
+ enum:
+ - ACTIVATION
+ - RENEWAL
+ - FINAL
+ type: string
+ licenseId:
+ type: string
+ licenseSlug:
+ type: string
+ lines:
+ items:
+ $ref: "#/components/schemas/InvoiceLine"
+ type: array
+ linesTruncated:
+ description: "Set when the lines did not fit the event: read the invoice for all of them"
+ type: boolean
+ overdue:
+ description: "Whether the invoice is overdue when read, the grace period of automatic collection included: the predicate of ?overdue=true. Computed by the invoice list and the invoice read; false in the answers of writes and in event payloads."
+ type: boolean
+ paidAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ providerKind:
+ description: "Who collects it. NOOP: the organization itself, through the handoff queue"
+ enum:
+ - NOOP
+ - STRIPE
+ type: string
+ serviceFrom:
+ description: Earliest start of its lines' service periods
+ format: date-time
+ type: string
+ serviceTo:
+ description: Latest end of its lines' service periods
+ format: date-time
+ type: string
+ status:
+ description: "MANUAL: issued, for the organization to collect. PAID, UNCOLLECTIBLE and VOID are final. DRAFT: held, waiting for a release or a recompose"
+ enum:
+ - DRAFT
+ - PUSHED
+ - PUSH_FAILED
+ - MANUAL
+ - PAID
+ - PAYMENT_FAILED
+ - UNCOLLECTIBLE
+ - VOID
+ type: string
+ subtotal:
+ description: Sum of the lines, in minor units
+ format: int64
+ type: integer
+ total:
+ description: subtotal − discountTotal, in minor units
+ format: int64
+ type: integer
+ updatedAt:
+ format: date-time
+ type: string
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
+ type: string
+ required:
+ - licenseId
+ - lines
+ - id
+ - kind
+ - boundaryAt
+ - serviceFrom
+ - serviceTo
+ - status
+ - holdReason
+ - providerKind
+ - collectionMethod
+ - currency
+ - subtotal
+ - discountTotal
+ - total
+ - issuedAt
+ - daysUntilDue
+ - dueAt
+ - paidAt
+ - customerSlug
+ - customerName
+ - instanceSlug
+ - instanceName
+ - licenseSlug
+ - handoffStatus
+ - overdue
+ - createdAt
+ - updatedAt
+ type: object
+ x-kaiten-payload: "#/components/schemas/IssuedInvoice"
+ License:
+ additionalProperties: false
+ properties:
+ createdAt:
+ description: Timestamp when this license was created
+ examples:
+ - "2023-10-01T12:00:00Z"
+ format: date-time
+ readOnly: true
+ type: string
+ description:
+ description: Detailed description of the license capabilities and restrictions
+ examples:
+ - This license provides access to all enterprise features including advanced analytics, priority support, and unlimited users.
+ maxLength: 500
+ type: string
+ familyId:
+ description: "Identifier of the license family this license is a version of. Stable across renames and new versions: two licenses sharing it are the same product. Always present in responses. On create, optional: names the family this license is a new version of, as familySlug does by slug; omit both to create a new family. On update, optional and must be the license's own family: a version cannot move between families (UpdateLicense.FamilyNotReassignable)."
+ examples:
+ - 123e4567-e89b-12d3-a456-426614174000
+ type: string
+ familySlug:
+ description: "Write-only on create: slug of the family this license is a new version of. familyId names the same family by identifier; sent together they must agree. Omit both to create a new family, of which this license becomes version 1. Never returned -- see familyId. Refused on update: a version cannot move between families."
+ examples:
+ - enterprise-license
+ maxLength: 100
+ minLength: 2
+ pattern: ^[a-z0-9][a-z0-9-]*[a-z0-9]$
+ type: string
+ writeOnly: true
+ id:
+ description: Unique identifier for the license
+ examples:
+ - 123e4567-e89b-12d3-a456-426614174000
+ readOnly: true
+ type: string
+ isDefault:
+ description: "Whether this version is the one its family puts forward: at most one version per family, and only a PUBLISHED one (CreateLicense.DefaultMustBePublished, UpdateLicense.DefaultMustBePublished). Resolution hands it out ahead of the highest-numbered published version, so it keeps being served when a newer version is published. Setting it takes the flag from the family's previous default, which gets a LICENSE_UPDATED event of its own. On update, sending false unsets it; the default cannot be archived until another version takes its place or the flag is unset (ArchiveLicense.DefaultMustBePublished)."
+ examples:
+ - false
+ type: boolean
+ lifecycleState:
+ description: Whether this version may be served. Always present in responses. Optional on create, where it is DRAFT or PUBLISHED and PUBLISHED when omitted; ARCHIVED is refused (CreateLicense.LifecycleStateNotSettable), since a version is archived by withdrawing it from sale. After that it changes only through publish-license (DRAFT to PUBLISHED), archive-license (PUBLISHED to ARCHIVED) and unarchive-license (ARCHIVED to PUBLISHED); update accepts the stored state and refuses any other (UpdateLicense.LifecycleStateNotSettable). Only a PUBLISHED version can be the family's default. An ARCHIVED version cannot be assigned to an instance (CreateInstance.LicenseArchived, UpdateInstance.LicenseArchived); instances already on it keep it.
+ enum:
+ - DRAFT
+ - PUBLISHED
+ - ARCHIVED
+ examples:
+ - PUBLISHED
+ type: string
+ name:
+ description: Display name of the license
+ examples:
+ - Enterprise License
+ maxLength: 100
+ minLength: 1
+ type: string
+ pricingType:
+ description: How this version is sold. FREE and PAID can be bought self-serve; CUSTOM sends a buyer to selfServeCtaUrl or a conversation. Always present in responses. On create it defaults to CUSTOM; on update, omit it or send null to keep the stored value.
+ enum:
+ - FREE
+ - PAID
+ - CUSTOM
+ examples:
+ - PAID
+ type:
+ - string
+ - "null"
+ requiresPaymentMethod:
+ description: Whether self-serve signup captures a payment method before activation. Always present in responses; false when omitted on create. On update, omit it or send null to keep the stored value.
+ examples:
+ - false
+ type:
+ - boolean
+ - "null"
+ selfServeCtaUrl:
+ description: "Where a buyer is sent when this version cannot be bought self-serve: an http(s) URL of at most 2048 characters (CreateLicense.InvalidSelfServeCtaUrl). Null when there is none. On update, omit it or send null to keep the stored value, or send an empty string to remove it."
+ examples:
+ - https://example.com/contact-sales
+ maxLength: 2048
+ type:
+ - string
+ - "null"
+ slug:
+ description: Optional URL-friendly identifier, unique per organization. When omitted, a new version of an existing family takes {familySlug}-v{version}, and any other license gets one generated from its name. A license that opens a new family gives the family its slug.
+ examples:
+ - enterprise-license
+ maxLength: 100
+ minLength: 2
+ pattern: ^[a-z0-9][a-z0-9-]*[a-z0-9]$
+ type: string
+ trialPeriodDays:
+ description: Trial length, in days, a subscription to this version starts with by default. Null when there is none. From 1 to 365 (CreateLicense.InvalidTrialDays, UpdateLicense.InvalidTrialDays). On update, omit it or send null to keep the stored value, or send 0 to remove the trial.
+ examples:
+ - 14
+ format: int32
+ maximum: 365
+ minimum: 0
+ type:
+ - integer
+ - "null"
+ type:
+ description: Type of the license determining its capabilities and restrictions
+ enum:
+ - DEVELOPMENT
+ - TRIAL
+ - PAID
+ - COMMUNITY
+ examples:
+ - paid
+ type: string
+ updatedAt:
+ description: Timestamp when this license was last updated
+ examples:
+ - "2023-10-02T12:00:00Z"
+ format: date-time
+ readOnly: true
+ type: string
+ version:
+ description: "Version of the license within its family, assigned by the server when the version is created and never changed afterwards: the version's derived slug ({familySlug}-v{version}, see slug) is built from it. A number is never reused within a family, even after its version is deleted. Create refuses any value (CreateLicense.VersionNotSettable); update accepts only the stored one, so a representation can be written back as it was read (UpdateLicense.VersionNotSettable)."
+ examples:
+ - "1"
+ readOnly: true
+ type: string
+ versionName:
+ description: Optional human-readable name for the license version. Defaults to "Version - ".
+ examples:
+ - Winter 2024 Release
+ maxLength: 50
+ type: string
+ required:
+ - id
+ - name
+ - description
+ - type
+ - version
+ - isDefault
+ - createdAt
+ - updatedAt
+ type: object
+ LicenseDelivered:
+ additionalProperties: false
+ properties:
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
+ type: string
+ createdAt:
+ description: Timestamp when this license was created
+ examples:
+ - "2023-10-01T12:00:00Z"
+ format: date-time
+ readOnly: true
+ type: string
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
+ type: string
+ description:
+ description: Detailed description of the license capabilities and restrictions
+ examples:
+ - This license provides access to all enterprise features including advanced analytics, priority support, and unlimited users.
+ maxLength: 500
+ type: string
+ familyId:
+ description: "Identifier of the license family this license is a version of. Stable across renames and new versions: two licenses sharing it are the same product. Always present in responses. On create, optional: names the family this license is a new version of, as familySlug does by slug; omit both to create a new family. On update, optional and must be the license's own family: a version cannot move between families (UpdateLicense.FamilyNotReassignable)."
+ examples:
+ - 123e4567-e89b-12d3-a456-426614174000
+ type: string
+ familySlug:
+ description: "Write-only on create: slug of the family this license is a new version of. familyId names the same family by identifier; sent together they must agree. Omit both to create a new family, of which this license becomes version 1. Never returned -- see familyId. Refused on update: a version cannot move between families."
+ examples:
+ - enterprise-license
+ maxLength: 100
+ minLength: 2
+ pattern: ^[a-z0-9][a-z0-9-]*[a-z0-9]$
+ type: string
+ writeOnly: true
+ id:
+ description: Unique identifier for the license
+ examples:
+ - 123e4567-e89b-12d3-a456-426614174000
+ readOnly: true
+ type: string
+ isDefault:
+ description: "Whether this version is the one its family puts forward: at most one version per family, and only a PUBLISHED one (CreateLicense.DefaultMustBePublished, UpdateLicense.DefaultMustBePublished). Resolution hands it out ahead of the highest-numbered published version, so it keeps being served when a newer version is published. Setting it takes the flag from the family's previous default, which gets a LICENSE_UPDATED event of its own. On update, sending false unsets it; the default cannot be archived until another version takes its place or the flag is unset (ArchiveLicense.DefaultMustBePublished)."
+ examples:
+ - false
+ type: boolean
+ lifecycleState:
+ description: Whether this version may be served. Always present in responses. Optional on create, where it is DRAFT or PUBLISHED and PUBLISHED when omitted; ARCHIVED is refused (CreateLicense.LifecycleStateNotSettable), since a version is archived by withdrawing it from sale. After that it changes only through publish-license (DRAFT to PUBLISHED), archive-license (PUBLISHED to ARCHIVED) and unarchive-license (ARCHIVED to PUBLISHED); update accepts the stored state and refuses any other (UpdateLicense.LifecycleStateNotSettable). Only a PUBLISHED version can be the family's default. An ARCHIVED version cannot be assigned to an instance (CreateInstance.LicenseArchived, UpdateInstance.LicenseArchived); instances already on it keep it.
+ enum:
+ - DRAFT
+ - PUBLISHED
+ - ARCHIVED
+ examples:
+ - PUBLISHED
+ type: string
+ name:
+ description: Display name of the license
+ examples:
+ - Enterprise License
+ maxLength: 100
+ minLength: 1
+ type: string
+ pricingType:
+ description: How this version is sold. FREE and PAID can be bought self-serve; CUSTOM sends a buyer to selfServeCtaUrl or a conversation. Always present in responses. On create it defaults to CUSTOM; on update, omit it or send null to keep the stored value.
+ enum:
+ - FREE
+ - PAID
+ - CUSTOM
+ examples:
+ - PAID
+ type:
+ - string
+ - "null"
+ requiresPaymentMethod:
+ description: Whether self-serve signup captures a payment method before activation. Always present in responses; false when omitted on create. On update, omit it or send null to keep the stored value.
+ examples:
+ - false
+ type:
+ - boolean
+ - "null"
+ selfServeCtaUrl:
+ description: "Where a buyer is sent when this version cannot be bought self-serve: an http(s) URL of at most 2048 characters (CreateLicense.InvalidSelfServeCtaUrl). Null when there is none. On update, omit it or send null to keep the stored value, or send an empty string to remove it."
+ examples:
+ - https://example.com/contact-sales
+ maxLength: 2048
+ type:
+ - string
+ - "null"
+ slug:
+ description: Optional URL-friendly identifier, unique per organization. When omitted, a new version of an existing family takes {familySlug}-v{version}, and any other license gets one generated from its name. A license that opens a new family gives the family its slug.
+ examples:
+ - enterprise-license
+ maxLength: 100
+ minLength: 2
+ pattern: ^[a-z0-9][a-z0-9-]*[a-z0-9]$
+ type: string
+ trialPeriodDays:
+ description: Trial length, in days, a subscription to this version starts with by default. Null when there is none. From 1 to 365 (CreateLicense.InvalidTrialDays, UpdateLicense.InvalidTrialDays). On update, omit it or send null to keep the stored value, or send 0 to remove the trial.
+ examples:
+ - 14
+ format: int32
+ maximum: 365
+ minimum: 0
+ type:
+ - integer
+ - "null"
+ type:
+ description: Type of the license determining its capabilities and restrictions
+ enum:
+ - DEVELOPMENT
+ - TRIAL
+ - PAID
+ - COMMUNITY
+ examples:
+ - paid
+ type: string
+ updatedAt:
+ description: Timestamp when this license was last updated
+ examples:
+ - "2023-10-02T12:00:00Z"
+ format: date-time
+ readOnly: true
+ type: string
+ version:
+ description: "Version of the license within its family, assigned by the server when the version is created and never changed afterwards: the version's derived slug ({familySlug}-v{version}, see slug) is built from it. A number is never reused within a family, even after its version is deleted. Create refuses any value (CreateLicense.VersionNotSettable); update accepts only the stored one, so a representation can be written back as it was read (UpdateLicense.VersionNotSettable)."
+ examples:
+ - "1"
+ readOnly: true
+ type: string
+ versionName:
+ description: Optional human-readable name for the license version. Defaults to "Version - ".
+ examples:
+ - Winter 2024 Release
+ maxLength: 50
+ type: string
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
+ type: string
+ required:
+ - id
+ - name
+ - description
+ - type
+ - version
+ - isDefault
+ - createdAt
+ - updatedAt
+ type: object
+ x-kaiten-payload: "#/components/schemas/License"
+ LicenseEntitlement:
+ additionalProperties: false
+ properties:
+ createdAt:
+ description: Timestamp when this entitlement was created
+ examples:
+ - "2023-10-01T12:00:00Z"
+ format: date-time
+ readOnly: true
+ type: string
+ createdBy:
+ $ref: "#/components/schemas/User"
+ description: User who created this entitlement
+ readOnly: true
+ entitlementGroups:
+ description: Groups associated with this entitlement
+ items:
+ $ref: "#/components/schemas/EntitlementGroupSummary"
+ readOnly: true
+ type:
+ - array
+ - "null"
+ entitlementName:
+ description: Name of the entitlement feature or service
+ examples:
+ - Premium Support
+ readOnly: true
+ type: string
+ entitlementSlug:
+ description: URL-friendly identifier for the entitlement
+ examples:
+ - premium-support
+ format: string
+ type: string
+ entitlementType:
+ description: Type of entitlement (BOOLEAN, NUMBER, or CONFIG)
+ enum:
+ - BOOLEAN
+ - NUMBER
+ - CONFIG
+ examples:
+ - NUMBER
+ readOnly: true
+ type: string
+ licenseId:
+ description: Unique identifier for the license this entitlement belongs to
+ examples:
+ - a1b2c3d4-e5f6-7g8h-9i0j-k1l2m3n4o5p6
+ readOnly: true
+ type: string
+ licenseSlug:
+ description: URL-friendly identifier for the license this entitlement belongs to
+ examples:
+ - enterprise-license
+ readOnly: true
+ type: string
+ limitCapExceededOveragePercent:
+ description: "Derives this grant's enforcement from its own numeric value: -1 when the value is unlimited (no cap, no overage); otherwise 0 rejects usage above the value (hard limit) and a positive percentage allows usage to exceed it by that much before being rejected (soft limit). Null for non-numeric (BOOLEAN/CONFIG) grants."
+ examples:
+ - 10
+ format: int32
+ type: integer
+ updatedAt:
+ description: Timestamp when this entitlement was last updated
+ examples:
+ - "2023-10-02T12:00:00Z"
+ format: date-time
+ readOnly: true
+ type: string
+ updatedBy:
+ $ref: "#/components/schemas/User"
+ description: User who last updated this entitlement
+ readOnly: true
+ value:
+ description: "Typed entitlement value. Supported shapes: {type:number,value:number}, {type:boolean,value:boolean}, {type:object,value:{...}}"
+ discriminator:
+ mapping:
+ boolean: "#/components/schemas/BooleanEntitlementValue"
+ number: "#/components/schemas/NumberEntitlementValue"
+ object: "#/components/schemas/ConfigEntitlementValue"
+ propertyName: type
+ oneOf:
+ - $ref: "#/components/schemas/NumberEntitlementValue"
+ - $ref: "#/components/schemas/BooleanEntitlementValue"
+ - $ref: "#/components/schemas/ConfigEntitlementValue"
+ required:
+ - entitlementName
+ - licenseId
+ - licenseSlug
+ - createdBy
+ - createdAt
+ - updatedBy
+ - updatedAt
+ - value
+ type: object
+ LicenseEntitlementDelivered:
+ additionalProperties: false
+ properties:
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
+ type: string
+ createdAt:
+ description: Timestamp when this entitlement was created
+ examples:
+ - "2023-10-01T12:00:00Z"
+ format: date-time
+ readOnly: true
+ type: string
+ createdBy:
+ $ref: "#/components/schemas/User"
+ description: User who created this entitlement
+ readOnly: true
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
+ type: string
+ entitlementGroups:
+ description: Groups associated with this entitlement
+ items:
+ $ref: "#/components/schemas/EntitlementGroupSummary"
+ readOnly: true
+ type:
+ - array
+ - "null"
+ entitlementName:
+ description: Name of the entitlement feature or service
+ examples:
+ - Premium Support
+ readOnly: true
+ type: string
+ entitlementSlug:
+ description: URL-friendly identifier for the entitlement
+ examples:
+ - premium-support
+ format: string
+ type: string
+ entitlementType:
+ description: Type of entitlement (BOOLEAN, NUMBER, or CONFIG)
+ enum:
+ - BOOLEAN
+ - NUMBER
+ - CONFIG
+ examples:
+ - NUMBER
+ readOnly: true
+ type: string
+ licenseId:
+ description: Unique identifier for the license this entitlement belongs to
+ examples:
+ - a1b2c3d4-e5f6-7g8h-9i0j-k1l2m3n4o5p6
+ readOnly: true
+ type: string
+ licenseSlug:
+ description: URL-friendly identifier for the license this entitlement belongs to
+ examples:
+ - enterprise-license
+ readOnly: true
+ type: string
+ limitCapExceededOveragePercent:
+ description: "Derives this grant's enforcement from its own numeric value: -1 when the value is unlimited (no cap, no overage); otherwise 0 rejects usage above the value (hard limit) and a positive percentage allows usage to exceed it by that much before being rejected (soft limit). Null for non-numeric (BOOLEAN/CONFIG) grants."
+ examples:
+ - 10
+ format: int32
+ type: integer
+ updatedAt:
+ description: Timestamp when this entitlement was last updated
+ examples:
+ - "2023-10-02T12:00:00Z"
+ format: date-time
+ readOnly: true
+ type: string
+ updatedBy:
+ $ref: "#/components/schemas/User"
+ description: User who last updated this entitlement
+ readOnly: true
+ value:
+ description: "Typed entitlement value. Supported shapes: {type:number,value:number}, {type:boolean,value:boolean}, {type:object,value:{...}}"
+ discriminator:
+ mapping:
+ boolean: "#/components/schemas/BooleanEntitlementValue"
+ number: "#/components/schemas/NumberEntitlementValue"
+ object: "#/components/schemas/ConfigEntitlementValue"
+ propertyName: type
+ oneOf:
+ - $ref: "#/components/schemas/NumberEntitlementValue"
+ - $ref: "#/components/schemas/BooleanEntitlementValue"
+ - $ref: "#/components/schemas/ConfigEntitlementValue"
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
+ type: string
+ required:
+ - entitlementName
+ - licenseId
+ - licenseSlug
+ - createdBy
+ - createdAt
+ - updatedBy
+ - updatedAt
+ - value
+ type: object
+ x-kaiten-payload: "#/components/schemas/LicenseEntitlement"
+ LicenseFamilyUpdate:
+ additionalProperties: false
+ properties:
+ fallbackLicenseFamilySlug:
+ description: Another FREE family of the organization (every version of it priced FREE), whose default PUBLISHED version an instance moves to when its subscription ends. "" removes the fallback. Anything else answers 422 .InvalidFallback, an unknown family included.
+ maxLength: 255
+ type:
+ - string
+ - "null"
+ isPublic:
+ description: List the family's default PUBLISHED version, with its ACTIVE prices, in the public catalogue
+ type:
+ - boolean
+ - "null"
+ trialEndWithoutPaymentMethod:
+ description: "CANCEL or CONVERT: what a trial charged automatically with a total above 0 becomes at its end when the customer has no ACTIVE payment method. CANCEL cancels it; CONVERT makes it ACTIVE, whose first charge then fails. Any other value answers 422 .InvalidTrialEndBehavior."
+ type:
+ - string
+ - "null"
+ trialReminderDays:
+ description: "How many days before a trial's end trial_will_end fires: an integer from 1 to 30 (else 422 .InvalidTrialReminderDays), for the trials started after the change"
+ format: int32
+ type:
+ - integer
+ - "null"
+ type: object
+ LicenseFamilyView:
+ additionalProperties: false
+ properties:
+ createdAt:
+ description: Timestamp when this family was created
+ examples:
+ - "2023-10-01T12:00:00Z"
+ format: date-time
+ readOnly: true
+ type: string
+ currentVersion:
+ $ref: "#/components/schemas/License"
+ description: "The version this read is about. By default the version the family resolves to: its default version if it has one, otherwise its highest-numbered PUBLISHED version. Under ?version=N it is the version asked for instead, whatever its lifecycle state. Absent when the family has no PUBLISHED version, which only a list or an include=versions read returns."
+ fallbackLicenseFamilySlug:
+ description: The FREE family whose default PUBLISHED version an instance of this family moves to when its subscription ends; null without one
+ type:
+ - string
+ - "null"
+ id:
+ description: Unique identifier for the license family
+ examples:
+ - 123e4567-e89b-12d3-a456-426614174000
+ readOnly: true
+ type: string
+ isPublic:
+ description: Whether the family's default PUBLISHED version is listed in the public catalogue. Private by default.
+ type: boolean
+ slug:
+ description: URL-friendly identifier of the family, unique per organization and stable across renames and new versions. Deleting the family's last version deletes the family and frees the slug.
+ examples:
+ - enterprise-license
+ type: string
+ trialEndWithoutPaymentMethod:
+ description: "What a trial charged automatically with a total above 0 becomes at its end when the customer has no ACTIVE payment method: CANCEL (the default) or CONVERT"
+ enum:
+ - CANCEL
+ - CONVERT
+ type: string
+ trialReminderDays:
+ description: How many days before a trial's end trial_will_end fires, for the trials started after it was set. 3 by default.
+ examples:
+ - 3
+ format: int32
+ maximum: 30
+ minimum: 1
+ type: integer
+ updatedAt:
+ description: Timestamp when a version was last added to this family, or when the family was created. Renames, lifecycle moves and default changes update the versions' own updatedAt, not this one.
+ examples:
+ - "2023-10-02T12:00:00Z"
+ format: date-time
+ readOnly: true
+ type: string
+ versionCount:
+ description: Number of versions in this family, whatever their lifecycle state
+ examples:
+ - 3
+ format: int32
+ type: integer
+ versions:
+ description: "Every version of the family, oldest first. Only returned when explicitly requested with include=versions, and then never null: a family always has at least one version."
+ items:
+ $ref: "#/components/schemas/License"
+ type: array
+ required:
+ - id
+ - slug
+ - versionCount
+ - isPublic
+ - fallbackLicenseFamilySlug
+ - trialEndWithoutPaymentMethod
+ - trialReminderDays
+ - createdAt
+ - updatedAt
+ type: object
+ LicenseFamilyViewDelivered:
+ additionalProperties: false
+ properties:
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
+ type: string
+ createdAt:
+ description: Timestamp when this family was created
+ examples:
+ - "2023-10-01T12:00:00Z"
+ format: date-time
+ readOnly: true
+ type: string
+ currentVersion:
+ $ref: "#/components/schemas/License"
+ description: "The version this read is about. By default the version the family resolves to: its default version if it has one, otherwise its highest-numbered PUBLISHED version. Under ?version=N it is the version asked for instead, whatever its lifecycle state. Absent when the family has no PUBLISHED version, which only a list or an include=versions read returns."
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
+ type: string
+ fallbackLicenseFamilySlug:
+ description: The FREE family whose default PUBLISHED version an instance of this family moves to when its subscription ends; null without one
+ type:
+ - string
+ - "null"
+ id:
+ description: Unique identifier for the license family
+ examples:
+ - 123e4567-e89b-12d3-a456-426614174000
+ readOnly: true
+ type: string
+ isPublic:
+ description: Whether the family's default PUBLISHED version is listed in the public catalogue. Private by default.
+ type: boolean
+ slug:
+ description: URL-friendly identifier of the family, unique per organization and stable across renames and new versions. Deleting the family's last version deletes the family and frees the slug.
+ examples:
+ - enterprise-license
+ type: string
+ trialEndWithoutPaymentMethod:
+ description: "What a trial charged automatically with a total above 0 becomes at its end when the customer has no ACTIVE payment method: CANCEL (the default) or CONVERT"
+ enum:
+ - CANCEL
+ - CONVERT
+ type: string
+ trialReminderDays:
+ description: How many days before a trial's end trial_will_end fires, for the trials started after it was set. 3 by default.
+ examples:
+ - 3
+ format: int32
+ maximum: 30
+ minimum: 1
+ type: integer
+ updatedAt:
+ description: Timestamp when a version was last added to this family, or when the family was created. Renames, lifecycle moves and default changes update the versions' own updatedAt, not this one.
+ examples:
+ - "2023-10-02T12:00:00Z"
+ format: date-time
+ readOnly: true
+ type: string
+ versionCount:
+ description: Number of versions in this family, whatever their lifecycle state
+ examples:
+ - 3
+ format: int32
+ type: integer
+ versions:
+ description: "Every version of the family, oldest first. Only returned when explicitly requested with include=versions, and then never null: a family always has at least one version."
+ items:
+ $ref: "#/components/schemas/License"
+ type: array
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
+ type: string
+ required:
+ - id
+ - slug
+ - versionCount
+ - isPublic
+ - fallbackLicenseFamilySlug
+ - trialEndWithoutPaymentMethod
+ - trialReminderDays
+ - createdAt
+ - updatedAt
+ type: object
+ x-kaiten-payload: "#/components/schemas/LicenseFamilyView"
+ LicensePriceChanges:
+ additionalProperties: false
+ properties:
+ billingPeriod:
+ description: See createLicensePrice.
+ enum:
+ - MONTHLY
+ - QUARTERLY
+ - SEMI_ANNUAL
+ - ANNUAL
+ type: string
+ billingTiming:
+ description: See createLicensePrice.
+ enum:
+ - ADVANCE
+ - ARREARS
+ type: string
+ displayLabel:
+ maxLength: 200
+ type: string
+ displayOrder:
+ format: int32
+ minimum: 0
+ type: integer
+ isDefault:
+ type: boolean
+ meteredEntitlementSlug:
+ description: See createLicensePrice.
+ type: string
+ unitAmountDecimal:
+ description: See createLicensePrice.
+ type: string
+ type: object
+ LicensePriceEventDelivered:
+ additionalProperties: false
+ properties:
+ billingModel:
+ description: "FLAT_FEE: an amount per billing period. USAGE_BASED: an amount per sale unit of the metered entitlement's usage. OVERAGE: an amount per sale unit of the usage above the licence's limit."
+ enum:
+ - FLAT_FEE
+ - USAGE_BASED
+ - OVERAGE
+ type: string
+ billingPeriod:
+ description: The period a FLAT_FEE price is charged for. Null on metered prices, which bill on the subscription's period.
+ enum:
+ - MONTHLY
+ - QUARTERLY
+ - SEMI_ANNUAL
+ - ANNUAL
+ - null
+ type:
+ - string
+ - "null"
+ billingTiming:
+ description: ADVANCE bills the period that starts; ARREARS the period that ended. Metered prices are always ARREARS.
+ enum:
+ - ADVANCE
+ - ARREARS
+ type: string
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
+ type: string
+ createdAt:
+ format: date-time
+ readOnly: true
+ type: string
+ currency:
+ description: ISO 4217 code, upper case. One currency per licence version.
+ examples:
+ - EUR
+ type: string
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
+ type: string
+ deprecatedAt:
+ description: When the price was deprecated
+ format: date-time
+ type:
+ - string
+ - "null"
+ displayLabel:
+ description: The invoice line's label; a default is derived when it is null.
+ examples:
+ - Pro plan
+ type:
+ - string
+ - "null"
+ displayOrder:
+ description: Order among the version's prices
+ examples:
+ - 0
+ format: int32
+ type: integer
+ familySlug:
+ description: The family of that version
+ type: string
+ id:
+ description: Unique identifier of the price
+ readOnly: true
+ type: string
+ isDefault:
+ description: The FLAT_FEE price picked for its billing period when none is named. At most one per period; only an ACTIVE FLAT_FEE price.
+ type: boolean
+ licenseSlug:
+ description: The licence version the price belongs to
+ type: string
+ metered:
+ description: What a metered price measures. Null on FLAT_FEE prices.
+ oneOf:
+ - $ref: "#/components/schemas/PriceMeter"
+ - enum:
+ - null
+ type: "null"
+ status:
+ description: A DEPRECATED price keeps billing what is already pinned to it but is no longer offered.
+ enum:
+ - ACTIVE
+ - DEPRECATED
+ type: string
+ unitAmount:
+ description: unitAmountDecimal as an integer number of minor units, when it is one; null otherwise.
+ examples:
+ - 2900
+ format: int64
+ type:
+ - integer
+ - "null"
+ unitAmountDecimal:
+ description: "Amount in the currency's minor units (cents for EUR): per period for FLAT_FEE, per sale unit for metered prices. Up to 12 decimal places."
+ examples:
+ - "2900"
+ type: string
+ updatedAt:
+ format: date-time
+ readOnly: true
+ type: string
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
+ type: string
+ required:
+ - licenseSlug
+ - familySlug
+ - id
+ - billingModel
+ - billingTiming
+ - billingPeriod
+ - currency
+ - unitAmount
+ - unitAmountDecimal
+ - metered
+ - displayLabel
+ - displayOrder
+ - isDefault
+ - status
+ - deprecatedAt
+ - createdAt
+ - updatedAt
+ type: object
+ x-kaiten-payload: "#/components/schemas/LicensePriceEvent"
+ LicensePriceUpdatedEventDelivered:
+ additionalProperties: false
+ properties:
+ billingModel:
+ description: "FLAT_FEE: an amount per billing period. USAGE_BASED: an amount per sale unit of the metered entitlement's usage. OVERAGE: an amount per sale unit of the usage above the licence's limit."
+ enum:
+ - FLAT_FEE
+ - USAGE_BASED
+ - OVERAGE
+ type: string
+ billingPeriod:
+ description: The period a FLAT_FEE price is charged for. Null on metered prices, which bill on the subscription's period.
+ enum:
+ - MONTHLY
+ - QUARTERLY
+ - SEMI_ANNUAL
+ - ANNUAL
+ - null
+ type:
+ - string
+ - "null"
+ billingTiming:
+ description: ADVANCE bills the period that starts; ARREARS the period that ended. Metered prices are always ARREARS.
+ enum:
+ - ADVANCE
+ - ARREARS
+ type: string
+ changedFields:
+ description: The members the update changed, as the API names them
+ items:
+ type: string
+ type:
+ - array
+ - "null"
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
+ type: string
+ createdAt:
+ format: date-time
+ readOnly: true
+ type: string
+ currency:
+ description: ISO 4217 code, upper case. One currency per licence version.
+ examples:
+ - EUR
+ type: string
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
+ type: string
+ deprecatedAt:
+ description: When the price was deprecated
+ format: date-time
+ type:
+ - string
+ - "null"
+ displayLabel:
+ description: The invoice line's label; a default is derived when it is null.
+ examples:
+ - Pro plan
+ type:
+ - string
+ - "null"
+ displayOrder:
+ description: Order among the version's prices
+ examples:
+ - 0
+ format: int32
+ type: integer
+ familySlug:
+ description: The family of that version
+ type: string
+ id:
+ description: Unique identifier of the price
+ readOnly: true
+ type: string
+ isDefault:
+ description: The FLAT_FEE price picked for its billing period when none is named. At most one per period; only an ACTIVE FLAT_FEE price.
+ type: boolean
+ licenseSlug:
+ description: The licence version the price belongs to
+ type: string
+ metered:
+ description: What a metered price measures. Null on FLAT_FEE prices.
+ oneOf:
+ - $ref: "#/components/schemas/PriceMeter"
+ - enum:
+ - null
+ type: "null"
+ status:
+ description: A DEPRECATED price keeps billing what is already pinned to it but is no longer offered.
+ enum:
+ - ACTIVE
+ - DEPRECATED
+ type: string
+ unitAmount:
+ description: unitAmountDecimal as an integer number of minor units, when it is one; null otherwise.
+ examples:
+ - 2900
+ format: int64
+ type:
+ - integer
+ - "null"
+ unitAmountDecimal:
+ description: "Amount in the currency's minor units (cents for EUR): per period for FLAT_FEE, per sale unit for metered prices. Up to 12 decimal places."
+ examples:
+ - "2900"
+ type: string
+ updatedAt:
+ format: date-time
+ readOnly: true
+ type: string
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
+ type: string
+ required:
+ - changedFields
+ - licenseSlug
+ - familySlug
+ - id
+ - billingModel
+ - billingTiming
+ - billingPeriod
+ - currency
+ - unitAmount
+ - unitAmountDecimal
+ - metered
+ - displayLabel
+ - displayOrder
+ - isDefault
+ - status
+ - deprecatedAt
+ - createdAt
+ - updatedAt
+ type: object
+ x-kaiten-payload: "#/components/schemas/LicensePriceUpdatedEvent"
+ LineDifference:
+ additionalProperties: false
+ properties:
+ externalLineId:
+ type: string
+ kaitenAmount:
+ format: int64
+ type: integer
+ lineId:
+ type: string
+ providerAmount:
+ format: int64
+ type: integer
+ seq:
+ format: int64
+ type: integer
+ required:
+ - lineId
+ - seq
+ - kaitenAmount
+ - providerAmount
+ - externalLineId
+ type: object
+ LineReportPage:
+ additionalProperties: false
+ properties:
+ items:
+ description: The reports, in reportSeq order
+ items:
+ $ref: "#/components/schemas/UsageReport"
+ type: array
+ nextAfterSeq:
+ description: Pass as afterSeq to read the next page. Absent on the last page.
+ format: int64
+ type: integer
+ required:
+ - items
+ type: object
+ List:
+ additionalProperties: false
+ properties:
+ data:
+ description: The page, newest first
+ items:
+ $ref: "#/components/schemas/Notification"
+ type: array
+ nextCursor:
+ description: Opaque cursor for the next page; null on the last page
+ type:
+ - string
+ - "null"
+ unreadCount:
+ description: Unread notifications, capped (see MaxUnreadCount)
+ format: int64
+ type: integer
+ required:
+ - data
+ - nextCursor
+ - unreadCount
+ type: object
+ ManagedUser:
+ additionalProperties: false
+ properties:
+ authorization:
+ $ref: "#/components/schemas/UserAuthorization"
+ createdAt:
+ format: date-time
+ type: string
+ email:
+ type:
+ - string
+ - "null"
+ externalId:
+ type: string
+ id:
+ type: string
+ name:
+ type: string
+ required:
+ - id
+ - externalId
+ - name
+ - email
+ - createdAt
+ - authorization
+ type: object
+ ManifestEnvelope:
+ additionalProperties: false
+ properties:
+ flags:
+ items:
+ $ref: "#/components/schemas/ManifestFlag"
+ type:
+ - array
+ - "null"
+ required:
+ - flags
+ type: object
+ ManifestFlag:
+ additionalProperties: false
+ properties:
+ defaultValue: {}
+ description:
+ type: string
+ key:
+ type: string
+ name:
+ type: string
+ type:
+ type: string
+ required:
+ - key
+ - type
+ - defaultValue
+ type: object
+ MarkReadResult:
+ additionalProperties: false
+ properties:
+ unreadCount:
+ description: Unread notifications after the change
+ format: int64
+ type: integer
+ updated:
+ description: Notifications newly marked read
+ format: int64
+ type: integer
+ required:
+ - updated
+ - unreadCount
+ type: object
+ Me:
+ additionalProperties: false
+ properties:
+ availablePermissions:
+ items:
+ type: string
+ type: array
+ availableRoles:
+ items:
+ $ref: "#/components/schemas/Role"
+ type: array
+ organizationId:
+ type: string
+ scopes:
+ items:
+ type: string
+ type: array
+ user:
+ $ref: "#/components/schemas/ManagedUser"
+ required:
+ - user
+ - organizationId
+ - scopes
+ - availableRoles
+ - availablePermissions
+ type: object
+ MetadataField:
+ additionalProperties: false
+ properties:
+ archivedAt:
+ description: Timestamp when the field was archived (soft delete). Absent if active.
+ format: date-time
+ readOnly: true
+ type: string
+ createdAt:
+ description: Timestamp when the field was created
+ examples:
+ - "2026-05-26T12:00:00Z"
+ format: date-time
+ readOnly: true
+ type: string
+ createdBy:
+ $ref: "#/components/schemas/User"
+ description: User who created this field
+ readOnly: true
+ displayOrder:
+ description: Sort order in the admin UI; ties are broken by createdAt ASC. Settable only through POST /metadata-fields/reorder; never echoed back on update.
+ examples:
+ - 0
+ format: int32
+ type: integer
+ id:
+ description: Unique identifier
+ examples:
+ - 123e4567-e89b-12d3-a456-426614174000
+ readOnly: true
+ type: string
+ jsonSchema:
+ additionalProperties: {}
+ description: "JSON Schema 2020-12 document describing the value space. Example: {\"type\":\"string\",\"enum\":[\"a\",\"b\"]}"
+ type: object
+ key:
+ description: Stable identifier, unique per (organization, resource type) on non-archived rows. Immutable after creation; update must echo back the stored value.
+ examples:
+ - region
+ maxLength: 100
+ minLength: 1
+ type: string
+ label:
+ description: Human-readable label shown in the admin UI
+ examples:
+ - Region
+ maxLength: 100
+ minLength: 1
+ type: string
+ resourceType:
+ description: Resource type this field applies to. Immutable after creation; update must echo back the stored value.
+ enum:
+ - DEPLOYMENT_ZONE
+ - INSTANCE
+ type: string
+ updatedAt:
+ description: Timestamp when the field was last updated
+ examples:
+ - "2026-05-26T12:00:00Z"
+ format: date-time
+ readOnly: true
+ type: string
+ updatedBy:
+ $ref: "#/components/schemas/User"
+ description: User who last updated this field
+ readOnly: true
+ required:
+ - id
+ - resourceType
+ - key
+ - label
+ - jsonSchema
+ - createdBy
+ - createdAt
+ - updatedBy
+ - updatedAt
+ type: object
+ MetadataFieldDelivered:
+ additionalProperties: false
+ properties:
+ archivedAt:
+ description: Timestamp when the field was archived (soft delete). Absent if active.
+ format: date-time
+ readOnly: true
+ type: string
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
+ type: string
+ createdAt:
+ description: Timestamp when the field was created
+ examples:
+ - "2026-05-26T12:00:00Z"
+ format: date-time
+ readOnly: true
+ type: string
+ createdBy:
+ $ref: "#/components/schemas/User"
+ description: User who created this field
+ readOnly: true
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
+ type: string
+ displayOrder:
+ description: Sort order in the admin UI; ties are broken by createdAt ASC. Settable only through POST /metadata-fields/reorder; never echoed back on update.
+ examples:
+ - 0
+ format: int32
+ type: integer
+ id:
+ description: Unique identifier
+ examples:
+ - 123e4567-e89b-12d3-a456-426614174000
+ readOnly: true
+ type: string
+ jsonSchema:
+ additionalProperties: {}
+ description: "JSON Schema 2020-12 document describing the value space. Example: {\"type\":\"string\",\"enum\":[\"a\",\"b\"]}"
+ type: object
+ key:
+ description: Stable identifier, unique per (organization, resource type) on non-archived rows. Immutable after creation; update must echo back the stored value.
+ examples:
+ - region
+ maxLength: 100
+ minLength: 1
+ type: string
+ label:
+ description: Human-readable label shown in the admin UI
+ examples:
+ - Region
+ maxLength: 100
+ minLength: 1
+ type: string
+ resourceType:
+ description: Resource type this field applies to. Immutable after creation; update must echo back the stored value.
+ enum:
+ - DEPLOYMENT_ZONE
+ - INSTANCE
+ type: string
+ updatedAt:
+ description: Timestamp when the field was last updated
+ examples:
+ - "2026-05-26T12:00:00Z"
+ format: date-time
+ readOnly: true
+ type: string
+ updatedBy:
+ $ref: "#/components/schemas/User"
+ description: User who last updated this field
+ readOnly: true
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
+ type: string
+ required:
+ - id
+ - resourceType
+ - key
+ - label
+ - jsonSchema
+ - createdBy
+ - createdAt
+ - updatedBy
+ - updatedAt
+ type: object
+ x-kaiten-payload: "#/components/schemas/MetadataField"
+ MetadataFieldReorderedDelivered:
+ additionalProperties: false
+ properties:
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
+ type: string
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
+ type: string
+ ids:
+ description: Metadata field ids in their new display order
+ items:
+ type: string
+ type:
+ - array
+ - "null"
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
+ type: string
+ required:
+ - ids
+ type: object
+ x-kaiten-payload: "#/components/schemas/MetadataFieldReordered"
+ MetadataFieldSchemaProposal:
+ additionalProperties: false
+ properties:
+ jsonSchema:
+ additionalProperties: {}
+ description: Candidate JSON Schema 2020-12 document to evaluate existing values against.
+ type: object
+ required:
+ - jsonSchema
+ type: object
+ MinimumAmount:
+ additionalProperties: false
+ properties:
+ currency:
+ examples:
+ - EUR
+ type: string
+ unitAmountDecimal:
+ description: In minor units, above 0
+ examples:
+ - "2900"
+ type: string
+ required:
+ - currency
+ - unitAmountDecimal
+ type: object
+ MismatchedInvoiceDelivered:
+ additionalProperties: false
+ properties:
+ boundaryAt:
+ description: The boundary the invoice bills. With the kind, it identifies the invoice within its subscription
+ format: date-time
+ type: string
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
+ type: string
+ collectionMethod:
+ enum:
+ - SEND_INVOICE
+ - CHARGE_AUTOMATICALLY
+ type: string
+ createdAt:
+ format: date-time
+ type: string
+ currency:
+ examples:
+ - EUR
+ type: string
+ customerName:
+ type: string
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
+ type: string
+ customerSlug:
+ description: As it was when the invoice was composed
+ type: string
+ daysUntilDue:
+ format: int32
+ type:
+ - integer
+ - "null"
+ discountTotal:
+ description: Sum of the discounts, in minor units
+ format: int64
+ type: integer
+ dueAt:
+ description: issuedAt + daysUntilDue
+ format: date-time
+ type:
+ - string
+ - "null"
+ handoffStatus:
+ description: "PENDING: waiting in the handoff queue for the organization's accounting system"
+ enum:
+ - NOT_REQUIRED
+ - PENDING
+ - ACKNOWLEDGED
+ type: string
+ holdReason:
+ description: "Why a DRAFT is held: the usage journal it was measured from failed a consistency check (LEDGER_*), or its provider cannot take that many lines (PROVIDER_ITEM_LIMIT)"
+ enum:
+ - LEDGER_SEQUENCE_GAP
+ - LEDGER_CHAIN_BREAK
+ - LEDGER_COUNTER_MISMATCH
+ - PROVIDER_ITEM_LIMIT
+ - null
+ type:
+ - string
+ - "null"
+ id:
+ description: "The invoice's identifier: the key an accounting system deduplicates on"
+ type: string
+ instanceName:
+ type: string
+ instanceSlug:
+ type: string
+ issuedAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ kind:
+ description: ACTIVATION bills a subscription's first period, RENEWAL the period that ended and the one that starts, FINAL what is left when a subscription ends
+ enum:
+ - ACTIVATION
+ - RENEWAL
+ - FINAL
+ type: string
+ licenseSlug:
+ type: string
+ overdue:
+ description: "Whether the invoice is overdue when read, the grace period of automatic collection included: the predicate of ?overdue=true. Computed by the invoice list and the invoice read; false in the answers of writes and in event payloads."
+ type: boolean
+ paidAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ providerKind:
+ description: "Who collects it. NOOP: the organization itself, through the handoff queue"
+ enum:
+ - NOOP
+ - STRIPE
+ type: string
+ reconciliationDetail:
+ $ref: "#/components/schemas/Reconciliation"
+ serviceFrom:
+ description: Earliest start of its lines' service periods
+ format: date-time
+ type: string
+ serviceTo:
+ description: Latest end of its lines' service periods
+ format: date-time
+ type: string
+ status:
+ description: "MANUAL: issued, for the organization to collect. PAID, UNCOLLECTIBLE and VOID are final. DRAFT: held, waiting for a release or a recompose"
+ enum:
+ - DRAFT
+ - PUSHED
+ - PUSH_FAILED
+ - MANUAL
+ - PAID
+ - PAYMENT_FAILED
+ - UNCOLLECTIBLE
+ - VOID
+ type: string
+ subtotal:
+ description: Sum of the lines, in minor units
+ format: int64
+ type: integer
+ total:
+ description: subtotal − discountTotal, in minor units
+ format: int64
+ type: integer
+ updatedAt:
+ format: date-time
+ type: string
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
+ type: string
+ required:
+ - reconciliationDetail
+ - id
+ - kind
+ - boundaryAt
+ - serviceFrom
+ - serviceTo
+ - status
+ - holdReason
+ - providerKind
+ - collectionMethod
+ - currency
+ - subtotal
+ - discountTotal
+ - total
+ - issuedAt
+ - daysUntilDue
+ - dueAt
+ - paidAt
+ - customerSlug
+ - customerName
+ - instanceSlug
+ - instanceName
+ - licenseSlug
+ - handoffStatus
+ - overdue
+ - createdAt
+ - updatedAt
+ type: object
+ x-kaiten-payload: "#/components/schemas/MismatchedInvoice"
+ NewAddon:
+ additionalProperties: false
+ properties:
+ description:
+ examples:
+ - Five more seats per unit
+ type: string
+ familyId:
+ description: Make this a new version of this family
+ type: string
+ familySlug:
+ description: Make this a new version of this family
+ examples:
+ - extra-seats
+ type: string
+ increaseBilling:
+ description: "Read only when the call opens a new family: how a mid-period increase of its ADVANCE add-ons is billed, TRUE_UP (the default) or NEXT_BOUNDARY; another value answers 422 .InvalidIncreaseBilling"
+ type: string
+ isDefault:
+ description: Make this version its family's default; the previous default loses it
+ type: boolean
+ lifecycleState:
+ default: PUBLISHED
+ enum:
+ - DRAFT
+ - PUBLISHED
+ type: string
+ maxQuantity:
+ description: The largest quantity an instance can hold
+ examples:
+ - 10
+ format: int32
+ type: integer
+ name:
+ examples:
+ - Extra seats
+ minLength: 1
+ type: string
+ pricingType:
+ enum:
+ - FREE
+ - PAID
+ - CUSTOM
+ type: string
+ slug:
+ description: "The version's slug. Default: {familySlug}-v{n} for a new version of a family, a slug generated from the name for a new family."
+ examples:
+ - extra-seats-v1
+ type: string
+ versionName:
+ description: "Default: Version - {n}"
+ type: string
+ required:
+ - name
+ - description
+ - pricingType
+ type: object
+ NewAddonGrant:
+ additionalProperties: false
+ properties:
+ entitlementSlug:
+ examples:
+ - seats
+ type: string
+ limitCapExceededOveragePercent:
+ description: "The overage the add-on allows: -1 with an unlimited value, 0 or more otherwise. Absent inherits the licence grant's."
+ format: int32
+ type: integer
+ overrideBehavior:
+ default: MAX
+ description: How a number grant combines with the licence's
+ enum:
+ - ADD
+ - OVERRIDE
+ - MAX
+ type: string
+ value:
+ additionalProperties: {}
+ description: "{type, value}, shaped like a licence grant: a number counts once per unit of quantity"
+ examples:
+ - type: number
+ value: 5
+ type: object
+ required:
+ - entitlementSlug
+ - value
+ type: object
+ NewAddonPrice:
+ additionalProperties: false
+ properties:
+ billingModel:
+ enum:
+ - FLAT_FEE
+ - USAGE_BASED
+ - OVERAGE
+ type: string
+ billingPeriod:
+ description: "Required on a FLAT_FEE price: it bills subscriptions of that period. Refused on a metered one."
+ enum:
+ - MONTHLY
+ - QUARTERLY
+ - SEMI_ANNUAL
+ - ANNUAL
+ type: string
+ billingTiming:
+ description: Defaults to ADVANCE for a FLAT_FEE price and ARREARS for a metered one, which must be ARREARS.
+ enum:
+ - ADVANCE
+ - ARREARS
+ type: string
+ currency:
+ description: ISO 4217 code, upper case. One currency per add-on version.
+ examples:
+ - EUR
+ type: string
+ displayLabel:
+ maxLength: 200
+ type: string
+ displayOrder:
+ format: int32
+ minimum: 0
+ type: integer
+ isDefault:
+ description: Makes this FLAT_FEE price the one that bills its period; the previous default of the period loses it.
+ type: boolean
+ meteredEntitlementSlug:
+ description: "The entitlement a metered price measures: granted by the add-on version, with a reset period, SUM or COUNT, NUMBER or NUMBER_AI_CREDIT."
+ examples:
+ - tokens
+ type: string
+ unitAmountDecimal:
+ description: "Amount in minor units: per unit and period for FLAT_FEE, per sale unit for metered prices."
+ examples:
+ - "900"
+ type: string
+ required:
+ - billingModel
+ - currency
+ - unitAmountDecimal
+ type: object
+ NewEntitlementException:
+ additionalProperties: false
+ properties:
+ endsAt:
+ description: "After startsAt and after now; absent or null: no end"
+ format: date-time
+ type:
+ - string
+ - "null"
+ entitlementSlug:
+ description: The entitlement it overrides; the instance's licence and add-ons need not grant it
+ examples:
+ - sso
+ type: string
+ kind:
+ description: SET works on every type; ADD and UNLIMITED on NUMBER and NUMBER_AI_CREDIT only
+ enum:
+ - SET
+ - ADD
+ - UNLIMITED
+ type: string
+ reason:
+ description: Why, 1 to 500 characters, not blank; shown to the vendor only
+ type: string
+ startsAt:
+ description: Now when absent; a past instant is taken as given, a future one schedules the exception
+ format: date-time
+ type: string
+ value:
+ anyOf:
+ - type: number
+ - type: boolean
+ - additionalProperties: true
+ type: object
+ - type: "null"
+ description: For SET, a number of 0 or more on the NUMBER types, a boolean on BOOLEAN, an object on CONFIG; for ADD, a number above 0; for UNLIMITED, absent or null
+ required:
+ - entitlementSlug
+ - kind
+ - reason
+ type: object
+ NewInstanceAddon:
+ additionalProperties: false
+ properties:
+ addonSlug:
+ description: The add-on version to attach
+ examples:
+ - extra-seats-v1
+ type: string
+ quantity:
+ default: 1
+ examples:
+ - 3
+ format: int32
+ minimum: 1
+ type: integer
+ required:
+ - addonSlug
+ type: object
+ NewLicensePrice:
+ additionalProperties: false
+ properties:
+ billingModel:
+ description: "FLAT_FEE: an amount per billing period. USAGE_BASED: per sale unit of usage. OVERAGE: per sale unit of usage above the licence's limit."
+ enum:
+ - FLAT_FEE
+ - USAGE_BASED
+ - OVERAGE
+ type: string
+ billingPeriod:
+ description: Required on a FLAT_FEE price, refused on a metered one (CreateLicensePrice.InvalidPeriod).
+ enum:
+ - MONTHLY
+ - QUARTERLY
+ - SEMI_ANNUAL
+ - ANNUAL
+ type: string
+ billingTiming:
+ description: Defaults to ADVANCE for a FLAT_FEE price and ARREARS for a metered one, which must be ARREARS (CreateLicensePrice.InvalidTiming).
+ enum:
+ - ADVANCE
+ - ARREARS
+ type: string
+ currency:
+ description: ISO 4217 code, upper case (CreateLicensePrice.InvalidCurrency). One currency per licence version (CreateLicensePrice.CurrencyMismatch).
+ examples:
+ - EUR
+ type: string
+ displayLabel:
+ description: The invoice line's label.
+ maxLength: 200
+ type: string
+ displayOrder:
+ description: Order among the version's prices.
+ format: int32
+ minimum: 0
+ type: integer
+ isDefault:
+ description: Makes this FLAT_FEE price the default of its billing period (CreateLicensePrice.DefaultConflict when another is).
+ type: boolean
+ meteredEntitlementSlug:
+ description: "The entitlement a metered price measures: granted by the version, with a reset period, SUM or COUNT, NUMBER or NUMBER_AI_CREDIT, and metered by no other active price of the version."
+ examples:
+ - tokens
+ type: string
+ unitAmountDecimal:
+ description: "Amount in minor units: per period for FLAT_FEE, per sale unit for metered prices. A non-negative decimal of at most 12 integer digits and 12 decimal places (CreateLicensePrice.InvalidAmount)."
+ examples:
+ - "2900"
+ type: string
+ required:
+ - billingModel
+ - currency
+ - unitAmountDecimal
+ type: object
+ NewPaymentMethodSession:
+ additionalProperties: false
+ properties:
+ currency:
+ description: The currency the payment method is set up in; required when the customer has no live subscription, whose currency is used otherwise
+ examples:
+ - EUR
+ type: string
+ returnUrl:
+ description: "Where the customer comes back to: https, or http on localhost. The session id is appended as kaiten_setup_session"
+ examples:
+ - https://app.example.test/billing
+ type: string
+ required:
+ - returnUrl
+ type: object
+ NewPortalSession:
+ additionalProperties: false
+ properties:
+ returnUrl:
+ description: https, or http on localhost
+ examples:
+ - https://app.example.test/billing
+ type: string
+ required:
+ - returnUrl
+ type: object
+ NewSessionPaymentMethodSession:
+ additionalProperties: false
+ properties:
+ currency:
+ description: The currency the payment method is set up in; required when the customer has no live subscription, whose currency is used otherwise
+ examples:
+ - EUR
+ type: string
+ returnUrl:
+ description: Where the setup page sends the customer back, with kaiten_setup_session appended. Its origin must be one the organization's publishable keys allow.
+ examples:
+ - https://app.example.test/billing
+ format: uri
+ type: string
+ required:
+ - returnUrl
+ type: object
+ NewSessionPortalSession:
+ additionalProperties: false
+ properties:
+ returnUrl:
+ description: Where the portal sends the customer back. Its origin must be one the organization's publishable keys allow.
+ examples:
+ - https://app.example.test/billing
+ format: uri
+ type: string
+ required:
+ - returnUrl
+ type: object
+ NewSubscription:
+ additionalProperties: false
+ properties:
+ addOns:
+ description: "Add-ons to attach with the subscription, under the rules of POST /instances/{instanceSlug}/addons; the ACTIVATION invoice bills them. One refused refuses the subscribe: 422 SubscribeInstance.AddonInvalid, the add-on's own code in errors[0].value.code."
+ items:
+ $ref: "#/components/schemas/SubscriptionAddon"
+ maxItems: 50
+ type:
+ - array
+ - "null"
+ basePriceId:
+ description: An ACTIVE FLAT_FEE price of the instance's licence version, which must be PUBLISHED. The subscription stays pinned to it, even once deprecated.
+ type: string
+ collectionMethod:
+ description: "Omitted: the organization's default, read at each invoice. CHARGE_AUTOMATICALLY needs a provider that charges automatically (SubscribeInstance.CollectionMethodUnsupported)."
+ enum:
+ - SEND_INVOICE
+ - CHARGE_AUTOMATICALLY
+ type: string
+ daysUntilDue:
+ description: "Payment terms in days, 0 to 365. Omitted: the organization's default, read at each invoice."
+ format: int32
+ type: integer
+ providerKind:
+ description: "Who issues and collects the invoices. NOOP (the default): the organization itself, through the handoff queue. A provider must be connected (SubscribeInstance.ProviderNotConnected); one that pushes invoices needs the customer's billing e-mail for SEND_INVOICE (.BillingEmailMissing) and the price's currency (.UnsupportedCurrency), and registers the customer first."
+ enum:
+ - NOOP
+ - STRIPE
+ type: string
+ startAt:
+ description: "The anchor periods are counted from, truncated to the second: now when omitted, else at most one billing period ago. A period that has already ended closes on the next pass."
+ format: date-time
+ type: string
+ trialDays:
+ description: "Days of trial from the anchor, 1 to 365: nothing is billed, and no usage counts, until it ends; the period close then issues the first invoice. 0 is no trial; any other value is refused (SubscribeInstance.InvalidTrialDays). Omitted: the licence's trialPeriodDays, else no trial; none either when the customer already had a trial of the licence's family (one per customer and family)."
+ format: int32
+ type: integer
+ voucherCode:
+ description: "A voucher to redeem with the subscription, once its add-ons are attached; a PRICE voucher applies from the ACTIVATION invoice. Refused: 422 SubscribeInstance.VoucherInvalid, the redemption's own code in errors[0].value.code."
+ maxLength: 64
+ minLength: 1
+ type: string
+ required:
+ - basePriceId
+ type: object
+ Notification:
+ additionalProperties: false
+ properties:
+ actionUrl:
+ description: Where clicking the notification goes
+ type: string
+ body:
+ description: Rendered detail line, when the event has one
+ type: string
+ createdAt:
+ description: When the event occurred
+ format: date-time
+ type: string
+ eventName:
+ description: Event this notification reports
+ examples:
+ - INSTANCE_DEPLOYED
+ type: string
+ eventType:
+ description: CloudEvents type of the same event -- the key it is published under in this document's webhooks section, so a client can line a notification up with the payload schema and the description already published there
+ examples:
+ - com.kaiten.instance.v1.deployed
+ type: string
+ id:
+ description: Identifier of the underlying audit trail entry
+ type: string
+ objectType:
+ description: What the notification is about -- the kind of page actionUrl opens, and what the list's objectType filter matches
+ examples:
+ - instance
+ type: string
+ readAt:
+ description: When this user read it; absent means unread
+ format: date-time
+ type: string
+ title:
+ description: Rendered headline
+ type: string
+ required:
+ - id
+ - eventName
+ - eventType
+ - objectType
+ - title
+ - createdAt
+ type: object
+ NumberEntitlementValue:
+ additionalProperties: false
+ properties:
+ event_count:
+ description: Number of events accumulated (server-computed)
+ examples:
+ - 2
+ format: int32
+ readOnly: true
+ type: integer
+ type:
+ description: Value type discriminator
+ enum:
+ - number
+ examples:
+ - number
+ type: string
+ value:
+ description: Numeric value
+ examples:
+ - 42.5
+ format: double
+ type: number
+ required:
+ - type
+ - value
+ type: object
+ Outcome:
+ additionalProperties: false
+ properties:
+ applied:
+ description: Invoices read and applied
+ format: int64
+ type: integer
+ error:
+ type: string
+ failed:
+ description: Invoices that could not be read or applied
+ format: int64
+ type: integer
+ providerKind:
+ enum:
+ - NOOP
+ - STRIPE
+ type: string
+ status:
+ enum:
+ - SUCCESS
+ - PARTIAL
+ - FAILED
+ type: string
+ syncedAt:
+ format: date-time
+ type: string
+ required:
+ - providerKind
+ - status
+ - applied
+ - failed
+ - syncedAt
+ type: object
+ OverageLimit:
+ additionalProperties: false
+ properties:
+ limitValue:
+ description: The limit, null when unlimited
+ examples:
+ - "100000"
+ type:
+ - string
+ - "null"
+ overagePercent:
+ description: Usage accepted above the limit, as a percent of it
+ examples:
+ - 50
+ format: int32
+ type: integer
+ rows:
+ description: Usage reports measured against it; 0 for a sample
+ format: int64
+ type: integer
+ required:
+ - limitValue
+ - overagePercent
+ - rows
+ type: object
+ PageAddon:
+ additionalProperties: false
+ properties:
+ hasMore:
+ description: Whether more rows exist beyond this page.
+ type: boolean
+ items:
+ items:
+ $ref: "#/components/schemas/Addon"
+ type: array
+ nextCursor:
+ description: Opaque cursor for the next page. Absent when hasMore is false.
+ type: string
+ required:
+ - items
+ - hasMore
+ type: object
+ PageAuditTrail:
+ additionalProperties: false
+ properties:
+ hasMore:
+ description: Whether more rows exist beyond this page.
+ type: boolean
+ items:
+ items:
+ $ref: "#/components/schemas/AuditTrail"
+ type: array
+ nextCursor:
+ description: Opaque cursor for the next page. Absent when hasMore is false.
+ type: string
+ required:
+ - items
+ - hasMore
+ type: object
+ PageCheckoutSessionAdmin:
+ additionalProperties: false
+ properties:
+ hasMore:
+ description: Whether more rows exist beyond this page.
+ type: boolean
+ items:
+ items:
+ $ref: "#/components/schemas/CheckoutSessionAdmin"
+ type: array
+ nextCursor:
+ description: Opaque cursor for the next page. Absent when hasMore is false.
+ type: string
+ required:
+ - items
+ - hasMore
+ type: object
+ PageComponent:
+ additionalProperties: false
+ properties:
+ hasMore:
+ description: Whether more rows exist beyond this page.
+ type: boolean
+ items:
+ items:
+ $ref: "#/components/schemas/Component"
+ type: array
+ nextCursor:
+ description: Opaque cursor for the next page. Absent when hasMore is false.
+ type: string
+ required:
+ - items
+ - hasMore
+ type: object
+ PageCustomer:
+ additionalProperties: false
+ properties:
+ hasMore:
+ description: Whether more rows exist beyond this page.
+ type: boolean
+ items:
+ items:
+ $ref: "#/components/schemas/Customer"
+ type: array
+ nextCursor:
+ description: Opaque cursor for the next page. Absent when hasMore is false.
+ type: string
+ required:
+ - items
+ - hasMore
+ type: object
+ PageDeploymentZone:
+ additionalProperties: false
+ properties:
+ hasMore:
+ description: Whether more rows exist beyond this page.
+ type: boolean
+ items:
+ items:
+ $ref: "#/components/schemas/DeploymentZone"
+ type: array
+ nextCursor:
+ description: Opaque cursor for the next page. Absent when hasMore is false.
+ type: string
+ required:
+ - items
+ - hasMore
+ type: object
+ PageEntitlement:
+ additionalProperties: false
+ properties:
+ hasMore:
+ description: Whether more rows exist beyond this page.
+ type: boolean
+ items:
+ items:
+ $ref: "#/components/schemas/Entitlement"
+ type: array
+ nextCursor:
+ description: Opaque cursor for the next page. Absent when hasMore is false.
+ type: string
+ required:
+ - items
+ - hasMore
+ type: object
+ PageEntitlementGroup:
+ additionalProperties: false
+ properties:
+ hasMore:
+ description: Whether more rows exist beyond this page.
+ type: boolean
+ items:
+ items:
+ $ref: "#/components/schemas/EntitlementGroup"
+ type: array
+ nextCursor:
+ description: Opaque cursor for the next page. Absent when hasMore is false.
+ type: string
+ required:
+ - items
+ - hasMore
+ type: object
+ PageFeatureFlag:
+ additionalProperties: false
+ properties:
+ hasMore:
+ description: Whether more rows exist beyond this page.
+ type: boolean
+ items:
+ items:
+ $ref: "#/components/schemas/FeatureFlag"
+ type: array
+ nextCursor:
+ description: Opaque cursor for the next page. Absent when hasMore is false.
+ type: string
+ required:
+ - items
+ - hasMore
+ type: object
+ PageInstance:
+ additionalProperties: false
+ properties:
+ hasMore:
+ description: Whether more rows exist beyond this page.
+ type: boolean
+ items:
+ items:
+ $ref: "#/components/schemas/Instance"
+ type: array
+ nextCursor:
+ description: Opaque cursor for the next page. Absent when hasMore is false.
+ type: string
+ required:
+ - items
+ - hasMore
+ type: object
+ PageInvoiceSummary:
+ additionalProperties: false
+ properties:
+ hasMore:
+ description: Whether more rows exist beyond this page.
+ type: boolean
+ items:
+ items:
+ $ref: "#/components/schemas/InvoiceSummary"
+ type: array
+ nextCursor:
+ description: Opaque cursor for the next page. Absent when hasMore is false.
+ type: string
+ required:
+ - items
+ - hasMore
+ type: object
+ PageLicense:
+ additionalProperties: false
+ properties:
+ hasMore:
+ description: Whether more rows exist beyond this page.
+ type: boolean
+ items:
+ items:
+ $ref: "#/components/schemas/License"
+ type: array
+ nextCursor:
+ description: Opaque cursor for the next page. Absent when hasMore is false.
+ type: string
+ required:
+ - items
+ - hasMore
+ type: object
+ PageLicenseEntitlement:
+ additionalProperties: false
+ properties:
+ hasMore:
+ description: Whether more rows exist beyond this page.
+ type: boolean
+ items:
+ items:
+ $ref: "#/components/schemas/LicenseEntitlement"
+ type: array
+ nextCursor:
+ description: Opaque cursor for the next page. Absent when hasMore is false.
+ type: string
+ required:
+ - items
+ - hasMore
+ type: object
+ PageLicenseFamilyView:
+ additionalProperties: false
+ properties:
+ hasMore:
+ description: Whether more rows exist beyond this page.
+ type: boolean
+ items:
+ items:
+ $ref: "#/components/schemas/LicenseFamilyView"
+ type: array
+ nextCursor:
+ description: Opaque cursor for the next page. Absent when hasMore is false.
+ type: string
+ required:
+ - items
+ - hasMore
+ type: object
+ PageManagedUser:
+ additionalProperties: false
+ properties:
+ hasMore:
+ description: Whether more rows exist beyond this page.
+ type: boolean
+ items:
+ items:
+ $ref: "#/components/schemas/ManagedUser"
+ type: array
+ nextCursor:
+ description: Opaque cursor for the next page. Absent when hasMore is false.
+ type: string
+ required:
+ - items
+ - hasMore
+ type: object
+ PageMetadataField:
+ additionalProperties: false
+ properties:
+ hasMore:
+ description: Whether more rows exist beyond this page.
+ type: boolean
+ items:
+ items:
+ $ref: "#/components/schemas/MetadataField"
+ type: array
+ nextCursor:
+ description: Opaque cursor for the next page. Absent when hasMore is false.
+ type: string
+ required:
+ - items
+ - hasMore
+ type: object
+ PagePublishableKey:
+ additionalProperties: false
+ properties:
+ hasMore:
+ description: Whether more rows exist beyond this page.
+ type: boolean
+ items:
+ items:
+ $ref: "#/components/schemas/PublishableKey"
+ type: array
+ nextCursor:
+ description: Opaque cursor for the next page. Absent when hasMore is false.
+ type: string
+ required:
+ - items
+ - hasMore
+ type: object
+ PageQueuedInvoice:
+ additionalProperties: false
+ properties:
+ hasMore:
+ description: Whether more rows exist beyond this page.
+ type: boolean
+ items:
+ items:
+ $ref: "#/components/schemas/QueuedInvoice"
+ type: array
+ nextCursor:
+ description: Opaque cursor for the next page. Absent when hasMore is false.
+ type: string
+ required:
+ - items
+ - hasMore
+ type: object
+ PageRedemption:
+ additionalProperties: false
+ properties:
+ hasMore:
+ description: Whether more rows exist beyond this page.
+ type: boolean
+ items:
+ items:
+ $ref: "#/components/schemas/Redemption"
+ type: array
+ nextCursor:
+ description: Opaque cursor for the next page. Absent when hasMore is false.
+ type: string
+ required:
+ - items
+ - hasMore
+ type: object
+ PageRelease:
+ additionalProperties: false
+ properties:
+ hasMore:
+ description: Whether more rows exist beyond this page.
+ type: boolean
+ items:
+ items:
+ $ref: "#/components/schemas/Release"
+ type: array
+ nextCursor:
+ description: Opaque cursor for the next page. Absent when hasMore is false.
+ type: string
+ required:
+ - items
+ - hasMore
+ type: object
+ PageServiceAccount:
+ additionalProperties: false
+ properties:
+ hasMore:
+ description: Whether more rows exist beyond this page.
+ type: boolean
+ items:
+ items:
+ $ref: "#/components/schemas/ServiceAccount"
+ type: array
+ nextCursor:
+ description: Opaque cursor for the next page. Absent when hasMore is false.
+ type: string
+ required:
+ - items
+ - hasMore
+ type: object
+ PageSessionInvoice:
+ additionalProperties: false
+ properties:
+ hasMore:
+ description: Whether more rows exist beyond this page.
+ type: boolean
+ items:
+ items:
+ $ref: "#/components/schemas/SessionInvoice"
+ type: array
+ nextCursor:
+ description: Opaque cursor for the next page. Absent when hasMore is false.
+ type: string
+ required:
+ - items
+ - hasMore
+ type: object
+ PageToken:
+ additionalProperties: false
+ properties:
+ hasMore:
+ description: Whether more rows exist beyond this page.
+ type: boolean
+ items:
+ items:
+ $ref: "#/components/schemas/Token"
+ type: array
+ nextCursor:
+ description: Opaque cursor for the next page. Absent when hasMore is false.
+ type: string
+ required:
+ - items
+ - hasMore
+ type: object
+ PageVoucher:
+ additionalProperties: false
+ properties:
+ hasMore:
+ description: Whether more rows exist beyond this page.
+ type: boolean
+ items:
+ items:
+ $ref: "#/components/schemas/Voucher"
+ type: array
+ nextCursor:
+ description: Opaque cursor for the next page. Absent when hasMore is false.
+ type: string
+ required:
+ - items
+ - hasMore
+ type: object
+ PaidInvoiceDelivered:
+ additionalProperties: false
+ properties:
+ boundaryAt:
+ description: The boundary the invoice bills. With the kind, it identifies the invoice within its subscription
+ format: date-time
+ type: string
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
+ type: string
+ collectionMethod:
+ enum:
+ - SEND_INVOICE
+ - CHARGE_AUTOMATICALLY
+ type: string
+ createdAt:
+ format: date-time
+ type: string
+ currency:
+ examples:
+ - EUR
+ type: string
+ customerName:
+ type: string
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
+ type: string
+ customerSlug:
+ description: As it was when the invoice was composed
+ type: string
+ daysUntilDue:
+ format: int32
+ type:
+ - integer
+ - "null"
+ discountTotal:
+ description: Sum of the discounts, in minor units
+ format: int64
+ type: integer
+ dueAt:
+ description: issuedAt + daysUntilDue
+ format: date-time
+ type:
+ - string
+ - "null"
+ externalReference:
+ type: string
+ handoffStatus:
+ description: "PENDING: waiting in the handoff queue for the organization's accounting system"
+ enum:
+ - NOT_REQUIRED
+ - PENDING
+ - ACKNOWLEDGED
+ type: string
+ holdReason:
+ description: "Why a DRAFT is held: the usage journal it was measured from failed a consistency check (LEDGER_*), or its provider cannot take that many lines (PROVIDER_ITEM_LIMIT)"
+ enum:
+ - LEDGER_SEQUENCE_GAP
+ - LEDGER_CHAIN_BREAK
+ - LEDGER_COUNTER_MISMATCH
+ - PROVIDER_ITEM_LIMIT
+ - null
+ type:
+ - string
+ - "null"
+ id:
+ description: "The invoice's identifier: the key an accounting system deduplicates on"
+ type: string
+ instanceName:
+ type: string
+ instanceSlug:
+ type: string
+ issuedAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ kind:
+ description: ACTIVATION bills a subscription's first period, RENEWAL the period that ended and the one that starts, FINAL what is left when a subscription ends
+ enum:
+ - ACTIVATION
+ - RENEWAL
+ - FINAL
+ type: string
+ licenseSlug:
+ type: string
+ note:
+ description: The note given when it was marked paid, kept only here
+ type: string
+ overdue:
+ description: "Whether the invoice is overdue when read, the grace period of automatic collection included: the predicate of ?overdue=true. Computed by the invoice list and the invoice read; false in the answers of writes and in event payloads."
+ type: boolean
+ paidAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ providerKind:
+ description: "Who collects it. NOOP: the organization itself, through the handoff queue"
+ enum:
+ - NOOP
+ - STRIPE
+ type: string
+ serviceFrom:
+ description: Earliest start of its lines' service periods
+ format: date-time
+ type: string
+ serviceTo:
+ description: Latest end of its lines' service periods
+ format: date-time
+ type: string
+ source:
+ description: "MARK_PAID: recorded by the organization. ZERO_TOTAL: nothing was owed"
+ enum:
+ - MARK_PAID
+ - PROVIDER
+ - ZERO_TOTAL
+ type: string
+ status:
+ description: "MANUAL: issued, for the organization to collect. PAID, UNCOLLECTIBLE and VOID are final. DRAFT: held, waiting for a release or a recompose"
+ enum:
+ - DRAFT
+ - PUSHED
+ - PUSH_FAILED
+ - MANUAL
+ - PAID
+ - PAYMENT_FAILED
+ - UNCOLLECTIBLE
+ - VOID
+ type: string
+ subtotal:
+ description: Sum of the lines, in minor units
+ format: int64
+ type: integer
+ total:
+ description: subtotal − discountTotal, in minor units
+ format: int64
+ type: integer
+ updatedAt:
+ format: date-time
+ type: string
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
+ type: string
+ required:
+ - source
+ - id
+ - kind
+ - boundaryAt
+ - serviceFrom
+ - serviceTo
+ - status
+ - holdReason
+ - providerKind
+ - collectionMethod
+ - currency
+ - subtotal
+ - discountTotal
+ - total
+ - issuedAt
+ - daysUntilDue
+ - dueAt
+ - paidAt
+ - customerSlug
+ - customerName
+ - instanceSlug
+ - instanceName
+ - licenseSlug
+ - handoffStatus
+ - overdue
+ - createdAt
+ - updatedAt
+ type: object
+ x-kaiten-payload: "#/components/schemas/PaidInvoice"
+ PatchInstanceBody:
+ additionalProperties: false
+ properties:
+ lifecycleStage:
+ description: "Commercial lifecycle stage (free-form; suggested: TRIAL, ACTIVE, AT_RISK, CHURNED). When omitted, the current stage is preserved; clearing it back to unset is not supported."
+ examples:
+ - AT_RISK
+ minLength: 1
+ type: string
+ status:
+ description: Operational status of the instance
+ enum:
+ - HEALTHY
+ - DEGRADED
+ - INCIDENT
+ - MAINTENANCE
+ examples:
+ - INCIDENT
+ type: string
+ type: object
+ Payment:
+ additionalProperties: false
+ properties:
+ hostedInvoiceUrl:
+ type: string
+ status:
+ description: "paid. requires_action: the customer must confirm the payment on hostedInvoiceUrl. failed: the charge was refused; save another payment method, or pay on hostedInvoiceUrl. processing: the outcome is not known yet; poll GET /public/session/invoices. not_required: nothing is due today."
+ enum:
+ - paid
+ - requires_action
+ - failed
+ - processing
+ - not_required
+ type: string
+ required:
+ - status
+ type: object
+ PaymentFailedInvoiceDelivered:
+ additionalProperties: false
+ properties:
+ boundaryAt:
+ description: The boundary the invoice bills. With the kind, it identifies the invoice within its subscription
+ format: date-time
+ type: string
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
+ type: string
+ collectionMethod:
+ enum:
+ - SEND_INVOICE
+ - CHARGE_AUTOMATICALLY
+ type: string
+ createdAt:
+ format: date-time
+ type: string
+ currency:
+ examples:
+ - EUR
+ type: string
+ customerName:
+ type: string
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
+ type: string
+ customerSlug:
+ description: As it was when the invoice was composed
+ type: string
+ daysUntilDue:
+ format: int32
+ type:
+ - integer
+ - "null"
+ discountTotal:
+ description: Sum of the discounts, in minor units
+ format: int64
+ type: integer
+ dueAt:
+ description: issuedAt + daysUntilDue
+ format: date-time
+ type:
+ - string
+ - "null"
+ failureCode:
+ description: The provider's decline or failure code, such as card_declined, expired_card, authentication_required or no_payment_method
+ type: string
+ handoffStatus:
+ description: "PENDING: waiting in the handoff queue for the organization's accounting system"
+ enum:
+ - NOT_REQUIRED
+ - PENDING
+ - ACKNOWLEDGED
+ type: string
+ holdReason:
+ description: "Why a DRAFT is held: the usage journal it was measured from failed a consistency check (LEDGER_*), or its provider cannot take that many lines (PROVIDER_ITEM_LIMIT)"
+ enum:
+ - LEDGER_SEQUENCE_GAP
+ - LEDGER_CHAIN_BREAK
+ - LEDGER_COUNTER_MISMATCH
+ - PROVIDER_ITEM_LIMIT
+ - null
+ type:
+ - string
+ - "null"
+ id:
+ description: "The invoice's identifier: the key an accounting system deduplicates on"
+ type: string
+ instanceName:
+ type: string
+ instanceSlug:
+ type: string
+ issuedAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ kind:
+ description: ACTIVATION bills a subscription's first period, RENEWAL the period that ended and the one that starts, FINAL what is left when a subscription ends
+ enum:
+ - ACTIVATION
+ - RENEWAL
+ - FINAL
+ type: string
+ licenseSlug:
+ type: string
+ overdue:
+ description: "Whether the invoice is overdue when read, the grace period of automatic collection included: the predicate of ?overdue=true. Computed by the invoice list and the invoice read; false in the answers of writes and in event payloads."
+ type: boolean
+ paidAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ providerKind:
+ description: "Who collects it. NOOP: the organization itself, through the handoff queue"
+ enum:
+ - NOOP
+ - STRIPE
+ type: string
+ requiresAction:
+ description: The customer must authenticate the payment on the invoice's hosted page
+ type: boolean
+ serviceFrom:
+ description: Earliest start of its lines' service periods
+ format: date-time
+ type: string
+ serviceTo:
+ description: Latest end of its lines' service periods
+ format: date-time
+ type: string
+ status:
+ description: "MANUAL: issued, for the organization to collect. PAID, UNCOLLECTIBLE and VOID are final. DRAFT: held, waiting for a release or a recompose"
+ enum:
+ - DRAFT
+ - PUSHED
+ - PUSH_FAILED
+ - MANUAL
+ - PAID
+ - PAYMENT_FAILED
+ - UNCOLLECTIBLE
+ - VOID
+ type: string
+ subtotal:
+ description: Sum of the lines, in minor units
+ format: int64
+ type: integer
+ total:
+ description: subtotal − discountTotal, in minor units
+ format: int64
+ type: integer
+ updatedAt:
+ format: date-time
+ type: string
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
+ type: string
+ required:
+ - failureCode
+ - requiresAction
+ - id
+ - kind
+ - boundaryAt
+ - serviceFrom
+ - serviceTo
+ - status
+ - holdReason
+ - providerKind
+ - collectionMethod
+ - currency
+ - subtotal
+ - discountTotal
+ - total
+ - issuedAt
+ - daysUntilDue
+ - dueAt
+ - paidAt
+ - customerSlug
+ - customerName
+ - instanceSlug
+ - instanceName
+ - licenseSlug
+ - handoffStatus
+ - overdue
+ - createdAt
+ - updatedAt
+ type: object
+ x-kaiten-payload: "#/components/schemas/PaymentFailedInvoice"
+ PaymentMethodAttachedDelivered:
+ additionalProperties: false
+ properties:
+ attachedAt:
+ format: date-time
+ type: string
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
+ type: string
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
+ type: string
+ customerSlug:
+ type: string
+ providerKind:
+ enum:
+ - STRIPE
+ type: string
+ status:
+ enum:
+ - ACTIVE
+ type: string
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
+ type: string
+ required:
+ - customerSlug
+ - providerKind
+ - status
+ - attachedAt
+ type: object
+ x-kaiten-payload: "#/components/schemas/PaymentMethodAttached"
+ PaymentMethodDetachedDelivered:
+ additionalProperties: false
+ properties:
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
+ type: string
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
+ type: string
+ customerSlug:
+ type: string
+ providerKind:
+ enum:
+ - STRIPE
+ type: string
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
+ type: string
+ required:
+ - customerSlug
+ - providerKind
+ type: object
+ x-kaiten-payload: "#/components/schemas/PaymentMethodDetached"
+ PaymentMethodExpiringDelivered:
+ additionalProperties: false
+ properties:
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
+ type: string
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
+ type: string
+ customerSlug:
+ type: string
+ expiresAt:
+ description: The last instant of the payment method's expiry month
+ format: date-time
+ type: string
+ providerKind:
+ enum:
+ - STRIPE
+ type: string
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
+ type: string
+ required:
+ - customerSlug
+ - providerKind
+ - expiresAt
+ type: object
+ x-kaiten-payload: "#/components/schemas/PaymentMethodExpiring"
+ PaymentMethodLabels:
+ additionalProperties: false
+ properties:
+ attachedAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ brand:
+ examples:
+ - visa
+ type:
+ - string
+ - "null"
+ expMonth:
+ examples:
+ - 12
+ format: int64
+ type:
+ - integer
+ - "null"
+ expYear:
+ examples:
+ - 2030
+ format: int64
+ type:
+ - integer
+ - "null"
+ last4:
+ examples:
+ - "4242"
+ type:
+ - string
+ - "null"
+ status:
+ description: "ACTIVE: charged automatically. EXPIRED, FAILED: a charge said it is no longer usable; save another one"
+ enum:
+ - ACTIVE
+ - EXPIRED
+ - FAILED
+ type: string
+ required:
+ - status
+ - brand
+ - last4
+ - expMonth
+ - expYear
+ - attachedAt
+ type: object
+ PaymentMethodSession:
+ additionalProperties: false
+ properties:
+ expiresAt:
+ format: date-time
+ type: string
+ sessionId:
+ description: Complete it with POST …/payment-method-session/{sessionId}/complete once the customer is back
+ type: string
+ url:
+ description: The provider's page; send the customer there
+ type: string
+ required:
+ - url
+ - sessionId
+ - expiresAt
+ type: object
+ PlainToken:
+ additionalProperties: false
+ properties:
+ createdAt:
+ format: date-time
+ readOnly: true
+ type: string
+ createdBy:
+ $ref: "#/components/schemas/User"
+ readOnly: true
+ expiresAt:
+ format: date-time
+ type: string
+ id:
+ readOnly: true
+ type: string
+ name:
+ type: string
+ revokedAt:
+ format: date-time
+ readOnly: true
+ type: string
+ revokedBy:
+ $ref: "#/components/schemas/User"
+ readOnly: true
+ scopes:
+ items:
+ type: string
+ type:
+ - array
+ - "null"
+ serviceAccountId:
+ readOnly: true
+ type: string
+ slug:
+ description: Optional URL-friendly identifier, unique per service account. Auto-generated if not provided.
+ maxLength: 100
+ minLength: 2
+ pattern: ^[a-z0-9][a-z0-9-]*[a-z0-9]$
+ type: string
+ token:
+ readOnly: true
+ type: string
+ required:
+ - name
+ - scopes
+ - createdAt
+ - createdBy
+ type: object
+ PlanChangeDelivered:
+ additionalProperties: false
+ properties:
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
+ type: string
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
+ type: string
+ fromLicenseSlug:
+ type: string
+ fromPriceId:
+ type: string
+ instanceSlug:
+ type: string
+ invoiceId:
+ description: The RENEWAL that billed the old plan's arrears and the new plan's advance
+ type: string
+ toLicenseSlug:
+ type: string
+ toPriceId:
+ type: string
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
+ type: string
+ required:
+ - instanceSlug
+ - fromLicenseSlug
+ - toLicenseSlug
+ - fromPriceId
+ - toPriceId
+ - invoiceId
+ type: object
+ x-kaiten-payload: "#/components/schemas/PlanChange"
+ PlanChangeScheduleDelivered:
+ additionalProperties: false
+ properties:
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
+ type: string
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
+ type: string
+ effectiveAt:
+ format: date-time
+ type: string
+ fromPriceId:
+ type: string
+ instanceSlug:
+ type: string
+ toPriceId:
+ type: string
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
+ type: string
+ required:
+ - instanceSlug
+ - fromPriceId
+ - toPriceId
+ - effectiveAt
+ type: object
+ x-kaiten-payload: "#/components/schemas/PlanChangeSchedule"
+ PlanChangeTarget:
+ additionalProperties: false
+ properties:
+ licensePriceId:
+ description: An ACTIVE FLAT_FEE price of a PUBLISHED licence version, in the subscription's currency
+ type: string
+ required:
+ - licensePriceId
+ type: object
+ PortalCapabilities:
+ additionalProperties: false
+ properties:
+ checkout:
+ description: "POST /public/session/checkout: the instance has no live subscription and its plan can be bought alone, or its live subscription is free (a FREE licence family, no add-on price above 0) and the catalogue has a self-serve plan, which the checkout replaces it with"
+ type: boolean
+ invoices:
+ description: "Billing is on: GET /public/session/invoices lists the customer's"
+ type: boolean
+ paymentMethods:
+ description: "The vendor's provider captures payment methods: the payment-method and portal sessions work"
+ type: boolean
+ unsubscribe:
+ description: "The instance's subscription is live and not already ending: POST /public/session/billing/cancel"
+ type: boolean
+ required:
+ - paymentMethods
+ - invoices
+ - unsubscribe
+ - checkout
+ type: object
+ PortalCustomer:
+ additionalProperties: false
+ properties:
+ billingEmail:
+ description: Where its invoices are sent
+ type:
+ - string
+ - "null"
+ name:
+ examples:
+ - Acme
+ type: string
+ slug:
+ examples:
+ - acme
+ type: string
+ required:
+ - slug
+ - name
+ - billingEmail
+ type: object
+ PortalEntitlement:
+ additionalProperties: false
+ properties:
+ currentPeriodEnd:
+ description: The usage window's end; null for a lifetime quota
+ format: date-time
+ type:
+ - string
+ - "null"
+ currentPeriodStart:
+ description: The usage window's start; null for a lifetime quota
+ format: date-time
+ type:
+ - string
+ - "null"
+ currentValue:
+ description: A number's usage in its current window; null for BOOLEAN and CONFIG
+ format: double
+ type:
+ - number
+ - "null"
+ limitCapExceededOveragePercent:
+ description: "How far above the limit usage is still accepted, in percent: 0 a hard limit; null for BOOLEAN and CONFIG"
+ format: int32
+ type:
+ - integer
+ - "null"
+ limitValue:
+ description: "The effective value: a number's limit (-1 unlimited), a BOOLEAN's flag, a CONFIG's object"
+ discriminator:
+ mapping:
+ boolean: "#/components/schemas/BooleanEntitlementValue"
+ number: "#/components/schemas/NumberEntitlementValue"
+ object: "#/components/schemas/ConfigEntitlementValue"
+ propertyName: type
+ oneOf:
+ - $ref: "#/components/schemas/NumberEntitlementValue"
+ - $ref: "#/components/schemas/BooleanEntitlementValue"
+ - $ref: "#/components/schemas/ConfigEntitlementValue"
+ - enum:
+ - null
+ type: "null"
+ maximumAllowedUsage:
+ description: The most usage accepted, limit × (1 + percent ÷ 100); null when unlimited, and for BOOLEAN and CONFIG
+ format: double
+ type:
+ - number
+ - "null"
+ name:
+ examples:
+ - Seats
+ type: string
+ percentageUsed:
+ description: currentValue ÷ limit × 100; null when unlimited or the limit is 0, and for BOOLEAN and CONFIG
+ format: double
+ type:
+ - number
+ - "null"
+ provenance:
+ description: What the licence, the add-ons and the boosts each contribute
+ oneOf:
+ - $ref: "#/components/schemas/Provenance"
+ - enum:
+ - null
+ type: "null"
+ slug:
+ examples:
+ - seats
+ type: string
+ source:
+ description: "license: the licence grants it; addon: an add-on does; exception: only the vendor's exception for this instance does"
+ enum:
+ - license
+ - addon
+ - exception
+ type: string
+ type:
+ enum:
+ - NUMBER
+ - NUMBER_AI_CREDIT
+ - BOOLEAN
+ - CONFIG
+ type: string
+ unlimited:
+ type: boolean
+ required:
+ - slug
+ - name
+ - type
+ - limitValue
+ - currentValue
+ - unlimited
+ - limitCapExceededOveragePercent
+ - maximumAllowedUsage
+ - percentageUsed
+ - currentPeriodStart
+ - currentPeriodEnd
+ - source
+ - provenance
+ type: object
+ PortalInstance:
+ additionalProperties: false
+ properties:
+ licenseName:
+ examples:
+ - Pro
+ type: string
+ licenseSlug:
+ examples:
+ - pro-v2
+ type: string
+ name:
+ examples:
+ - Acme prod
+ type: string
+ slug:
+ examples:
+ - acme-prod
+ type: string
+ required:
+ - slug
+ - name
+ - licenseSlug
+ - licenseName
+ type: object
+ PortalInstanceSummary:
+ additionalProperties: false
+ properties:
+ billingStatus:
+ description: Its subscription's status; null when it was never subscribed
+ enum:
+ - INCOMPLETE
+ - TRIAL
+ - ACTIVE
+ - PAST_DUE
+ - CANCELED
+ - null
+ type:
+ - string
+ - "null"
+ licenseSlug:
+ examples:
+ - pro-v2
+ type: string
+ name:
+ examples:
+ - Acme prod
+ type: string
+ slug:
+ examples:
+ - acme-prod
+ type: string
+ required:
+ - slug
+ - name
+ - licenseSlug
+ - billingStatus
+ type: object
+ PortalPaymentMethod:
+ additionalProperties: false
+ properties:
+ brand:
+ examples:
+ - visa
+ type:
+ - string
+ - "null"
+ expMonth:
+ examples:
+ - 12
+ format: int64
+ type:
+ - integer
+ - "null"
+ expYear:
+ examples:
+ - 2030
+ format: int64
+ type:
+ - integer
+ - "null"
+ last4:
+ examples:
+ - "4242"
+ type:
+ - string
+ - "null"
+ status:
+ description: "EXPIRED, FAILED: save another one"
+ enum:
+ - ACTIVE
+ - EXPIRED
+ - FAILED
+ type: string
+ required:
+ - status
+ - brand
+ - last4
+ - expMonth
+ - expYear
+ type: object
+ PortalSession:
+ additionalProperties: false
+ properties:
+ url:
+ type: string
+ required:
+ - url
+ type: object
+ PortalTrial:
+ additionalProperties: false
+ properties:
+ charge:
+ description: What the conversion would charge, discounts included, before any tax the provider adds; null when nothing would be
+ oneOf:
+ - $ref: "#/components/schemas/PortalTrialCharge"
+ - enum:
+ - null
+ type: "null"
+ endsAt:
+ format: date-time
+ type: string
+ paymentMethod:
+ description: "The ACTIVE card the conversion would be charged to; null without one: ask for a card"
+ oneOf:
+ - $ref: "#/components/schemas/PortalTrialPayment"
+ - enum:
+ - null
+ type: "null"
+ reminderAt:
+ description: When trial_will_end is, or was, due
+ format: date-time
+ type:
+ - string
+ - "null"
+ required:
+ - endsAt
+ - reminderAt
+ - charge
+ - paymentMethod
+ type: object
+ PortalTrialCharge:
+ additionalProperties: false
+ properties:
+ amount:
+ format: int64
+ type: integer
+ billingPeriod:
+ enum:
+ - MONTHLY
+ - QUARTERLY
+ - SEMI_ANNUAL
+ - ANNUAL
+ type: string
+ currency:
+ examples:
+ - EUR
+ type: string
+ required:
+ - amount
+ - currency
+ - billingPeriod
+ type: object
+ PortalTrialPayment:
+ additionalProperties: false
+ properties:
+ brand:
+ examples:
+ - visa
+ type:
+ - string
+ - "null"
+ last4:
+ examples:
+ - "4242"
+ type:
+ - string
+ - "null"
+ required:
+ - brand
+ - last4
+ type: object
+ PortalUpcomingInvoice:
+ additionalProperties: false
+ properties:
+ boundaryAt:
+ description: When it is issued
+ format: date-time
+ type: string
+ currency:
+ examples:
+ - EUR
+ type: string
+ discountTotal:
+ description: Minor units
+ format: int64
+ type: integer
+ subtotal:
+ description: Minor units
+ format: int64
+ type: integer
+ total:
+ description: Minor units
+ format: int64
+ type: integer
+ required:
+ - currency
+ - subtotal
+ - discountTotal
+ - total
+ - boundaryAt
+ type: object
+ PreferenceChoices:
+ additionalProperties: false
+ properties:
+ events:
+ description: Events to set; events left out keep their current value
+ items:
+ $ref: "#/components/schemas/PreferenceUpdate"
+ type:
+ - array
+ - "null"
+ required:
+ - events
+ type: object
+ PreferenceEvent:
+ additionalProperties: false
+ properties:
+ channels:
+ additionalProperties:
+ type: boolean
+ description: Effective value per channel
+ type: object
+ eventName:
+ examples:
+ - INSTANCE_DEPLOYED
+ type: string
+ eventType:
+ description: CloudEvents type of the same event; see Notification.eventType
+ examples:
+ - com.kaiten.instance.v1.deployed
+ type: string
+ group:
+ description: Catalog group the client renders this under
+ examples:
+ - deployments
+ type: string
+ label:
+ description: Human-readable event name
+ type: string
+ required:
+ - eventName
+ - eventType
+ - label
+ - group
+ - channels
+ type: object
+ PreferenceMatrix:
+ additionalProperties: false
+ properties:
+ channels:
+ description: Channels this installation serves, in display order
+ items:
+ type: string
+ type: array
+ events:
+ description: Every notifiable event
+ items:
+ $ref: "#/components/schemas/PreferenceEvent"
+ type: array
+ required:
+ - channels
+ - events
+ type: object
+ PreferenceUpdate:
+ additionalProperties: false
+ properties:
+ channels:
+ additionalProperties:
+ type: boolean
+ type: object
+ eventName:
+ type: string
+ required:
+ - eventName
+ - channels
+ type: object
+ Price:
+ additionalProperties: false
+ properties:
+ billingModel:
+ description: "FLAT_FEE: an amount per billing period. USAGE_BASED: an amount per sale unit of the metered entitlement's usage. OVERAGE: an amount per sale unit of the usage above the licence's limit."
+ enum:
+ - FLAT_FEE
+ - USAGE_BASED
+ - OVERAGE
+ type: string
+ billingPeriod:
+ description: The period a FLAT_FEE price is charged for. Null on metered prices, which bill on the subscription's period.
+ enum:
+ - MONTHLY
+ - QUARTERLY
+ - SEMI_ANNUAL
+ - ANNUAL
+ - null
+ type:
+ - string
+ - "null"
+ billingTiming:
+ description: ADVANCE bills the period that starts; ARREARS the period that ended. Metered prices are always ARREARS.
+ enum:
+ - ADVANCE
+ - ARREARS
+ type: string
+ createdAt:
+ format: date-time
+ readOnly: true
+ type: string
+ currency:
+ description: ISO 4217 code, upper case. One currency per licence version.
+ examples:
+ - EUR
+ type: string
+ deprecatedAt:
+ description: When the price was deprecated
+ format: date-time
+ type:
+ - string
+ - "null"
+ displayLabel:
+ description: The invoice line's label; a default is derived when it is null.
+ examples:
+ - Pro plan
+ type:
+ - string
+ - "null"
+ displayOrder:
+ description: Order among the version's prices
+ examples:
+ - 0
+ format: int32
+ type: integer
+ id:
+ description: Unique identifier of the price
+ readOnly: true
+ type: string
+ isDefault:
+ description: The FLAT_FEE price picked for its billing period when none is named. At most one per period; only an ACTIVE FLAT_FEE price.
+ type: boolean
+ metered:
+ description: What a metered price measures. Null on FLAT_FEE prices.
+ oneOf:
+ - $ref: "#/components/schemas/PriceMeter"
+ - enum:
+ - null
+ type: "null"
+ status:
+ description: A DEPRECATED price keeps billing what is already pinned to it but is no longer offered.
+ enum:
+ - ACTIVE
+ - DEPRECATED
+ type: string
+ unitAmount:
+ description: unitAmountDecimal as an integer number of minor units, when it is one; null otherwise.
+ examples:
+ - 2900
+ format: int64
+ type:
+ - integer
+ - "null"
+ unitAmountDecimal:
+ description: "Amount in the currency's minor units (cents for EUR): per period for FLAT_FEE, per sale unit for metered prices. Up to 12 decimal places."
+ examples:
+ - "2900"
+ type: string
+ updatedAt:
+ format: date-time
+ readOnly: true
+ type: string
+ required:
+ - id
+ - billingModel
+ - billingTiming
+ - billingPeriod
+ - currency
+ - unitAmount
+ - unitAmountDecimal
+ - metered
+ - displayLabel
+ - displayOrder
+ - isDefault
+ - status
+ - deprecatedAt
+ - createdAt
+ - updatedAt
+ type: object
+ PriceMeter:
+ additionalProperties: false
+ properties:
+ entitlementSlug:
+ description: The metered entitlement
+ examples:
+ - tokens
+ type: string
+ saleUnitFactor:
+ description: "Measured units in one sale unit, captured when the price was created (1 when the entitlement has no sale unit): 10000 bills per 10,000 tokens."
+ examples:
+ - "10000"
+ type: string
+ saleUnitPlural:
+ description: The sale unit's plural label; null when the entitlement has none
+ examples:
+ - 10k tokens
+ type:
+ - string
+ - "null"
+ saleUnitSingular:
+ description: The sale unit's singular label; null when the entitlement has none
+ examples:
+ - 10k tokens
+ type:
+ - string
+ - "null"
+ required:
+ - entitlementSlug
+ - saleUnitFactor
+ - saleUnitSingular
+ - saleUnitPlural
+ type: object
+ Problem:
+ additionalProperties: false
+ properties:
+ code:
+ description: Stable, machine-readable error code.
+ examples:
+ - License.NotFound
+ type: string
+ detail:
+ description: A human-readable explanation specific to this occurrence of the problem.
+ examples:
+ - license not found
+ type: string
+ errorId:
+ description: Correlation id for the server-side log entry describing the withheld cause.
+ examples:
+ - 3f6b1a2c-7c1e-4d0b-9d5f-2b0a1c4e8d31
+ type: string
+ errors:
+ description: Optional list of individual error details
+ items:
+ $ref: "#/components/schemas/ErrorDetail"
+ type:
+ - array
+ - "null"
+ instance:
+ description: A URI reference that identifies the specific occurrence of the problem.
+ examples:
+ - /api/licenses/enterprise
+ type: string
+ status:
+ description: HTTP status code
+ examples:
+ - 404
+ format: int64
+ type: integer
+ title:
+ description: A short, human-readable summary of the problem type. This value should not change between occurrences of the error.
+ examples:
+ - Not Found
+ type: string
+ type:
+ default: about:blank
+ description: A URI reference to human-readable documentation for the error.
+ examples:
+ - https://tools.ietf.org/html/rfc7231#section-6.5.4
+ format: uri
+ type: string
+ type: object
+ Profile:
+ additionalProperties: false
+ properties:
+ email:
+ format: email
+ maxLength: 320
+ type: string
+ name:
+ maxLength: 200
+ minLength: 1
+ type: string
+ required:
+ - name
+ - email
+ type: object
+ Provenance:
+ additionalProperties: false
+ properties:
+ addons:
+ description: The active add-on grants, in attachment order
+ items:
+ $ref: "#/components/schemas/ProvenanceAddon"
+ type: array
+ boosts:
+ description: The boosts in their window, in redemption order
+ items:
+ $ref: "#/components/schemas/ProvenanceBoost"
+ type: array
+ exception:
+ description: The instance's exception in force at the read, the last layer; null without one. Never its reason or author
+ oneOf:
+ - $ref: "#/components/schemas/ProvenanceException"
+ - enum:
+ - null
+ type: "null"
+ license:
+ description: The licence's grant; null when only add-ons grant the entitlement
+ oneOf:
+ - $ref: "#/components/schemas/ProvenanceLicense"
+ - enum:
+ - null
+ type: "null"
+ number:
+ description: How a number was composed; null for BOOLEAN and CONFIG, and when no add-on, boost or exception changes it
+ oneOf:
+ - $ref: "#/components/schemas/ProvenanceNumber"
+ - enum:
+ - null
+ type: "null"
+ required:
+ - license
+ - addons
+ - boosts
+ - exception
+ - number
+ type: object
+ ProvenanceAddon:
+ additionalProperties: false
+ properties:
+ addonEntitlementId:
+ type: string
+ addonId:
+ type: string
+ attachedAt:
+ format: date-time
+ type: string
+ instanceAddonId:
+ type: string
+ limitCapExceededOveragePercent:
+ format: int32
+ type:
+ - integer
+ - "null"
+ overrideBehavior:
+ enum:
+ - ADD
+ - OVERRIDE
+ - MAX
+ type: string
+ quantity:
+ format: int32
+ type: integer
+ value:
+ description: One unit's grant
+ discriminator:
+ mapping:
+ boolean: "#/components/schemas/BooleanEntitlementValue"
+ number: "#/components/schemas/NumberEntitlementValue"
+ object: "#/components/schemas/ConfigEntitlementValue"
+ propertyName: type
+ oneOf:
+ - $ref: "#/components/schemas/NumberEntitlementValue"
+ - $ref: "#/components/schemas/BooleanEntitlementValue"
+ - $ref: "#/components/schemas/ConfigEntitlementValue"
+ required:
+ - instanceAddonId
+ - addonId
+ - addonEntitlementId
+ - quantity
+ - overrideBehavior
+ - value
+ - limitCapExceededOveragePercent
+ - attachedAt
+ type: object
+ ProvenanceBoost:
+ additionalProperties: false
+ properties:
+ effectiveExpiresAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ effectiveStartsAt:
+ format: date-time
+ type: string
+ instanceVoucherId:
+ type: string
+ modifierType:
+ enum:
+ - SET
+ - ADD
+ - MULTIPLY
+ - UNLIMITED
+ type: string
+ modifierValue:
+ format: double
+ type:
+ - number
+ - "null"
+ redeemedAt:
+ format: date-time
+ type: string
+ value:
+ description: The value a BOOLEAN or CONFIG SET boost sets, in the {type, value} shape; null for the NUMBER modifiers
+ discriminator:
+ mapping:
+ boolean: "#/components/schemas/BooleanEntitlementValue"
+ number: "#/components/schemas/NumberEntitlementValue"
+ object: "#/components/schemas/ConfigEntitlementValue"
+ propertyName: type
+ oneOf:
+ - $ref: "#/components/schemas/NumberEntitlementValue"
+ - $ref: "#/components/schemas/BooleanEntitlementValue"
+ - $ref: "#/components/schemas/ConfigEntitlementValue"
+ - enum:
+ - null
+ type: "null"
+ voucherEntitlementGrantId:
+ type: string
+ voucherId:
+ type: string
+ required:
+ - instanceVoucherId
+ - voucherId
+ - voucherEntitlementGrantId
+ - modifierType
+ - modifierValue
+ - value
+ - redeemedAt
+ - effectiveStartsAt
+ - effectiveExpiresAt
+ type: object
+ ProvenanceException:
+ additionalProperties: false
+ properties:
+ endsAt:
+ description: "Null: no end"
+ format: date-time
+ type:
+ - string
+ - "null"
+ instanceEntitlementExceptionId:
+ type: string
+ kind:
+ enum:
+ - SET
+ - ADD
+ - UNLIMITED
+ type: string
+ startsAt:
+ format: date-time
+ type: string
+ value:
+ description: The exception's value, in the {type, value} shape; null for UNLIMITED
+ discriminator:
+ mapping:
+ boolean: "#/components/schemas/BooleanEntitlementValue"
+ number: "#/components/schemas/NumberEntitlementValue"
+ object: "#/components/schemas/ConfigEntitlementValue"
+ propertyName: type
+ oneOf:
+ - $ref: "#/components/schemas/NumberEntitlementValue"
+ - $ref: "#/components/schemas/BooleanEntitlementValue"
+ - $ref: "#/components/schemas/ConfigEntitlementValue"
+ - enum:
+ - null
+ type: "null"
+ required:
+ - instanceEntitlementExceptionId
+ - kind
+ - value
+ - startsAt
+ - endsAt
+ type: object
+ ProvenanceLicense:
+ additionalProperties: false
+ properties:
+ licenseEntitlementId:
+ type: string
+ limitCapExceededOveragePercent:
+ format: int32
+ type:
+ - integer
+ - "null"
+ value:
+ discriminator:
+ mapping:
+ boolean: "#/components/schemas/BooleanEntitlementValue"
+ number: "#/components/schemas/NumberEntitlementValue"
+ object: "#/components/schemas/ConfigEntitlementValue"
+ propertyName: type
+ oneOf:
+ - $ref: "#/components/schemas/NumberEntitlementValue"
+ - $ref: "#/components/schemas/BooleanEntitlementValue"
+ - $ref: "#/components/schemas/ConfigEntitlementValue"
+ required:
+ - licenseEntitlementId
+ - value
+ - limitCapExceededOveragePercent
+ type: object
+ ProvenanceNumber:
+ additionalProperties: false
+ properties:
+ afterAddons:
+ format: double
+ type: number
+ afterBoosts:
+ description: "The value before the exception: -1 when unlimited"
+ format: double
+ type: number
+ boostAdd:
+ format: double
+ type:
+ - number
+ - "null"
+ boostMultiply:
+ format: double
+ type:
+ - number
+ - "null"
+ boostSet:
+ format: double
+ type:
+ - number
+ - "null"
+ effective:
+ description: -1 when unlimited
+ format: double
+ type: number
+ license:
+ description: The licence's value; null when only add-ons or an exception grant it
+ format: double
+ type:
+ - number
+ - "null"
+ unlimited:
+ description: The licence, an add-on or a boost is unlimited; only an exception's SET makes it finite again
+ type: boolean
+ required:
+ - license
+ - afterAddons
+ - boostSet
+ - boostAdd
+ - boostMultiply
+ - unlimited
+ - afterBoosts
+ - effective
+ type: object
+ ProviderCapabilities:
+ additionalProperties: false
+ properties:
+ automaticCollection:
+ type: boolean
+ billingPortal:
+ type: boolean
+ paymentMethodCapture:
+ type: boolean
+ required:
+ - paymentMethodCapture
+ - billingPortal
+ - automaticCollection
+ type: object
+ ProviderChangeDelivered:
+ additionalProperties: false
+ properties:
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
+ type: string
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
+ type: string
+ effectiveFrom:
+ description: Invoices already composed keep their provider
+ enum:
+ - next_composition
+ type: string
+ from:
+ enum:
+ - NOOP
+ - STRIPE
+ type: string
+ instanceSlug:
+ type: string
+ to:
+ enum:
+ - NOOP
+ - STRIPE
+ type: string
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
+ type: string
+ required:
+ - instanceSlug
+ - from
+ - to
+ - effectiveFrom
+ type: object
+ x-kaiten-payload: "#/components/schemas/ProviderChange"
+ ProviderLineLimit:
+ additionalProperties: false
+ properties:
+ items:
+ description: The provider items the invoice needs
+ format: int32
+ type: integer
+ limit:
+ description: The most the provider takes on one invoice
+ format: int32
+ type: integer
+ providerKind:
+ enum:
+ - STRIPE
+ type: string
+ required:
+ - providerKind
+ - items
+ - limit
+ type: object
+ ProviderRecord:
+ additionalProperties: false
+ properties:
+ externalCustomerId:
+ type:
+ - string
+ - "null"
+ externalInvoiceId:
+ type:
+ - string
+ - "null"
+ hostedInvoiceUrl:
+ type:
+ - string
+ - "null"
+ invoiceNumber:
+ description: The provider's invoice number
+ type:
+ - string
+ - "null"
+ invoicePdfUrl:
+ type:
+ - string
+ - "null"
+ lastPaymentError:
+ description: "Why the last automatic charge failed, as the provider coded it: authentication_required (the customer must confirm the payment on the hosted invoice page), card_declined, expired_card, no_payment_method..."
+ type:
+ - string
+ - "null"
+ lastPushError:
+ description: The provider's code and message of the last failed push step
+ type:
+ - string
+ - "null"
+ nextPushAt:
+ description: When the push queue tries it next; null when it waits for a human (finalization in the provider) or is pushed
+ format: date-time
+ type:
+ - string
+ - "null"
+ pushAttempts:
+ format: int32
+ type: integer
+ pushedAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ reconciledAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ reconciliationDetail:
+ description: What differs, on a MISMATCH
+ oneOf:
+ - $ref: "#/components/schemas/Reconciliation"
+ - enum:
+ - null
+ type: "null"
+ reconciliationStatus:
+ enum:
+ - MATCHED
+ - MISMATCH
+ - null
+ type:
+ - string
+ - "null"
+ status:
+ description: The provider's status, mirrored verbatim
+ enum:
+ - draft
+ - open
+ - paid
+ - uncollectible
+ - void
+ - null
+ type:
+ - string
+ - "null"
+ syncedAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ totalExcludingTax:
+ description: The provider's total excluding tax, read back
+ format: int64
+ type:
+ - integer
+ - "null"
+ required:
+ - externalCustomerId
+ - externalInvoiceId
+ - invoiceNumber
+ - status
+ - hostedInvoiceUrl
+ - invoicePdfUrl
+ - pushAttempts
+ - nextPushAt
+ - lastPushError
+ - lastPaymentError
+ - pushedAt
+ - syncedAt
+ - totalExcludingTax
+ - reconciliationStatus
+ - reconciledAt
+ - reconciliationDetail
+ type: object
+ ProviderSync:
+ additionalProperties: false
+ properties:
+ consecutiveFailures:
+ format: int32
+ type: integer
+ lagSeconds:
+ description: Seconds since the last pass; null before the first
+ format: int64
+ type:
+ - integer
+ - "null"
+ lastFullSweepAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ lastSuccessfulSyncAt:
+ description: The end of the last pass that ended SUCCESS; null before the first (V31-11)
+ format: date-time
+ type:
+ - string
+ - "null"
+ lastSyncError:
+ type:
+ - string
+ - "null"
+ lastSyncStatus:
+ enum:
+ - SUCCESS
+ - PARTIAL
+ - FAILED
+ - null
+ type:
+ - string
+ - "null"
+ lastSyncedAt:
+ description: The end of the last pass, whatever its status; null before the first
+ format: date-time
+ type:
+ - string
+ - "null"
+ providerKind:
+ enum:
+ - NOOP
+ - STRIPE
+ type: string
+ required:
+ - providerKind
+ - lastSyncedAt
+ - lastSuccessfulSyncAt
+ - lastSyncStatus
+ - lastSyncError
+ - consecutiveFailures
+ - lagSeconds
+ - lastFullSweepAt
+ type: object
+ PublicAddOn:
+ additionalProperties: false
+ properties:
+ addonId:
+ format: uuid
+ type: string
+ addonSlug:
+ examples:
+ - extra-seats-v1
+ type: string
+ compatibleLicenseFamilies:
+ description: Public licence families this add-on fits
+ items:
+ type: string
+ type: array
+ description:
+ type: string
+ entitlements:
+ description: User-facing entitlements one unit grants
+ items:
+ $ref: "#/components/schemas/PublicAddOnEntitlement"
+ type: array
+ familySlug:
+ examples:
+ - extra-seats
+ type: string
+ maxQuantity:
+ description: Most units one instance may attach; null when unbounded
+ format: int32
+ type:
+ - integer
+ - "null"
+ name:
+ type: string
+ prices:
+ items:
+ $ref: "#/components/schemas/PublicPrice"
+ type: array
+ pricingType:
+ enum:
+ - FREE
+ - PAID
+ - CUSTOM
+ type: string
+ required:
+ - familySlug
+ - addonSlug
+ - addonId
+ - name
+ - description
+ - pricingType
+ - maxQuantity
+ - prices
+ - entitlements
+ - compatibleLicenseFamilies
+ type: object
+ PublicAddOnEntitlement:
+ additionalProperties: false
+ properties:
+ limitCapExceededOveragePercent:
+ format: int32
+ type:
+ - integer
+ - "null"
+ name:
+ type: string
+ overrideBehavior:
+ description: "How the grant combines with the plan's: added to it, replacing it, or the larger of the two"
+ enum:
+ - ADD
+ - OVERRIDE
+ - MAX
+ type: string
+ slug:
+ type: string
+ type:
+ enum:
+ - BOOLEAN
+ - NUMBER
+ - NUMBER_AI_CREDIT
+ - CONFIG
+ type: string
+ value: {}
+ required:
+ - slug
+ - name
+ - type
+ - value
+ - overrideBehavior
+ - limitCapExceededOveragePercent
+ type: object
+ PublicBranding:
+ additionalProperties: false
+ properties:
+ poweredByRemovable:
+ description: Whether the vendor's plan lets its storefront and portal hide "Powered by Kaiten" (the remove-powered-by entitlement). False while Kaiten's licensing authority cannot answer; always true self-hosted
+ type: boolean
+ required:
+ - poweredByRemovable
+ type: object
+ PublicCatalog:
+ additionalProperties: false
+ properties:
+ addOns:
+ description: The default version of each public add-on family; empty when include omits addons
+ items:
+ $ref: "#/components/schemas/PublicAddOn"
+ type: array
+ branding:
+ $ref: "#/components/schemas/PublicBranding"
+ capabilities:
+ $ref: "#/components/schemas/PublicCatalogCapabilities"
+ plans:
+ description: The default version of each public licence family, by family slug
+ items:
+ $ref: "#/components/schemas/PublicPlan"
+ type: array
+ required:
+ - plans
+ - addOns
+ - capabilities
+ - branding
+ type: object
+ PublicCatalogCapabilities:
+ additionalProperties: false
+ properties:
+ anonymousCheckout:
+ description: "Whether this publishable key starts anonymous checkouts (POST /public/checkout-sessions): on, with a Turnstile site key and secret"
+ type: boolean
+ checkout:
+ description: Whether a plan of the catalogue can be bought alone, through a customer session's checkout (POST /public/session/checkout)
+ type: boolean
+ paymentMethods:
+ description: Whether the organization's billing provider captures payment methods
+ type: boolean
+ turnstileSiteKey:
+ description: The Turnstile site key the page renders its widget with; null unless anonymousCheckout
+ type:
+ - string
+ - "null"
+ required:
+ - checkout
+ - paymentMethods
+ - anonymousCheckout
+ - turnstileSiteKey
+ type: object
+ PublicMeter:
+ additionalProperties: false
+ properties:
+ entitlementSlug:
+ examples:
+ - tokens
+ type: string
+ saleUnitFactor:
+ description: Measured units in one sale unit
+ examples:
+ - "10000"
+ type: string
+ saleUnitPlural:
+ description: The sale unit's plural label; null when the entitlement has none
+ examples:
+ - 10k tokens
+ type:
+ - string
+ - "null"
+ saleUnitSingular:
+ description: The sale unit's singular label; null when the entitlement has none
+ examples:
+ - 10k tokens
+ type:
+ - string
+ - "null"
+ required:
+ - entitlementSlug
+ - saleUnitFactor
+ - saleUnitSingular
+ - saleUnitPlural
+ type: object
+ PublicPlan:
+ additionalProperties: false
+ properties:
+ compatibleAddonFamilies:
+ description: Public add-on families whose listed version fits this plan
+ items:
+ type: string
+ type: array
+ currency:
+ description: ISO 4217 code of the plan's prices; null when it has none
+ examples:
+ - EUR
+ type:
+ - string
+ - "null"
+ description:
+ type: string
+ entitlements:
+ description: User-facing entitlements the plan grants, in display order
+ items:
+ $ref: "#/components/schemas/PublicPlanEntitlement"
+ type: array
+ familySlug:
+ examples:
+ - pro
+ type: string
+ licenseId:
+ format: uuid
+ type: string
+ licenseSlug:
+ description: The version's own slug
+ examples:
+ - pro-v3
+ type: string
+ lifecycleState:
+ description: "Always PUBLISHED: only the default version is listed, and a default version is published"
+ enum:
+ - PUBLISHED
+ type: string
+ name:
+ examples:
+ - Pro
+ type: string
+ prices:
+ description: ACTIVE prices, in display order
+ items:
+ $ref: "#/components/schemas/PublicPrice"
+ type: array
+ pricingType:
+ description: "CUSTOM: priced on request; render selfServeCtaUrl"
+ enum:
+ - FREE
+ - PAID
+ - CUSTOM
+ type: string
+ requiresPaymentMethod:
+ description: A card is captured before subscribing, even when nothing is due
+ type: boolean
+ selfServe:
+ description: "Whether a customer can subscribe to this plan alone: not CUSTOM, with a default ACTIVE flat-fee price, and either FREE or sold through a provider that captures payment methods. Otherwise render selfServeCtaUrl."
+ type: boolean
+ selfServeCtaUrl:
+ description: Where to send a customer who cannot subscribe alone ("Contact us")
+ type:
+ - string
+ - "null"
+ trialPeriodDays:
+ description: Length of the free trial; null when the plan has none
+ format: int32
+ type:
+ - integer
+ - "null"
+ required:
+ - familySlug
+ - licenseSlug
+ - licenseId
+ - name
+ - description
+ - lifecycleState
+ - pricingType
+ - trialPeriodDays
+ - requiresPaymentMethod
+ - selfServe
+ - selfServeCtaUrl
+ - currency
+ - prices
+ - compatibleAddonFamilies
+ - entitlements
+ type: object
+ PublicPlanEntitlement:
+ additionalProperties: false
+ properties:
+ description:
+ type:
+ - string
+ - "null"
+ displayOrder:
+ format: int32
+ type: integer
+ icon:
+ type:
+ - string
+ - "null"
+ limitCapExceededOveragePercent:
+ description: "How far usage may exceed a NUMBER grant before it is refused: 0 is a hard limit, -1 is unlimited; null for other types"
+ format: int32
+ type:
+ - integer
+ - "null"
+ name:
+ examples:
+ - Seats
+ type: string
+ saleUnitFactor:
+ type:
+ - string
+ - "null"
+ saleUnitPlural:
+ type:
+ - string
+ - "null"
+ saleUnitSingular:
+ type:
+ - string
+ - "null"
+ slug:
+ examples:
+ - seats
+ type: string
+ type:
+ enum:
+ - BOOLEAN
+ - NUMBER
+ - NUMBER_AI_CREDIT
+ - CONFIG
+ type: string
+ unitPlural:
+ examples:
+ - seats
+ type:
+ - string
+ - "null"
+ unitSingular:
+ examples:
+ - seat
+ type:
+ - string
+ - "null"
+ unlimited:
+ description: A NUMBER grant with no limit
+ type: boolean
+ value:
+ description: "The granted value: a boolean, a number, or a configuration object"
+ required:
+ - slug
+ - name
+ - description
+ - type
+ - value
+ - unlimited
+ - limitCapExceededOveragePercent
+ - icon
+ - unitSingular
+ - unitPlural
+ - saleUnitSingular
+ - saleUnitPlural
+ - saleUnitFactor
+ - displayOrder
+ type: object
+ PublicPrice:
+ additionalProperties: false
+ properties:
+ billingModel:
+ description: "FLAT_FEE: per billing period. USAGE_BASED: per sale unit of usage. OVERAGE: per sale unit of usage above the limit. Never sum metered prices into a headline."
+ enum:
+ - FLAT_FEE
+ - USAGE_BASED
+ - OVERAGE
+ type: string
+ billingPeriod:
+ description: The period a FLAT_FEE price is charged for; null on metered prices
+ enum:
+ - MONTHLY
+ - QUARTERLY
+ - SEMI_ANNUAL
+ - ANNUAL
+ - null
+ type:
+ - string
+ - "null"
+ billingTiming:
+ enum:
+ - ADVANCE
+ - ARREARS
+ type: string
+ currency:
+ examples:
+ - EUR
+ type: string
+ displayLabel:
+ description: The price's label; null when it has none
+ examples:
+ - Pro plan
+ type:
+ - string
+ - "null"
+ displayOrder:
+ format: int32
+ type: integer
+ id:
+ format: uuid
+ type: string
+ isDefault:
+ description: The FLAT_FEE price shown for its billing period
+ type: boolean
+ metered:
+ description: What a metered price measures; null on FLAT_FEE prices
+ oneOf:
+ - $ref: "#/components/schemas/PublicMeter"
+ - enum:
+ - null
+ type: "null"
+ unitAmount:
+ description: The amount in minor units, when it is a whole number of them; null otherwise (render unitAmountDecimal)
+ examples:
+ - 2900
+ format: int64
+ type:
+ - integer
+ - "null"
+ unitAmountDecimal:
+ description: The amount in minor units, up to 12 decimal places
+ examples:
+ - "2900"
+ type: string
+ required:
+ - id
+ - billingModel
+ - billingTiming
+ - billingPeriod
+ - currency
+ - unitAmount
+ - unitAmountDecimal
+ - metered
+ - displayLabel
+ - displayOrder
+ - isDefault
+ type: object
+ PublicSurface:
+ additionalProperties: false
+ properties:
+ enabled:
+ description: "Whether publishable keys and customer sessions work for the organization: billing is on for it"
+ type: boolean
+ required:
+ - enabled
+ type: object
+ PublishableKey:
+ additionalProperties: false
+ properties:
+ allowedOrigins:
+ description: "Browser origins allowed to send the key. Empty: no browser origin may; requests without an Origin header (server-side rendering) still may."
+ examples:
+ - - https://www.example.com
+ items:
+ type: string
+ type: array
+ anonymousCheckout:
+ description: Whether the key may start an anonymous checkout (POST /public/checkout-sessions). Needs a Turnstile site key and secret.
+ type: boolean
+ createdAt:
+ format: date-time
+ readOnly: true
+ type: string
+ id:
+ description: Unique identifier of the key
+ readOnly: true
+ type: string
+ keyHint:
+ description: The last four characters of the key
+ examples:
+ - x9Qa
+ readOnly: true
+ type: string
+ label:
+ description: What the key is for, to tell keys apart
+ examples:
+ - Marketing site
+ type: string
+ lastUsedAt:
+ description: When the key last authenticated a request, to the minute; null before its first
+ format: date-time
+ readOnly: true
+ type:
+ - string
+ - "null"
+ revokedAt:
+ description: When the key was revoked; null while it is live
+ format: date-time
+ readOnly: true
+ type:
+ - string
+ - "null"
+ turnstileSecretConfigured:
+ description: Whether a Turnstile secret is stored for the key. The secret itself is write-only.
+ readOnly: true
+ type: boolean
+ turnstileSiteKey:
+ description: "The Turnstile site key the page renders its widget with; null without one. Public: the catalogue hands it to the page."
+ type:
+ - string
+ - "null"
+ updatedAt:
+ format: date-time
+ readOnly: true
+ type: string
+ required:
+ - id
+ - label
+ - keyHint
+ - allowedOrigins
+ - anonymousCheckout
+ - turnstileSiteKey
+ - turnstileSecretConfigured
+ - lastUsedAt
+ - createdAt
+ - updatedAt
+ - revokedAt
+ type: object
+ PublishableKeyCreated:
+ additionalProperties: false
+ properties:
+ allowedOrigins:
+ description: "Browser origins allowed to send the key. Empty: no browser origin may; requests without an Origin header (server-side rendering) still may."
+ examples:
+ - - https://www.example.com
+ items:
+ type: string
+ type: array
+ anonymousCheckout:
+ description: Whether the key may start an anonymous checkout (POST /public/checkout-sessions). Needs a Turnstile site key and secret.
+ type: boolean
+ createdAt:
+ format: date-time
+ readOnly: true
+ type: string
+ id:
+ description: Unique identifier of the key
+ readOnly: true
+ type: string
+ key:
+ description: "The key itself. Shown once: only its digest is stored."
+ examples:
+ - pk_3q2-7wEXAMPLEkeyBodyOfFortyThreeCharactersXyz
+ readOnly: true
+ type: string
+ keyHint:
+ description: The last four characters of the key
+ examples:
+ - x9Qa
+ readOnly: true
+ type: string
+ label:
+ description: What the key is for, to tell keys apart
+ examples:
+ - Marketing site
+ type: string
+ lastUsedAt:
+ description: When the key last authenticated a request, to the minute; null before its first
+ format: date-time
+ readOnly: true
+ type:
+ - string
+ - "null"
+ revokedAt:
+ description: When the key was revoked; null while it is live
+ format: date-time
+ readOnly: true
+ type:
+ - string
+ - "null"
+ turnstileSecretConfigured:
+ description: Whether a Turnstile secret is stored for the key. The secret itself is write-only.
+ readOnly: true
+ type: boolean
+ turnstileSiteKey:
+ description: "The Turnstile site key the page renders its widget with; null without one. Public: the catalogue hands it to the page."
+ type:
+ - string
+ - "null"
+ updatedAt:
+ format: date-time
+ readOnly: true
+ type: string
+ required:
+ - key
+ - id
+ - label
+ - keyHint
+ - allowedOrigins
+ - anonymousCheckout
+ - turnstileSiteKey
+ - turnstileSecretConfigured
+ - lastUsedAt
+ - createdAt
+ - updatedAt
+ - revokedAt
+ type: object
+ PublishableKeyDraft:
+ additionalProperties: false
+ properties:
+ allowedOrigins:
+ description: "Browser origins allowed to send the key, as scheme://host[:port]: https, or http for localhost. Empty: no browser origin may."
+ examples:
+ - - https://www.example.com
+ items:
+ type: string
+ maxItems: 50
+ type: array
+ anonymousCheckout:
+ description: Open POST /public/checkout-sessions to the key. Needs turnstileSiteKey and turnstileSecret, else 422 .TurnstileNotConfigured. False by default.
+ type: boolean
+ label:
+ description: What the key is for
+ examples:
+ - Marketing site
+ maxLength: 100
+ minLength: 1
+ type: string
+ turnstileSecret:
+ description: "The Turnstile secret the server verifies tokens with. Stored in Vault, never returned: reads say turnstileSecretConfigured. 422 .VaultNotConfigured on a deployment without Vault."
+ maxLength: 255
+ minLength: 1
+ type: string
+ turnstileSiteKey:
+ description: The Turnstile site key the page renders its widget with
+ pattern: ^[A-Za-z0-9_-]{1,255}$
+ type: string
+ required:
+ - label
+ - allowedOrigins
+ type: object
+ PublishableKeyEventDelivered:
+ additionalProperties: false
+ properties:
+ allowedOrigins:
+ items:
+ type: string
+ type: array
+ anonymousCheckout:
+ type: boolean
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
+ type: string
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
+ type: string
+ id:
+ type: string
+ keyHint:
+ description: The last four characters of the key
+ type: string
+ label:
+ type: string
+ revokedAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ turnstileSiteKey:
+ type:
+ - string
+ - "null"
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
+ type: string
+ required:
+ - id
+ - label
+ - keyHint
+ - allowedOrigins
+ - anonymousCheckout
+ - turnstileSiteKey
+ - revokedAt
+ type: object
+ x-kaiten-payload: "#/components/schemas/PublishableKeyEvent"
+ PublishableKeyPatch:
+ additionalProperties: false
+ properties:
+ allowedOrigins:
+ description: Replaces the allowed browser origins. Takes effect on the next request.
+ items:
+ type: string
+ maxItems: 50
+ type: array
+ anonymousCheckout:
+ description: Open or close POST /public/checkout-sessions to the key. On only with a Turnstile site key and secret, else 422 .TurnstileNotConfigured.
+ type: boolean
+ label:
+ description: What the key is for
+ maxLength: 100
+ minLength: 1
+ type: string
+ turnstileSecret:
+ description: The Turnstile secret, stored in Vault and never returned; "" deletes it. 422 .VaultNotConfigured on a deployment without Vault.
+ maxLength: 255
+ type: string
+ turnstileSiteKey:
+ description: The Turnstile site key; "" clears it
+ pattern: ^[A-Za-z0-9_-]{0,255}$
+ type: string
+ type: object
+ PushFailedInvoiceDelivered:
+ additionalProperties: false
+ properties:
+ boundaryAt:
+ description: The boundary the invoice bills. With the kind, it identifies the invoice within its subscription
+ format: date-time
+ type: string
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
+ type: string
+ collectionMethod:
+ enum:
+ - SEND_INVOICE
+ - CHARGE_AUTOMATICALLY
+ type: string
+ createdAt:
+ format: date-time
+ type: string
+ currency:
+ examples:
+ - EUR
+ type: string
+ customerName:
+ type: string
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
+ type: string
+ customerSlug:
+ description: As it was when the invoice was composed
+ type: string
+ daysUntilDue:
+ format: int32
+ type:
+ - integer
+ - "null"
+ discountTotal:
+ description: Sum of the discounts, in minor units
+ format: int64
+ type: integer
+ dueAt:
+ description: issuedAt + daysUntilDue
+ format: date-time
+ type:
+ - string
+ - "null"
+ handoffStatus:
+ description: "PENDING: waiting in the handoff queue for the organization's accounting system"
+ enum:
+ - NOT_REQUIRED
+ - PENDING
+ - ACKNOWLEDGED
+ type: string
+ holdReason:
+ description: "Why a DRAFT is held: the usage journal it was measured from failed a consistency check (LEDGER_*), or its provider cannot take that many lines (PROVIDER_ITEM_LIMIT)"
+ enum:
+ - LEDGER_SEQUENCE_GAP
+ - LEDGER_CHAIN_BREAK
+ - LEDGER_COUNTER_MISMATCH
+ - PROVIDER_ITEM_LIMIT
+ - null
+ type:
+ - string
+ - "null"
+ id:
+ description: "The invoice's identifier: the key an accounting system deduplicates on"
+ type: string
+ instanceName:
+ type: string
+ instanceSlug:
+ type: string
+ issuedAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ kind:
+ description: ACTIVATION bills a subscription's first period, RENEWAL the period that ended and the one that starts, FINAL what is left when a subscription ends
+ enum:
+ - ACTIVATION
+ - RENEWAL
+ - FINAL
+ type: string
+ lastPushError:
+ description: The provider's code and message, never a request body
+ type: string
+ licenseSlug:
+ type: string
+ overdue:
+ description: "Whether the invoice is overdue when read, the grace period of automatic collection included: the predicate of ?overdue=true. Computed by the invoice list and the invoice read; false in the answers of writes and in event payloads."
+ type: boolean
+ paidAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ providerKind:
+ description: "Who collects it. NOOP: the organization itself, through the handoff queue"
+ enum:
+ - NOOP
+ - STRIPE
+ type: string
+ pushAttempts:
+ format: int32
+ type: integer
+ serviceFrom:
+ description: Earliest start of its lines' service periods
+ format: date-time
+ type: string
+ serviceTo:
+ description: Latest end of its lines' service periods
+ format: date-time
+ type: string
+ status:
+ description: "MANUAL: issued, for the organization to collect. PAID, UNCOLLECTIBLE and VOID are final. DRAFT: held, waiting for a release or a recompose"
+ enum:
+ - DRAFT
+ - PUSHED
+ - PUSH_FAILED
+ - MANUAL
+ - PAID
+ - PAYMENT_FAILED
+ - UNCOLLECTIBLE
+ - VOID
+ type: string
+ subtotal:
+ description: Sum of the lines, in minor units
+ format: int64
+ type: integer
+ total:
+ description: subtotal − discountTotal, in minor units
+ format: int64
+ type: integer
+ updatedAt:
+ format: date-time
+ type: string
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
+ type: string
+ required:
+ - pushAttempts
+ - lastPushError
+ - id
+ - kind
+ - boundaryAt
+ - serviceFrom
+ - serviceTo
+ - status
+ - holdReason
+ - providerKind
+ - collectionMethod
+ - currency
+ - subtotal
+ - discountTotal
+ - total
+ - issuedAt
+ - daysUntilDue
+ - dueAt
+ - paidAt
+ - customerSlug
+ - customerName
+ - instanceSlug
+ - instanceName
+ - licenseSlug
+ - handoffStatus
+ - overdue
+ - createdAt
+ - updatedAt
+ type: object
+ x-kaiten-payload: "#/components/schemas/PushFailedInvoice"
+ PushFailures:
+ additionalProperties: false
+ properties:
+ count:
+ format: int64
+ type: integer
+ oldestFailedAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ required:
+ - count
+ - oldestFailedAt
+ type: object
+ PushedInvoiceDelivered:
+ additionalProperties: false
+ properties:
+ boundaryAt:
+ description: The boundary the invoice bills. With the kind, it identifies the invoice within its subscription
+ format: date-time
+ type: string
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
+ type: string
+ collectionMethod:
+ enum:
+ - SEND_INVOICE
+ - CHARGE_AUTOMATICALLY
+ type: string
+ createdAt:
+ format: date-time
+ type: string
+ currency:
+ examples:
+ - EUR
+ type: string
+ customerName:
+ type: string
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
+ type: string
+ customerSlug:
+ description: As it was when the invoice was composed
+ type: string
+ daysUntilDue:
+ format: int32
+ type:
+ - integer
+ - "null"
+ discountTotal:
+ description: Sum of the discounts, in minor units
+ format: int64
+ type: integer
+ dueAt:
+ description: issuedAt + daysUntilDue
+ format: date-time
+ type:
+ - string
+ - "null"
+ externalInvoiceId:
+ type: string
+ handoffStatus:
+ description: "PENDING: waiting in the handoff queue for the organization's accounting system"
+ enum:
+ - NOT_REQUIRED
+ - PENDING
+ - ACKNOWLEDGED
+ type: string
+ holdReason:
+ description: "Why a DRAFT is held: the usage journal it was measured from failed a consistency check (LEDGER_*), or its provider cannot take that many lines (PROVIDER_ITEM_LIMIT)"
+ enum:
+ - LEDGER_SEQUENCE_GAP
+ - LEDGER_CHAIN_BREAK
+ - LEDGER_COUNTER_MISMATCH
+ - PROVIDER_ITEM_LIMIT
+ - null
+ type:
+ - string
+ - "null"
+ id:
+ description: "The invoice's identifier: the key an accounting system deduplicates on"
+ type: string
+ instanceName:
+ type: string
+ instanceSlug:
+ type: string
+ issuedAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ kind:
+ description: ACTIVATION bills a subscription's first period, RENEWAL the period that ended and the one that starts, FINAL what is left when a subscription ends
+ enum:
+ - ACTIVATION
+ - RENEWAL
+ - FINAL
+ type: string
+ licenseSlug:
+ type: string
+ overdue:
+ description: "Whether the invoice is overdue when read, the grace period of automatic collection included: the predicate of ?overdue=true. Computed by the invoice list and the invoice read; false in the answers of writes and in event payloads."
+ type: boolean
+ paidAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ providerInvoiceNumber:
+ type: string
+ providerKind:
+ description: "Who collects it. NOOP: the organization itself, through the handoff queue"
+ enum:
+ - NOOP
+ - STRIPE
+ type: string
+ serviceFrom:
+ description: Earliest start of its lines' service periods
+ format: date-time
+ type: string
+ serviceTo:
+ description: Latest end of its lines' service periods
+ format: date-time
+ type: string
+ status:
+ description: "MANUAL: issued, for the organization to collect. PAID, UNCOLLECTIBLE and VOID are final. DRAFT: held, waiting for a release or a recompose"
+ enum:
+ - DRAFT
+ - PUSHED
+ - PUSH_FAILED
+ - MANUAL
+ - PAID
+ - PAYMENT_FAILED
+ - UNCOLLECTIBLE
+ - VOID
+ type: string
+ subtotal:
+ description: Sum of the lines, in minor units
+ format: int64
+ type: integer
+ total:
+ description: subtotal − discountTotal, in minor units
+ format: int64
+ type: integer
+ updatedAt:
+ format: date-time
+ type: string
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
+ type: string
+ required:
+ - externalInvoiceId
+ - id
+ - kind
+ - boundaryAt
+ - serviceFrom
+ - serviceTo
+ - status
+ - holdReason
+ - providerKind
+ - collectionMethod
+ - currency
+ - subtotal
+ - discountTotal
+ - total
+ - issuedAt
+ - daysUntilDue
+ - dueAt
+ - paidAt
+ - customerSlug
+ - customerName
+ - instanceSlug
+ - instanceName
+ - licenseSlug
+ - handoffStatus
+ - overdue
+ - createdAt
+ - updatedAt
+ type: object
+ x-kaiten-payload: "#/components/schemas/PushedInvoice"
+ QueuedInvoice:
+ additionalProperties: false
+ properties:
+ boundaryAt:
+ description: The boundary the invoice bills. With the kind, it identifies the invoice within its subscription
+ format: date-time
+ type: string
+ collectionMethod:
+ enum:
+ - SEND_INVOICE
+ - CHARGE_AUTOMATICALLY
+ type: string
+ createdAt:
+ format: date-time
+ type: string
+ currency:
+ examples:
+ - EUR
+ type: string
+ customerName:
+ type: string
+ customerSlug:
+ description: As it was when the invoice was composed
+ type: string
+ daysUntilDue:
+ format: int32
+ type:
+ - integer
+ - "null"
+ discountTotal:
+ description: Sum of the discounts, in minor units
+ format: int64
+ type: integer
+ dueAt:
+ description: issuedAt + daysUntilDue
+ format: date-time
+ type:
+ - string
+ - "null"
+ handoff:
+ $ref: "#/components/schemas/InvoiceHandoff"
+ handoffStatus:
+ description: "PENDING: waiting in the handoff queue for the organization's accounting system"
+ enum:
+ - NOT_REQUIRED
+ - PENDING
+ - ACKNOWLEDGED
+ type: string
+ holdReason:
+ description: "Why a DRAFT is held: the usage journal it was measured from failed a consistency check (LEDGER_*), or its provider cannot take that many lines (PROVIDER_ITEM_LIMIT)"
+ enum:
+ - LEDGER_SEQUENCE_GAP
+ - LEDGER_CHAIN_BREAK
+ - LEDGER_COUNTER_MISMATCH
+ - PROVIDER_ITEM_LIMIT
+ - null
+ type:
+ - string
+ - "null"
+ id:
+ description: "The invoice's identifier: the key an accounting system deduplicates on"
+ type: string
+ instanceName:
+ type: string
+ instanceSlug:
+ type: string
+ issuedAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ kind:
+ description: ACTIVATION bills a subscription's first period, RENEWAL the period that ended and the one that starts, FINAL what is left when a subscription ends
+ enum:
+ - ACTIVATION
+ - RENEWAL
+ - FINAL
+ type: string
+ licenseSlug:
+ type: string
+ overdue:
+ description: "Whether the invoice is overdue when read, the grace period of automatic collection included: the predicate of ?overdue=true. Computed by the invoice list and the invoice read; false in the answers of writes and in event payloads."
+ type: boolean
+ paidAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ providerKind:
+ description: "Who collects it. NOOP: the organization itself, through the handoff queue"
+ enum:
+ - NOOP
+ - STRIPE
+ type: string
+ serviceFrom:
+ description: Earliest start of its lines' service periods
+ format: date-time
+ type: string
+ serviceTo:
+ description: Latest end of its lines' service periods
+ format: date-time
+ type: string
+ status:
+ description: "MANUAL: issued, for the organization to collect. PAID, UNCOLLECTIBLE and VOID are final. DRAFT: held, waiting for a release or a recompose"
+ enum:
+ - DRAFT
+ - PUSHED
+ - PUSH_FAILED
+ - MANUAL
+ - PAID
+ - PAYMENT_FAILED
+ - UNCOLLECTIBLE
+ - VOID
+ type: string
+ subtotal:
+ description: Sum of the lines, in minor units
+ format: int64
+ type: integer
+ total:
+ description: subtotal − discountTotal, in minor units
+ format: int64
+ type: integer
+ updatedAt:
+ format: date-time
+ type: string
+ required:
+ - handoff
+ - id
+ - kind
+ - boundaryAt
+ - serviceFrom
+ - serviceTo
+ - status
+ - holdReason
+ - providerKind
+ - collectionMethod
+ - currency
+ - subtotal
+ - discountTotal
+ - total
+ - issuedAt
+ - daysUntilDue
+ - dueAt
+ - paidAt
+ - customerSlug
+ - customerName
+ - instanceSlug
+ - instanceName
+ - licenseSlug
+ - handoffStatus
+ - overdue
+ - createdAt
+ - updatedAt
+ type: object
+ ReadSelection:
+ additionalProperties: false
+ properties:
+ all:
+ description: Mark every notification read, up to now
+ type: boolean
+ ids:
+ description: Notifications to mark read
+ items:
+ type: string
+ maxItems: 200
+ type:
+ - array
+ - "null"
+ type: object
+ Reconciliation:
+ additionalProperties: false
+ properties:
+ discounts:
+ description: Allocations of DISCOUNT lines the provider applied with another amount, or not at all
+ items:
+ $ref: "#/components/schemas/DiscountDifference"
+ type: array
+ dueDate:
+ description: A SEND_INVOICE invoice the provider gives another due date than Kaiten's dueAt
+ oneOf:
+ - $ref: "#/components/schemas/DueDateDifference"
+ - enum:
+ - null
+ type: "null"
+ extraDiscounts:
+ description: Discounts the provider applied that Kaiten did not create, such as a coupon added in its dashboard
+ items:
+ $ref: "#/components/schemas/ExtraDiscount"
+ type: array
+ extraInProvider:
+ description: Provider lines Kaiten does not have
+ items:
+ type: string
+ type: array
+ inclusiveTax:
+ description: Whether the provider's subtotal was compared, its tax being included in the amounts
+ type: boolean
+ lines:
+ items:
+ $ref: "#/components/schemas/LineDifference"
+ type: array
+ missingInProvider:
+ description: Kaiten lines the provider does not have
+ items:
+ type: string
+ type: array
+ totals:
+ $ref: "#/components/schemas/TotalsDifference"
+ warnings:
+ description: What the comparison noticed without calling it a mismatch (§12.7, V31-60)
+ items:
+ type: string
+ type:
+ - array
+ - "null"
+ required:
+ - lines
+ - missingInProvider
+ - extraInProvider
+ - discounts
+ - extraDiscounts
+ - totals
+ - inclusiveTax
+ - dueDate
+ type: object
+ RedeemableCode:
+ additionalProperties: false
+ properties:
+ code:
+ examples:
+ - SUMMER-2026-LAUNCH
+ maxLength: 64
+ minLength: 1
+ type: string
+ required:
+ - code
+ type: object
+ Redemption:
+ additionalProperties: false
+ properties:
+ applicationsCount:
+ description: Invoices a PRICE voucher discounted
+ format: int32
+ type: integer
+ applicationsMax:
+ description: 1 for ONE_TIME, durationInPeriods for REPEATING; null for FOREVER
+ format: int32
+ type:
+ - integer
+ - "null"
+ codeHint:
+ type: string
+ effectiveExpiresAt:
+ description: When a boost stops applying; null for a PRICE voucher, counted in invoices, and for FOREVER
+ format: date-time
+ type:
+ - string
+ - "null"
+ effectiveStartsAt:
+ format: date-time
+ type: string
+ expiredAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ id:
+ readOnly: true
+ type: string
+ instanceSlug:
+ type: string
+ redeemedAt:
+ format: date-time
+ type: string
+ revokedAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ revokedReason:
+ type:
+ - string
+ - "null"
+ status:
+ enum:
+ - ACTIVE
+ - EXPIRED
+ - REVOKED
+ type: string
+ voucherId:
+ type: string
+ voucherName:
+ type: string
+ voucherType:
+ enum:
+ - PRICE
+ - ENTITLEMENT_BOOST
+ - FLAG_GRANT
+ - COMPOSITE
+ type: string
+ required:
+ - id
+ - voucherId
+ - voucherName
+ - voucherType
+ - codeHint
+ - instanceSlug
+ - redeemedAt
+ - effectiveStartsAt
+ - effectiveExpiresAt
+ - applicationsCount
+ - applicationsMax
+ - status
+ - expiredAt
+ - revokedAt
+ - revokedReason
+ type: object
+ RedemptionDelivered:
+ additionalProperties: false
+ properties:
+ applicationsCount:
+ description: Invoices a PRICE voucher discounted
+ format: int32
+ type: integer
+ applicationsMax:
+ description: 1 for ONE_TIME, durationInPeriods for REPEATING; null for FOREVER
+ format: int32
+ type:
+ - integer
+ - "null"
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
+ type: string
+ codeHint:
+ type: string
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
+ type: string
+ effectiveExpiresAt:
+ description: When a boost stops applying; null for a PRICE voucher, counted in invoices, and for FOREVER
+ format: date-time
+ type:
+ - string
+ - "null"
+ effectiveStartsAt:
+ format: date-time
+ type: string
+ expiredAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ id:
+ readOnly: true
+ type: string
+ instanceSlug:
+ type: string
+ redeemedAt:
+ format: date-time
+ type: string
+ revokedAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ revokedReason:
+ type:
+ - string
+ - "null"
+ status:
+ enum:
+ - ACTIVE
+ - EXPIRED
+ - REVOKED
+ type: string
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
+ type: string
+ voucherId:
+ type: string
+ voucherName:
+ type: string
+ voucherType:
+ enum:
+ - PRICE
+ - ENTITLEMENT_BOOST
+ - FLAG_GRANT
+ - COMPOSITE
+ type: string
+ required:
+ - id
+ - voucherId
+ - voucherName
+ - voucherType
+ - codeHint
+ - instanceSlug
+ - redeemedAt
+ - effectiveStartsAt
+ - effectiveExpiresAt
+ - applicationsCount
+ - applicationsMax
+ - status
+ - expiredAt
+ - revokedAt
+ - revokedReason
+ type: object
+ x-kaiten-payload: "#/components/schemas/Redemption"
+ RedemptionRules:
+ additionalProperties: false
+ properties:
+ annualOnly:
+ description: Only for an instance with a live ANNUAL subscription
+ type: boolean
+ firstTimeOnly:
+ description: Only for a customer none of whose instances has paid an invoice
+ type: boolean
+ minimumSubscriptionAmount:
+ $ref: "#/components/schemas/MinimumAmount"
+ description: Only for a live subscription whose base price is at least this. Strictly positive; omit it for no minimum (0 is refused with .InvalidRedemptionRules).
+ type: object
+ Release:
+ additionalProperties: false
+ properties:
+ componentIds:
+ description: List of component IDs to link to this release
+ examples:
+ - - 123e4567-e89b-12d3-a456-426614174000
+ items:
+ type: string
+ type:
+ - array
+ - "null"
+ writeOnly: true
+ components:
+ description: List of components included in this release, fixed at creation
+ items:
+ $ref: "#/components/schemas/Component"
+ readOnly: true
+ type:
+ - array
+ - "null"
+ createdAt:
+ description: Timestamp when the release was created
+ examples:
+ - "2023-10-01T12:00:00Z"
+ format: date-time
+ readOnly: true
+ type: string
+ createdBy:
+ $ref: "#/components/schemas/User"
+ description: User who created this release
+ readOnly: true
+ description:
+ description: Description of the release
+ examples:
+ - "This release fixes everything. CHANGELOG: stuff was done"
+ maxLength: 500
+ type: string
+ id:
+ description: Unique identifier for the release
+ examples:
+ - 123e4567-e89b-12d3-a456-426614174000
+ readOnly: true
+ type: string
+ slug:
+ description: Optional URL-friendly identifier, unique per organization. Auto-generated if not provided.
+ examples:
+ - v1-0-0
+ maxLength: 100
+ minLength: 2
+ pattern: ^[a-z0-9][a-z0-9-]*[a-z0-9]$
+ type: string
+ version:
+ description: Version of the release
+ examples:
+ - v1.0.0
+ maxLength: 100
+ minLength: 1
+ type: string
+ required:
+ - id
+ - version
+ - createdBy
+ - createdAt
+ type: object
+ ReleaseDelivered:
+ additionalProperties: false
+ properties:
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
+ type: string
+ componentIds:
+ description: List of component IDs to link to this release
+ examples:
+ - - 123e4567-e89b-12d3-a456-426614174000
+ items:
+ type: string
+ type:
+ - array
+ - "null"
+ writeOnly: true
+ components:
+ description: List of components included in this release, fixed at creation
+ items:
+ $ref: "#/components/schemas/Component"
+ readOnly: true
+ type:
+ - array
+ - "null"
+ createdAt:
+ description: Timestamp when the release was created
+ examples:
+ - "2023-10-01T12:00:00Z"
+ format: date-time
+ readOnly: true
+ type: string
+ createdBy:
+ $ref: "#/components/schemas/User"
+ description: User who created this release
+ readOnly: true
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
+ type: string
+ description:
+ description: Description of the release
+ examples:
+ - "This release fixes everything. CHANGELOG: stuff was done"
+ maxLength: 500
+ type: string
+ id:
+ description: Unique identifier for the release
+ examples:
+ - 123e4567-e89b-12d3-a456-426614174000
+ readOnly: true
+ type: string
+ slug:
+ description: Optional URL-friendly identifier, unique per organization. Auto-generated if not provided.
+ examples:
+ - v1-0-0
+ maxLength: 100
+ minLength: 2
+ pattern: ^[a-z0-9][a-z0-9-]*[a-z0-9]$
+ type: string
+ version:
+ description: Version of the release
+ examples:
+ - v1.0.0
+ maxLength: 100
+ minLength: 1
+ type: string
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
+ type: string
+ required:
+ - id
+ - version
+ - createdBy
+ - createdAt
+ type: object
+ x-kaiten-payload: "#/components/schemas/Release"
+ ReleasedInvoiceDelivered:
+ additionalProperties: false
+ properties:
+ boundaryAt:
+ description: The boundary the invoice bills. With the kind, it identifies the invoice within its subscription
+ format: date-time
+ type: string
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
+ type: string
+ collectionMethod:
+ enum:
+ - SEND_INVOICE
+ - CHARGE_AUTOMATICALLY
+ type: string
+ createdAt:
+ format: date-time
+ type: string
+ currency:
+ examples:
+ - EUR
+ type: string
+ customerName:
+ type: string
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
+ type: string
+ customerSlug:
+ description: As it was when the invoice was composed
+ type: string
+ daysUntilDue:
+ format: int32
+ type:
+ - integer
+ - "null"
+ discountTotal:
+ description: Sum of the discounts, in minor units
+ format: int64
+ type: integer
+ dueAt:
+ description: issuedAt + daysUntilDue
+ format: date-time
+ type:
+ - string
+ - "null"
+ handoffStatus:
+ description: "PENDING: waiting in the handoff queue for the organization's accounting system"
+ enum:
+ - NOT_REQUIRED
+ - PENDING
+ - ACKNOWLEDGED
+ type: string
+ holdDetail:
+ $ref: "#/components/schemas/HoldDetail"
+ description: The hold it left, which the invoice no longer shows
+ holdReason:
+ description: "Why a DRAFT is held: the usage journal it was measured from failed a consistency check (LEDGER_*), or its provider cannot take that many lines (PROVIDER_ITEM_LIMIT)"
+ enum:
+ - LEDGER_SEQUENCE_GAP
+ - LEDGER_CHAIN_BREAK
+ - LEDGER_COUNTER_MISMATCH
+ - PROVIDER_ITEM_LIMIT
+ - null
+ type:
+ - string
+ - "null"
+ id:
+ description: "The invoice's identifier: the key an accounting system deduplicates on"
+ type: string
+ instanceName:
+ type: string
+ instanceSlug:
+ type: string
+ issuedAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ kind:
+ description: ACTIVATION bills a subscription's first period, RENEWAL the period that ended and the one that starts, FINAL what is left when a subscription ends
+ enum:
+ - ACTIVATION
+ - RENEWAL
+ - FINAL
+ type: string
+ licenseSlug:
+ type: string
+ overdue:
+ description: "Whether the invoice is overdue when read, the grace period of automatic collection included: the predicate of ?overdue=true. Computed by the invoice list and the invoice read; false in the answers of writes and in event payloads."
+ type: boolean
+ paidAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ providerKind:
+ description: "Who collects it. NOOP: the organization itself, through the handoff queue"
+ enum:
+ - NOOP
+ - STRIPE
+ type: string
+ releaseReason:
+ type: string
+ releasedBy:
+ description: The user who released it, or system when a later check found the journal sound
+ type: string
+ serviceFrom:
+ description: Earliest start of its lines' service periods
+ format: date-time
+ type: string
+ serviceTo:
+ description: Latest end of its lines' service periods
+ format: date-time
+ type: string
+ status:
+ description: "MANUAL: issued, for the organization to collect. PAID, UNCOLLECTIBLE and VOID are final. DRAFT: held, waiting for a release or a recompose"
+ enum:
+ - DRAFT
+ - PUSHED
+ - PUSH_FAILED
+ - MANUAL
+ - PAID
+ - PAYMENT_FAILED
+ - UNCOLLECTIBLE
+ - VOID
+ type: string
+ subtotal:
+ description: Sum of the lines, in minor units
+ format: int64
+ type: integer
+ total:
+ description: subtotal − discountTotal, in minor units
+ format: int64
+ type: integer
+ updatedAt:
+ format: date-time
+ type: string
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
+ type: string
+ required:
+ - releaseReason
+ - releasedBy
+ - holdDetail
+ - id
+ - kind
+ - boundaryAt
+ - serviceFrom
+ - serviceTo
+ - status
+ - holdReason
+ - providerKind
+ - collectionMethod
+ - currency
+ - subtotal
+ - discountTotal
+ - total
+ - issuedAt
+ - daysUntilDue
+ - dueAt
+ - paidAt
+ - customerSlug
+ - customerName
+ - instanceSlug
+ - instanceName
+ - licenseSlug
+ - handoffStatus
+ - overdue
+ - createdAt
+ - updatedAt
+ type: object
+ x-kaiten-payload: "#/components/schemas/ReleasedInvoice"
+ ReorderMetadataFieldsInput:
+ additionalProperties: false
+ properties:
+ ids:
+ items:
+ type: string
+ type:
+ - array
+ - "null"
+ required:
+ - ids
+ type: object
+ ReportEntitlementUsageBody:
+ additionalProperties: false
+ properties:
+ behavior:
+ description: "Report behavior: append folds the value into the stored total through the aggregation method; set overwrites it"
+ enum:
+ - append
+ - set
+ type: string
+ metadata:
+ additionalProperties: {}
+ description: "Optional metadata for the usage report, a JSON object stored with it in the usage history when its compact encoding is at most 4 KiB. Above that it is not stored, the report is still counted, and the response carries Kaiten-Metadata-Dropped: too_large. It must contain no personal data: anyone who can read the organization's instances can read it, for as long as the usage history is kept. Numbers are read as 64-bit floats, so send large identifiers as strings."
+ type: object
+ value:
+ description: Reported entitlement value, discriminated by the 'type' field. Usage reporting accepts the number variant only.
+ discriminator:
+ mapping:
+ boolean: "#/components/schemas/BooleanEntitlementValue"
+ number: "#/components/schemas/NumberEntitlementValue"
+ object: "#/components/schemas/ConfigEntitlementValue"
+ propertyName: type
+ oneOf:
+ - $ref: "#/components/schemas/NumberEntitlementValue"
+ - $ref: "#/components/schemas/BooleanEntitlementValue"
+ - $ref: "#/components/schemas/ConfigEntitlementValue"
+ required:
+ - value
+ type: object
+ ReportedEntitlementUsage:
+ additionalProperties: false
+ properties:
+ currentPeriodEnd:
+ description: End of the current usage window (exclusive). Null for a lifetime entitlement (no configured reset period).
+ examples:
+ - "2026-04-01T00:00:00Z"
+ format: date-time
+ type: string
+ currentPeriodStart:
+ description: Start of the current usage window (inclusive). Null for a lifetime entitlement (no configured reset period).
+ examples:
+ - "2026-03-01T00:00:00Z"
+ format: date-time
+ type: string
+ entitlementId:
+ description: Unique identifier for the entitlement
+ examples:
+ - 123e4567-e89b-12d3-a456-426614174000
+ type: string
+ entitlementSlug:
+ description: URL-friendly identifier of the entitlement
+ examples:
+ - premium-support
+ type: string
+ licenseId:
+ description: Unique identifier for the license to which this entitlement usage belongs
+ examples:
+ - 123e4567-e89b-12d3-a456-426614174001
+ type: string
+ licenseSlug:
+ description: URL-friendly identifier of the license
+ examples:
+ - enterprise-license
+ type: string
+ limit:
+ description: The effective value this usage is measured against (the licence's grant, with the instance's add-ons and boosts applied; source and provenance say which), discriminated by the 'type' field. Absent when nothing grants the entitlement a value; -1 means unlimited for a NUMBER entitlement.
+ discriminator:
+ mapping:
+ boolean: "#/components/schemas/BooleanEntitlementValue"
+ number: "#/components/schemas/NumberEntitlementValue"
+ object: "#/components/schemas/ConfigEntitlementValue"
+ propertyName: type
+ oneOf:
+ - $ref: "#/components/schemas/NumberEntitlementValue"
+ - $ref: "#/components/schemas/BooleanEntitlementValue"
+ - $ref: "#/components/schemas/ConfigEntitlementValue"
+ limitCapExceededOveragePercent:
+ description: "How far above limit usage is still accepted, in percent: -1 unlimited, 0 a hard limit. Absent for BOOLEAN and CONFIG."
+ format: int32
+ type: integer
+ provenance:
+ description: "What the licence, the add-ons and the boosts each contribute. Null on a replay (Idempotent-Replayed): the journal does not store it."
+ oneOf:
+ - $ref: "#/components/schemas/Provenance"
+ - enum:
+ - null
+ type: "null"
+ source:
+ description: "license: the licence grants it (add-ons, boosts and an exception may change it); addon: an add-on attached does; exception: only the instance's exception does. Null on a replay (Idempotent-Replayed): the journal does not store it."
+ enum:
+ - license
+ - addon
+ - exception
+ - null
+ type:
+ - string
+ - "null"
+ transactionId:
+ description: The Idempotency-Key this report was accepted under, echoed on the first answer and on every replay; null when the request carried none. A client can tell from it that the server honours the key.
+ type:
+ - string
+ - "null"
+ value:
+ description: Resolved entitlement value, discriminated by the 'type' field
+ discriminator:
+ mapping:
+ boolean: "#/components/schemas/BooleanEntitlementValue"
+ number: "#/components/schemas/NumberEntitlementValue"
+ object: "#/components/schemas/ConfigEntitlementValue"
+ propertyName: type
+ oneOf:
+ - $ref: "#/components/schemas/NumberEntitlementValue"
+ - $ref: "#/components/schemas/BooleanEntitlementValue"
+ - $ref: "#/components/schemas/ConfigEntitlementValue"
+ required:
+ - source
+ - provenance
+ - transactionId
+ - entitlementId
+ - entitlementSlug
+ - licenseId
+ - licenseSlug
+ - value
+ type: object
+ RevocationReason:
+ additionalProperties: false
+ properties:
+ reason:
+ description: 1 to 500 characters; .ReasonRequired otherwise
+ examples:
+ - Granted by mistake
+ maxLength: 500
+ minLength: 1
+ type: string
+ required:
+ - reason
+ type: object
+ Role:
+ additionalProperties: false
+ properties:
+ builtIn:
+ type: boolean
+ description:
+ type: string
+ isAdministrator:
+ type: boolean
+ name:
+ type: string
+ permissions:
+ items:
+ type: string
+ type: array
+ slug:
+ type: string
+ required:
+ - slug
+ - name
+ - description
+ - permissions
+ - builtIn
+ - isAdministrator
+ type: object
+ RoleDefinition:
+ additionalProperties: false
+ properties:
+ description:
+ maxLength: 1000
+ type: string
+ name:
+ maxLength: 200
+ minLength: 1
+ type: string
+ permissions:
+ items:
+ type: string
+ type: array
+ required:
+ - name
+ - description
+ - permissions
+ type: object
+ RolloutDate:
+ additionalProperties: false
+ properties:
+ end:
+ $ref: "#/components/schemas/RolloutStep"
+ start:
+ $ref: "#/components/schemas/RolloutStep"
+ type:
+ enum:
+ - rollout_date
+ type: string
+ required:
+ - type
+ - start
+ - end
+ type: object
+ RolloutDateTargeting:
+ additionalProperties: false
+ properties:
+ end:
+ $ref: "#/components/schemas/RolloutStep"
+ examples:
+ - date: "2024-01-31T23:59:59Z"
+ percentage: 100
+ variant: variant_a
+ name:
+ examples:
+ - Targeting Rule 1
+ type: string
+ rule:
+ description: CEL expression for basicTargeting evaluation
+ examples:
+ - user.group == 'beta'
+ maxLength: 10000
+ type: string
+ start:
+ $ref: "#/components/schemas/RolloutStep"
+ examples:
+ - date: "2024-01-01T00:00:00Z"
+ percentage: 0
+ variant: variant_a
+ type:
+ enum:
+ - rollout_date
+ type: string
+ required:
+ - type
+ - name
+ - rule
+ - start
+ - end
+ type: object
+ RolloutPercentage:
+ additionalProperties: false
+ properties:
+ distribution:
+ additionalProperties:
+ format: int64
+ type: integer
+ type: object
+ type:
+ enum:
+ - rollout_percentage
+ type: string
+ required:
+ - type
+ - distribution
+ type: object
+ RolloutPercentageTargeting:
+ additionalProperties: false
+ properties:
+ distribution:
+ additionalProperties:
+ format: int64
+ type: integer
+ examples:
+ - variant_a: 70
+ variant_b: 30
+ type: object
+ name:
+ examples:
+ - Targeting Rule 1234
+ type: string
+ rule:
+ description: CEL expression for basicTargeting evaluation
+ examples:
+ - user.group == 'beta'
+ maxLength: 10000
+ type: string
+ type:
+ enum:
+ - rollout_percentage
+ type: string
+ required:
+ - type
+ - name
+ - rule
+ - distribution
+ type: object
+ RolloutStep:
+ additionalProperties: false
+ properties:
+ date:
+ examples:
+ - "2024-01-15T10:00:00Z"
+ format: date-time
+ type: string
+ percentage:
+ examples:
+ - 50
+ format: int64
+ type: integer
+ variant:
+ examples:
+ - variant_b
+ type: string
+ required:
+ - variant
+ - percentage
+ - date
+ type: object
+ SampleUsageAmount:
+ additionalProperties: false
+ properties:
+ entitlementSlug:
+ description: An entitlement an ACTIVE price of the version meters
+ examples:
+ - tokens
+ type: string
+ quantity:
+ description: "Usage in the entitlement's measured units, a non-negative decimal string. Rated as one reset window against the version's grant: usage above limit × (1 + overage percent / 100) would be rejected, so it is capped, and the line says capped."
+ examples:
+ - "130500"
+ type: string
+ required:
+ - entitlementSlug
+ - quantity
+ type: object
+ ScheduledChange:
+ additionalProperties: false
+ properties:
+ effectiveAt:
+ description: "The boundary it applies at: the current period's end"
+ format: date-time
+ type: string
+ licenseSlug:
+ description: The licence version of that price
+ type: string
+ price:
+ $ref: "#/components/schemas/Price"
+ description: The FLAT_FEE price the subscription moves to
+ scheduledAt:
+ format: date-time
+ type: string
+ required:
+ - price
+ - licenseSlug
+ - scheduledAt
+ - effectiveAt
+ type: object
+ ServiceAccount:
+ additionalProperties: false
+ properties:
+ createdAt:
+ format: date-time
+ readOnly: true
+ type: string
+ createdBy:
+ $ref: "#/components/schemas/User"
+ readOnly: true
+ externalId:
+ readOnly: true
+ type: string
+ id:
+ readOnly: true
+ type: string
+ name:
+ minLength: 1
+ type: string
+ slug:
+ description: Optional URL-friendly identifier, unique per organization. Auto-generated if not provided.
+ maxLength: 100
+ minLength: 2
+ pattern: ^[a-z0-9][a-z0-9-]*[a-z0-9]$
+ type: string
+ tokens:
+ items:
+ $ref: "#/components/schemas/Token"
+ readOnly: true
+ type:
+ - array
+ - "null"
+ required:
+ - name
+ - tokens
+ type: object
+ SessionAddon:
+ additionalProperties: false
+ properties:
+ addonSlug:
+ examples:
+ - extra-seats-v1
+ type: string
+ familySlug:
+ examples:
+ - extra-seats
+ type: string
+ maxQuantity:
+ description: The most one instance may hold; null when unbounded
+ format: int32
+ type:
+ - integer
+ - "null"
+ name:
+ examples:
+ - Extra seats
+ type: string
+ pendingQuantity:
+ description: A decrease scheduled for the next renewal, 0 for a removal; null when nothing is scheduled
+ format: int32
+ type:
+ - integer
+ - "null"
+ prices:
+ description: "What it is billed: the flat fee of the subscription's period and the metered prices. Quantity changes are billed from the next renewal, with no proration."
+ items:
+ $ref: "#/components/schemas/PublicPrice"
+ type: array
+ quantity:
+ description: 0 when the add-on is not held
+ examples:
+ - 3
+ format: int32
+ type: integer
+ required:
+ - addonSlug
+ - familySlug
+ - name
+ - quantity
+ - maxQuantity
+ - pendingQuantity
+ - prices
+ type: object
+ SessionAddonQuantity:
+ additionalProperties: false
+ properties:
+ quantity:
+ description: 0 removes the add-on; otherwise at most its maxQuantity
+ examples:
+ - 3
+ format: int32
+ minimum: 0
+ type: integer
+ required:
+ - quantity
+ type: object
+ SessionAddonSelection:
+ additionalProperties: false
+ properties:
+ addonSlug:
+ type: string
+ quantity:
+ format: int32
+ type: integer
+ required:
+ - addonSlug
+ - quantity
+ type: object
+ SessionBoost:
+ additionalProperties: false
+ properties:
+ entitlementSlug:
+ examples:
+ - tokens
+ type: string
+ modifierType:
+ enum:
+ - SET
+ - ADD
+ - MULTIPLY
+ - UNLIMITED
+ type: string
+ modifierValue:
+ description: Absent for UNLIMITED, and on a BOOLEAN or CONFIG boost
+ examples:
+ - "2"
+ type: string
+ value:
+ anyOf:
+ - type: boolean
+ - additionalProperties: true
+ type: object
+ - type: "null"
+ description: "A BOOLEAN or CONFIG boost's value: true or false, or the document"
+ required:
+ - entitlementSlug
+ - modifierType
+ type: object
+ SessionCancellation:
+ additionalProperties: false
+ properties:
+ reason:
+ description: Why the customer cancels, up to 500 characters; the vendor sees it on the subscription
+ type: string
+ type: object
+ SessionCheckout:
+ additionalProperties: false
+ properties:
+ activationInvoice:
+ $ref: "#/components/schemas/SessionInvoice"
+ description: "subscribed: the invoice of the first period; absent during a trial"
+ dueToday:
+ $ref: "#/components/schemas/DueToday"
+ description: "preview: what the checkout bills today; zero, with no lines, during a trial"
+ expiresAt:
+ description: "requires_payment_method: when the setup page expires"
+ format: date-time
+ type: string
+ payment:
+ $ref: "#/components/schemas/Payment"
+ description: "subscribed: how paying the activation invoice went"
+ setupSessionId:
+ description: "requires_payment_method: send it back as setupSessionId once the customer returns"
+ type: string
+ setupUrl:
+ description: "requires_payment_method: the provider's page where the customer saves a payment method"
+ type: string
+ status:
+ enum:
+ - preview
+ - requires_payment_method
+ - subscribed
+ type: string
+ subscription:
+ $ref: "#/components/schemas/SessionSubscription"
+ description: "subscribed: the subscription started"
+ trialEndsAt:
+ description: "preview: when the trial would end, if there is one"
+ format: date-time
+ type: string
+ required:
+ - status
+ type: object
+ SessionCheckoutOrder:
+ additionalProperties: false
+ properties:
+ addOns:
+ description: Public add-ons that fit the plan
+ items:
+ $ref: "#/components/schemas/SubscriptionAddon"
+ maxItems: 50
+ type:
+ - array
+ - "null"
+ billingEmail:
+ description: Where invoices go. Required when the customer has none, except on a dryRun; stored on the customer when given.
+ maxLength: 254
+ type: string
+ dryRun:
+ description: Answer what the checkout would bill today, and change nothing
+ type: boolean
+ licensePriceId:
+ description: An ACTIVE flat-fee price of a plan in the public catalogue. The instance is moved to that price's version when it is on another one.
+ type: string
+ returnUrl:
+ description: Where the setup page sends the customer back, with kaiten_setup_session appended. Required when a payment method has to be captured. Its origin must be one the organization's publishable keys allow.
+ format: uri
+ type: string
+ setupSessionId:
+ description: "The setupSessionId a requires_payment_method answer gave, once the customer is back from the setup page: the payment method saved there is checked with the provider and used."
+ type: string
+ trial:
+ description: "false skips the plan's trial. Omitted: the plan's trial, if it has one."
+ type: boolean
+ voucherCode:
+ description: Validated and redeemed in the checkout; any refusal answers .VoucherInvalid
+ maxLength: 64
+ minLength: 1
+ type: string
+ required:
+ - licensePriceId
+ type: object
+ SessionDiscount:
+ additionalProperties: false
+ properties:
+ appliesTo:
+ enum:
+ - LICENSE_BASE
+ - ADDONS
+ - BOTH
+ - SELECTED_PRICES
+ type: string
+ currency:
+ description: The currency of a FIXED_AMOUNT discount
+ examples:
+ - EUR
+ type: string
+ type:
+ enum:
+ - PERCENTAGE
+ - FIXED_AMOUNT
+ type: string
+ value:
+ description: A percentage in (0, 100], or an amount in minor units of currency
+ examples:
+ - "30"
+ type: string
+ required:
+ - type
+ - value
+ - appliesTo
+ type: object
+ SessionEventDelivered:
+ additionalProperties: false
+ properties:
+ billingPeriod:
+ enum:
+ - MONTHLY
+ - QUARTERLY
+ - SEMI_ANNUAL
+ - ANNUAL
+ type: string
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
+ type: string
+ clientReferenceId:
+ description: The vendor's opaque reference, as given at the start
+ type:
+ - string
+ - "null"
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
+ type: string
+ customerSlug:
+ description: The customer the completion created; null before
+ type:
+ - string
+ - "null"
+ email:
+ description: The address the visitor gave at the start
+ type: string
+ instanceSlug:
+ description: The instance the completion created; null before
+ type:
+ - string
+ - "null"
+ licenseSlug:
+ type: string
+ sessionId:
+ type: string
+ status:
+ description: OPEN on started, COMPLETED on completed; on abandoned, ABANDONED (never completed, slugs null) or EXPIRED (its INCOMPLETE subscription expired or was canceled unpaid, slugs set)
+ enum:
+ - OPEN
+ - COMPLETED
+ - ABANDONED
+ - EXPIRED
+ type: string
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
+ type: string
+ required:
+ - sessionId
+ - email
+ - clientReferenceId
+ - licenseSlug
+ - billingPeriod
+ - customerSlug
+ - instanceSlug
+ - status
+ type: object
+ x-kaiten-payload: "#/components/schemas/SessionEvent"
+ SessionInvoice:
+ additionalProperties: false
+ properties:
+ currency:
+ examples:
+ - EUR
+ type: string
+ discountTotal:
+ description: Minor units
+ format: int64
+ type: integer
+ dueAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ externalReference:
+ description: The vendor's own number for an invoice it collects itself
+ type:
+ - string
+ - "null"
+ hostedInvoiceUrl:
+ description: Where the customer pays the invoice, or confirms a payment that needs authenticating
+ type:
+ - string
+ - "null"
+ id:
+ format: uuid
+ type: string
+ invoicePdfUrl:
+ type:
+ - string
+ - "null"
+ issuedAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ kind:
+ enum:
+ - ACTIVATION
+ - RENEWAL
+ - FINAL
+ type: string
+ lines:
+ items:
+ $ref: "#/components/schemas/SessionLine"
+ type: array
+ paidAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ providerInvoiceNumber:
+ type:
+ - string
+ - "null"
+ serviceFrom:
+ format: date-time
+ type: string
+ serviceTo:
+ format: date-time
+ type: string
+ status:
+ description: "PUSHED: issued and awaiting payment. PAYMENT_FAILED: a charge failed; pay on hostedInvoiceUrl. MANUAL: issued, the vendor collects it."
+ enum:
+ - MANUAL
+ - PUSHED
+ - PAID
+ - PAYMENT_FAILED
+ - UNCOLLECTIBLE
+ - VOID
+ type: string
+ subtotal:
+ description: Minor units
+ format: int64
+ type: integer
+ total:
+ description: Minor units
+ format: int64
+ type: integer
+ required:
+ - id
+ - kind
+ - status
+ - currency
+ - subtotal
+ - discountTotal
+ - total
+ - issuedAt
+ - dueAt
+ - paidAt
+ - serviceFrom
+ - serviceTo
+ - providerInvoiceNumber
+ - externalReference
+ - hostedInvoiceUrl
+ - invoicePdfUrl
+ - lines
+ type: object
+ SessionLine:
+ additionalProperties: false
+ properties:
+ amount:
+ description: Minor units; negative on a DISCOUNT line
+ format: int64
+ type: integer
+ description:
+ type: string
+ label:
+ type: string
+ quantity:
+ description: In sale units, a decimal string
+ type: string
+ seq:
+ format: int64
+ type: integer
+ serviceFrom:
+ format: date-time
+ type: string
+ serviceTo:
+ format: date-time
+ type: string
+ type:
+ enum:
+ - BASE
+ - ADDON
+ - USAGE
+ - OVERAGE
+ - DISCOUNT
+ type: string
+ unitAmountDecimal:
+ description: Minor units; null on a DISCOUNT line
+ type:
+ - string
+ - "null"
+ required:
+ - seq
+ - type
+ - label
+ - description
+ - quantity
+ - unitAmountDecimal
+ - amount
+ - serviceFrom
+ - serviceTo
+ type: object
+ SessionPortal:
+ additionalProperties: false
+ properties:
+ addOns:
+ description: The add-ons the instance holds
+ items:
+ $ref: "#/components/schemas/SessionAddon"
+ type: array
+ branding:
+ $ref: "#/components/schemas/PublicBranding"
+ capabilities:
+ $ref: "#/components/schemas/PortalCapabilities"
+ customer:
+ $ref: "#/components/schemas/PortalCustomer"
+ entitlements:
+ description: "The instance's effective entitlements: its licence's, raised or added by add-ons and boosts"
+ items:
+ $ref: "#/components/schemas/PortalEntitlement"
+ type: array
+ instance:
+ description: The session's instance; null for a session bound to the customer only
+ oneOf:
+ - $ref: "#/components/schemas/PortalInstance"
+ - enum:
+ - null
+ type: "null"
+ instances:
+ description: The customer's instances, for a session bound to the customer only; empty otherwise
+ items:
+ $ref: "#/components/schemas/PortalInstanceSummary"
+ type: array
+ paymentMethod:
+ description: The payment method the customer's invoices are charged to; null when none is saved
+ oneOf:
+ - $ref: "#/components/schemas/PortalPaymentMethod"
+ - enum:
+ - null
+ type: "null"
+ subscription:
+ description: The instance's subscription; null when it has none, or the session is bound to no instance
+ oneOf:
+ - $ref: "#/components/schemas/SessionSubscription"
+ - enum:
+ - null
+ type: "null"
+ trial:
+ description: "TRIAL only: when the trial ends, when its reminder is due, what the conversion would charge and to which card (V32-09); null otherwise"
+ oneOf:
+ - $ref: "#/components/schemas/PortalTrial"
+ - enum:
+ - null
+ type: "null"
+ upcomingInvoice:
+ description: What the next boundary will bill, on usage so far; null without a live subscription
+ oneOf:
+ - $ref: "#/components/schemas/PortalUpcomingInvoice"
+ - enum:
+ - null
+ type: "null"
+ updatedAt:
+ description: When this snapshot was read
+ format: date-time
+ type: string
+ vouchers:
+ description: The instance's ACTIVE redemptions. Never a code.
+ items:
+ $ref: "#/components/schemas/SessionRedemption"
+ type: array
+ required:
+ - customer
+ - instance
+ - instances
+ - subscription
+ - entitlements
+ - addOns
+ - vouchers
+ - paymentMethod
+ - upcomingInvoice
+ - trial
+ - capabilities
+ - branding
+ - updatedAt
+ type: object
+ SessionRedeem:
+ additionalProperties: false
+ properties:
+ code:
+ description: The code as the customer typed it; case and separators do not matter
+ examples:
+ - SUMMER-2026-LAUNCH
+ maxLength: 64
+ minLength: 1
+ type: string
+ dryRun:
+ description: Check, and answer the redemption the call would make, without making it; counts as one attempt
+ type: boolean
+ required:
+ - code
+ type: object
+ SessionRedemption:
+ additionalProperties: false
+ properties:
+ applicationsCount:
+ description: Invoices its PRICE part discounted; 0 without one
+ format: int32
+ type: integer
+ applicationsMax:
+ description: "How many it discounts in all: 1 for ONE_TIME, its periods for REPEATING; null for FOREVER and without a PRICE part"
+ format: int32
+ type:
+ - integer
+ - "null"
+ appliesFromInvoice:
+ description: "next: a PRICE part that has not discounted an invoice yet -- it applies from the next invoice composed, never to one already issued; null otherwise"
+ enum:
+ - next
+ - null
+ type:
+ - string
+ - "null"
+ effectiveExpiresAt:
+ description: When a boost or a flag grant stops applying; null when it does not expire, and for a PRICE voucher, counted in invoices
+ format: date-time
+ type:
+ - string
+ - "null"
+ effectiveStartsAt:
+ format: date-time
+ type: string
+ name:
+ examples:
+ - Summer launch
+ type: string
+ status:
+ enum:
+ - ACTIVE
+ - EXPIRED
+ - REVOKED
+ type: string
+ voucherType:
+ enum:
+ - PRICE
+ - ENTITLEMENT_BOOST
+ - FLAG_GRANT
+ - COMPOSITE
+ type: string
+ required:
+ - name
+ - voucherType
+ - status
+ - appliesFromInvoice
+ - applicationsCount
+ - applicationsMax
+ - effectiveStartsAt
+ - effectiveExpiresAt
+ type: object
+ SessionScheduledChange:
+ additionalProperties: false
+ properties:
+ effectiveAt:
+ description: "When it applies: the current period's end"
+ format: date-time
+ type: string
+ price:
+ $ref: "#/components/schemas/PublicPrice"
+ description: The price the subscription moves to
+ required:
+ - price
+ - effectiveAt
+ type: object
+ SessionSubscription:
+ additionalProperties: false
+ properties:
+ basePrice:
+ $ref: "#/components/schemas/PublicPrice"
+ billingPeriod:
+ enum:
+ - MONTHLY
+ - QUARTERLY
+ - SEMI_ANNUAL
+ - ANNUAL
+ type: string
+ cancelAtPeriodEnd:
+ description: The subscription ends at currentPeriodEnd; reactivate it before then to keep it
+ type: boolean
+ canceledAt:
+ description: When the subscription was canceled
+ format: date-time
+ type:
+ - string
+ - "null"
+ cancellationReason:
+ description: Why the system ended it; null when it did not, a vendor's own reason is never shown
+ enum:
+ - TRIAL_ENDED_WITHOUT_PAYMENT_METHOD
+ - INCOMPLETE_EXPIRED
+ - null
+ type:
+ - string
+ - "null"
+ collectionMethod:
+ enum:
+ - SEND_INVOICE
+ - CHARGE_AUTOMATICALLY
+ type: string
+ currency:
+ examples:
+ - EUR
+ type: string
+ currentPeriodEnd:
+ description: "When the current period ends: the renewal date, or the trial's end"
+ format: date-time
+ type: string
+ currentPeriodStart:
+ format: date-time
+ type: string
+ incompleteExpiresAt:
+ description: "INCOMPLETE only: when the checkout ends unpaid"
+ format: date-time
+ type:
+ - string
+ - "null"
+ providerKind:
+ enum:
+ - NOOP
+ - STRIPE
+ type: string
+ scheduledChange:
+ description: A plan change the vendor scheduled for the next boundary
+ oneOf:
+ - $ref: "#/components/schemas/SessionScheduledChange"
+ - enum:
+ - null
+ type: "null"
+ status:
+ enum:
+ - INCOMPLETE
+ - TRIAL
+ - ACTIVE
+ - PAST_DUE
+ - CANCELED
+ type: string
+ trialEndsAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ required:
+ - status
+ - providerKind
+ - collectionMethod
+ - currency
+ - billingPeriod
+ - basePrice
+ - currentPeriodStart
+ - currentPeriodEnd
+ - trialEndsAt
+ - incompleteExpiresAt
+ - cancelAtPeriodEnd
+ - canceledAt
+ - cancellationReason
+ - scheduledChange
+ type: object
+ SessionVoucher:
+ additionalProperties: false
+ properties:
+ boosts:
+ description: "ENTITLEMENT_BOOST: what it raises"
+ items:
+ $ref: "#/components/schemas/SessionBoost"
+ type:
+ - array
+ - "null"
+ discount:
+ $ref: "#/components/schemas/SessionDiscount"
+ description: "PRICE: what it takes off"
+ duration:
+ description: "PRICE: how many invoices it discounts (one, durationInPeriods, or every one). ENTITLEMENT_BOOST and FLAG_GRANT: how long it lasts, in billing periods. COMPOSITE: both, each for its part, which it carries each of."
+ enum:
+ - ONE_TIME
+ - REPEATING
+ - FOREVER
+ type: string
+ durationInPeriods:
+ format: int32
+ type: integer
+ flagGrants:
+ description: "FLAG_GRANT: the flag variants it grants"
+ items:
+ $ref: "#/components/schemas/FlagGrant"
+ type:
+ - array
+ - "null"
+ name:
+ examples:
+ - Summer launch
+ type: string
+ voucherType:
+ enum:
+ - PRICE
+ - ENTITLEMENT_BOOST
+ - FLAG_GRANT
+ - COMPOSITE
+ type: string
+ required:
+ - name
+ - voucherType
+ - duration
+ type: object
+ SessionVoucherCheck:
+ additionalProperties: false
+ properties:
+ code:
+ description: The code as the customer typed it; case and separators do not matter
+ examples:
+ - SUMMER-2026-LAUNCH
+ maxLength: 64
+ minLength: 1
+ type: string
+ licensePriceId:
+ description: "The price the customer is about to check out: the checks that read the subscription read this price"
+ type: string
+ required:
+ - code
+ type: object
+ SessionVoucherValidity:
+ additionalProperties: false
+ properties:
+ valid:
+ type: boolean
+ voucher:
+ $ref: "#/components/schemas/SessionVoucher"
+ description: What the voucher gives, when valid. Never its code.
+ required:
+ - valid
+ type: object
+ StartedSubscription:
+ additionalProperties: false
+ properties:
+ activationInvoice:
+ $ref: "#/components/schemas/InvoiceSummary"
+ description: "The invoice of the first period, when the base price bills in advance; absent otherwise. Charged automatically, it is pushed and charged before this answer when the provider answers within 20 seconds: its status says how it went."
+ anchorAt:
+ description: The instant its periods are counted from
+ format: date-time
+ type: string
+ basePrice:
+ $ref: "#/components/schemas/Price"
+ description: The FLAT_FEE price the subscription is pinned to, deprecated or not
+ billingPeriod:
+ enum:
+ - MONTHLY
+ - QUARTERLY
+ - SEMI_ANNUAL
+ - ANNUAL
+ type: string
+ cancelAtPeriodEnd:
+ type: boolean
+ cancelRequestedAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ canceledAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ cancellationReason:
+ type:
+ - string
+ - "null"
+ collectionMethod:
+ description: "Effective: the subscription's own, else the organization's default"
+ enum:
+ - SEND_INVOICE
+ - CHARGE_AUTOMATICALLY
+ type: string
+ collectionMethodOverride:
+ description: The subscription's own collection method; null when it takes the organization's
+ type:
+ - string
+ - "null"
+ createdAt:
+ format: date-time
+ type: string
+ currency:
+ examples:
+ - EUR
+ type: string
+ currentPeriodEnd:
+ description: "The next boundary: the period closes, and its invoice is composed, after it"
+ format: date-time
+ type: string
+ currentPeriodStart:
+ format: date-time
+ type: string
+ customerName:
+ type: string
+ customerSlug:
+ type: string
+ daysUntilDue:
+ description: "Effective: the subscription's own, else the organization's default"
+ format: int32
+ type: integer
+ daysUntilDueOverride:
+ description: The subscription's own payment terms; null when it takes the organization's
+ format: int32
+ type:
+ - integer
+ - "null"
+ id:
+ type: string
+ incompleteExpiresAt:
+ description: "When a checkout waiting for its payment ends unpaid: INCOMPLETE, or a free subscription that carries one"
+ format: date-time
+ type:
+ - string
+ - "null"
+ instanceName:
+ type: string
+ instanceSlug:
+ description: As it was at the subscription's last write
+ type: string
+ pastDueSince:
+ format: date-time
+ type:
+ - string
+ - "null"
+ pendingLicenseSlug:
+ description: The licence version a checkout waiting for its payment moves the instance to once paid
+ type:
+ - string
+ - "null"
+ providerKind:
+ description: "Who collects its invoices. NOOP: the organization itself, through the handoff queue"
+ enum:
+ - NOOP
+ - STRIPE
+ type: string
+ reminderAt:
+ description: "When trial_will_end is (or was) emitted: trialEndsAt minus the family's trialReminderDays as it was when the trial started; set with trialEndsAt"
+ format: date-time
+ type:
+ - string
+ - "null"
+ scheduledChange:
+ description: A plan change waiting for the next boundary
+ oneOf:
+ - $ref: "#/components/schemas/ScheduledChange"
+ - enum:
+ - null
+ type: "null"
+ startedAt:
+ description: "When billing started: usage before it is never billed"
+ format: date-time
+ type: string
+ status:
+ enum:
+ - INCOMPLETE
+ - TRIAL
+ - ACTIVE
+ - PAST_DUE
+ - CANCELED
+ type: string
+ trialEndsAt:
+ description: "When the trial ends, or ended: usage before it is never billed"
+ format: date-time
+ type:
+ - string
+ - "null"
+ updatedAt:
+ format: date-time
+ type: string
+ required:
+ - id
+ - instanceSlug
+ - instanceName
+ - customerSlug
+ - customerName
+ - status
+ - providerKind
+ - collectionMethod
+ - collectionMethodOverride
+ - daysUntilDue
+ - daysUntilDueOverride
+ - basePrice
+ - billingPeriod
+ - currency
+ - anchorAt
+ - startedAt
+ - currentPeriodStart
+ - currentPeriodEnd
+ - trialEndsAt
+ - reminderAt
+ - pendingLicenseSlug
+ - incompleteExpiresAt
+ - scheduledChange
+ - cancelAtPeriodEnd
+ - cancelRequestedAt
+ - canceledAt
+ - cancellationReason
+ - pastDueSince
+ - createdAt
+ - updatedAt
+ type: object
+ StatusChangeDelivered:
+ additionalProperties: false
+ properties:
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
+ type: string
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
+ type: string
+ from:
+ enum:
+ - INCOMPLETE
+ - TRIAL
+ - ACTIVE
+ - PAST_DUE
+ - CANCELED
+ type: string
+ instanceBillingId:
+ type: string
+ instanceSlug:
+ type: string
+ reason:
+ enum:
+ - TRIAL_ENDED
+ - INVOICE_OVERDUE
+ - PAYMENT_FAILED
+ - SETTLED
+ type: string
+ to:
+ enum:
+ - INCOMPLETE
+ - TRIAL
+ - ACTIVE
+ - PAST_DUE
+ - CANCELED
+ type: string
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
+ type: string
+ required:
+ - instanceBillingId
+ - instanceSlug
+ - from
+ - to
+ - reason
+ type: object
+ x-kaiten-payload: "#/components/schemas/StatusChange"
+ SubscriptionAddon:
+ additionalProperties: false
+ properties:
+ addonSlug:
+ description: The add-on version to attach
+ examples:
+ - extra-seats-v1
+ type: string
+ quantity:
+ default: 1
+ examples:
+ - 3
+ format: int32
+ minimum: 1
+ type: integer
+ required:
+ - addonSlug
+ type: object
+ SubscriptionCancellation:
+ additionalProperties: false
+ properties:
+ mode:
+ description: "AT_PERIOD_END (the default): the period paid for runs out, then the FINAL invoice is issued. IMMEDIATE: the FINAL invoice is issued now, flat fees billed in full and nothing refunded."
+ enum:
+ - AT_PERIOD_END
+ - IMMEDIATE
+ type: string
+ reason:
+ description: Up to 500 characters
+ type: string
+ type: object
+ SubscriptionTerms:
+ additionalProperties: false
+ properties:
+ collectionMethod:
+ description: "null: the organization's default. CHARGE_AUTOMATICALLY needs a provider that charges automatically (UpdateInstanceBilling.CollectionMethodUnsupported)."
+ enum:
+ - SEND_INVOICE
+ - CHARGE_AUTOMATICALLY
+ type:
+ - string
+ - "null"
+ daysUntilDue:
+ description: "0 to 365; null: the organization's default"
+ format: int32
+ type:
+ - integer
+ - "null"
+ providerKind:
+ description: "Who issues and collects the invoices, from the next one on. NOOP: the organization itself, through the handoff queue. A provider must be connected (UpdateInstanceBilling.ProviderNotConnected); one that pushes invoices needs the customer's billing e-mail for SEND_INVOICE (.BillingEmailMissing) and the subscription's currency (.UnsupportedCurrency)."
+ enum:
+ - NOOP
+ - STRIPE
+ type: string
+ type: object
+ SyncFailureDelivered:
+ additionalProperties: false
+ properties:
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
+ type: string
+ consecutiveFailures:
+ format: int32
+ type: integer
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
+ type: string
+ lastSyncError:
+ type: string
+ lastSyncedAt:
+ format: date-time
+ type: string
+ providerKind:
+ type: string
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
+ type: string
+ required:
+ - providerKind
+ - consecutiveFailures
+ - lastSyncError
+ - lastSyncedAt
+ type: object
+ x-kaiten-payload: "#/components/schemas/SyncFailure"
+ SyncReport:
+ additionalProperties: false
+ properties:
+ providers:
+ items:
+ $ref: "#/components/schemas/Outcome"
+ type: array
+ required:
+ - providers
+ type: object
+ SystemTokenIssuanceDelivered:
+ additionalProperties: false
+ properties:
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
+ type: string
+ credentialKind:
+ description: Credential class that performed the mint; "platform" is the system:kaiten identity
+ examples:
+ - platform
+ type: string
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
+ type: string
+ expiresAt:
+ description: Absent when the minted credential does not expire
+ format: date-time
+ type: string
+ issuedAt:
+ description: When the credential was minted
+ format: date-time
+ type: string
+ issuedTokenId:
+ description: The credential that was minted
+ type: string
+ name:
+ description: Human-readable label the caller gave the minted credential
+ examples:
+ - ci-deploy
+ type: string
+ platformTokenId:
+ description: Platform credential that performed the mint; revoking it revokes everything it issued
+ type: string
+ scopes:
+ description: Scopes granted to the minted credential
+ examples:
+ - - read:customers
+ items:
+ type: string
+ type:
+ - array
+ - "null"
+ slug:
+ description: Server-assigned identifier of the minted credential
+ examples:
+ - system-kaiten-9f2c1a
+ type: string
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
+ type: string
+ required:
+ - credentialKind
+ - platformTokenId
+ - issuedTokenId
+ - name
+ - slug
+ - scopes
+ - issuedAt
+ type: object
+ x-kaiten-payload: "#/components/schemas/SystemTokenIssuance"
+ TargetingContext:
+ additionalProperties: false
+ properties:
+ roots:
+ description: "Identifiers the server guarantees, and what is reachable below each. Not a closed set: a host may target on its own attributes too"
+ items:
+ $ref: "#/components/schemas/TargetingContextNode"
+ type: array
+ required:
+ - roots
+ type: object
+ TargetingContextNode:
+ additionalProperties: false
+ properties:
+ description:
+ description: What this value means, shown by the editor
+ type: string
+ entries:
+ description: Shape of each known key, for a map whose entries differ. Named after the key
+ items:
+ $ref: "#/components/schemas/TargetingContextNode"
+ type: array
+ fields:
+ description: Named children, for an object
+ items:
+ $ref: "#/components/schemas/TargetingContextNode"
+ type: array
+ knownKeys:
+ description: Keys this map currently has, for completion. Not a closed set
+ items:
+ type: string
+ type: array
+ name:
+ description: Name as it is written in a rule
+ type: string
+ optional:
+ description: Whether this value may be absent, in which case has() is worth using
+ type: boolean
+ type:
+ description: Shape of this node's value
+ enum:
+ - string
+ - number
+ - boolean
+ - object
+ - map
+ - dyn
+ type: string
+ values:
+ $ref: "#/components/schemas/TargetingContextNode"
+ description: Shape shared by every entry, for a map
+ required:
+ - name
+ - type
+ type: object
+ TargetingRuleDraft:
+ additionalProperties: false
+ properties:
+ rule:
+ description: The CEL targeting rule to check. May be blank, which is itself a mistake this reports
+ examples:
+ - __kaiten.license.familySlug == 'scale'
+ maxLength: 10000
+ type: string
+ required:
+ - rule
+ type: object
+ TargetingRuleIssue:
+ additionalProperties: false
+ properties:
+ column:
+ description: 1-based column the problem starts on, or 0 when it has no position
+ format: int64
+ type: integer
+ endColumn:
+ description: 1-based column just past the problem, or 0 when only a point is known
+ format: int64
+ type: integer
+ endLine:
+ description: 1-based line the problem ends on, or 0 when only a point is known
+ format: int64
+ type: integer
+ line:
+ description: 1-based line the problem starts on, or 0 when it has no position
+ format: int64
+ type: integer
+ message:
+ description: What is wrong, in the same words the write path refuses it with
+ type: string
+ required:
+ - message
+ - line
+ - column
+ type: object
+ TargetingRuleRehearsal:
+ additionalProperties: false
+ properties:
+ evaluationError:
+ description: Why the engine could not answer, when it could not — e.g. the rule reads a host attribute this context does not carry. At a real evaluation the rule would simply not match
+ type: string
+ facts:
+ additionalProperties: {}
+ description: The __kaiten namespace as enriched for this context — what a rule actually reads. Empty sub-roots mean the context resolved nothing for them
+ type: object
+ issues:
+ description: Problems the lint found. The rule is only run when there are none
+ items:
+ $ref: "#/components/schemas/TargetingRuleIssue"
+ type: array
+ matched:
+ description: Whether the rule matched this context. Meaningful only when valid and evaluationError is empty
+ type: boolean
+ valid:
+ description: Whether the rule lints clean
+ type: boolean
+ required:
+ - facts
+ - issues
+ - valid
+ - matched
+ type: object
+ TargetingRuleTrial:
+ additionalProperties: false
+ properties:
+ context:
+ additionalProperties: {}
+ description: The evaluation context to rehearse against, as a client would send it. Anything under __kaiten is discarded, exactly as at a real evaluation
+ type: object
+ rule:
+ description: The CEL targeting rule to rehearse
+ examples:
+ - __kaiten.license.familySlug == 'scale'
+ maxLength: 10000
+ type: string
+ targetingKey:
+ description: The key the evaluation would be bucketed on. Resolves license and entitlement facts when it names a customer slug
+ type: string
+ required:
+ - rule
+ type: object
+ TargetingRuleVerdict:
+ additionalProperties: false
+ properties:
+ issues:
+ description: Every problem found, positioned where it is written. Empty when the rule is accepted
+ items:
+ $ref: "#/components/schemas/TargetingRuleIssue"
+ type: array
+ valid:
+ description: Whether the write path would accept this rule
+ type: boolean
+ required:
+ - issues
+ - valid
+ type: object
+ Targetings:
+ description: Polymorphic list of targeting rules (Basic, RolloutDate, RolloutPercentage)
+ items:
+ anyOf:
+ - $ref: "#/components/schemas/BasicTargeting"
+ - $ref: "#/components/schemas/RolloutDateTargeting"
+ - $ref: "#/components/schemas/RolloutPercentageTargeting"
+ type:
+ - array
+ - "null"
+ Token:
+ additionalProperties: false
+ properties:
+ createdAt:
+ format: date-time
+ readOnly: true
+ type: string
+ createdBy:
+ $ref: "#/components/schemas/User"
+ readOnly: true
+ expiresAt:
+ format: date-time
+ type: string
+ id:
+ readOnly: true
+ type: string
+ name:
+ type: string
+ revokedAt:
+ format: date-time
+ readOnly: true
+ type: string
+ revokedBy:
+ $ref: "#/components/schemas/User"
+ readOnly: true
+ scopes:
+ items:
+ type: string
+ type:
+ - array
+ - "null"
+ serviceAccountId:
+ readOnly: true
+ type: string
+ slug:
+ description: Optional URL-friendly identifier, unique per service account. Auto-generated if not provided.
+ maxLength: 100
+ minLength: 2
+ pattern: ^[a-z0-9][a-z0-9-]*[a-z0-9]$
+ type: string
+ required:
+ - name
+ - scopes
+ - createdAt
+ - createdBy
+ type: object
+ TotalsDifference:
+ additionalProperties: false
+ properties:
+ kaitenTotal:
+ format: int64
+ type: integer
+ providerSubtotal:
+ format: int64
+ type: integer
+ providerTotalDiscount:
+ description: "With inclusive tax: the provider's discounts, already taken off its subtotal"
+ format: int64
+ type: integer
+ providerTotalExcludingTax:
+ format: int64
+ type: integer
+ required:
+ - kaitenTotal
+ - providerTotalExcludingTax
+ type: object
+ TrialCharge:
+ additionalProperties: false
+ properties:
+ amount:
+ format: int64
+ type: integer
+ billingPeriod:
+ enum:
+ - MONTHLY
+ - QUARTERLY
+ - SEMI_ANNUAL
+ - ANNUAL
+ type: string
+ currency:
+ examples:
+ - EUR
+ type: string
+ required:
+ - amount
+ - currency
+ - billingPeriod
+ type: object
+ TrialCustomer:
+ additionalProperties: false
+ properties:
+ billingEmail:
+ description: "Personal data: the second payload that carries an e-mail (D-56)"
+ type:
+ - string
+ - "null"
+ name:
+ type: string
+ slug:
+ type: string
+ required:
+ - slug
+ - name
+ - billingEmail
+ type: object
+ TrialInstance:
+ additionalProperties: false
+ properties:
+ name:
+ type: string
+ slug:
+ type: string
+ required:
+ - slug
+ - name
+ type: object
+ TrialPaymentMethod:
+ additionalProperties: false
+ properties:
+ brand:
+ examples:
+ - visa
+ type:
+ - string
+ - "null"
+ expMonth:
+ format: int64
+ type:
+ - integer
+ - "null"
+ expYear:
+ format: int64
+ type:
+ - integer
+ - "null"
+ last4:
+ examples:
+ - "4242"
+ type:
+ - string
+ - "null"
+ required:
+ - brand
+ - last4
+ - expMonth
+ - expYear
+ type: object
+ TrialWillEndDelivered:
+ additionalProperties: false
+ properties:
+ anchorAt:
+ description: The instant its periods are counted from
+ format: date-time
+ type: string
+ basePrice:
+ $ref: "#/components/schemas/Price"
+ description: The FLAT_FEE price the subscription is pinned to, deprecated or not
+ billingPeriod:
+ enum:
+ - MONTHLY
+ - QUARTERLY
+ - SEMI_ANNUAL
+ - ANNUAL
+ type: string
+ cancelAtPeriodEnd:
+ type: boolean
+ cancelRequestedAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ canceledAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ cancellationReason:
+ type:
+ - string
+ - "null"
+ charge:
+ description: What the conversion would charge, as upcoming-invoice previews it, discounts included and before any tax the provider adds; null when nothing would be
+ oneOf:
+ - $ref: "#/components/schemas/TrialCharge"
+ - enum:
+ - null
+ type: "null"
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
+ type: string
+ collectionMethod:
+ description: "Effective: the subscription's own, else the organization's default"
+ enum:
+ - SEND_INVOICE
+ - CHARGE_AUTOMATICALLY
+ type: string
+ collectionMethodOverride:
+ description: The subscription's own collection method; null when it takes the organization's
+ type:
+ - string
+ - "null"
+ createdAt:
+ format: date-time
+ type: string
+ currency:
+ examples:
+ - EUR
+ type: string
+ currentPeriodEnd:
+ description: "The next boundary: the period closes, and its invoice is composed, after it"
+ format: date-time
+ type: string
+ currentPeriodStart:
+ format: date-time
+ type: string
+ customer:
+ $ref: "#/components/schemas/TrialCustomer"
+ customerName:
+ type: string
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
+ type: string
+ customerSlug:
+ type: string
+ daysLeft:
+ description: Whole days from the event to trialEndsAt, rounded up; 0 once it passed
+ format: int64
+ type: integer
+ daysUntilDue:
+ description: "Effective: the subscription's own, else the organization's default"
+ format: int32
+ type: integer
+ daysUntilDueOverride:
+ description: The subscription's own payment terms; null when it takes the organization's
+ format: int32
+ type:
+ - integer
+ - "null"
+ familySlug:
+ description: Its family
+ type: string
+ id:
+ type: string
+ incompleteExpiresAt:
+ description: "When a checkout waiting for its payment ends unpaid: INCOMPLETE, or a free subscription that carries one"
+ format: date-time
+ type:
+ - string
+ - "null"
+ instance:
+ $ref: "#/components/schemas/TrialInstance"
+ instanceName:
+ type: string
+ instanceSlug:
+ description: As it was at the subscription's last write
+ type: string
+ licenseName:
+ description: The licence version of the base price
+ type: string
+ pastDueSince:
+ format: date-time
+ type:
+ - string
+ - "null"
+ paymentMethod:
+ description: The ACTIVE card the conversion would be charged to; null without one, or on NOOP
+ oneOf:
+ - $ref: "#/components/schemas/TrialPaymentMethod"
+ - enum:
+ - null
+ type: "null"
+ pendingLicenseSlug:
+ description: The licence version a checkout waiting for its payment moves the instance to once paid
+ type:
+ - string
+ - "null"
+ providerKind:
+ description: "Who collects its invoices. NOOP: the organization itself, through the handoff queue"
+ enum:
+ - NOOP
+ - STRIPE
+ type: string
+ reminderAt:
+ description: "When trial_will_end is (or was) emitted: trialEndsAt minus the family's trialReminderDays as it was when the trial started; set with trialEndsAt"
+ format: date-time
+ type:
+ - string
+ - "null"
+ scheduledChange:
+ description: A plan change waiting for the next boundary
+ oneOf:
+ - $ref: "#/components/schemas/ScheduledChange"
+ - enum:
+ - null
+ type: "null"
+ startedAt:
+ description: "When billing started: usage before it is never billed"
+ format: date-time
+ type: string
+ status:
+ enum:
+ - INCOMPLETE
+ - TRIAL
+ - ACTIVE
+ - PAST_DUE
+ - CANCELED
+ type: string
+ trialEndsAt:
+ description: "When the trial ends, or ended: usage before it is never billed"
+ format: date-time
+ type:
+ - string
+ - "null"
+ updatedAt:
+ format: date-time
+ type: string
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
+ type: string
+ required:
+ - customer
+ - instance
+ - licenseName
+ - familySlug
+ - daysLeft
+ - charge
+ - paymentMethod
+ - id
+ - instanceSlug
+ - instanceName
+ - customerSlug
+ - customerName
+ - status
+ - providerKind
+ - collectionMethod
+ - collectionMethodOverride
+ - daysUntilDue
+ - daysUntilDueOverride
+ - basePrice
+ - billingPeriod
+ - currency
+ - anchorAt
+ - startedAt
+ - currentPeriodStart
+ - currentPeriodEnd
+ - trialEndsAt
+ - reminderAt
+ - pendingLicenseSlug
+ - incompleteExpiresAt
+ - scheduledChange
+ - cancelAtPeriodEnd
+ - cancelRequestedAt
+ - canceledAt
+ - cancellationReason
+ - pastDueSince
+ - createdAt
+ - updatedAt
+ type: object
+ x-kaiten-payload: "#/components/schemas/TrialWillEnd"
+ UncollectibleInvoiceDelivered:
+ additionalProperties: false
+ properties:
+ boundaryAt:
+ description: The boundary the invoice bills. With the kind, it identifies the invoice within its subscription
+ format: date-time
+ type: string
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
+ type: string
+ collectionMethod:
+ enum:
+ - SEND_INVOICE
+ - CHARGE_AUTOMATICALLY
+ type: string
+ createdAt:
+ format: date-time
+ type: string
+ currency:
+ examples:
+ - EUR
+ type: string
+ customerName:
+ type: string
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
+ type: string
+ customerSlug:
+ description: As it was when the invoice was composed
+ type: string
+ daysUntilDue:
+ format: int32
+ type:
+ - integer
+ - "null"
+ discountTotal:
+ description: Sum of the discounts, in minor units
+ format: int64
+ type: integer
+ dueAt:
+ description: issuedAt + daysUntilDue
+ format: date-time
+ type:
+ - string
+ - "null"
+ handoffStatus:
+ description: "PENDING: waiting in the handoff queue for the organization's accounting system"
+ enum:
+ - NOT_REQUIRED
+ - PENDING
+ - ACKNOWLEDGED
+ type: string
+ holdReason:
+ description: "Why a DRAFT is held: the usage journal it was measured from failed a consistency check (LEDGER_*), or its provider cannot take that many lines (PROVIDER_ITEM_LIMIT)"
+ enum:
+ - LEDGER_SEQUENCE_GAP
+ - LEDGER_CHAIN_BREAK
+ - LEDGER_COUNTER_MISMATCH
+ - PROVIDER_ITEM_LIMIT
+ - null
+ type:
+ - string
+ - "null"
+ id:
+ description: "The invoice's identifier: the key an accounting system deduplicates on"
+ type: string
+ instanceName:
+ type: string
+ instanceSlug:
+ type: string
+ issuedAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ kind:
+ description: ACTIVATION bills a subscription's first period, RENEWAL the period that ended and the one that starts, FINAL what is left when a subscription ends
+ enum:
+ - ACTIVATION
+ - RENEWAL
+ - FINAL
+ type: string
+ licenseSlug:
+ type: string
+ overdue:
+ description: "Whether the invoice is overdue when read, the grace period of automatic collection included: the predicate of ?overdue=true. Computed by the invoice list and the invoice read; false in the answers of writes and in event payloads."
+ type: boolean
+ paidAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ providerKind:
+ description: "Who collects it. NOOP: the organization itself, through the handoff queue"
+ enum:
+ - NOOP
+ - STRIPE
+ type: string
+ reason:
+ type: string
+ serviceFrom:
+ description: Earliest start of its lines' service periods
+ format: date-time
+ type: string
+ serviceTo:
+ description: Latest end of its lines' service periods
+ format: date-time
+ type: string
+ status:
+ description: "MANUAL: issued, for the organization to collect. PAID, UNCOLLECTIBLE and VOID are final. DRAFT: held, waiting for a release or a recompose"
+ enum:
+ - DRAFT
+ - PUSHED
+ - PUSH_FAILED
+ - MANUAL
+ - PAID
+ - PAYMENT_FAILED
+ - UNCOLLECTIBLE
+ - VOID
+ type: string
+ subtotal:
+ description: Sum of the lines, in minor units
+ format: int64
+ type: integer
+ total:
+ description: subtotal − discountTotal, in minor units
+ format: int64
+ type: integer
+ updatedAt:
+ format: date-time
+ type: string
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
+ type: string
+ required:
+ - reason
+ - id
+ - kind
+ - boundaryAt
+ - serviceFrom
+ - serviceTo
+ - status
+ - holdReason
+ - providerKind
+ - collectionMethod
+ - currency
+ - subtotal
+ - discountTotal
+ - total
+ - issuedAt
+ - daysUntilDue
+ - dueAt
+ - paidAt
+ - customerSlug
+ - customerName
+ - instanceSlug
+ - instanceName
+ - licenseSlug
+ - handoffStatus
+ - overdue
+ - createdAt
+ - updatedAt
+ type: object
+ x-kaiten-payload: "#/components/schemas/UncollectibleInvoice"
+ UsageLedger:
+ additionalProperties: false
+ properties:
+ retentionMonths:
+ description: How long usage reports are kept, in months; null when they are kept forever (ENV) or the retention is unknown (UNKNOWN)
+ format: int64
+ type:
+ - integer
+ - "null"
+ retentionSource:
+ description: "KBK: the organization's licence or an add-on; ENV: KAITEN_USAGE_LEDGER_RETENTION_MONTHS, with no licensing authority; UNKNOWN: the licensing authority could not tell"
+ enum:
+ - KBK
+ - ENV
+ - UNKNOWN
+ type: string
+ required:
+ - retentionMonths
+ - retentionSource
+ type: object
+ UsageReport:
+ additionalProperties: false
+ properties:
+ aggregationMethod:
+ description: The entitlement's aggregation method when the report was accepted
+ examples:
+ - SUM
+ type: string
+ behavior:
+ description: append adds the value through the aggregation method; set overwrites the counter
+ enum:
+ - append
+ - set
+ type: string
+ delta:
+ description: valueAfter minus valueBefore; negative for a set that lowered the counter
+ examples:
+ - "600"
+ type: string
+ entitlementId:
+ description: The entitlement the report was made for. It may since have been deleted.
+ format: uuid
+ type: string
+ eventCountAfter:
+ description: Reports counted in the window after this one
+ examples:
+ - 1
+ format: int32
+ type: integer
+ instanceId:
+ description: The instance the report was made for. It may since have been deleted.
+ format: uuid
+ type: string
+ licenseId:
+ description: The instance's licence when the report was accepted
+ format: uuid
+ type: string
+ limitValue:
+ description: The limit in force when the report was accepted. Null when unlimited.
+ examples:
+ - "1000"
+ type:
+ - string
+ - "null"
+ overageDelta:
+ description: "How much the usage above limitValue moved: max(0, valueAfter - limitValue) - max(0, valueBefore - limitValue). 0 when unlimited."
+ examples:
+ - "0"
+ type: string
+ overagePercent:
+ description: The overage allowed above limitValue, in percent, when the report was accepted. Null when unlimited.
+ examples:
+ - 50
+ format: int32
+ type:
+ - integer
+ - "null"
+ properties:
+ additionalProperties: {}
+ description: The report's metadata; null when none was stored
+ type:
+ - object
+ - "null"
+ reportSeq:
+ description: Position of the report among the pair's accepted reports, from 1, without gaps
+ examples:
+ - 42
+ format: int64
+ type: integer
+ reportedAt:
+ description: When the server accepted the report (UTC, millisecond precision)
+ examples:
+ - "2026-10-05T08:00:00.000Z"
+ format: date-time
+ type: string
+ reportedValue:
+ description: "The value as sent: a delta for append, an absolute value for set"
+ examples:
+ - "600"
+ type: string
+ transactionId:
+ description: The report's idempotency key; null when it was sent without one
+ examples:
+ - llm-call-9f2c:tokens
+ type:
+ - string
+ - "null"
+ valueAfter:
+ description: The counter after the report
+ examples:
+ - "600"
+ type: string
+ valueBefore:
+ description: The counter before the report, after any window reset
+ examples:
+ - "0"
+ type: string
+ windowEnd:
+ description: End of the usage window the report counted in (exclusive). Null for a lifetime entitlement.
+ format: date-time
+ type:
+ - string
+ - "null"
+ windowStart:
+ description: Start of the usage window the report counted in (inclusive). Null for a lifetime entitlement.
+ format: date-time
+ type:
+ - string
+ - "null"
+ required:
+ - instanceId
+ - entitlementId
+ - reportSeq
+ - reportedAt
+ - behavior
+ - aggregationMethod
+ - reportedValue
+ - valueBefore
+ - valueAfter
+ - delta
+ - overageDelta
+ - eventCountAfter
+ - windowStart
+ - windowEnd
+ - limitValue
+ - overagePercent
+ - licenseId
+ - transactionId
+ - properties
+ type: object
+ UsageReportPage:
+ additionalProperties: false
+ properties:
+ items:
+ description: The reports, in reportSeq order
+ items:
+ $ref: "#/components/schemas/UsageReport"
+ type: array
+ nextAfterSeq:
+ description: Pass as afterSeq to read the next page. Absent on the last page.
+ examples:
+ - 100
+ format: int64
+ type: integer
+ required:
+ - items
+ type: object
+ User:
+ additionalProperties: false
+ properties:
+ id:
+ description: ID of the user
+ examples:
+ - 123e4567-e89b-12d3-a456-426614174000
+ type: string
+ name:
+ description: Name of the user
+ examples:
+ - John Doe
+ type: string
+ required:
+ - id
+ type: object
+ UserAuthorization:
+ additionalProperties: false
+ properties:
+ enabled:
+ type: boolean
+ permissions:
+ items:
+ type: string
+ type: array
+ roles:
+ items:
+ type: string
+ type: array
+ required:
+ - enabled
+ - roles
+ - permissions
+ type: object
+ UserEnablement:
+ additionalProperties: false
+ properties:
+ enabled:
+ type: boolean
+ required:
+ - enabled
+ type: object
+ UserRoleAssignments:
+ additionalProperties: false
+ properties:
+ permissions:
+ items:
+ type: string
+ type: array
+ roles:
+ items:
+ type: string
+ type: array
+ required:
+ - roles
+ - permissions
+ type: object
+ Validity:
+ additionalProperties: false
+ properties:
+ reason:
+ description: "The first failing check. DISCOUNT_LIMIT_EXCEEDED: a line of the instance's invoices would bear more than 20 discounts"
+ enum:
+ - NOT_FOUND
+ - NOT_ACTIVE
+ - NOT_YET_VALID
+ - EXPIRED
+ - EXHAUSTED
+ - ALREADY_REDEEMED
+ - NOT_ELIGIBLE
+ - CURRENCY_MISMATCH
+ - DISCOUNT_LIMIT_EXCEEDED
+ type: string
+ rule:
+ description: "With NOT_ELIGIBLE: the eligibility rule"
+ enum:
+ - RESTRICTED_CUSTOMER
+ - LICENSE_NOT_APPLICABLE
+ - ADDON_NOT_APPLICABLE
+ - FIRST_TIME_ONLY
+ - ANNUAL_ONLY
+ - MINIMUM_SUBSCRIPTION_AMOUNT
+ - NOTHING_TO_BOOST
+ type: string
+ valid:
+ type: boolean
+ voucher:
+ $ref: "#/components/schemas/Voucher"
+ description: The voucher, without its code, when the code names one
+ required:
+ - valid
+ type: object
+ Variant:
+ additionalProperties: false
+ properties:
+ description:
+ type: string
+ name:
+ type: string
+ value: {}
+ required:
+ - name
+ - description
+ - value
+ type: object
+ VoidedInvoiceDelivered:
+ additionalProperties: false
+ properties:
+ boundaryAt:
+ description: The boundary the invoice bills. With the kind, it identifies the invoice within its subscription
+ format: date-time
+ type: string
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
+ type: string
+ collectionMethod:
+ enum:
+ - SEND_INVOICE
+ - CHARGE_AUTOMATICALLY
+ type: string
+ createdAt:
+ format: date-time
+ type: string
+ currency:
+ examples:
+ - EUR
+ type: string
+ customerName:
+ type: string
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
+ type: string
+ customerSlug:
+ description: As it was when the invoice was composed
+ type: string
+ daysUntilDue:
+ format: int32
+ type:
+ - integer
+ - "null"
+ discountTotal:
+ description: Sum of the discounts, in minor units
+ format: int64
+ type: integer
+ dueAt:
+ description: issuedAt + daysUntilDue
+ format: date-time
+ type:
+ - string
+ - "null"
+ handoffStatus:
+ description: "PENDING: waiting in the handoff queue for the organization's accounting system"
+ enum:
+ - NOT_REQUIRED
+ - PENDING
+ - ACKNOWLEDGED
+ type: string
+ holdReason:
+ description: "Why a DRAFT is held: the usage journal it was measured from failed a consistency check (LEDGER_*), or its provider cannot take that many lines (PROVIDER_ITEM_LIMIT)"
+ enum:
+ - LEDGER_SEQUENCE_GAP
+ - LEDGER_CHAIN_BREAK
+ - LEDGER_COUNTER_MISMATCH
+ - PROVIDER_ITEM_LIMIT
+ - null
+ type:
+ - string
+ - "null"
+ id:
+ description: "The invoice's identifier: the key an accounting system deduplicates on"
+ type: string
+ instanceName:
+ type: string
+ instanceSlug:
+ type: string
+ issuedAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ kind:
+ description: ACTIVATION bills a subscription's first period, RENEWAL the period that ended and the one that starts, FINAL what is left when a subscription ends
+ enum:
+ - ACTIVATION
+ - RENEWAL
+ - FINAL
+ type: string
+ licenseSlug:
+ type: string
+ overdue:
+ description: "Whether the invoice is overdue when read, the grace period of automatic collection included: the predicate of ?overdue=true. Computed by the invoice list and the invoice read; false in the answers of writes and in event payloads."
+ type: boolean
+ paidAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ providerKind:
+ description: "Who collects it. NOOP: the organization itself, through the handoff queue"
+ enum:
+ - NOOP
+ - STRIPE
+ type: string
+ serviceFrom:
+ description: Earliest start of its lines' service periods
+ format: date-time
+ type: string
+ serviceTo:
+ description: Latest end of its lines' service periods
+ format: date-time
+ type: string
+ status:
+ description: "MANUAL: issued, for the organization to collect. PAID, UNCOLLECTIBLE and VOID are final. DRAFT: held, waiting for a release or a recompose"
+ enum:
+ - DRAFT
+ - PUSHED
+ - PUSH_FAILED
+ - MANUAL
+ - PAID
+ - PAYMENT_FAILED
+ - UNCOLLECTIBLE
+ - VOID
+ type: string
+ subtotal:
+ description: Sum of the lines, in minor units
+ format: int64
+ type: integer
+ total:
+ description: subtotal − discountTotal, in minor units
+ format: int64
+ type: integer
+ updatedAt:
+ format: date-time
+ type: string
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
+ type: string
+ voidReason:
+ type: string
+ required:
+ - voidReason
+ - id
+ - kind
+ - boundaryAt
+ - serviceFrom
+ - serviceTo
+ - status
+ - holdReason
+ - providerKind
+ - collectionMethod
+ - currency
+ - subtotal
+ - discountTotal
+ - total
+ - issuedAt
+ - daysUntilDue
+ - dueAt
+ - paidAt
+ - customerSlug
+ - customerName
+ - instanceSlug
+ - instanceName
+ - licenseSlug
+ - handoffStatus
+ - overdue
+ - createdAt
+ - updatedAt
+ type: object
+ x-kaiten-payload: "#/components/schemas/VoidedInvoice"
+ Voucher:
+ additionalProperties: false
+ properties:
+ applicableAddonIds:
+ description: Only instances holding one of these add-on versions can redeem it; any when empty
+ items:
+ type: string
+ type: array
+ applicableAddonPriceIds:
+ items:
+ type: string
+ type: array
+ applicableLicenseIds:
+ description: Only instances on these licence versions can redeem it; any when empty
+ items:
+ type: string
+ type: array
+ applicableLicensePriceIds:
+ items:
+ type: string
+ type: array
+ code:
+ description: The code to redeem. Returned to read:vouchers callers only; never in an event.
+ examples:
+ - SUMMER-2026-LAUNCH
+ type: string
+ codeHint:
+ description: The last four characters of the normalized code
+ examples:
+ - UNCH
+ type: string
+ createdAt:
+ format: date-time
+ readOnly: true
+ type: string
+ currency:
+ description: The currency of a FIXED_AMOUNT discount
+ examples:
+ - EUR
+ type:
+ - string
+ - "null"
+ description:
+ type:
+ - string
+ - "null"
+ duration:
+ description: "PRICE: how many invoices it discounts (one, durationInPeriods, or every one). BOOST and FLAG_GRANT: how long a redemption lasts, in the subscription's billing periods. COMPOSITE: both, each for its part."
+ enum:
+ - ONE_TIME
+ - REPEATING
+ - FOREVER
+ type: string
+ durationInPeriods:
+ format: int32
+ type:
+ - integer
+ - "null"
+ expiresAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ flagGrants:
+ description: The flag variants a FLAG_GRANT grants
+ items:
+ $ref: "#/components/schemas/FlagGrant"
+ type: array
+ grants:
+ description: What an ENTITLEMENT_BOOST changes
+ items:
+ $ref: "#/components/schemas/Grant"
+ type: array
+ id:
+ readOnly: true
+ type: string
+ maxRedemptions:
+ format: int32
+ type:
+ - integer
+ - "null"
+ name:
+ examples:
+ - Summer launch
+ type: string
+ priceAppliesTo:
+ enum:
+ - LICENSE_BASE
+ - ADDONS
+ - BOTH
+ - SELECTED_PRICES
+ - null
+ type:
+ - string
+ - "null"
+ priceDiscountType:
+ enum:
+ - PERCENTAGE
+ - FIXED_AMOUNT
+ - null
+ type:
+ - string
+ - "null"
+ priceDiscountValue:
+ description: A percentage in (0, 100], or an integer amount in minor units of currency
+ examples:
+ - "30"
+ type:
+ - string
+ - "null"
+ redemptionRules:
+ $ref: "#/components/schemas/RedemptionRules"
+ redemptionsCount:
+ format: int32
+ type: integer
+ restrictedCustomerSlug:
+ description: Only this customer's instances can redeem it
+ type:
+ - string
+ - "null"
+ startsAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ status:
+ enum:
+ - DRAFT
+ - ACTIVE
+ - EXPIRED
+ - EXHAUSTED
+ - ARCHIVED
+ type: string
+ updatedAt:
+ format: date-time
+ readOnly: true
+ type: string
+ voucherType:
+ enum:
+ - PRICE
+ - ENTITLEMENT_BOOST
+ - FLAG_GRANT
+ - COMPOSITE
+ type: string
+ required:
+ - id
+ - codeHint
+ - name
+ - description
+ - voucherType
+ - status
+ - duration
+ - durationInPeriods
+ - maxRedemptions
+ - redemptionsCount
+ - startsAt
+ - expiresAt
+ - priceDiscountType
+ - priceDiscountValue
+ - currency
+ - priceAppliesTo
+ - applicableLicensePriceIds
+ - applicableAddonPriceIds
+ - grants
+ - flagGrants
+ - applicableLicenseIds
+ - applicableAddonIds
+ - restrictedCustomerSlug
+ - redemptionRules
+ - createdAt
+ - updatedAt
+ type: object
+ VoucherCheck:
+ additionalProperties: false
+ properties:
+ code:
+ examples:
+ - SUMMER-2026-LAUNCH
+ maxLength: 64
+ minLength: 1
+ type: string
+ instanceSlug:
+ description: Also run the checks that need the redeeming instance
+ type: string
+ licensePriceId:
+ description: "The flat-fee price a subscription would start on: the licence, period, amount and currency checks read it instead of the instance's live subscription"
+ type: string
+ required:
+ - code
+ type: object
+ VoucherCode:
+ additionalProperties: false
+ properties:
+ code:
+ examples:
+ - SUMMER-2026-LAUNCH
+ maxLength: 64
+ minLength: 1
+ type: string
+ required:
+ - code
+ type: object
+ VoucherDelivered:
+ additionalProperties: false
+ properties:
+ applicableAddonIds:
+ description: Only instances holding one of these add-on versions can redeem it; any when empty
+ items:
+ type: string
+ type: array
+ applicableAddonPriceIds:
+ items:
+ type: string
+ type: array
+ applicableLicenseIds:
+ description: Only instances on these licence versions can redeem it; any when empty
+ items:
+ type: string
+ type: array
+ applicableLicensePriceIds:
+ items:
+ type: string
+ type: array
+ checkoutSessionId:
+ description: "With via checkout_session: the session"
+ format: uuid
+ type: string
+ code:
+ description: The code to redeem. Returned to read:vouchers callers only; never in an event.
+ examples:
+ - SUMMER-2026-LAUNCH
+ type: string
+ codeHint:
+ description: The last four characters of the normalized code
+ examples:
+ - UNCH
+ type: string
+ createdAt:
+ format: date-time
+ readOnly: true
+ type: string
+ currency:
+ description: The currency of a FIXED_AMOUNT discount
+ examples:
+ - EUR
+ type:
+ - string
+ - "null"
+ customerSessionId:
+ description: "With via customer_session: the session"
+ format: uuid
+ type: string
+ description:
+ type:
+ - string
+ - "null"
+ duration:
+ description: "PRICE: how many invoices it discounts (one, durationInPeriods, or every one). BOOST and FLAG_GRANT: how long a redemption lasts, in the subscription's billing periods. COMPOSITE: both, each for its part."
+ enum:
+ - ONE_TIME
+ - REPEATING
+ - FOREVER
+ type: string
+ durationInPeriods:
+ format: int32
+ type:
+ - integer
+ - "null"
+ expiresAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ flagGrants:
+ description: The flag variants a FLAG_GRANT grants
+ items:
+ $ref: "#/components/schemas/FlagGrant"
+ type: array
+ grants:
+ description: What an ENTITLEMENT_BOOST changes
+ items:
+ $ref: "#/components/schemas/Grant"
+ type: array
+ id:
+ readOnly: true
+ type: string
+ maxRedemptions:
+ format: int32
+ type:
+ - integer
+ - "null"
+ name:
+ examples:
+ - Summer launch
+ type: string
+ priceAppliesTo:
+ enum:
+ - LICENSE_BASE
+ - ADDONS
+ - BOTH
+ - SELECTED_PRICES
+ - null
+ type:
+ - string
+ - "null"
+ priceDiscountType:
+ enum:
+ - PERCENTAGE
+ - FIXED_AMOUNT
+ - null
+ type:
+ - string
+ - "null"
+ priceDiscountValue:
+ description: A percentage in (0, 100], or an integer amount in minor units of currency
+ examples:
+ - "30"
+ type:
+ - string
+ - "null"
+ redemptionRules:
+ $ref: "#/components/schemas/RedemptionRules"
+ redemptionsCount:
+ format: int32
+ type: integer
+ restrictedCustomerSlug:
+ description: Only this customer's instances can redeem it
+ type:
+ - string
+ - "null"
+ startsAt:
+ format: date-time
+ type:
+ - string
+ - "null"
+ status:
+ enum:
+ - DRAFT
+ - ACTIVE
+ - EXPIRED
+ - EXHAUSTED
+ - ARCHIVED
+ type: string
+ updatedAt:
+ format: date-time
+ readOnly: true
+ type: string
+ via:
+ description: "customer_session: written through a kst_ customer session, under the vendor principal that minted it. checkout_session: written by an anonymous checkout, under system:kaiten. Absent otherwise"
+ enum:
+ - customer_session
+ - checkout_session
+ type: string
+ voucherType:
+ enum:
+ - PRICE
+ - ENTITLEMENT_BOOST
+ - FLAG_GRANT
+ - COMPOSITE
+ type: string
+ required:
+ - id
+ - codeHint
+ - name
+ - description
+ - voucherType
+ - status
+ - duration
+ - durationInPeriods
+ - maxRedemptions
+ - redemptionsCount
+ - startsAt
+ - expiresAt
+ - priceDiscountType
+ - priceDiscountValue
+ - currency
+ - priceAppliesTo
+ - applicableLicensePriceIds
+ - applicableAddonPriceIds
+ - grants
+ - flagGrants
+ - applicableLicenseIds
+ - applicableAddonIds
+ - restrictedCustomerSlug
+ - redemptionRules
+ - createdAt
+ - updatedAt
+ type: object
+ x-kaiten-payload: "#/components/schemas/Voucher"
+ VoucherDraft:
+ additionalProperties: false
+ properties:
+ applicableAddonIds:
+ items:
+ type: string
+ type:
+ - array
+ - "null"
+ applicableAddonPriceIds:
+ description: "With SELECTED_PRICES: the add-on prices it discounts"
+ items:
+ type: string
+ type:
+ - array
+ - "null"
+ applicableLicenseIds:
+ items:
+ type: string
+ type:
+ - array
+ - "null"
+ applicableLicensePriceIds:
+ description: "With SELECTED_PRICES: the licence prices it discounts"
+ items:
+ type: string
+ type:
+ - array
+ - "null"
+ code:
+ description: "8 to 64 of [A-Za-z0-9_-]. Generated when absent: 16 characters, 80 random bits. Matched without case or separators."
+ examples:
+ - SUMMER-2026-LAUNCH
+ type: string
+ currency:
+ description: Required with FIXED_AMOUNT, refused otherwise
+ examples:
+ - EUR
+ type: string
+ description:
+ maxLength: 2000
+ type: string
+ duration:
+ enum:
+ - ONE_TIME
+ - REPEATING
+ - FOREVER
+ type: string
+ durationInPeriods:
+ description: Required with REPEATING, refused otherwise
+ format: int32
+ type: integer
+ expiresAt:
+ format: date-time
+ type: string
+ flagGrants:
+ description: Required on a FLAG_GRANT, optional on a COMPOSITE, one per flag, refused otherwise
+ items:
+ $ref: "#/components/schemas/FlagGrant"
+ type:
+ - array
+ - "null"
+ grants:
+ description: Required on an ENTITLEMENT_BOOST, optional on a COMPOSITE, refused otherwise
+ items:
+ $ref: "#/components/schemas/Grant"
+ type:
+ - array
+ - "null"
+ maxRedemptions:
+ format: int32
+ minimum: 1
+ type: integer
+ name:
+ examples:
+ - Summer launch
+ maxLength: 200
+ minLength: 1
+ type: string
+ priceAppliesTo:
+ enum:
+ - LICENSE_BASE
+ - ADDONS
+ - BOTH
+ - SELECTED_PRICES
+ type: string
+ priceDiscountType:
+ enum:
+ - PERCENTAGE
+ - FIXED_AMOUNT
+ type: string
+ priceDiscountValue:
+ description: A percentage in (0, 100], or an integer amount in minor units
+ examples:
+ - "30"
+ type: string
+ redemptionRules:
+ $ref: "#/components/schemas/RedemptionRules"
+ restrictedCustomerSlug:
+ type: string
+ startsAt:
+ format: date-time
+ type: string
+ voucherType:
+ enum:
+ - PRICE
+ - ENTITLEMENT_BOOST
+ - FLAG_GRANT
+ - COMPOSITE
+ type: string
+ required:
+ - name
+ - voucherType
+ - duration
+ type: object
+ securitySchemes:
+ bearerAuth:
+ bearerFormat: JWT
+ description: "A JWT verified by the proxy in front of this API, or a Kaiten access token (`ksh_...`) — send either as `Authorization: Bearer `. The scopes listed on each operation are checked server-side against current stored user assignments or stored service-token permissions, never JWT claims; `write:` implies `read:`, and `read:*` / `write:*` are wildcards over every module. `x-kaiten-scopes` lists every scope such a token can carry, including those enforced by a service in front of this API rather than by one of its operations."
+ scheme: bearer
+ type: http
+ x-kaiten-scopes:
+ - read:addons
+ - read:billing
+ - read:components
+ - read:customer_sessions
+ - read:customers
- read:deployment_zones
- read:entitlements
- read:feature_flags
@@ -3196,10 +16551,18 @@ components:
- read:metadata_fields
- read:notifications
- read:organizations
+ - read:profile
+ - read:publishable_keys
- read:releases
- read:tokens
+ - read:users
+ - read:voucher_redemptions
+ - read:vouchers
- read:webhooks
+ - write:addons
+ - write:billing
- write:components
+ - write:customer_sessions
- write:customers
- write:deployment_zones
- write:entitlements
@@ -3209,9 +16572,29 @@ components:
- write:metadata_fields
- write:notifications
- write:organizations
+ - write:profile
+ - write:publishable_keys
- write:releases
- write:tokens
+ - write:users
+ - write:voucher_redemptions
+ - write:vouchers
- write:webhooks
+ checkoutSession:
+ bearerFormat: ksc_
+ description: "An anonymous checkout session (`ksc_...`), returned once by POST /public/checkout-sessions and sent as `Authorization: Bearer ksc_...`. It reads and confirms that one session, at /public/checkout-sessions/{sessionId}, and nothing else; another session's id answers 401. A browser request must come from an origin of the publishable key that started the session."
+ scheme: bearer
+ type: http
+ customerSession:
+ bearerFormat: kst_
+ description: "A customer session (`kst_...`), minted by the vendor's backend with POST /customer-sessions and sent as `Authorization: Bearer kst_...`. It acts for one customer -- and one of its instances, when bound -- on the /public/session routes and nowhere else. A browser request must come from an origin one of the organization's publishable keys allows."
+ scheme: bearer
+ type: http
+ publishableKey:
+ description: "A Kaiten publishable key (`pk_...`). Not a secret: it ships in web pages, and authorizes reading the organization's public catalogue and nothing else. A browser request must come from one of the key's allowed origins. Never send it as `Authorization`; a request carrying `Authorization` is refused."
+ in: header
+ name: X-Kaiten-Publishable-Key
+ type: apiKey
info:
contact:
email: support@kaiten.sh
@@ -3221,34 +16604,13550 @@ info:
version: 1.0.0
openapi: 3.1.0
paths:
+ /addon-families:
+ get:
+ description: Every add-on family of the organization with its versions, newest family first. Requires billing to be enabled for the organization.
+ operationId: listAddonFamilies
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ items:
+ $ref: "#/components/schemas/AddonFamily"
+ type: array
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - read:addons
+ summary: List add-on families
+ tags:
+ - addons
+ /addon-families/{familySlug}:
+ get:
+ description: One add-on family with its versions and its current version. Requires billing to be enabled for the organization.
+ operationId: getAddonFamily
+ parameters:
+ - description: Add-on family slug
+ in: path
+ name: familySlug
+ required: true
+ schema:
+ description: Add-on family slug
+ examples:
+ - extra-seats
+ type: string
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/AddonFamily"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - read:addons
+ summary: Get an add-on family
+ tags:
+ - addons
+ patch:
+ description: "Changes a family's own settings: whether the public catalogue lists it (families are private until made public), and how a mid-period increase of its ADVANCE add-ons is billed. An absent member keeps the stored value. Emits ADDON_UPDATED on each of its versions. Requires billing to be enabled for the organization. 404 .NotFound; 422 .InvalidIncreaseBilling."
+ operationId: updateAddonFamily
+ parameters:
+ - description: Add-on family slug
+ in: path
+ name: familySlug
+ required: true
+ schema:
+ description: Add-on family slug
+ examples:
+ - extra-seats
+ type: string
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/AddonFamilyVisibility"
+ required: true
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/AddonFamily"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - write:addons
+ summary: Update an add-on family
+ tags:
+ - addons
+ /addons:
+ get:
+ description: The organization's add-on versions, newest first. Requires billing to be enabled for the organization.
+ operationId: listAddons
+ parameters:
+ - description: Only versions in this state
+ explode: false
+ in: query
+ name: lifecycleState
+ schema:
+ description: Only versions in this state
+ enum:
+ - DRAFT
+ - PUBLISHED
+ - ARCHIVED
+ type: string
+ - description: Only versions of this family
+ explode: false
+ in: query
+ name: familySlug
+ schema:
+ description: Only versions of this family
+ type: string
+ - description: Opaque pagination cursor from a previous response's nextCursor
+ explode: false
+ in: query
+ name: cursor
+ schema:
+ description: Opaque pagination cursor from a previous response's nextCursor
+ type: string
+ - description: Maximum number of entries to return (default 50, max 200)
+ explode: false
+ in: query
+ name: limit
+ schema:
+ description: Maximum number of entries to return (default 50, max 200)
+ format: int32
+ maximum: 200
+ minimum: 1
+ type: integer
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/PageAddon"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - read:addons
+ summary: List add-on versions
+ tags:
+ - addons
+ post:
+ description: "Creates an add-on version: the first of a new family, or the next version of the family familySlug (or familyId) names. Emits ADDON_CREATED. Requires billing to be enabled for the organization."
+ operationId: createAddon
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/NewAddon"
+ required: true
+ responses:
+ "201":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Addon"
+ description: Created
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - write:addons
+ summary: Create an add-on version
+ tags:
+ - addons
+ /addons/{addonSlug}:
+ delete:
+ description: Deletes a version that was never attached and grants nothing (409 DeleteAddon.InUseConflict otherwise); its family goes with its last version. Emits ADDON_DELETED. Requires billing to be enabled for the organization.
+ operationId: deleteAddon
+ parameters:
+ - description: Add-on version slug
+ in: path
+ name: addonSlug
+ required: true
+ schema:
+ description: Add-on version slug
+ examples:
+ - extra-seats-v1
+ type: string
+ responses:
+ "204":
+ description: No Content
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - write:addons
+ summary: Delete an add-on version
+ tags:
+ - addons
+ get:
+ description: One add-on version. Requires billing to be enabled for the organization.
+ operationId: getAddon
+ parameters:
+ - description: Add-on version slug
+ in: path
+ name: addonSlug
+ required: true
+ schema:
+ description: Add-on version slug
+ examples:
+ - extra-seats-v1
+ type: string
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Addon"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - read:addons
+ summary: Get an add-on version
+ tags:
+ - addons
+ put:
+ description: Replaces a version's name, description, version name, default flag and maximum quantity. Emits ADDON_UPDATED. Requires billing to be enabled for the organization.
+ operationId: updateAddon
+ parameters:
+ - description: Add-on version slug
+ in: path
+ name: addonSlug
+ required: true
+ schema:
+ description: Add-on version slug
+ examples:
+ - extra-seats-v1
+ type: string
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/AddonChanges"
+ required: true
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Addon"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - write:addons
+ summary: Update an add-on version
+ tags:
+ - addons
+ /addons/{addonSlug}/archive:
+ post:
+ description: "Withdraws a PUBLISHED version from sale: instances holding it keep it, none can attach it again (409 ArchiveAddon.NotPublished otherwise). Emits ADDON_ARCHIVED. Requires billing to be enabled for the organization."
+ operationId: archiveAddon
+ parameters:
+ - description: Add-on version slug
+ in: path
+ name: addonSlug
+ required: true
+ schema:
+ description: Add-on version slug
+ examples:
+ - extra-seats-v1
+ type: string
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Addon"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - write:addons
+ summary: Archive an add-on version
+ tags:
+ - addons
+ /addons/{addonSlug}/compatible-license-families:
+ get:
+ description: "The licence families an instance must be on to attach this add-on version. None: it cannot be attached. Requires billing to be enabled for the organization."
+ operationId: listAddonCompatibility
+ parameters:
+ - description: Add-on version slug
+ in: path
+ name: addonSlug
+ required: true
+ schema:
+ description: Add-on version slug
+ examples:
+ - extra-seats-v1
+ type: string
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/CompatibleLicenseFamilies"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - read:addons
+ summary: List the licence families an add-on fits
+ tags:
+ - addons
+ /addons/{addonSlug}/compatible-license-families/{familySlug}:
+ delete:
+ description: Instances already holding the add-on keep it; no new attachment from the family. Idempotent. Emits ADDON_UPDATED. Requires billing to be enabled for the organization.
+ operationId: removeAddonCompatibility
+ parameters:
+ - description: Add-on version slug
+ in: path
+ name: addonSlug
+ required: true
+ schema:
+ description: Add-on version slug
+ examples:
+ - extra-seats-v1
+ type: string
+ - description: License family slug
+ in: path
+ name: familySlug
+ required: true
+ schema:
+ description: License family slug
+ examples:
+ - pro
+ type: string
+ responses:
+ "204":
+ description: No Content
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - write:addons
+ summary: Stop an add-on fitting a licence family
+ tags:
+ - addons
+ put:
+ description: Lets instances on any version of the licence family attach this add-on version. Idempotent. Emits ADDON_UPDATED. Requires billing to be enabled for the organization.
+ operationId: setAddonCompatibility
+ parameters:
+ - description: Add-on version slug
+ in: path
+ name: addonSlug
+ required: true
+ schema:
+ description: Add-on version slug
+ examples:
+ - extra-seats-v1
+ type: string
+ - description: License family slug
+ in: path
+ name: familySlug
+ required: true
+ schema:
+ description: License family slug
+ examples:
+ - pro
+ type: string
+ responses:
+ "204":
+ description: No Content
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - write:addons
+ summary: Make an add-on fit a licence family
+ tags:
+ - addons
+ /addons/{addonSlug}/entitlements:
+ get:
+ description: What one add-on version grants per unit of quantity. Requires billing to be enabled for the organization.
+ operationId: listAddonEntitlements
+ parameters:
+ - description: Add-on version slug
+ in: path
+ name: addonSlug
+ required: true
+ schema:
+ description: Add-on version slug
+ examples:
+ - extra-seats-v1
+ type: string
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ items:
+ $ref: "#/components/schemas/AddonEntitlement"
+ type: array
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - read:addons
+ summary: List an add-on version's grants
+ tags:
+ - addons
+ post:
+ description: Grants an entitlement per unit of an add-on version. Refused while an instance with a live subscription holds the version (409 AssignAddonEntitlement.BillingActive). Emits ADDON_ENTITLEMENT_ASSIGNED. Requires billing to be enabled for the organization.
+ operationId: assignAddonEntitlement
+ parameters:
+ - description: Add-on version slug
+ in: path
+ name: addonSlug
+ required: true
+ schema:
+ description: Add-on version slug
+ examples:
+ - extra-seats-v1
+ type: string
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/NewAddonGrant"
+ required: true
+ responses:
+ "201":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/AddonEntitlement"
+ description: Created
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - write:addons
+ summary: Grant an entitlement through an add-on
+ tags:
+ - addons
+ /addons/{addonSlug}/entitlements/{entitlementSlug}:
+ delete:
+ description: Removes a grant. Refused while an instance with a live subscription holds the version (409 UnassignAddonEntitlement.BillingActive) and while an ACTIVE price of the version meters it (409 UnassignAddonEntitlement.MeteredByPrice). Emits ADDON_ENTITLEMENT_UNASSIGNED. Requires billing to be enabled for the organization.
+ operationId: unassignAddonEntitlement
+ parameters:
+ - description: Add-on version slug
+ in: path
+ name: addonSlug
+ required: true
+ schema:
+ description: Add-on version slug
+ examples:
+ - extra-seats-v1
+ type: string
+ - description: Entitlement slug
+ in: path
+ name: entitlementSlug
+ required: true
+ schema:
+ description: Entitlement slug
+ examples:
+ - seats
+ type: string
+ responses:
+ "204":
+ description: No Content
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - write:addons
+ summary: Remove an add-on grant
+ tags:
+ - addons
+ get:
+ description: One grant of an add-on version. Requires billing to be enabled for the organization.
+ operationId: getAddonEntitlement
+ parameters:
+ - description: Add-on version slug
+ in: path
+ name: addonSlug
+ required: true
+ schema:
+ description: Add-on version slug
+ examples:
+ - extra-seats-v1
+ type: string
+ - description: Entitlement slug
+ in: path
+ name: entitlementSlug
+ required: true
+ schema:
+ description: Entitlement slug
+ examples:
+ - seats
+ type: string
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/AddonEntitlement"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - read:addons
+ summary: Get an add-on grant
+ tags:
+ - addons
+ put:
+ description: Replaces a grant's value, override behaviour and overage. Refused while an instance with a live subscription holds the version (409 UpdateAddonEntitlement.BillingActive). Emits ADDON_ENTITLEMENT_UPDATED. Requires billing to be enabled for the organization.
+ operationId: updateAddonEntitlement
+ parameters:
+ - description: Add-on version slug
+ in: path
+ name: addonSlug
+ required: true
+ schema:
+ description: Add-on version slug
+ examples:
+ - extra-seats-v1
+ type: string
+ - description: Entitlement slug
+ in: path
+ name: entitlementSlug
+ required: true
+ schema:
+ description: Entitlement slug
+ examples:
+ - seats
+ type: string
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/AddonGrant"
+ required: true
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/AddonEntitlement"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - write:addons
+ summary: Update an add-on grant
+ tags:
+ - addons
+ /addons/{addonSlug}/prices:
+ get:
+ description: The prices of one add-on version, in display order. Requires billing to be enabled for the organization.
+ operationId: listAddonPrices
+ parameters:
+ - description: Add-on version slug
+ in: path
+ name: addonSlug
+ required: true
+ schema:
+ description: Add-on version slug
+ examples:
+ - extra-seats-v1
+ type: string
+ - description: Only prices in this status; both when omitted
+ explode: false
+ in: query
+ name: status
+ schema:
+ description: Only prices in this status; both when omitted
+ enum:
+ - ACTIVE
+ - DEPRECATED
+ type: string
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ items:
+ $ref: "#/components/schemas/Price"
+ type: array
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - read:addons
+ summary: List an add-on version's prices
+ tags:
+ - addons
+ post:
+ description: "Adds a price to an add-on version, under the rules of a licence price. A price cannot change once created: create another and deprecate this one. Refused while an instance with a live subscription holds the version (409 CreateAddonPrice.BillingActive). Emits ADDON_PRICE_CREATED. Requires billing to be enabled for the organization."
+ operationId: createAddonPrice
+ parameters:
+ - description: Add-on version slug
+ in: path
+ name: addonSlug
+ required: true
+ schema:
+ description: Add-on version slug
+ examples:
+ - extra-seats-v1
+ type: string
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/NewAddonPrice"
+ required: true
+ responses:
+ "201":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Price"
+ description: Created
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - write:addons
+ summary: Create an add-on price
+ tags:
+ - addons
+ /addons/{addonSlug}/prices/{priceId}/deprecate:
+ post:
+ description: "Stops offering a price. A default price cannot be deprecated (409 DeprecateAddonPrice.IsDefault): it is what bills every instance holding the version; retire it through a new version. Emits ADDON_PRICE_DEPRECATED. Requires billing to be enabled for the organization."
+ operationId: deprecateAddonPrice
+ parameters:
+ - description: Add-on version slug
+ in: path
+ name: addonSlug
+ required: true
+ schema:
+ description: Add-on version slug
+ examples:
+ - extra-seats-v1
+ type: string
+ - description: Price id
+ in: path
+ name: priceId
+ required: true
+ schema:
+ description: Price id
+ type: string
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Price"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - write:addons
+ summary: Deprecate an add-on price
+ tags:
+ - addons
+ /addons/{addonSlug}/publish:
+ post:
+ description: Puts a DRAFT version on sale (409 PublishAddon.NotADraft otherwise). Emits ADDON_PUBLISHED. Requires billing to be enabled for the organization.
+ operationId: publishAddon
+ parameters:
+ - description: Add-on version slug
+ in: path
+ name: addonSlug
+ required: true
+ schema:
+ description: Add-on version slug
+ examples:
+ - extra-seats-v1
+ type: string
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Addon"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - write:addons
+ summary: Publish an add-on version
+ tags:
+ - addons
+ /addons/{addonSlug}/unarchive:
+ post:
+ description: Puts an ARCHIVED version back on sale (409 UnarchiveAddon.NotArchived otherwise). Emits ADDON_UNARCHIVED. Requires billing to be enabled for the organization.
+ operationId: unarchiveAddon
+ parameters:
+ - description: Add-on version slug
+ in: path
+ name: addonSlug
+ required: true
+ schema:
+ description: Add-on version slug
+ examples:
+ - extra-seats-v1
+ type: string
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Addon"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - write:addons
+ summary: Unarchive an add-on version
+ tags:
+ - addons
+ /authorization:
+ get:
+ description: Returns current stored grants for an active human or service-token caller, not JWT permissions. No permission is required. Credentials and token identifiers are never returned. Responses must not be cached.
+ operationId: GetAuthorization
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Authorization"
+ description: OK
+ headers:
+ Cache-Control:
+ schema:
+ type: string
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth: []
+ summary: Read current caller authorization
+ tags:
+ - identity
+ /billing/capabilities:
+ get:
+ description: Whether billing is enabled for the organization, and if not why; who can collect its invoices; and which parts of billing this release ships, so a client shows only what works. Answers even when billing is disabled.
+ operationId: getBillingCapabilities
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/BillingCapabilities"
+ description: OK
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - read:billing
+ summary: Get billing capabilities
+ tags:
+ - billing
+ /billing/close-periods:
+ post:
+ description: "Closes the subscriptions whose period has ended now, rather than at the period-close job's next pass: each one's RENEWAL invoice is composed and issued (or held, when its usage journal fails a check) and its period advances. A subscription several periods behind closes one period at a time until it is current. Only what is due closes, at most the configured batch size per call. Requires billing to be enabled for the organization."
+ operationId: closeBillingPeriods
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/ClosePeriodsScope"
+ required: true
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/ClosePeriodsReport"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - write:billing
+ summary: Close due billing periods
+ tags:
+ - billing
+ /billing/handoff:
+ get:
+ description: "The invoices waiting for the organization's accounting system (PENDING), or already acknowledged by it, oldest issue first. Reading never leases: claim to take invoices. Requires billing to be enabled for the organization."
+ operationId: listHandoff
+ parameters:
+ - description: PENDING (the default) or ACKNOWLEDGED; .InvalidStatus otherwise
+ explode: false
+ in: query
+ name: status
+ schema:
+ description: PENDING (the default) or ACKNOWLEDGED; .InvalidStatus otherwise
+ enum:
+ - PENDING
+ - ACKNOWLEDGED
+ type: string
+ - description: Opaque cursor from the previous page's nextCursor
+ explode: false
+ in: query
+ name: cursor
+ schema:
+ description: Opaque cursor from the previous page's nextCursor
+ type: string
+ - description: Page size, 50 by default, 200 at most
+ explode: false
+ in: query
+ name: limit
+ schema:
+ description: Page size, 50 by default, 200 at most
+ format: int32
+ maximum: 200
+ minimum: 0
+ type: integer
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/PageQueuedInvoice"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - read:billing
+ summary: List the handoff queue
+ tags:
+ - billing
+ /billing/handoff/claim:
+ post:
+ description: "Leases the oldest invoices waiting for the organization's accounting system, under one lease. Two consumers claiming at once get different invoices. An invoice not acknowledged before its lease expires is claimed again: the queue delivers at least once, so a consumer deduplicates on the invoice id. A claim is not idempotent: a lost response leaves its invoices leased until leasedUntil. Requires billing to be enabled for the organization."
+ operationId: claimHandoff
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/HandoffClaimSize"
+ required: true
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/HandoffClaim"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - write:billing
+ summary: Claim invoices from the handoff queue
+ tags:
+ - billing
+ /billing/handoff/{invoiceId}/ack:
+ post:
+ description: "Records that the organization's accounting system booked an invoice from the handoff queue. Safe to repeat: the same acknowledgement again, or one that adds a reference the invoice did not have, answers 200. Another reference than the one it was acknowledged under is refused (AckHandoff.ReferenceMismatch), which a consumer can take as already handed off. Requires billing to be enabled for the organization."
+ operationId: ackHandoff
+ parameters:
+ - description: Invoice identifier
+ in: path
+ name: invoiceId
+ required: true
+ schema:
+ description: Invoice identifier
+ type: string
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/HandoffBooking"
+ required: true
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Invoice"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - write:billing
+ summary: Acknowledge a handed-off invoice
+ tags:
+ - billing
+ /billing/health:
+ get:
+ description: "What the organization's billing needs attention for, computed when read: periods waiting for their close, held invoices, invoices a payment provider keeps refusing, reconciliation mismatches, each provider's sync, overdue invoices and the handoff backlog. Requires billing to be enabled for the organization."
+ operationId: getBillingHealth
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/BillingHealth"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - read:billing
+ summary: Get the billing health
+ tags:
+ - billing
+ /billing/settings:
+ get:
+ description: "The organization's billing defaults: the collection method and payment terms a subscription gets when it names none of its own. An organization that never wrote them has the defaults (SEND_INVOICE, 30 days)."
+ operationId: getBillingSettings
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/BillingSettings"
+ description: OK
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - read:billing
+ summary: Get billing settings
+ tags:
+ - billing
+ put:
+ description: Replaces the organization's billing defaults. A subscription that names its own terms keeps them; the others take these at their next invoice.
+ operationId: updateBillingSettings
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/BillingSettings"
+ required: true
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/BillingSettings"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - write:billing
+ summary: Update billing settings
+ tags:
+ - billing
+ /billing/sync:
+ post:
+ description: "Runs one sync pass now for each payment provider the organization has connected: payments, voids and finalizations done in the provider are mirrored, as the periodic pass does. Refused when none is connected (409 SyncProvider.NotConnected). Requires billing to be enabled for the organization."
+ operationId: syncBillingProvider
+ responses:
+ "202":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/SyncReport"
+ description: Accepted
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - write:billing
+ summary: Sync with the payment providers now
+ tags:
+ - billing
+ /checkout-sessions:
+ get:
+ description: "The organization's anonymous checkouts, newest first: how a vendor that missed or receives no checkout_session events links a buyer to its own account. Never a ksc_ or a voucher code. Requires billing to be enabled for the organization. 400 CheckoutSessions.InvalidCursor."
+ operationId: listCheckoutSessions
+ parameters:
+ - description: Repeatable; a session OPEN past expiresAt is ABANDONED
+ explode: true
+ in: query
+ name: status
+ schema:
+ description: Repeatable; a session OPEN past expiresAt is ABANDONED
+ items:
+ enum:
+ - OPEN
+ - AWAITING_PAYMENT
+ - COMPLETED
+ - ABANDONED
+ - EXPIRED
+ type: string
+ type:
+ - array
+ - "null"
+ - description: The visitor's e-mail, in any letter case
+ explode: false
+ in: query
+ name: email
+ schema:
+ description: The visitor's e-mail, in any letter case
+ type: string
+ - description: The vendor's own reference, exactly
+ explode: false
+ in: query
+ name: clientReferenceId
+ schema:
+ description: The vendor's own reference, exactly
+ type: string
+ - description: Opaque pagination cursor from a previous response's nextCursor
+ explode: false
+ in: query
+ name: cursor
+ schema:
+ description: Opaque pagination cursor from a previous response's nextCursor
+ type: string
+ - description: Maximum number of entries to return (default 50, max 200)
+ explode: false
+ in: query
+ name: limit
+ schema:
+ description: Maximum number of entries to return (default 50, max 200)
+ format: int32
+ maximum: 200
+ minimum: 1
+ type: integer
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/PageCheckoutSessionAdmin"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - read:billing
+ summary: List checkout sessions
+ tags:
+ - checkout-sessions
+ /checkout-sessions/{sessionId}:
+ get:
+ description: One anonymous checkout, as the vendor reads it. Never a ksc_ or a voucher code. Requires billing to be enabled for the organization. 404 GetCheckoutSessionAdmin.NotFound, also for a session deleted 90 days after it closed.
+ operationId: getCheckoutSessionAdmin
+ parameters:
+ - in: path
+ name: sessionId
+ required: true
+ schema:
+ type: string
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/CheckoutSessionAdmin"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - read:billing
+ summary: Get a checkout session
+ tags:
+ - checkout-sessions
/components:
get:
- description: Returns a cursor-paginated page of components for the current organization.
- operationId: list-components
+ description: Returns a cursor-paginated page of components for the current organization.
+ operationId: list-components
+ parameters:
+ - description: Opaque pagination cursor from a previous response's nextCursor
+ explode: false
+ in: query
+ name: cursor
+ schema:
+ description: Opaque pagination cursor from a previous response's nextCursor
+ type: string
+ - description: Maximum number of entries to return (default 50, max 200)
+ explode: false
+ in: query
+ name: limit
+ schema:
+ description: Maximum number of entries to return (default 50, max 200)
+ format: int32
+ maximum: 200
+ minimum: 1
+ type: integer
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/PageComponent"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - read:components
+ summary: List components
+ tags:
+ - components
+ post:
+ description: Create a new standalone component. Optionally derive it from a previous component version.
+ operationId: create-component
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Component"
+ required: true
+ responses:
+ "201":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Component"
+ description: Created
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - write:components
+ summary: Create a new component
+ tags:
+ - components
+ /components/{componentSlug}:
+ delete:
+ description: Delete a component by slug if it is not linked to any release.
+ operationId: delete-component
+ parameters:
+ - description: Component slug
+ in: path
+ name: componentSlug
+ required: true
+ schema:
+ description: Component slug
+ examples:
+ - api-gateway-v1-2-3
+ type: string
+ responses:
+ "204":
+ description: No Content
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - write:components
+ summary: Delete a component
+ tags:
+ - components
+ get:
+ description: Returns a single component by its slug.
+ operationId: get-component
+ parameters:
+ - description: Component slug
+ in: path
+ name: componentSlug
+ required: true
+ schema:
+ description: Component slug
+ examples:
+ - api-gateway-v1-2-3
+ type: string
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Component"
+ description: OK
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - read:components
+ summary: Get a component by slug
+ tags:
+ - components
+ put:
+ description: Update a component in place when unlinked, or create a new version when it is already linked to releases.
+ operationId: update-component
+ parameters:
+ - description: Component slug
+ in: path
+ name: componentSlug
+ required: true
+ schema:
+ description: Component slug
+ examples:
+ - api-gateway-v1-2-3
+ type: string
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Component"
+ required: true
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Component"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - write:components
+ summary: Update a component
+ tags:
+ - components
+ /connectors:
+ get:
+ description: Returns every registered connector with its version and settings schema, ordered by name. The registry is platform-wide rather than per-organization and holds one row per connector, so the response is not paginated.
+ operationId: list-connectors
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ items:
+ $ref: "#/components/schemas/Connector"
+ type:
+ - array
+ - "null"
+ description: OK
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - read:organizations
+ summary: List registered connectors
+ tags:
+ - connectors
+ /connectors/{connectorName}:
+ get:
+ description: Retrieves a registered connector resource including version and settings schema
+ operationId: get-connector
+ parameters:
+ - description: Stable connector name
+ in: path
+ name: connectorName
+ required: true
+ schema:
+ description: Stable connector name
+ examples:
+ - kaiten.integration.crm.attio
+ type: string
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Connector"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - read:organizations
+ summary: Get connector registration
+ tags:
+ - connectors
+ /connectors/{connectorName}/activation:
+ delete:
+ description: "Deactivates a connector for the authenticated organization. Idempotent, and never refused for an unregistered or unlicensed connector: an organization must always be able to stop using something."
+ operationId: deactivate-connector
+ parameters:
+ - description: Stable connector name
+ in: path
+ name: connectorName
+ required: true
+ schema:
+ description: Stable connector name
+ examples:
+ - kaiten.integration.crm.attio
+ type: string
+ responses:
+ "204":
+ description: No Content
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - write:organizations
+ summary: Deactivate connector
+ tags:
+ - connectors
+ put:
+ description: "Activates a registered connector for the authenticated organization. Idempotent: activating an already-active connector succeeds without changing when it was first activated. Refused when the organization's license does not include the connector."
+ operationId: activate-connector
+ parameters:
+ - description: Stable connector name
+ in: path
+ name: connectorName
+ required: true
+ schema:
+ description: Stable connector name
+ examples:
+ - kaiten.integration.crm.attio
+ type: string
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/ConnectorActivation"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - write:organizations
+ summary: Activate connector
+ tags:
+ - connectors
+ /connectors/{connectorName}/settings:
+ delete:
+ description: Delete connector settings for the authenticated organization
+ operationId: delete-connector-settings
+ parameters:
+ - description: Stable connector name
+ in: path
+ name: connectorName
+ required: true
+ schema:
+ description: Stable connector name
+ examples:
+ - kaiten.integration.crm.attio
+ type: string
+ responses:
+ "204":
+ description: No Content
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - write:organizations
+ summary: Delete connector settings
+ tags:
+ - connectors
+ get:
+ description: Get settings for a connector scoped to the authenticated organization. Write-only fields (flagged `writeOnly` in the connector settings schema) are returned redacted as `***`.
+ operationId: get-connector-settings
+ parameters:
+ - description: Stable connector name
+ in: path
+ name: connectorName
+ required: true
+ schema:
+ description: Stable connector name
+ examples:
+ - kaiten.integration.crm.attio
+ type: string
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/ConnectorSettings"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - read:organizations
+ summary: Get connector settings
+ tags:
+ - connectors
+ put:
+ description: Create or update connector settings for the authenticated organization. Write-only fields (flagged `writeOnly` in the connector settings schema) may be omitted or sent as `***` to keep the stored value; they are returned redacted.
+ operationId: update-connector-settings
+ parameters:
+ - description: Stable connector name
+ in: path
+ name: connectorName
+ required: true
+ schema:
+ description: Stable connector name
+ examples:
+ - kaiten.integration.crm.attio
+ type: string
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/ConnectorSettings"
+ description: Connector settings payload to upsert
+ required: true
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/ConnectorSettings"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - write:organizations
+ summary: Upsert connector settings
+ tags:
+ - connectors
+ /connectors/{connectorName}/settings/schema:
+ get:
+ description: Get the JSON schema used to validate connector settings payloads
+ operationId: get-connector-settings-schema
+ parameters:
+ - description: Stable connector name
+ in: path
+ name: connectorName
+ required: true
+ schema:
+ description: Stable connector name
+ examples:
+ - kaiten.integration.crm.attio
+ type: string
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/ConnectorSettingsSchema"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - read:organizations
+ summary: Get connector settings schema
+ tags:
+ - connectors
+ /connectors/{connectorName}/state:
+ get:
+ description: "Returns whether the connector is registered on this installation, whether the authenticated organization's license includes it, and whether the organization has activated it. Answers 200 with `available: false` for a connector this installation does not have."
+ operationId: get-connector-state
+ parameters:
+ - description: Stable connector name
+ in: path
+ name: connectorName
+ required: true
+ schema:
+ description: Stable connector name
+ examples:
+ - kaiten.integration.crm.attio
+ type: string
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/ConnectorState"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - read:organizations
+ summary: Get connector state for this organization
+ tags:
+ - connectors
+ /customer-sessions:
+ post:
+ description: "Mints a kst_ session for one of the organization's customers, optionally bound to one of its instances, for that customer's browser to call the /api/public/session routes: checkout, invoices. Call it from your backend, where the customer is signed in, and hand the token to the page; mint a new one before it expires. What the session writes is attributed to the caller. Limited to 50 sessions a second per organization. Requires billing to be enabled for the organization."
+ operationId: createCustomerSession
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/CustomerSessionDraft"
+ required: true
+ responses:
+ "201":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/CreatedCustomerSession"
+ description: Created
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "429":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Too Many Requests
+ headers:
+ Retry-After:
+ description: Seconds to wait before the next attempt
+ schema:
+ minimum: 0
+ type: integer
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - write:customer_sessions
+ summary: Create a customer session
+ tags:
+ - customer-sessions
+ /customer-sessions/{sessionId}/revoke:
+ post:
+ description: "The session stops authenticating on the next request: on sign-out, or when the customer's access changes. Idempotent. Requires billing to be enabled for the organization."
+ operationId: revokeCustomerSession
+ parameters:
+ - description: Customer session id
+ in: path
+ name: sessionId
+ required: true
+ schema:
+ description: Customer session id
+ type: string
+ responses:
+ "204":
+ description: No Content
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - write:customer_sessions
+ summary: Revoke a customer session
+ tags:
+ - customer-sessions
+ /customers:
+ get:
+ description: Returns a cursor-paginated page of customers for the current organization.
+ operationId: list-customers
+ parameters:
+ - description: Opaque pagination cursor from a previous response's nextCursor
+ explode: false
+ in: query
+ name: cursor
+ schema:
+ description: Opaque pagination cursor from a previous response's nextCursor
+ type: string
+ - description: Maximum number of entries to return (default 50, max 200)
+ explode: false
+ in: query
+ name: limit
+ schema:
+ description: Maximum number of entries to return (default 50, max 200)
+ format: int32
+ maximum: 200
+ minimum: 1
+ type: integer
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/PageCustomer"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - read:customers
+ summary: List customers
+ tags:
+ - customers
+ post:
+ description: Create a new customer with the provided details.
+ operationId: create-customer
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Customer"
+ required: true
+ responses:
+ "201":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Customer"
+ description: Created
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - write:customers
+ summary: Create a new customer
+ tags:
+ - customers
+ /customers/{customerSlug}:
+ delete:
+ description: Delete a customer by their slug.
+ operationId: delete-customer
+ parameters:
+ - in: path
+ name: customerSlug
+ required: true
+ schema:
+ type: string
+ responses:
+ "204":
+ description: No Content
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - write:customers
+ summary: Delete a customer
+ tags:
+ - customers
+ get:
+ description: Returns a single customer by their slug.
+ operationId: get-customer
+ parameters:
+ - in: path
+ name: customerSlug
+ required: true
+ schema:
+ type: string
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Customer"
+ description: OK
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - read:customers
+ summary: Get a customer by slug
+ tags:
+ - customers
+ put:
+ description: Update a customer's details. Slug and integrations are not renameable/settable through this endpoint -- omit them, or send the values already on record.
+ operationId: update-customer
+ parameters:
+ - in: path
+ name: customerSlug
+ required: true
+ schema:
+ type: string
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Customer"
+ required: true
+ responses:
+ "204":
+ description: No Content
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - write:customers
+ summary: Update a customer
+ tags:
+ - customers
+ /customers/{customerSlug}/billing:
+ get:
+ description: "The customer's billing e-mail and its side in each payment provider: its id there and the labels of its default payment method (never card data). 404 GetCustomerBilling.CustomerNotFound. Requires billing to be enabled for the organization."
+ operationId: getCustomerBilling
+ parameters:
+ - description: Customer slug
+ in: path
+ name: customerSlug
+ required: true
+ schema:
+ description: Customer slug
+ type: string
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/CustomerBilling"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - read:billing
+ summary: Get a customer's billing
+ tags:
+ - billing
+ /customers/{customerSlug}/billing/payment-method:
+ delete:
+ description: Removes the customer's payment method from the payment provider. 409 .InUseByAutomaticCollection while a live subscription of the customer is charged automatically, .NoPaymentMethod; 503 .ProviderUnavailable. Requires billing to be enabled for the organization.
+ operationId: detachPaymentMethod
+ parameters:
+ - description: Customer slug
+ in: path
+ name: customerSlug
+ required: true
+ schema:
+ description: Customer slug
+ type: string
+ responses:
+ "204":
+ description: No Content
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - write:billing
+ summary: Detach a customer's payment method
+ tags:
+ - billing
+ /customers/{customerSlug}/billing/payment-method-session:
+ post:
+ description: Ensures the customer in the payment provider, then opens the provider's hosted page where the customer saves a payment method; Kaiten never sees card data. When the customer comes back to returnUrl, complete the session server-side. 404 .CustomerNotFound; 422 .ProviderNotConnected, .InvalidReturnUrl, .CurrencyRequired; 503 .ProviderUnavailable. Requires billing to be enabled for the organization.
+ operationId: createPaymentMethodSession
+ parameters:
+ - description: Customer slug
+ in: path
+ name: customerSlug
+ required: true
+ schema:
+ description: Customer slug
+ type: string
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/NewPaymentMethodSession"
+ required: true
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/PaymentMethodSession"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - write:billing
+ summary: Open a page saving a customer's payment method
+ tags:
+ - billing
+ /customers/{customerSlug}/billing/payment-method-session/{sessionId}/complete:
+ post:
+ description: Checks the setup session with the payment provider (the redirect is never trusted) and makes the payment method saved there the one the customer's invoices are charged to. Idempotent. 404 .SessionNotFound (unknown, or another customer's); 409 .SessionNotComplete; 503 .ProviderUnavailable. Requires billing to be enabled for the organization.
+ operationId: completePaymentMethodSession
+ parameters:
+ - description: Customer slug
+ in: path
+ name: customerSlug
+ required: true
+ schema:
+ description: Customer slug
+ type: string
+ - description: The setup session id the customer came back with
+ in: path
+ name: sessionId
+ required: true
+ schema:
+ description: The setup session id the customer came back with
+ type: string
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/CompletedPaymentMethodSession"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - write:billing
+ summary: Complete a payment-method session
+ tags:
+ - billing
+ /customers/{customerSlug}/billing/portal-session:
+ post:
+ description: Opens the payment provider's hosted portal for the customer, where it manages payment methods and invoices; changes reach Kaiten through sync. 422 .ProviderNotConnected, .CustomerNotOnProvider, .InvalidReturnUrl; 503 .ProviderUnavailable. Requires billing to be enabled for the organization.
+ operationId: createPortalSession
+ parameters:
+ - description: Customer slug
+ in: path
+ name: customerSlug
+ required: true
+ schema:
+ description: Customer slug
+ type: string
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/NewPortalSession"
+ required: true
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/PortalSession"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - write:billing
+ summary: Open a customer's billing portal
+ tags:
+ - billing
+ /customers/{customerSlug}/integrations/{integrationName}:
+ delete:
+ description: Delete a single integration for a customer
+ operationId: delete-customer-integration
+ parameters:
+ - description: Customer slug
+ in: path
+ name: customerSlug
+ required: true
+ schema:
+ description: Customer slug
+ examples:
+ - acme-corp
+ type: string
+ - description: Integration adapter name
+ in: path
+ name: integrationName
+ required: true
+ schema:
+ description: Integration adapter name
+ examples:
+ - kaiten.integration.crm.attio
+ type: string
+ responses:
+ "204":
+ description: No Content
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - write:customers
+ summary: Delete a customer integration
+ tags:
+ - customers
+ get:
+ description: Retrieve a single integration for a customer
+ operationId: get-customer-integration
+ parameters:
+ - description: Customer slug
+ in: path
+ name: customerSlug
+ required: true
+ schema:
+ description: Customer slug
+ examples:
+ - acme-corp
+ type: string
+ - description: Integration adapter name
+ in: path
+ name: integrationName
+ required: true
+ schema:
+ description: Integration adapter name
+ examples:
+ - kaiten.integration.crm.attio
+ type: string
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/CustomerIntegration"
+ description: OK
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - read:customers
+ summary: Get a customer integration
+ tags:
+ - customers
+ post:
+ description: Create a single integration for a customer
+ operationId: create-customer-integration
+ parameters:
+ - description: Customer slug
+ in: path
+ name: customerSlug
+ required: true
+ schema:
+ description: Customer slug
+ examples:
+ - acme-corp
+ type: string
+ - description: Integration adapter name
+ in: path
+ name: integrationName
+ required: true
+ schema:
+ description: Integration adapter name
+ examples:
+ - kaiten.integration.crm.attio
+ type: string
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/CustomerIntegration"
+ description: Integration payload
+ required: true
+ responses:
+ "201":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/CustomerIntegration"
+ description: Created
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - write:customers
+ summary: Create a customer integration
+ tags:
+ - customers
+ put:
+ description: Update a single integration for a customer
+ operationId: update-customer-integration
+ parameters:
+ - description: Customer slug
+ in: path
+ name: customerSlug
+ required: true
+ schema:
+ description: Customer slug
+ examples:
+ - acme-corp
+ type: string
+ - description: Integration adapter name
+ in: path
+ name: integrationName
+ required: true
+ schema:
+ description: Integration adapter name
+ examples:
+ - kaiten.integration.crm.attio
+ type: string
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/CustomerIntegration"
+ description: Integration payload
+ required: true
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/CustomerIntegration"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - write:customers
+ summary: Update a customer integration
+ tags:
+ - customers
+ /deployment-zones:
+ get:
+ description: Returns a cursor-paginated page of deployment zones for the current organization. A deployment zone is one place a customer runs a release -- a target they name and classify themselves, holding the release currently on it. It is not an installation of Kaiten; that sense of the term belongs to operations and is not reachable through this API.
+ operationId: list-deployment-zones
+ parameters:
+ - description: Opaque pagination cursor from a previous response's nextCursor
+ explode: false
+ in: query
+ name: cursor
+ schema:
+ description: Opaque pagination cursor from a previous response's nextCursor
+ type: string
+ - description: Maximum number of entries to return (default 50, max 200)
+ explode: false
+ in: query
+ name: limit
+ schema:
+ description: Maximum number of entries to return (default 50, max 200)
+ format: int32
+ maximum: 200
+ minimum: 1
+ type: integer
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/PageDeploymentZone"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - read:deployment_zones
+ summary: List deployment zones
+ tags:
+ - deploymentZones
+ post:
+ description: Create a new deployment zone with the provided details. Supplying releaseId records the zone's first deployment. A deployment zone is one place a customer runs a release -- a target they name and classify themselves, holding the release currently on it. It is not an installation of Kaiten; that sense of the term belongs to operations and is not reachable through this API.
+ operationId: create-deployment-zone
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/DeploymentZone"
+ required: true
+ responses:
+ "201":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/DeploymentZone"
+ description: Created
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - write:deployment_zones
+ summary: Create a new deployment zone
+ tags:
+ - deploymentZones
+ /deployment-zones/{deploymentZoneSlug}:
+ delete:
+ description: Delete a deployment zone by their slug.
+ operationId: delete-deployment-zone
+ parameters:
+ - in: path
+ name: deploymentZoneSlug
+ required: true
+ schema:
+ type: string
+ responses:
+ "204":
+ description: No Content
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - write:deployment_zones
+ summary: Delete a deployment zone
+ tags:
+ - deploymentZones
+ get:
+ description: Returns a single deployment zone by their slug.
+ operationId: get-deployment-zone-by-slug
+ parameters:
+ - in: path
+ name: deploymentZoneSlug
+ required: true
+ schema:
+ type: string
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/DeploymentZone"
+ description: OK
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - read:deployment_zones
+ summary: Get a deployment zone by slug
+ tags:
+ - deploymentZones
+ put:
+ description: "Update a deployment zone's details. Changing releaseId records a deployment: the zone's current release becomes the new one and the change is appended to its deployment history, including when the new release is one the zone already ran (a rollback). Omitting releaseId, or echoing back the current one, leaves the history untouched."
+ operationId: update-deploymentZone
+ parameters:
+ - in: path
+ name: deploymentZoneSlug
+ required: true
+ schema:
+ type: string
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/DeploymentZone"
+ required: true
+ responses:
+ "204":
+ description: No Content
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - write:deployment_zones
+ summary: Update a deployment zone
+ tags:
+ - deploymentZones
+ /entitlement-groups:
+ get:
+ description: Returns a cursor-paginated page of entitlement groups for the current organization.
+ operationId: list-entitlement-groups
+ parameters:
+ - description: Opaque pagination cursor from a previous response's nextCursor
+ explode: false
+ in: query
+ name: cursor
+ schema:
+ description: Opaque pagination cursor from a previous response's nextCursor
+ type: string
+ - description: Maximum number of entries to return (default 50, max 200)
+ explode: false
+ in: query
+ name: limit
+ schema:
+ description: Maximum number of entries to return (default 50, max 200)
+ format: int32
+ maximum: 200
+ minimum: 1
+ type: integer
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/PageEntitlementGroup"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - read:entitlements
+ summary: List all entitlement groups
+ tags:
+ - entitlement-groups
+ post:
+ description: Create a new entitlement group to aggregate entitlements
+ operationId: create-entitlement-group
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/EntitlementGroup"
+ required: true
+ responses:
+ "201":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/EntitlementGroup"
+ description: Created
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - write:entitlements
+ summary: Create a new entitlement group
+ tags:
+ - entitlement-groups
+ /entitlement-groups/{entitlementGroupSlug}:
+ delete:
+ description: Delete an entitlement group by its slug. This does not delete the member entitlements.
+ operationId: delete-entitlement-group
+ parameters:
+ - in: path
+ name: entitlementGroupSlug
+ required: true
+ schema:
+ type: string
+ responses:
+ "204":
+ description: No Content
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - write:entitlements
+ summary: Delete an entitlement group
+ tags:
+ - entitlement-groups
+ get:
+ description: Retrieve details of a specific entitlement group by its slug
+ operationId: get-entitlement-group
+ parameters:
+ - in: path
+ name: entitlementGroupSlug
+ required: true
+ schema:
+ type: string
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/EntitlementGroup"
+ description: OK
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - read:entitlements
+ summary: Get entitlement group by slug
+ tags:
+ - entitlement-groups
+ put:
+ description: Update an existing entitlement group with the provided details
+ operationId: update-entitlement-group
+ parameters:
+ - in: path
+ name: entitlementGroupSlug
+ required: true
+ schema:
+ type: string
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/EntitlementGroup"
+ required: true
+ responses:
+ "204":
+ description: No Content
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - write:entitlements
+ summary: Update an entitlement group
+ tags:
+ - entitlement-groups
+ /entitlement-groups/{entitlementGroupSlug}/entitlements:
+ post:
+ description: Add an existing entitlement as a member of an entitlement group
+ operationId: add-entitlement-to-group
+ parameters:
+ - in: path
+ name: entitlementGroupSlug
+ required: true
+ schema:
+ type: string
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Command"
+ required: true
+ responses:
+ "201":
+ description: Created
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - write:entitlements
+ summary: Add an entitlement to a group
+ tags:
+ - entitlement-groups
+ /entitlement-groups/{entitlementGroupSlug}/entitlements/{entitlementSlug}:
+ delete:
+ description: Remove an entitlement from an entitlement group. The entitlement itself is not deleted.
+ operationId: remove-entitlement-from-group
+ parameters:
+ - in: path
+ name: entitlementGroupSlug
+ required: true
+ schema:
+ type: string
+ - in: path
+ name: entitlementSlug
+ required: true
+ schema:
+ type: string
+ responses:
+ "204":
+ description: No Content
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - write:entitlements
+ summary: Remove an entitlement from a group
+ tags:
+ - entitlement-groups
+ /entitlement-groups/{entitlementGroupSlug}/usage:
+ get:
+ description: Retrieve the aggregated usage metrics of all member entitlements for a given instance
+ operationId: get-entitlement-group-usage
+ parameters:
+ - in: path
+ name: entitlementGroupSlug
+ required: true
+ schema:
+ type: string
+ - description: Instance slug to get usage for
+ explode: false
+ in: query
+ name: instance
+ required: true
+ schema:
+ description: Instance slug to get usage for
+ type: string
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ items:
+ $ref: "#/components/schemas/EntitlementGroupUsage"
+ type:
+ - array
+ - "null"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - read:entitlements
+ summary: Get aggregated usage for an entitlement group
+ tags:
+ - entitlement-groups
+ /entitlements:
+ get:
+ description: Returns a cursor-paginated page of entitlements for the current organization.
+ operationId: list-entitlements
+ parameters:
+ - description: Opaque pagination cursor from a previous response's nextCursor
+ explode: false
+ in: query
+ name: cursor
+ schema:
+ description: Opaque pagination cursor from a previous response's nextCursor
+ type: string
+ - description: Maximum number of entries to return (default 50, max 200)
+ explode: false
+ in: query
+ name: limit
+ schema:
+ description: Maximum number of entries to return (default 50, max 200)
+ format: int32
+ maximum: 200
+ minimum: 1
+ type: integer
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/PageEntitlement"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - read:entitlements
+ summary: List all entitlements
+ tags:
+ - entitlements
+ post:
+ description: Create a new entitlement with the provided details
+ operationId: create-entitlement
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Entitlement"
+ required: true
+ responses:
+ "201":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Entitlement"
+ description: Created
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - write:entitlements
+ summary: Create a new entitlement
+ tags:
+ - entitlements
+ /entitlements/{entitlementSlug}:
+ delete:
+ description: "Delete an entitlement by its slug. Its usage is deleted with it. An entitlement that a license or an add-on still grants, a license or an add-on price still meters, or a voucher boost still grants is refused with a 409 that counts each: remove the grants first. A price and a voucher boost are never deleted, so the entitlement they hold stays."
+ operationId: delete-entitlement
+ parameters:
+ - in: path
+ name: entitlementSlug
+ required: true
+ schema:
+ type: string
+ responses:
+ "204":
+ description: No Content
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - write:entitlements
+ summary: Delete an entitlement
+ tags:
+ - entitlements
+ get:
+ description: Retrieve details of a specific entitlement by its slug
+ operationId: get-entitlement
+ parameters:
+ - in: path
+ name: entitlementSlug
+ required: true
+ schema:
+ type: string
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Entitlement"
+ description: OK
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - read:entitlements
+ summary: Get entitlement by slug
+ tags:
+ - entitlements
+ put:
+ description: Update an existing entitlement with the provided details
+ operationId: update-entitlement
+ parameters:
+ - in: path
+ name: entitlementSlug
+ required: true
+ schema:
+ type: string
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Entitlement"
+ required: true
+ responses:
+ "204":
+ description: No Content
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - write:entitlements
+ summary: Update an entitlement
+ tags:
+ - entitlements
+ /feature-flags:
+ get:
+ description: Returns a cursor-paginated page of feature flags for the given organization.
+ operationId: get-feature-flags
+ parameters:
+ - description: Opaque pagination cursor from a previous response's nextCursor
+ explode: false
+ in: query
+ name: cursor
+ schema:
+ description: Opaque pagination cursor from a previous response's nextCursor
+ type: string
+ - description: Maximum number of entries to return (default 50, max 200)
+ explode: false
+ in: query
+ name: limit
+ schema:
+ description: Maximum number of entries to return (default 50, max 200)
+ format: int32
+ maximum: 200
+ minimum: 1
+ type: integer
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/PageFeatureFlag"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - read:feature_flags
+ summary: Get a list of feature flags
+ tags:
+ - featureflags
+ post:
+ description: Create a new featureflag with the provided details
+ operationId: create-feature-flag
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/FeatureFlag"
+ description: Feature flag details
+ required: true
+ responses:
+ "201":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/FeatureFlag"
+ description: Created feature flag
+ description: Created
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - write:feature_flags
+ summary: Create a new featureflag
+ tags:
+ - featureflags
+ /feature-flags/targeting/context:
+ get:
+ description: "Returns the identifiers a targeting rule may read, with their types and descriptions, including the organization's own entitlement slugs. Intended for an editor: it is what autocomplete and hover documentation are built from. The set is not closed — a rule may also target on attributes the caller supplies in its own evaluation context."
+ operationId: get-targeting-context
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/TargetingContext"
+ description: OK
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - read:feature_flags
+ summary: Get the targeting context schema
+ tags:
+ - featureflags
+ /feature-flags/targeting/lint:
+ post:
+ description: "Runs the same check create and update run before accepting a targeting rule, and reports what it found without writing anything. A rule that references a fact or an entitlement that does not exist does not fail loudly at evaluation — it simply never matches — so this is what lets an editor say so while the rule is being written. A rejected rule is a 200 carrying `valid: false`, not a client error."
+ operationId: lint-targeting-rule
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/TargetingRuleDraft"
+ required: true
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/TargetingRuleVerdict"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - write:feature_flags
+ summary: Check a targeting rule
+ tags:
+ - featureflags
+ /feature-flags/targeting/test:
+ post:
+ description: "Runs a rule the way an evaluation would, without a flag and without writing anything: the context is enriched from the same sources (license, entitlements, instance, customer, deployment zone), anything the caller forged under __kaiten is discarded the same way, and the same engine answers. The response carries the enriched facts so an author can see exactly what the rule read — which is usually the answer to why it did or did not match. Nothing is metered or audited: this is authoring, not serving."
+ operationId: test-targeting-rule
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/TargetingRuleTrial"
+ required: true
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/TargetingRuleRehearsal"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - write:feature_flags
+ summary: Rehearse a targeting rule against a context
+ tags:
+ - featureflags
+ /feature-flags/{featureFlagSlug}:
+ delete:
+ description: Delete a featureflag with the provided details. A flag a voucher grants a variant of cannot be deleted (409 DeleteFeatureFlag.InUseConflict); it can be disabled.
+ operationId: delete-feature-flag
+ parameters:
+ - in: path
+ name: featureFlagSlug
+ required: true
+ schema:
+ type: string
+ responses:
+ "204":
+ description: No Content
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - write:feature_flags
+ summary: Delete a featureflag
+ tags:
+ - featureflags
+ get:
+ description: Get a feature flag with the provided slug
+ operationId: get-feature-flag
+ parameters:
+ - in: path
+ name: featureFlagSlug
+ required: true
+ schema:
+ type: string
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/FeatureFlag"
+ description: OK
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - read:feature_flags
+ summary: Get a feature flag by slug
+ tags:
+ - featureflags
+ put:
+ description: Update a featureflag with the provided details. Sending a slug renames the flag; omitting it keeps the current one.
+ operationId: update-feature-flag
+ parameters:
+ - in: path
+ name: featureFlagSlug
+ required: true
+ schema:
+ type: string
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/FeatureFlag"
+ description: Feature flag details
+ required: true
+ responses:
+ "204":
+ description: No Content
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - write:feature_flags
+ summary: Update a featureflag
+ tags:
+ - featureflags
+ /instances:
+ get:
+ description: Returns a cursor-paginated page of instances for the current organization.
+ operationId: getInstances
+ parameters:
+ - description: Opaque pagination cursor from a previous response's nextCursor
+ explode: false
+ in: query
+ name: cursor
+ schema:
+ description: Opaque pagination cursor from a previous response's nextCursor
+ type: string
+ - description: Maximum number of entries to return (default 50, max 200)
+ explode: false
+ in: query
+ name: limit
+ schema:
+ description: Maximum number of entries to return (default 50, max 200)
+ format: int32
+ maximum: 200
+ minimum: 1
+ type: integer
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/PageInstance"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - read:instances
+ summary: List all instances
+ tags:
+ - instances
+ post:
+ description: Create a new instance with the provided details
+ operationId: createInstance
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Instance"
+ required: true
+ responses:
+ "201":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Instance"
+ description: Created
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - write:instances
+ summary: Create a new instance
+ tags:
+ - instances
+ /instances/{instanceSlug}:
+ delete:
+ description: Delete an instance with the provided slug
+ operationId: deleteInstance
+ parameters:
+ - description: Instance slug
+ in: path
+ name: instanceSlug
+ required: true
+ schema:
+ description: Instance slug
+ examples:
+ - instance-slug
+ type: string
+ responses:
+ "204":
+ description: No Content
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - write:instances
+ summary: Delete an instance
+ tags:
+ - instances
+ get:
+ description: Retrieve an instance by their unique slug
+ operationId: getInstance
+ parameters:
+ - description: Instance slug
+ in: path
+ name: instanceSlug
+ required: true
+ schema:
+ description: Instance slug
+ examples:
+ - instance-slug
+ type: string
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Instance"
+ description: OK
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - read:instances
+ summary: Get an instance by slug
+ tags:
+ - instances
+ patch:
+ description: Partially update an instance. Currently supports the operational status and the commercial lifecycle stage.
+ operationId: patchInstance
+ parameters:
+ - description: Instance slug
+ in: path
+ name: instanceSlug
+ required: true
+ schema:
+ description: Instance slug
+ examples:
+ - instance-slug
+ type: string
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/PatchInstanceBody"
+ required: true
+ responses:
+ "204":
+ description: No Content
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - write:instances
+ summary: Patch an instance
+ tags:
+ - instances
+ put:
+ description: Update an instance with the provided details
+ operationId: updateInstance
+ parameters:
+ - description: Instance slug
+ in: path
+ name: instanceSlug
+ required: true
+ schema:
+ description: Instance slug
+ examples:
+ - instance-slug
+ type: string
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Instance"
+ required: true
+ responses:
+ "204":
+ description: No Content
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - write:instances
+ summary: Update an instance
+ tags:
+ - instances
+ /instances/{instanceSlug}/addons:
+ get:
+ description: The add-ons an instance holds, in attachment order, each with the prices its subscription bills. Requires billing to be enabled for the organization.
+ operationId: listInstanceAddons
+ parameters:
+ - description: Instance slug
+ in: path
+ name: instanceSlug
+ required: true
+ schema:
+ description: Instance slug
+ examples:
+ - acme-prod
+ type: string
+ - description: Also list the add-ons removed from the instance
+ explode: false
+ in: query
+ name: includeRemoved
+ schema:
+ description: Also list the add-ons removed from the instance
+ type: boolean
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ items:
+ $ref: "#/components/schemas/InstanceAddon"
+ type: array
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - read:instances
+ summary: List an instance's add-ons
+ tags:
+ - instances
+ post:
+ description: "Gives an instance an add-on version and a quantity. The entitlements apply at once; a subscription bills the quantity held at each boundary, without proration. One version per family: moving to another version is detach then attach. Emits INSTANCE_ADDON_ADDED. Requires billing to be enabled for the organization."
+ operationId: attachInstanceAddon
+ parameters:
+ - description: Instance slug
+ in: path
+ name: instanceSlug
+ required: true
+ schema:
+ description: Instance slug
+ examples:
+ - acme-prod
+ type: string
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/NewInstanceAddon"
+ required: true
+ responses:
+ "201":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/InstanceAddon"
+ description: Created
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ headers:
+ Retry-After:
+ description: "On .BoundaryPending: seconds until the period's close is expected to have run"
+ schema:
+ minimum: 0
+ type: integer
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - write:instances
+ summary: Attach an add-on to an instance
+ tags:
+ - instances
+ /instances/{instanceSlug}/addons/{addonSlug}:
+ delete:
+ description: Removes an add-on from an instance, at once, a removal the customer scheduled included. The entitlements drop at once; nothing is refunded. The attachment stays readable with includeRemoved. Emits INSTANCE_ADDON_REMOVED. Requires billing to be enabled for the organization.
+ operationId: detachInstanceAddon
+ parameters:
+ - description: Instance slug
+ in: path
+ name: instanceSlug
+ required: true
+ schema:
+ description: Instance slug
+ examples:
+ - acme-prod
+ type: string
+ - description: Add-on version slug
+ in: path
+ name: addonSlug
+ required: true
+ schema:
+ description: Add-on version slug
+ examples:
+ - extra-seats-v1
+ type: string
+ responses:
+ "204":
+ description: No Content
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ headers:
+ Retry-After:
+ description: "On .BoundaryPending: seconds until the period's close is expected to have run"
+ schema:
+ minimum: 0
+ type: integer
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - write:instances
+ summary: Remove an add-on from an instance
+ tags:
+ - instances
+ patch:
+ description: Changes how many units of an add-on an instance holds, at once, a decrease included. The entitlements follow at once; the next invoice bills the new quantity. It cancels a decrease the customer scheduled (pendingQuantity), even when the quantity is the one held. Emits INSTANCE_ADDON_QUANTITY_CHANGED. Requires billing to be enabled for the organization.
+ operationId: setInstanceAddonQuantity
+ parameters:
+ - description: Instance slug
+ in: path
+ name: instanceSlug
+ required: true
+ schema:
+ description: Instance slug
+ examples:
+ - acme-prod
+ type: string
+ - description: Add-on version slug
+ in: path
+ name: addonSlug
+ required: true
+ schema:
+ description: Add-on version slug
+ examples:
+ - extra-seats-v1
+ type: string
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/AddonQuantity"
+ required: true
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/InstanceAddon"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ headers:
+ Retry-After:
+ description: "On .BoundaryPending: seconds until the period's close is expected to have run"
+ schema:
+ minimum: 0
+ type: integer
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - write:instances
+ summary: Change an instance add-on's quantity
+ tags:
+ - instances
+ /instances/{instanceSlug}/audit-trails:
+ get:
+ description: "Returns audit trail events for a given instance, optionally filtered by event name and time range. Results are cursor-paginated (default limit: 50, max: 200) — pass the previous response's nextCursor as the cursor parameter to fetch the next page."
+ operationId: getAuditTrails
+ parameters:
+ - description: Instance slug
+ in: path
+ name: instanceSlug
+ required: true
+ schema:
+ description: Instance slug
+ examples:
+ - instance-slug
+ type: string
+ - description: Filter by exact event name (e.g. ENTITLEMENT_VALUE_GET)
+ explode: false
+ in: query
+ name: event_name
+ schema:
+ description: Filter by exact event name (e.g. ENTITLEMENT_VALUE_GET)
+ type: string
+ - description: RFC3339 timestamp — include entries with timestamp ≥ this value
+ explode: false
+ in: query
+ name: after
+ schema:
+ description: RFC3339 timestamp — include entries with timestamp ≥ this value
+ type: string
+ - description: RFC3339 timestamp — include entries with timestamp ≤ this value
+ explode: false
+ in: query
+ name: before
+ schema:
+ description: RFC3339 timestamp — include entries with timestamp ≤ this value
+ type: string
+ - description: Maximum number of entries to return (default 50, max 200)
+ explode: false
+ in: query
+ name: limit
+ schema:
+ description: Maximum number of entries to return (default 50, max 200)
+ format: int32
+ maximum: 200
+ minimum: 1
+ type: integer
+ - description: Opaque pagination cursor from a previous response's nextCursor
+ explode: false
+ in: query
+ name: cursor
+ schema:
+ description: Opaque pagination cursor from a previous response's nextCursor
+ type: string
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/PageAuditTrail"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - read:instances
+ summary: List audit trail entries for an instance
+ tags:
+ - instances
+ /instances/{instanceSlug}/billing:
+ get:
+ description: The instance's subscription, live or CANCELED; 404 when it was never subscribed. Requires billing to be enabled for the organization.
+ operationId: getInstanceBilling
+ parameters:
+ - description: Instance slug
+ in: path
+ name: instanceSlug
+ required: true
+ schema:
+ description: Instance slug
+ examples:
+ - acme-prod
+ type: string
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/InstanceBilling"
+ description: OK
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - read:billing
+ summary: Get an instance's subscription
+ tags:
+ - billing
+ patch:
+ description: Changes the subscription's payment provider, collection method and payment terms from its next invoice on; null restores the organization's default. Invoices already composed keep their own, and keep routing to the provider that issued them. Moving to a provider that pushes invoices registers the customer with it first (503 UpdateInstanceBilling.ProviderUnavailable when it cannot be reached). A provider change emits INSTANCE_BILLING_PROVIDER_CHANGED. Requires billing to be enabled for the organization.
+ operationId: updateInstanceBilling
+ parameters:
+ - description: Instance slug
+ in: path
+ name: instanceSlug
+ required: true
+ schema:
+ description: Instance slug
+ examples:
+ - acme-prod
+ type: string
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/SubscriptionTerms"
+ application/octet-stream:
+ schema:
+ contentMediaType: application/octet-stream
+ format: binary
+ type: string
+ required: true
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/InstanceBilling"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ headers:
+ Retry-After:
+ description: "On .BoundaryPending: seconds until the period's close is expected to have run"
+ schema:
+ minimum: 0
+ type: integer
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - write:billing
+ summary: Change a subscription's terms or provider
+ tags:
+ - billing
+ post:
+ description: Starts billing an instance on a FLAT_FEE price of its licence version. A base price that bills in advance issues the first period's invoice (ACTIVATION) at once; charged automatically, it is pushed and charged before the answer when the provider answers within 20 seconds. Each period then closes into a RENEWAL. Add-ons and a voucher can be started with it, in the same transaction. A CANCELED subscription is subscribed again. While the subscription is live the instance's customer and licence cannot change. Requires billing to be enabled for the organization.
+ operationId: subscribeInstance
+ parameters:
+ - description: Instance slug
+ in: path
+ name: instanceSlug
+ required: true
+ schema:
+ description: Instance slug
+ examples:
+ - acme-prod
+ type: string
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/NewSubscription"
+ required: true
+ responses:
+ "201":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/StartedSubscription"
+ description: Created
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - write:billing
+ summary: Subscribe an instance
+ tags:
+ - billing
+ /instances/{instanceSlug}/billing/cancel:
+ post:
+ description: "Cancels an instance's subscription: a trial at once with no invoice; otherwise at the period's end (repeating it changes nothing), or immediately with a FINAL invoice. Cancellation changes billing only: entitlements and licence dates stay the vendor's to change. Requires billing to be enabled for the organization."
+ operationId: cancelSubscription
+ parameters:
+ - description: Instance slug
+ in: path
+ name: instanceSlug
+ required: true
+ schema:
+ description: Instance slug
+ examples:
+ - acme-prod
+ type: string
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/SubscriptionCancellation"
+ required: true
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/CanceledSubscription"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ headers:
+ Retry-After:
+ description: "On .BoundaryPending: seconds until the period's close is expected to have run"
+ schema:
+ minimum: 0
+ type: integer
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - write:billing
+ summary: Cancel a subscription
+ tags:
+ - billing
+ /instances/{instanceSlug}/billing/reactivate:
+ post:
+ description: Reverts a cancellation scheduled for the period end, before it. A canceled subscription is subscribed again instead. Requires billing to be enabled for the organization.
+ operationId: reactivateSubscription
+ parameters:
+ - description: Instance slug
+ in: path
+ name: instanceSlug
+ required: true
+ schema:
+ description: Instance slug
+ examples:
+ - acme-prod
+ type: string
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/InstanceBilling"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ headers:
+ Retry-After:
+ description: "On .BoundaryPending: seconds until the period's close is expected to have run"
+ schema:
+ minimum: 0
+ type: integer
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - write:billing
+ summary: Reactivate a subscription
+ tags:
+ - billing
+ /instances/{instanceSlug}/billing/scheduled-change:
+ delete:
+ description: Drops the plan change scheduled for the next boundary. Requires billing to be enabled for the organization.
+ operationId: cancelPlanChange
+ parameters:
+ - description: Instance slug
+ in: path
+ name: instanceSlug
+ required: true
+ schema:
+ description: Instance slug
+ examples:
+ - acme-prod
+ type: string
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/InstanceBilling"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ headers:
+ Retry-After:
+ description: "On .BoundaryPending: seconds until the period's close is expected to have run"
+ schema:
+ minimum: 0
+ type: integer
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - write:billing
+ summary: Cancel a scheduled plan change
+ tags:
+ - billing
+ put:
+ description: Schedules a move to another FLAT_FEE price, of any PUBLISHED licence version in the subscription's currency, for the next boundary. That boundary's RENEWAL bills the old plan's arrears and the new plan's advance, and moves the instance to the new version; there is no proration. Scheduling another target replaces the change, the same one changes nothing. A scheduled target's price cannot be deprecated, nor its version archived. Requires billing to be enabled for the organization.
+ operationId: schedulePlanChange
+ parameters:
+ - description: Instance slug
+ in: path
+ name: instanceSlug
+ required: true
+ schema:
+ description: Instance slug
+ examples:
+ - acme-prod
+ type: string
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/PlanChangeTarget"
+ required: true
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/InstanceBilling"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ headers:
+ Retry-After:
+ description: "On .BoundaryPending: seconds until the period's close is expected to have run"
+ schema:
+ minimum: 0
+ type: integer
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - write:billing
+ summary: Schedule a plan change
+ tags:
+ - billing
+ /instances/{instanceSlug}/billing/upcoming-invoice:
+ get:
+ description: "The invoice the subscription's next boundary will issue, composed from its usage so far, without writing anything: lines carry the period they will bill, quantities what was measured until now. wouldHold lists the meters whose usage journal fails a check and would hold the invoice. Requires billing to be enabled for the organization."
+ operationId: getUpcomingInvoice
+ parameters:
+ - description: Instance slug
+ in: path
+ name: instanceSlug
+ required: true
+ schema:
+ description: Instance slug
+ examples:
+ - acme-prod
+ type: string
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/InvoicePreview"
+ description: OK
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - read:billing
+ summary: Preview an instance's upcoming invoice
+ tags:
+ - billing
+ /instances/{instanceSlug}/entitlement-exceptions:
+ get:
+ description: The instance's exceptions, latest start first, each with its status at the read. Works whether or not billing is enabled.
+ operationId: listEntitlementExceptions
+ parameters:
+ - description: Instance slug
+ in: path
+ name: instanceSlug
+ required: true
+ schema:
+ description: Instance slug
+ examples:
+ - acme-prod
+ type: string
+ - description: "active: neither revoked nor ended, scheduled ones included; all: every exception of the instance"
+ explode: false
+ in: query
+ name: filter
+ schema:
+ default: active
+ description: "active: neither revoked nor ended, scheduled ones included; all: every exception of the instance"
+ enum:
+ - active
+ - all
+ type: string
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ items:
+ $ref: "#/components/schemas/EntitlementException"
+ type: array
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - read:instances
+ summary: List an instance's entitlement exceptions
+ tags:
+ - instances
+ post:
+ description: "The vendor's explicit, dated decision on one entitlement of one instance, without a code: the last layer of the effective entitlements, after the licence, the add-ons and the boosts. It wins over them and may lower the value. One unrevoked exception per instance and entitlement at any instant. Emits INSTANCE_ENTITLEMENT_EXCEPTION_CREATED. Works whether or not billing is enabled."
+ operationId: createEntitlementException
+ parameters:
+ - description: Instance slug
+ in: path
+ name: instanceSlug
+ required: true
+ schema:
+ description: Instance slug
+ examples:
+ - acme-prod
+ type: string
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/NewEntitlementException"
+ required: true
+ responses:
+ "201":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/EntitlementException"
+ description: Created
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - write:instances
+ summary: Set an exception on an instance's entitlement
+ tags:
+ - instances
+ /instances/{instanceSlug}/entitlement-exceptions/{exceptionId}:
+ delete:
+ description: "Revokes an exception and keeps it, readable with filter=all: a scheduled one never starts, an active one ends at once. Revoking one already revoked or ended changes nothing. Emits INSTANCE_ENTITLEMENT_EXCEPTION_REVOKED. Works whether or not billing is enabled."
+ operationId: revokeEntitlementException
+ parameters:
+ - description: Instance slug
+ in: path
+ name: instanceSlug
+ required: true
+ schema:
+ description: Instance slug
+ examples:
+ - acme-prod
+ type: string
+ - description: Exception id
+ in: path
+ name: exceptionId
+ required: true
+ schema:
+ description: Exception id
+ type: string
+ responses:
+ "204":
+ description: No Content
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - write:instances
+ summary: Revoke an entitlement exception
+ tags:
+ - instances
+ /instances/{instanceSlug}/entitlements/usage:
+ get:
+ description: Retrieve a list of all entitlements usage metrics for a specific instance
+ operationId: getEntitlementsUsageMetrics
+ parameters:
+ - description: Instance slug
+ in: path
+ name: instanceSlug
+ required: true
+ schema:
+ description: Instance slug
+ examples:
+ - instance-slug
+ type: string
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ items:
+ $ref: "#/components/schemas/EntitlementUsage"
+ type:
+ - array
+ - "null"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - read:instances
+ summary: List all entitlements usage metrics
+ tags:
+ - instances
+ /instances/{instanceSlug}/entitlements/{entitlementSlug}/usage:
+ get:
+ description: Retrieve the usage metrics for a specific entitlement in a given instance
+ operationId: getEntitlementUsageMetrics
+ parameters:
+ - description: Instance slug
+ in: path
+ name: instanceSlug
+ required: true
+ schema:
+ description: Instance slug
+ examples:
+ - instance-slug
+ type: string
+ - description: Entitlement slug
+ in: path
+ name: entitlementSlug
+ required: true
+ schema:
+ description: Entitlement slug
+ examples:
+ - entitlement-slug
+ type: string
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/EntitlementUsage"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - read:instances
+ summary: Get entitlement usage metrics for an instance
+ tags:
+ - instances
+ post:
+ description: "Report a usage metric for a specific entitlement in a given instance, with optional metadata. The server dates every report on receipt; the request carries no timestamp. Send an Idempotency-Key to make retries safe: without one, a report sent twice counts twice."
+ operationId: reportEntitlementUsageMetric
+ parameters:
+ - description: Instance slug
+ in: path
+ name: instanceSlug
+ required: true
+ schema:
+ description: Instance slug
+ examples:
+ - instance-slug
+ type: string
+ - description: Entitlement slug
+ in: path
+ name: entitlementSlug
+ required: true
+ schema:
+ description: Entitlement slug
+ examples:
+ - entitlement-slug
+ type: string
+ - description: "Optional idempotency key: the id of the business event this report counts, 1 to 128 characters of [A-Za-z0-9._:-], matched exactly and case-sensitively. A report sent again with the same key and the same behavior and value within KAITEN_USAGE_IDEMPOTENCY_WINDOW (35 days by default) is applied once: the retry answers 200 with the original response and the Idempotent-Replayed header, and changes nothing. The same key with another behavior or value answers 409 ReportEntitlementUsageMetric.TransactionIdReused. A rejected report does not consume its key. Scoped to the instance and entitlement: one business event may feed two meters under one key. Stored and read back as transactionId."
+ in: header
+ name: Idempotency-Key
+ schema:
+ description: "Optional idempotency key: the id of the business event this report counts, 1 to 128 characters of [A-Za-z0-9._:-], matched exactly and case-sensitively. A report sent again with the same key and the same behavior and value within KAITEN_USAGE_IDEMPOTENCY_WINDOW (35 days by default) is applied once: the retry answers 200 with the original response and the Idempotent-Replayed header, and changes nothing. The same key with another behavior or value answers 409 ReportEntitlementUsageMetric.TransactionIdReused. A rejected report does not consume its key. Scoped to the instance and entitlement: one business event may feed two meters under one key. Stored and read back as transactionId."
+ examples:
+ - llm-call-9f2c:tokens
+ type: string
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/ReportEntitlementUsageBody"
+ required: true
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/ReportedEntitlementUsage"
+ description: OK
+ headers:
+ Idempotent-Replayed:
+ schema:
+ description: "true when the report replays an earlier one sent with the same Idempotency-Key: the body is that report's original response and nothing was counted again"
+ type: string
+ Kaiten-Metadata-Dropped:
+ schema:
+ description: too_large when the report's metadata was above 4 KiB and was not stored; the report itself was counted
+ type: string
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - write:instances
+ summary: Report entitlement usage metric for an instance
+ tags:
+ - instances
+ /instances/{instanceSlug}/entitlements/{entitlementSlug}/usage/reports:
+ get:
+ description: "The usage history of one instance and entitlement: every accepted report in the range, with the counter before and after it and the limit in force, in reportSeq order, paged with afterSeq. Decimals are strings. Reports are kept for the organization's usage history retention."
+ operationId: listUsageReports
+ parameters:
+ - description: Instance slug
+ in: path
+ name: instanceSlug
+ required: true
+ schema:
+ description: Instance slug
+ examples:
+ - instance-slug
+ type: string
+ - description: Entitlement slug
+ in: path
+ name: entitlementSlug
+ required: true
+ schema:
+ description: Entitlement slug
+ examples:
+ - entitlement-slug
+ type: string
+ - description: Start of the range, inclusive (RFC 3339). Defaults to 30 days before to, moved up to the start of the organization's usage history when that is later. An explicit from before it answers 422 ListUsageReports.OutsideRetention.
+ explode: false
+ in: query
+ name: from
+ schema:
+ description: Start of the range, inclusive (RFC 3339). Defaults to 30 days before to, moved up to the start of the organization's usage history when that is later. An explicit from before it answers 422 ListUsageReports.OutsideRetention.
+ format: date-time
+ type: string
+ - description: End of the range, exclusive (RFC 3339). Defaults to now.
+ explode: false
+ in: query
+ name: to
+ schema:
+ description: End of the range, exclusive (RFC 3339). Defaults to now.
+ format: date-time
+ type: string
+ - description: "Return the reports after this reportSeq: the nextAfterSeq of the previous page"
+ explode: false
+ in: query
+ name: afterSeq
+ schema:
+ description: "Return the reports after this reportSeq: the nextAfterSeq of the previous page"
+ format: int64
+ minimum: 0
+ type: integer
+ - description: Maximum number of reports to return (default 100, max 500)
+ explode: false
+ in: query
+ name: limit
+ schema:
+ description: Maximum number of reports to return (default 100, max 500)
+ format: int32
+ maximum: 500
+ minimum: 1
+ type: integer
+ - description: Only the report sent with this idempotency key
+ explode: false
+ in: query
+ name: transactionId
+ schema:
+ description: Only the report sent with this idempotency key
+ maxLength: 128
+ type: string
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/UsageReportPage"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - read:instances
+ summary: List an entitlement's usage reports for an instance
+ tags:
+ - instances
+ /instances/{instanceSlug}/entitlements/{entitlementSlug}/usage/reports/export:
+ get:
+ description: Streams every report of one instance and entitlement in the range as CSV or NDJSON, in reportSeq order. Decimals are written exactly as the journal stores them.
+ operationId: exportUsageReports
+ parameters:
+ - description: Instance slug
+ in: path
+ name: instanceSlug
+ required: true
+ schema:
+ description: Instance slug
+ examples:
+ - instance-slug
+ type: string
+ - description: Entitlement slug
+ in: path
+ name: entitlementSlug
+ required: true
+ schema:
+ description: Entitlement slug
+ examples:
+ - entitlement-slug
+ type: string
+ - description: Start of the range, inclusive (RFC 3339). Defaults to 30 days before to, moved up to the start of the organization's usage history when that is later. An explicit from before it answers 422 ExportUsageReports.OutsideRetention.
+ explode: false
+ in: query
+ name: from
+ schema:
+ description: Start of the range, inclusive (RFC 3339). Defaults to 30 days before to, moved up to the start of the organization's usage history when that is later. An explicit from before it answers 422 ExportUsageReports.OutsideRetention.
+ format: date-time
+ type: string
+ - description: End of the range, exclusive (RFC 3339). Defaults to now. At most 366 days after from.
+ explode: false
+ in: query
+ name: to
+ schema:
+ description: End of the range, exclusive (RFC 3339). Defaults to now. At most 366 days after from.
+ format: date-time
+ type: string
+ - description: "csv (the default): RFC 4180 with a header row. json: NDJSON, one report per line, in the shape listUsageReports returns. Anything else answers 422 ExportUsageReports.InvalidFormat."
+ explode: false
+ in: query
+ name: format
+ schema:
+ description: "csv (the default): RFC 4180 with a header row. json: NDJSON, one report per line, in the shape listUsageReports returns. Anything else answers 422 ExportUsageReports.InvalidFormat."
+ examples:
+ - csv
+ type: string
+ responses:
+ "200":
+ content:
+ application/x-ndjson:
+ schema:
+ type: string
+ text/csv:
+ schema:
+ type: string
+ description: "The reports, as an attachment: CSV with a header row for format=csv, one JSON object per line for format=json"
+ headers:
+ Content-Disposition:
+ description: attachment, with a file name naming the range
+ schema:
+ type: string
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - read:instances
+ summary: Export an entitlement's usage reports for an instance
+ tags:
+ - instances
+ /instances/{instanceSlug}/integrations/{integrationName}:
+ delete:
+ description: Delete a single integration for an instance
+ operationId: delete-instance-integration
+ parameters:
+ - description: Instance slug
+ in: path
+ name: instanceSlug
+ required: true
+ schema:
+ description: Instance slug
+ examples:
+ - instance-slug
+ type: string
+ - description: Integration adapter name
+ in: path
+ name: integrationName
+ required: true
+ schema:
+ description: Integration adapter name
+ examples:
+ - kaiten.integration.crm.attio
+ type: string
+ responses:
+ "204":
+ description: No Content
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - write:instances
+ summary: Delete an instance integration
+ tags:
+ - instances
+ get:
+ description: Retrieve a single integration for an instance
+ operationId: get-instance-integration
+ parameters:
+ - description: Instance slug
+ in: path
+ name: instanceSlug
+ required: true
+ schema:
+ description: Instance slug
+ examples:
+ - instance-slug
+ type: string
+ - description: Integration adapter name
+ in: path
+ name: integrationName
+ required: true
+ schema:
+ description: Integration adapter name
+ examples:
+ - kaiten.integration.crm.attio
+ type: string
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/InstanceIntegration"
+ description: OK
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - read:instances
+ summary: Get an instance integration
+ tags:
+ - instances
+ post:
+ description: Create a single integration for an instance
+ operationId: create-instance-integration
+ parameters:
+ - description: Instance slug
+ in: path
+ name: instanceSlug
+ required: true
+ schema:
+ description: Instance slug
+ examples:
+ - instance-slug
+ type: string
+ - description: Integration adapter name
+ in: path
+ name: integrationName
+ required: true
+ schema:
+ description: Integration adapter name
+ examples:
+ - kaiten.integration.crm.attio
+ type: string
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/InstanceIntegration"
+ description: Integration payload
+ required: true
+ responses:
+ "201":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/InstanceIntegration"
+ description: Created
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - write:instances
+ summary: Create an instance integration
+ tags:
+ - instances
+ put:
+ description: Update a single integration for an instance
+ operationId: update-instance-integration
+ parameters:
+ - description: Instance slug
+ in: path
+ name: instanceSlug
+ required: true
+ schema:
+ description: Instance slug
+ examples:
+ - instance-slug
+ type: string
+ - description: Integration adapter name
+ in: path
+ name: integrationName
+ required: true
+ schema:
+ description: Integration adapter name
+ examples:
+ - kaiten.integration.crm.attio
+ type: string
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/InstanceIntegration"
+ description: Integration payload
+ required: true
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/InstanceIntegration"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - write:instances
+ summary: Update an instance integration
+ tags:
+ - instances
+ /instances/{instanceSlug}/invoices:
+ get:
+ description: The invoices of one instance's subscription, across every time it was subscribed, with the filters of listInvoices. Requires billing to be enabled for the organization.
+ operationId: listInstanceInvoices
+ parameters:
+ - description: Instance slug
+ in: path
+ name: instanceSlug
+ required: true
+ schema:
+ description: Instance slug
+ examples:
+ - acme-prod
+ type: string
+ - description: Only invoices in these statuses; repeat the parameter for several
+ explode: true
+ in: query
+ name: status
+ schema:
+ description: Only invoices in these statuses; repeat the parameter for several
+ items:
+ enum:
+ - DRAFT
+ - PUSHED
+ - PUSH_FAILED
+ - MANUAL
+ - PAID
+ - PAYMENT_FAILED
+ - UNCOLLECTIBLE
+ - VOID
+ type: string
+ type:
+ - array
+ - "null"
+ - explode: false
+ in: query
+ name: kind
+ schema:
+ enum:
+ - ACTIVATION
+ - RENEWAL
+ - FINAL
+ type: string
+ - explode: false
+ in: query
+ name: providerKind
+ schema:
+ enum:
+ - NOOP
+ - STRIPE
+ type: string
+ - description: "Only this customer's invoices: composed under this slug, or of the customer that has it now"
+ explode: false
+ in: query
+ name: customerSlug
+ schema:
+ description: "Only this customer's invoices: composed under this slug, or of the customer that has it now"
+ type: string
+ - description: Only issued invoices still unpaid past their due date
+ explode: false
+ in: query
+ name: overdue
+ schema:
+ description: Only issued invoices still unpaid past their due date
+ type: boolean
+ - description: Only DRAFTs held for their usage journal
+ explode: false
+ in: query
+ name: held
+ schema:
+ description: Only DRAFTs held for their usage journal
+ type: boolean
+ - explode: false
+ in: query
+ name: handoffStatus
+ schema:
+ enum:
+ - NOT_REQUIRED
+ - PENDING
+ - ACKNOWLEDGED
+ type: string
+ - description: Issued at or after this instant (RFC 3339)
+ explode: false
+ in: query
+ name: issuedFrom
+ schema:
+ description: Issued at or after this instant (RFC 3339)
+ format: date-time
+ type: string
+ - description: Issued before this instant (RFC 3339)
+ explode: false
+ in: query
+ name: issuedTo
+ schema:
+ description: Issued before this instant (RFC 3339)
+ format: date-time
+ type: string
+ - description: Billing a boundary at or after this instant
+ explode: false
+ in: query
+ name: boundaryFrom
+ schema:
+ description: Billing a boundary at or after this instant
+ format: date-time
+ type: string
+ - description: Billing a boundary before this instant
+ explode: false
+ in: query
+ name: boundaryTo
+ schema:
+ description: Billing a boundary before this instant
+ format: date-time
+ type: string
+ - description: "Only invoices changed at or after this instant, oldest change first instead of newest invoice first: store the last updatedAt read and pass it next time to miss no change"
+ explode: false
+ in: query
+ name: updatedSince
+ schema:
+ description: "Only invoices changed at or after this instant, oldest change first instead of newest invoice first: store the last updatedAt read and pass it next time to miss no change"
+ format: date-time
+ type: string
+ - description: Opaque cursor from the previous page's nextCursor
+ explode: false
+ in: query
+ name: cursor
+ schema:
+ description: Opaque cursor from the previous page's nextCursor
+ type: string
+ - description: Page size, 50 by default, 200 at most
+ explode: false
+ in: query
+ name: limit
+ schema:
+ description: Page size, 50 by default, 200 at most
+ format: int32
+ maximum: 200
+ minimum: 0
+ type: integer
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/PageInvoiceSummary"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - read:billing
+ summary: List an instance's invoices
+ tags:
+ - billing
+ /instances/{instanceSlug}/vouchers:
+ get:
+ description: The vouchers an instance redeemed, newest first. Requires billing to be enabled for the organization.
+ operationId: listInstanceVouchers
+ parameters:
+ - description: Instance slug
+ in: path
+ name: instanceSlug
+ required: true
+ schema:
+ description: Instance slug
+ examples:
+ - acme-prod
+ type: string
+ - explode: false
+ in: query
+ name: status
+ schema:
+ enum:
+ - ACTIVE
+ - EXPIRED
+ - REVOKED
+ type: string
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ items:
+ $ref: "#/components/schemas/Redemption"
+ type: array
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - read:voucher_redemptions
+ summary: List an instance's vouchers
+ tags:
+ - vouchers
+ /instances/{instanceSlug}/vouchers/redeem:
+ post:
+ description: Redeems a voucher for an instance. A boost applies from the next read or report, for its duration; a PRICE voucher discounts the subscription's next invoices. 422 .DiscountLimitExceeded when a line of the instance's invoices would bear more than 20 discounts, whatever the provider (a Stripe invoice item takes 20). Emits INSTANCE_VOUCHER_REDEEMED, and VOUCHER_EXHAUSTED with the last redemption. Requires billing to be enabled for the organization.
+ operationId: redeemVoucher
+ parameters:
+ - description: Instance slug
+ in: path
+ name: instanceSlug
+ required: true
+ schema:
+ description: Instance slug
+ examples:
+ - acme-prod
+ type: string
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/RedeemableCode"
+ required: true
+ responses:
+ "201":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Redemption"
+ description: Created
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - write:voucher_redemptions
+ summary: Redeem a voucher
+ tags:
+ - vouchers
+ /instances/{instanceSlug}/vouchers/{instanceVoucherId}/revoke:
+ post:
+ description: "Stops a redemption from applying: a boost ends at once, a discount applies to no further invoice. Invoices already issued are untouched. Emits INSTANCE_VOUCHER_REVOKED. Requires billing to be enabled for the organization."
+ operationId: revokeInstanceVoucher
+ parameters:
+ - description: Instance slug
+ in: path
+ name: instanceSlug
+ required: true
+ schema:
+ description: Instance slug
+ examples:
+ - acme-prod
+ type: string
+ - description: Redemption id
+ in: path
+ name: instanceVoucherId
+ required: true
+ schema:
+ description: Redemption id
+ type: string
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/RevocationReason"
+ required: true
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Redemption"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - write:vouchers
+ summary: Revoke a redemption
+ tags:
+ - vouchers
+ /integration/{adapter}/customer/{externalId}:
+ get:
+ description: Resolve a customer through one of its integrations using adapter + external ID.
+ operationId: get-customer-integration-by-external-id
+ parameters:
+ - description: Integration adapter without the kaiten.integration. prefix
+ in: path
+ name: adapter
+ required: true
+ schema:
+ description: Integration adapter without the kaiten.integration. prefix
+ examples:
+ - crm.attio
+ type: string
+ - description: External identifier in the third-party system
+ in: path
+ name: externalId
+ required: true
+ schema:
+ description: External identifier in the third-party system
+ examples:
+ - rec_12345
+ type: string
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/CustomerIntegrationResource"
+ description: OK
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - read:customers
+ summary: Get a customer by integration external ID
+ tags:
+ - integrations
+ - customers
+ patch:
+ description: Create or partially update a customer resolved through adapter + external ID. Missing fields are left unchanged.
+ operationId: upsert-customer-integration-by-external-id
+ parameters:
+ - description: Integration adapter without the kaiten.integration. prefix
+ in: path
+ name: adapter
+ required: true
+ schema:
+ description: Integration adapter without the kaiten.integration. prefix
+ examples:
+ - crm.attio
+ type: string
+ - description: External identifier in the third-party system
+ in: path
+ name: externalId
+ required: true
+ schema:
+ description: External identifier in the third-party system
+ examples:
+ - rec_12345
+ type: string
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/CustomerBody"
+ required: true
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/CustomerIntegrationResource"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - write:customers
+ summary: Upsert a customer by integration external ID
+ tags:
+ - integrations
+ - customers
+ /integration/{adapter}/instance/{externalId}:
+ get:
+ description: Resolve an instance through one of its integrations using adapter + external ID.
+ operationId: get-instance-integration-by-external-id
+ parameters:
+ - description: Integration adapter without the kaiten.integration. prefix
+ in: path
+ name: adapter
+ required: true
+ schema:
+ description: Integration adapter without the kaiten.integration. prefix
+ examples:
+ - crm.attio
+ type: string
+ - description: External identifier in the third-party system
+ in: path
+ name: externalId
+ required: true
+ schema:
+ description: External identifier in the third-party system
+ examples:
+ - rec_12345
+ type: string
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/InstanceIntegrationResource"
+ description: OK
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - read:instances
+ summary: Get an instance by integration external ID
+ tags:
+ - integrations
+ - instances
+ patch:
+ description: Create or partially update an instance resolved through adapter + external ID. Missing fields are left unchanged.
+ operationId: upsert-instance-integration-by-external-id
+ parameters:
+ - description: Integration adapter without the kaiten.integration. prefix
+ in: path
+ name: adapter
+ required: true
+ schema:
+ description: Integration adapter without the kaiten.integration. prefix
+ examples:
+ - crm.attio
+ type: string
+ - description: External identifier in the third-party system
+ in: path
+ name: externalId
+ required: true
+ schema:
+ description: External identifier in the third-party system
+ examples:
+ - rec_12345
+ type: string
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/InstanceBody"
+ required: true
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/InstanceIntegrationResource"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - write:instances
+ summary: Upsert an instance by integration external ID
+ tags:
+ - integrations
+ - instances
+ /invoices:
+ get:
+ description: The organization's invoices, newest first; with updatedSince, the ones changed since an instant, oldest change first, for incremental sync. Invoices outlive their instance and customer. Requires billing to be enabled for the organization.
+ operationId: listInvoices
+ parameters:
+ - description: Only invoices in these statuses; repeat the parameter for several
+ explode: true
+ in: query
+ name: status
+ schema:
+ description: Only invoices in these statuses; repeat the parameter for several
+ items:
+ enum:
+ - DRAFT
+ - PUSHED
+ - PUSH_FAILED
+ - MANUAL
+ - PAID
+ - PAYMENT_FAILED
+ - UNCOLLECTIBLE
+ - VOID
+ type: string
+ type:
+ - array
+ - "null"
+ - explode: false
+ in: query
+ name: kind
+ schema:
+ enum:
+ - ACTIVATION
+ - RENEWAL
+ - FINAL
+ type: string
+ - explode: false
+ in: query
+ name: providerKind
+ schema:
+ enum:
+ - NOOP
+ - STRIPE
+ type: string
+ - description: "Only this customer's invoices: composed under this slug, or of the customer that has it now"
+ explode: false
+ in: query
+ name: customerSlug
+ schema:
+ description: "Only this customer's invoices: composed under this slug, or of the customer that has it now"
+ type: string
+ - description: Only issued invoices still unpaid past their due date
+ explode: false
+ in: query
+ name: overdue
+ schema:
+ description: Only issued invoices still unpaid past their due date
+ type: boolean
+ - description: Only DRAFTs held for their usage journal
+ explode: false
+ in: query
+ name: held
+ schema:
+ description: Only DRAFTs held for their usage journal
+ type: boolean
+ - explode: false
+ in: query
+ name: handoffStatus
+ schema:
+ enum:
+ - NOT_REQUIRED
+ - PENDING
+ - ACKNOWLEDGED
+ type: string
+ - description: Issued at or after this instant (RFC 3339)
+ explode: false
+ in: query
+ name: issuedFrom
+ schema:
+ description: Issued at or after this instant (RFC 3339)
+ format: date-time
+ type: string
+ - description: Issued before this instant (RFC 3339)
+ explode: false
+ in: query
+ name: issuedTo
+ schema:
+ description: Issued before this instant (RFC 3339)
+ format: date-time
+ type: string
+ - description: Billing a boundary at or after this instant
+ explode: false
+ in: query
+ name: boundaryFrom
+ schema:
+ description: Billing a boundary at or after this instant
+ format: date-time
+ type: string
+ - description: Billing a boundary before this instant
+ explode: false
+ in: query
+ name: boundaryTo
+ schema:
+ description: Billing a boundary before this instant
+ format: date-time
+ type: string
+ - description: "Only invoices changed at or after this instant, oldest change first instead of newest invoice first: store the last updatedAt read and pass it next time to miss no change"
+ explode: false
+ in: query
+ name: updatedSince
+ schema:
+ description: "Only invoices changed at or after this instant, oldest change first instead of newest invoice first: store the last updatedAt read and pass it next time to miss no change"
+ format: date-time
+ type: string
+ - description: Opaque cursor from the previous page's nextCursor
+ explode: false
+ in: query
+ name: cursor
+ schema:
+ description: Opaque cursor from the previous page's nextCursor
+ type: string
+ - description: Page size, 50 by default, 200 at most
+ explode: false
+ in: query
+ name: limit
+ schema:
+ description: Page size, 50 by default, 200 at most
+ format: int32
+ maximum: 200
+ minimum: 0
+ type: integer
+ - description: "Only this instance's invoices: composed under this slug, or of the instance that has it now"
+ explode: false
+ in: query
+ name: instanceSlug
+ schema:
+ description: "Only this instance's invoices: composed under this slug, or of the instance that has it now"
+ type: string
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/PageInvoiceSummary"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - read:billing
+ summary: List invoices
+ tags:
+ - billing
+ /invoices/export:
+ get:
+ description: "Streams the invoices the filters of listInvoices select, as CSV or NDJSON. Amounts are integer minor units with the currency's exponent beside them, so a spreadsheet never sees a float. The export carries billing e-mails: it is an authenticated read for the organization's accounting. Requires billing to be enabled for the organization."
+ operationId: exportInvoices
+ parameters:
+ - description: Only invoices in these statuses; repeat the parameter for several
+ explode: true
+ in: query
+ name: status
+ schema:
+ description: Only invoices in these statuses; repeat the parameter for several
+ items:
+ enum:
+ - DRAFT
+ - PUSHED
+ - PUSH_FAILED
+ - MANUAL
+ - PAID
+ - PAYMENT_FAILED
+ - UNCOLLECTIBLE
+ - VOID
+ type: string
+ type:
+ - array
+ - "null"
+ - explode: false
+ in: query
+ name: kind
+ schema:
+ enum:
+ - ACTIVATION
+ - RENEWAL
+ - FINAL
+ type: string
+ - explode: false
+ in: query
+ name: providerKind
+ schema:
+ enum:
+ - NOOP
+ - STRIPE
+ type: string
+ - description: "Only this customer's invoices: composed under this slug, or of the customer that has it now"
+ explode: false
+ in: query
+ name: customerSlug
+ schema:
+ description: "Only this customer's invoices: composed under this slug, or of the customer that has it now"
+ type: string
+ - description: Only issued invoices still unpaid past their due date
+ explode: false
+ in: query
+ name: overdue
+ schema:
+ description: Only issued invoices still unpaid past their due date
+ type: boolean
+ - description: Only DRAFTs held for their usage journal
+ explode: false
+ in: query
+ name: held
+ schema:
+ description: Only DRAFTs held for their usage journal
+ type: boolean
+ - explode: false
+ in: query
+ name: handoffStatus
+ schema:
+ enum:
+ - NOT_REQUIRED
+ - PENDING
+ - ACKNOWLEDGED
+ type: string
+ - description: Issued at or after this instant (RFC 3339)
+ explode: false
+ in: query
+ name: issuedFrom
+ schema:
+ description: Issued at or after this instant (RFC 3339)
+ format: date-time
+ type: string
+ - description: Issued before this instant (RFC 3339)
+ explode: false
+ in: query
+ name: issuedTo
+ schema:
+ description: Issued before this instant (RFC 3339)
+ format: date-time
+ type: string
+ - description: Billing a boundary at or after this instant
+ explode: false
+ in: query
+ name: boundaryFrom
+ schema:
+ description: Billing a boundary at or after this instant
+ format: date-time
+ type: string
+ - description: Billing a boundary before this instant
+ explode: false
+ in: query
+ name: boundaryTo
+ schema:
+ description: Billing a boundary before this instant
+ format: date-time
+ type: string
+ - description: "Only invoices changed at or after this instant, oldest change first instead of newest invoice first: store the last updatedAt read and pass it next time to miss no change"
+ explode: false
+ in: query
+ name: updatedSince
+ schema:
+ description: "Only invoices changed at or after this instant, oldest change first instead of newest invoice first: store the last updatedAt read and pass it next time to miss no change"
+ format: date-time
+ type: string
+ - description: Opaque cursor from the previous page's nextCursor
+ explode: false
+ in: query
+ name: cursor
+ schema:
+ description: Opaque cursor from the previous page's nextCursor
+ type: string
+ - description: Page size, 50 by default, 200 at most
+ explode: false
+ in: query
+ name: limit
+ schema:
+ description: Page size, 50 by default, 200 at most
+ format: int32
+ maximum: 200
+ minimum: 0
+ type: integer
+ - description: Only this instance's invoices
+ explode: false
+ in: query
+ name: instanceSlug
+ schema:
+ description: Only this instance's invoices
+ type: string
+ - description: "csv (the default): RFC 4180 with a header row. json: NDJSON, one invoice with its lines per line. Anything else answers 422 ExportInvoices.InvalidFormat."
+ explode: false
+ in: query
+ name: format
+ schema:
+ description: "csv (the default): RFC 4180 with a header row. json: NDJSON, one invoice with its lines per line. Anything else answers 422 ExportInvoices.InvalidFormat."
+ examples:
+ - csv
+ type: string
+ - description: "For csv: line (the default), one row per invoice line with the invoice's columns repeated; invoice, one row per invoice. Anything else answers 422 ExportInvoices.InvalidGranularity."
+ explode: false
+ in: query
+ name: granularity
+ schema:
+ description: "For csv: line (the default), one row per invoice line with the invoice's columns repeated; invoice, one row per invoice. Anything else answers 422 ExportInvoices.InvalidGranularity."
+ examples:
+ - line
+ type: string
+ responses:
+ "200":
+ content:
+ application/x-ndjson:
+ schema:
+ type: string
+ text/csv:
+ schema:
+ type: string
+ description: The invoices, as an attachment
+ headers:
+ Content-Disposition:
+ description: attachment, with a file name
+ schema:
+ type: string
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - read:billing
+ summary: Export invoices
+ tags:
+ - billing
+ /invoices/{invoiceId}:
+ get:
+ description: "One invoice with its lines, the customer and instance as they were when it was composed, its hold and its handoff. Addressed by id at organization level: an invoice outlives its instance and customer. Requires billing to be enabled for the organization."
+ operationId: getInvoice
+ parameters:
+ - description: Invoice identifier
+ in: path
+ name: invoiceId
+ required: true
+ schema:
+ description: Invoice identifier
+ type: string
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Invoice"
+ description: OK
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - read:billing
+ summary: Get an invoice
+ tags:
+ - billing
+ /invoices/{invoiceId}/lines/{lineId}/reports:
+ get:
+ description: "The usage reports a USAGE or OVERAGE line was measured from: its pair's reports dated in its service period, in reportSeq order. Summed by reset window and floored at 0, they give the line's measured quantity. Addressed by invoice, it answers after the instance was deleted. Requires billing to be enabled for the organization."
+ operationId: listInvoiceLineReports
+ parameters:
+ - description: Invoice identifier
+ in: path
+ name: invoiceId
+ required: true
+ schema:
+ description: Invoice identifier
+ type: string
+ - description: Line identifier, as the invoice's lines carry it
+ in: path
+ name: lineId
+ required: true
+ schema:
+ description: Line identifier, as the invoice's lines carry it
+ type: string
+ - description: Reports after this reportSeq; 0 starts at the first
+ explode: false
+ in: query
+ name: afterSeq
+ schema:
+ description: Reports after this reportSeq; 0 starts at the first
+ format: int64
+ minimum: 0
+ type: integer
+ - description: Page size, 100 by default, 500 at most
+ explode: false
+ in: query
+ name: limit
+ schema:
+ description: Page size, 100 by default, 500 at most
+ format: int32
+ maximum: 500
+ minimum: 0
+ type: integer
+ - description: "json (the default): one page. csv: every report, streamed as RFC 4180 with a header row. Anything else answers 422 ListInvoiceLineReports.InvalidFormat."
+ explode: false
+ in: query
+ name: format
+ schema:
+ description: "json (the default): one page. csv: every report, streamed as RFC 4180 with a header row. Anything else answers 422 ListInvoiceLineReports.InvalidFormat."
+ examples:
+ - json
+ type: string
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/LineReportPage"
+ text/csv:
+ schema:
+ type: string
+ description: One page of reports (json), or every report as a CSV attachment (csv)
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - read:billing
+ summary: List the usage reports behind an invoice line
+ tags:
+ - billing
+ /invoices/{invoiceId}/mark-paid:
+ post:
+ description: Records that a MANUAL invoice was paid. An invoice still waiting in the handoff queue is acknowledged by the same write. Marking it paid again with the same external reference answers it unchanged. An invoice a payment provider collects is settled there. Requires billing to be enabled for the organization.
+ operationId: markInvoicePaid
+ parameters:
+ - description: Invoice identifier
+ in: path
+ name: invoiceId
+ required: true
+ schema:
+ description: Invoice identifier
+ type: string
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/InvoicePayment"
+ required: true
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Invoice"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - write:billing
+ summary: Mark an invoice paid
+ tags:
+ - billing
+ /invoices/{invoiceId}/recompose:
+ post:
+ description: "Composes an invoice again from the usage journal as it is now, keeping its BASE lines as they were sold. A held DRAFT is rewritten in place (200), under the subscription's current provider and terms: issued when its journal is now sound and its provider takes as many items, held again otherwise. A VOID invoice gets a replacement for the same boundary (201), under the subscription's current terms and billing e-mail. Any other invoice must be voided first. Requires billing to be enabled for the organization."
+ operationId: recomposeInvoice
+ parameters:
+ - description: Invoice identifier
+ in: path
+ name: invoiceId
+ required: true
+ schema:
+ description: Invoice identifier
+ type: string
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Invoice"
+ description: OK
+ "201":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Invoice"
+ description: The replacement of a VOID invoice, for the same boundary
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - write:billing
+ summary: Recompose an invoice
+ tags:
+ - billing
+ /invoices/{invoiceId}/release-hold:
+ post:
+ description: "Accepts a held invoice as composed and issues it: MANUAL and pending in the handoff queue, or PAID when nothing is owed. The release keeps who released it and why. 409 .ProviderItemLimit for a PROVIDER_ITEM_LIMIT hold, which its provider would refuse: switch the subscription to NoOp and recompose in place, or void and recompose after that switch. Requires billing to be enabled for the organization."
+ operationId: releaseInvoiceHold
+ parameters:
+ - description: Invoice identifier
+ in: path
+ name: invoiceId
+ required: true
+ schema:
+ description: Invoice identifier
+ type: string
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/HoldRelease"
+ required: true
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Invoice"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - write:billing
+ summary: Release a held invoice
+ tags:
+ - billing
+ /invoices/{invoiceId}/retry-push:
+ post:
+ description: Puts a DRAFT or PUSH_FAILED invoice of a payment provider back in the push queue now, which resumes the push where its last attempt stopped. A draft waiting for finalization in its provider is finalized at once. Refused for a held invoice (409 RetryInvoicePush.Held) and for any other (409 RetryInvoicePush.InvalidStatus). Requires billing to be enabled for the organization.
+ operationId: retryInvoicePush
+ parameters:
+ - description: Invoice identifier
+ in: path
+ name: invoiceId
+ required: true
+ schema:
+ description: Invoice identifier
+ type: string
+ responses:
+ "202":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Invoice"
+ description: Accepted
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - write:billing
+ summary: Retry pushing an invoice to its provider
+ tags:
+ - billing
+ /invoices/{invoiceId}/sync:
+ post:
+ description: "Reads an invoice from its payment provider now and applies what the provider says: its finalization, payment, write-off or void. Refused for an invoice not in a provider (409 SyncInvoice.NotPushed). Requires billing to be enabled for the organization."
+ operationId: syncInvoice
+ parameters:
+ - description: Invoice identifier
+ in: path
+ name: invoiceId
+ required: true
+ schema:
+ description: Invoice identifier
+ type: string
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Invoice"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - write:billing
+ summary: Sync an invoice with its provider
+ tags:
+ - billing
+ /invoices/{invoiceId}/void:
+ post:
+ description: Voids an invoice not yet settled -- a DRAFT, held or not, a MANUAL one, or one a payment provider issued -- which frees its boundary for a recompose. A provider's invoice is voided there first, and here only once the provider confirms (503 VoidInvoice.ProviderUnavailable leaves it unchanged); one the provider reports paid is refused. A paid or written-off invoice cannot be voided. A handoff still pending stays so, and its consumer sees the VOID. Voiding a VOID invoice again answers it unchanged. Requires billing to be enabled for the organization.
+ operationId: voidInvoice
+ parameters:
+ - description: Invoice identifier
+ in: path
+ name: invoiceId
+ required: true
+ schema:
+ description: Invoice identifier
+ type: string
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/InvoiceVoid"
+ required: true
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Invoice"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - write:billing
+ summary: Void an invoice
+ tags:
+ - billing
+ /invoices/{invoiceId}/write-off:
+ post:
+ description: "Records that the organization gave up collecting a MANUAL invoice: it becomes UNCOLLECTIBLE. A handoff still pending stays so, and its consumer sees the new status. Writing off an UNCOLLECTIBLE invoice again answers it unchanged. Requires billing to be enabled for the organization."
+ operationId: writeOffInvoice
+ parameters:
+ - description: Invoice identifier
+ in: path
+ name: invoiceId
+ required: true
+ schema:
+ description: Invoice identifier
+ type: string
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/InvoiceWriteOff"
+ required: true
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Invoice"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - write:billing
+ summary: Write off an invoice
+ tags:
+ - billing
+ /license-families:
+ get:
+ description: "Returns a cursor-paginated page of license families for the current organization, each with the version it currently resolves to: the family's default version, or its highest-numbered published version. A family whose versions are all drafts or all archived is still listed, with no current version."
+ operationId: list-license-families
+ parameters:
+ - description: Opaque pagination cursor from a previous response's nextCursor
+ explode: false
+ in: query
+ name: cursor
+ schema:
+ description: Opaque pagination cursor from a previous response's nextCursor
+ type: string
+ - description: Maximum number of entries to return (default 50, max 200)
+ explode: false
+ in: query
+ name: limit
+ schema:
+ description: Maximum number of entries to return (default 50, max 200)
+ format: int32
+ maximum: 200
+ minimum: 1
+ type: integer
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/PageLicenseFamilyView"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - read:licenses
+ summary: List license families
+ tags:
+ - license-families
+ /license-families/{familySlug}:
+ get:
+ description: |-
+ Resolves a license family to the version it currently serves, returned as currentVersion: the family's default version if it has one, otherwise its highest-numbered PUBLISHED version. This is the stable address for "the current version of this product", so a caller never has to name a version slug.
+
+ A family with a default keeps serving it when a newer version is published -- the default is what the vendor puts forward, and superseding it is an explicit act (make another version the default, or unset it). A family without a default follows publication: the newest PUBLISHED version wins.
+
+ Returns 404 GetLicenseFamily.NoPublishedVersion when the family exists but has no PUBLISHED version, since there is nothing to resolve to. Use ?include=versions to read such a family anyway: with the history requested, the family is returned with its versions and no currentVersion. Use ?version=N for explicit access to one version, including draft and archived ones.
+ operationId: get-license-family
+ parameters:
+ - description: License family slug
+ in: path
+ name: familySlug
+ required: true
+ schema:
+ description: License family slug
+ examples:
+ - enterprise-license
+ type: string
+ - description: "Address this version number instead of resolving the current one. Explicit historical access: the version is returned in currentVersion whatever its lifecycle state, draft and archived included, and the resolution rule does not apply."
+ explode: false
+ in: query
+ name: version
+ schema:
+ description: "Address this version number instead of resolving the current one. Explicit historical access: the version is returned in currentVersion whatever its lifecycle state, draft and archived included, and the resolution rule does not apply."
+ format: int32
+ minimum: 1
+ type: integer
+ - description: "Extra representations to add, comma-separated. Only 'versions' exists today: it adds every version of the family, oldest first, and makes a family with no PUBLISHED version readable instead of a 404. A list, so that another representation can be added later without changing the parameter's type."
+ explode: false
+ in: query
+ name: include
+ schema:
+ description: "Extra representations to add, comma-separated. Only 'versions' exists today: it adds every version of the family, oldest first, and makes a family with no PUBLISHED version readable instead of a 404. A list, so that another representation can be added later without changing the parameter's type."
+ items:
+ enum:
+ - versions
+ type: string
+ type: array
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/LicenseFamilyView"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - read:licenses
+ summary: Get a license family by slug
+ tags:
+ - license-families
+ patch:
+ description: "Changes a family's own settings: whether the public catalogue lists it (families are private until made public), its fallback family, what a trial without a payment method becomes at its end, and the trial reminder's lead. An absent member keeps the stored value. 404 .NotFound; 422 .InvalidFallback, .InvalidTrialEndBehavior, .InvalidTrialReminderDays."
+ operationId: updateLicenseFamily
+ parameters:
+ - description: License family slug
+ in: path
+ name: familySlug
+ required: true
+ schema:
+ description: License family slug
+ examples:
+ - pro
+ type: string
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/LicenseFamilyUpdate"
+ required: true
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/LicenseFamilyView"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - write:licenses
+ summary: Update a license family
+ tags:
+ - licenses
+ /licenses:
+ get:
+ description: Returns a cursor-paginated page of licenses for the current organization.
+ operationId: get-licenses
+ parameters:
+ - description: Opaque pagination cursor from a previous response's nextCursor
+ explode: false
+ in: query
+ name: cursor
+ schema:
+ description: Opaque pagination cursor from a previous response's nextCursor
+ type: string
+ - description: Maximum number of entries to return (default 50, max 200)
+ explode: false
+ in: query
+ name: limit
+ schema:
+ description: Maximum number of entries to return (default 50, max 200)
+ format: int32
+ maximum: 200
+ minimum: 1
+ type: integer
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/PageLicense"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - read:licenses
+ summary: GetUser all licenses
+ tags:
+ - licenses
+ post:
+ description: |-
+ Create a new license with the provided details. Without familySlug or familyId this creates a new license family and the license becomes its version 1. With one of them it adds the next version to that family (familySlug names it by slug, familyId by identifier; sent together they must agree), and the version number is assigned by the server as the next one in the family's sequence. The version starts PUBLISHED, or DRAFT when lifecycleState says so; ARCHIVED is refused (422 CreateLicense.LifecycleStateNotSettable), because a version is archived with archive-license once it has been on sale.
+
+ Where the installation limits how many licenses an organization may have, only a new family counts against that limit (409 CreateLicense.EntitlementLimitReached once it is reached): a new version of an existing family never does.
+ operationId: create-license
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/License"
+ required: true
+ responses:
+ "201":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/License"
+ description: Created
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - write:licenses
+ summary: Create a new license
+ tags:
+ - licenses
+ /licenses/{licenseSlug}:
+ delete:
+ description: Delete a license with the provided details. Deleting the last version of a license family also deletes the family, and frees its slug for a new product to take. A deleted version's number is not reused by the next version of its family.
+ operationId: delete-license
+ parameters:
+ - description: License slug
+ in: path
+ name: licenseSlug
+ required: true
+ schema:
+ description: License slug
+ examples:
+ - license-slug
+ type: string
+ responses:
+ "204":
+ description: No Content
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - write:licenses
+ summary: Delete a license
+ tags:
+ - licenses
+ get:
+ description: Retrieve a license by their unique slug
+ operationId: get-license
+ parameters:
+ - description: License slug
+ in: path
+ name: licenseSlug
+ required: true
+ schema:
+ description: License slug
+ examples:
+ - license-slug
+ type: string
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/License"
+ description: OK
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - read:licenses
+ summary: Get a license by slug
+ tags:
+ - licenses
+ put:
+ description: Update a license with the provided details
+ operationId: update-license
+ parameters:
+ - description: License slug
+ in: path
+ name: licenseSlug
+ required: true
+ schema:
+ description: License slug
+ examples:
+ - license-slug
+ type: string
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/License"
+ required: true
+ responses:
+ "204":
+ description: No Content
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - write:licenses
+ summary: Update a license
+ tags:
+ - licenses
+ /licenses/{licenseSlug}/archive:
+ post:
+ description: "Withdraws a PUBLISHED license version from sale: its family stops resolving to it and no instance can be assigned to it any more, while instances already on it keep it. Only a published version can be archived (409 ArchiveLicense.NotPublished) -- a draft that was never on sale is deleted instead -- and not while it is its family's default (409 ArchiveLicense.DefaultMustBePublished): make another version the default, or unset it, first. Emits LICENSE_ARCHIVED."
+ operationId: archive-license
+ parameters:
+ - description: License version slug
+ in: path
+ name: licenseSlug
+ required: true
+ schema:
+ description: License version slug
+ examples:
+ - enterprise-license-v2
+ type: string
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/License"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - write:licenses
+ summary: Archive a license version
+ tags:
+ - licenses
+ /licenses/{licenseSlug}/entitlements:
+ get:
+ description: Returns a cursor-paginated page of entitlements granted by a license, identified by its unique slug.
+ operationId: get-license-entitlements
+ parameters:
+ - description: License slug
+ in: path
+ name: licenseSlug
+ required: true
+ schema:
+ description: License slug
+ examples:
+ - license-slug
+ type: string
+ - description: Opaque pagination cursor from a previous response's nextCursor
+ explode: false
+ in: query
+ name: cursor
+ schema:
+ description: Opaque pagination cursor from a previous response's nextCursor
+ type: string
+ - description: Maximum number of entries to return (default 50, max 200)
+ explode: false
+ in: query
+ name: limit
+ schema:
+ description: Maximum number of entries to return (default 50, max 200)
+ format: int32
+ maximum: 200
+ minimum: 1
+ type: integer
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/PageLicenseEntitlement"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - read:licenses
+ summary: Get all entitlements for a license
+ tags:
+ - licenses
+ post:
+ description: Associate an entitlement with a license by providing the license slug and entitlement details
+ operationId: associate-entitlement-with-license
+ parameters:
+ - description: License slug
+ in: path
+ name: licenseSlug
+ required: true
+ schema:
+ description: License slug
+ examples:
+ - license-slug
+ type: string
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/LicenseEntitlement"
+ required: true
+ responses:
+ "204":
+ description: No Content
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - write:licenses
+ summary: Associate an entitlement with a license
+ tags:
+ - licenses
+ /licenses/{licenseSlug}/entitlements/{entitlementSlug}:
+ delete:
+ description: Delete a license entitlement with the provided details
+ operationId: delete-license-entitlement
+ parameters:
+ - in: path
+ name: licenseSlug
+ required: true
+ schema:
+ type: string
+ - in: path
+ name: entitlementSlug
+ required: true
+ schema:
+ type: string
+ responses:
+ "204":
+ description: No Content
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - write:licenses
+ summary: Delete a license entitlement
+ tags:
+ - licenses
+ get:
+ description: Retrieve a entitlement for a license by their unique slugs
+ operationId: get-license-entitlement
+ parameters:
+ - in: path
+ name: licenseSlug
+ required: true
+ schema:
+ type: string
+ - in: path
+ name: entitlementSlug
+ required: true
+ schema:
+ type: string
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/LicenseEntitlement"
+ description: OK
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - read:licenses
+ summary: Get a license entitlement
+ tags:
+ - licenses
+ put:
+ description: Update an license entitlement with the provided details
+ operationId: update-license-entitlement
+ parameters:
+ - in: path
+ name: licenseSlug
+ required: true
+ schema:
+ type: string
+ - in: path
+ name: entitlementSlug
+ required: true
+ schema:
+ type: string
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/LicenseEntitlement"
+ required: true
+ responses:
+ "204":
+ description: No Content
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - write:licenses
+ summary: Update an license entitlement
+ tags:
+ - licenses
+ /licenses/{licenseSlug}/invoice-preview:
+ post:
+ description: "Composes the RENEWAL invoice a subscription to this licence version would be billed at a boundary now, without writing anything: an instance's usage or a sample, rated in arrears over the billing period that ends; the base price for the period that starts (or the one that ends, for an ARREARS base); the listed add-ons; a voucher's discount. Works on DRAFT versions, to check prices before publishing. Requires billing to be enabled for the organization."
+ operationId: previewLicenseInvoice
+ parameters:
+ - description: License version slug
+ in: path
+ name: licenseSlug
+ required: true
+ schema:
+ description: License version slug
+ examples:
+ - pro-v2
+ type: string
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/InvoicePreviewScenario"
+ required: true
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/InvoicePreview"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - read:licenses
+ summary: Preview a license invoice
+ tags:
+ - licenses
+ /licenses/{licenseSlug}/prices:
+ get:
+ description: The prices of one licence version, in display order. Requires billing to be enabled for the organization.
+ operationId: listLicensePrices
+ parameters:
+ - description: License version slug
+ in: path
+ name: licenseSlug
+ required: true
+ schema:
+ description: License version slug
+ examples:
+ - pro-v2
+ type: string
+ - description: Only prices in this status; both when omitted
+ explode: false
+ in: query
+ name: status
+ schema:
+ description: Only prices in this status; both when omitted
+ enum:
+ - ACTIVE
+ - DEPRECATED
+ type: string
+ - description: Only prices of this billing model
+ explode: false
+ in: query
+ name: billingModel
+ schema:
+ description: Only prices of this billing model
+ enum:
+ - FLAT_FEE
+ - USAGE_BASED
+ - OVERAGE
+ type: string
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ items:
+ $ref: "#/components/schemas/Price"
+ type: array
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - read:licenses
+ summary: List a license version's prices
+ tags:
+ - licenses
+ post:
+ description: Adds a price to a licence version. A metered price's sale-unit factor is captured from its entitlement now and never changes. Requires billing to be enabled for the organization.
+ operationId: createLicensePrice
+ parameters:
+ - description: License version slug
+ in: path
+ name: licenseSlug
+ required: true
+ schema:
+ description: License version slug
+ examples:
+ - pro-v2
+ type: string
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/NewLicensePrice"
+ required: true
+ responses:
+ "201":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Price"
+ description: Created
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - write:licenses
+ summary: Create a license price
+ tags:
+ - licenses
+ /licenses/{licenseSlug}/prices/{priceId}:
+ get:
+ description: One price of a licence version. Requires billing to be enabled for the organization.
+ operationId: getLicensePrice
+ parameters:
+ - description: License version slug
+ in: path
+ name: licenseSlug
+ required: true
+ schema:
+ description: License version slug
+ examples:
+ - pro-v2
+ type: string
+ - description: Price identifier
+ in: path
+ name: priceId
+ required: true
+ schema:
+ description: Price identifier
+ type: string
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Price"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - read:licenses
+ summary: Get a license price
+ tags:
+ - licenses
+ patch:
+ description: Edits a price of a DRAFT licence version; the prices of a published version are immutable (UpdateLicensePrice.VersionNotDraft). Currency and billing model cannot change. Requires billing to be enabled for the organization.
+ operationId: updateLicensePrice
+ parameters:
+ - description: License version slug
+ in: path
+ name: licenseSlug
+ required: true
+ schema:
+ description: License version slug
+ examples:
+ - pro-v2
+ type: string
+ - description: Price identifier
+ in: path
+ name: priceId
+ required: true
+ schema:
+ description: Price identifier
+ type: string
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/LicensePriceChanges"
+ required: true
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Price"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - write:licenses
+ summary: Update a license price
+ tags:
+ - licenses
+ /licenses/{licenseSlug}/prices/{priceId}/deprecate:
+ post:
+ description: "Retires a price from the catalogue: it keeps billing what is already pinned to it, is no longer offered, and stops being a default. Deprecation cannot be undone. Requires billing to be enabled for the organization."
+ operationId: deprecateLicensePrice
+ parameters:
+ - description: License version slug
+ in: path
+ name: licenseSlug
+ required: true
+ schema:
+ description: License version slug
+ examples:
+ - pro-v2
+ type: string
+ - description: Price identifier
+ in: path
+ name: priceId
+ required: true
+ schema:
+ description: Price identifier
+ type: string
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Price"
+ description: OK
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - write:licenses
+ summary: Deprecate a license price
+ tags:
+ - licenses
+ /licenses/{licenseSlug}/publish:
+ post:
+ description: "Puts a DRAFT license version on sale: it becomes PUBLISHED, so it can be made its family's default, and its family resolves to it when the family has no default and this is its highest published version. Only a draft can be published (409 PublishLicense.NotADraft); an archived version goes back on sale with unarchive-license. Emits LICENSE_PUBLISHED."
+ operationId: publish-license
+ parameters:
+ - description: License version slug
+ in: path
+ name: licenseSlug
+ required: true
+ schema:
+ description: License version slug
+ examples:
+ - enterprise-license-v2
+ type: string
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/License"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - write:licenses
+ summary: Publish a license version
+ tags:
+ - licenses
+ /licenses/{licenseSlug}/unarchive:
+ post:
+ description: "Puts an ARCHIVED license version back on sale: it is PUBLISHED again, can be assigned to instances, and can be what its family resolves to. Only an archived version can be unarchived (409 UnarchiveLicense.NotArchived). Emits LICENSE_UNARCHIVED."
+ operationId: unarchive-license
+ parameters:
+ - description: License version slug
+ in: path
+ name: licenseSlug
+ required: true
+ schema:
+ description: License version slug
+ examples:
+ - enterprise-license-v2
+ type: string
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/License"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - write:licenses
+ summary: Unarchive a license version
+ tags:
+ - licenses
+ /me:
+ get:
+ description: Available to every active human member, including members with no assigned roles or permissions.
+ operationId: GetMe
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Me"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - read:profile
+ summary: Read current user and effective authorization
+ tags:
+ - users
+ /metadata-fields:
+ get:
+ description: "Returns a cursor-paginated page of the metadata fields declared for the org and the given resource type, ordered by displayOrder then createdAt. Only active fields are listed, unless includeArchived is set: an archived field's id is what unarchive-metadata-field takes."
+ operationId: list-metadata-fields
+ parameters:
+ - explode: false
+ in: query
+ name: resourceType
+ required: true
+ schema:
+ enum:
+ - DEPLOYMENT_ZONE
+ - INSTANCE
+ type: string
+ - description: Include archived fields too, each with its archivedAt
+ explode: false
+ in: query
+ name: includeArchived
+ schema:
+ description: Include archived fields too, each with its archivedAt
+ type: boolean
+ - description: Opaque pagination cursor from a previous response's nextCursor
+ explode: false
+ in: query
+ name: cursor
+ schema:
+ description: Opaque pagination cursor from a previous response's nextCursor
+ type: string
+ - description: Maximum number of entries to return (default 50, max 200)
+ explode: false
+ in: query
+ name: limit
+ schema:
+ description: Maximum number of entries to return (default 50, max 200)
+ format: int32
+ maximum: 200
+ minimum: 1
+ type: integer
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/PageMetadataField"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - read:metadata_fields
+ summary: List metadata fields for a resource type
+ tags:
+ - metadataFields
+ post:
+ description: Declares a typed metadata field for a given resource type (DEPLOYMENT_ZONE, INSTANCE). The json_schema is validated against JSON Schema 2020-12 before insertion. Restricted to write:metadata_fields scope (admin-only policy).
+ operationId: create-metadata-field
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/MetadataField"
+ required: true
+ responses:
+ "201":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/MetadataField"
+ description: Created
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - bearerAuth:
+ - write:metadata_fields
+ summary: Create a metadata field
+ tags:
+ - metadataFields
+ /metadata-fields/reorder:
+ post:
+ description: Sets the displayOrder of the listed fields to their position in the array (0-indexed). All ids must belong to the same organization, resource type, and be non-archived; the entire reorder is applied in a single transaction.
+ operationId: reorder-metadata-fields
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/ReorderMetadataFieldsInput"
+ required: true
+ responses:
+ "204":
+ description: No Content
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - write:metadata_fields
+ summary: Reorder metadata fields
+ tags:
+ - metadataFields
+ /metadata-fields/{id}:
+ patch:
+ description: "Updates label and json_schema. `key` and `resourceType` are immutable, and `displayOrder` belongs to POST /metadata-fields/reorder. The new json_schema is validated against JSON Schema 2020-12 and the transition rules: type changes, cardinality changes, and free-string ↔ enum toggles are rejected with 422."
+ operationId: update-metadata-field
+ parameters:
+ - in: path
+ name: id
+ required: true
+ schema:
+ type: string
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/MetadataField"
+ required: true
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/MetadataField"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - write:metadata_fields
+ summary: Update a metadata field
+ tags:
+ - metadataFields
+ /metadata-fields/{id}/archive:
+ post:
+ description: Soft-deletes a metadata field. The row stays in the DB (its archived_at is set) so historic metadata is still interpretable; the field stops appearing in the active schema composition.
+ operationId: archive-metadata-field
+ parameters:
+ - in: path
+ name: id
+ required: true
+ schema:
+ type: string
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/MetadataField"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - write:metadata_fields
+ summary: Archive a metadata field
+ tags:
+ - metadataFields
+ /metadata-fields/{id}/dry-run:
+ post:
+ description: Computes, server-side, how many existing resources of this field's resource type carry a value under this field's key that would NOT satisfy the supplied candidate JSON Schema, plus a small sample. Read-only — does not mutate the field. Replaces the former client-side dry-run that pulled every resource's metadata.
+ operationId: dry-run-metadata-field
+ parameters:
+ - in: path
+ name: id
+ required: true
+ schema:
+ type: string
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/MetadataFieldSchemaProposal"
+ required: true
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Impact"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - write:metadata_fields
+ summary: Preview the impact of a metadata field schema change
+ tags:
+ - metadataFields
+ /metadata-fields/{id}/unarchive:
+ post:
+ description: Restores a previously archived metadata field (clears its archived_at). The field re-enters the active schema composition. Returns 409 if an active field already uses the same key for this resource type, or 404 if the field does not exist or is not archived.
+ operationId: unarchive-metadata-field
+ parameters:
+ - in: path
+ name: id
+ required: true
+ schema:
+ type: string
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/MetadataField"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - write:metadata_fields
+ summary: Unarchive a metadata field
+ tags:
+ - metadataFields
+ /ofrep/v1/evaluate/flags:
+ post:
+ description: |-
+ OFREP bulk evaluation request.
+ The endpoint is called by the client providers to evaluate all flags at once.
+ operationId: evaluateFlagsBulk
+ parameters:
+ - description: The request will be processed only if ETag doesn't match any of the values listed.
+ in: header
+ name: If-None-Match
+ schema:
+ type: string
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/BulkEvaluationRequest"
+ description: EvaluateVariant multiple flags in one request
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ oneOf:
+ - properties:
+ flags:
+ description: Array of evaluation results
+ items:
+ oneOf:
+ - $ref: "#/components/schemas/EvaluationSuccess"
+ - $ref: "#/components/schemas/EvaluationFailure"
+ type: array
+ - $ref: "#/components/schemas/BulkEvaluationFailure"
+ description: OFREP successful evaluation response
+ headers:
+ ETag:
+ description: Entity tag used for cache validation
+ schema:
+ type: string
+ "304":
+ description: Bulk evaluation is not modified
+ "400":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/BulkEvaluationFailure"
+ description: Bad evaluation request
+ "401":
+ description: Unauthorized - You need credentials to access the API
+ "403":
+ description: Forbidden - You are not authorized to access the API
+ "429":
+ description: Rate limit reached on the Flag Management System
+ headers:
+ Retry-After:
+ description: Indicates when to retry the request again
+ schema:
+ type: string
+ "500":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/BulkEvaluationFailure"
+ description: Internal server error
+ security:
+ - bearerAuth:
+ - read:feature_flags
+ summary: OFREP bulk flag evaluation contract
+ tags:
+ - OFREP Core
+ /ofrep/v1/evaluate/flags/{key}:
+ post:
+ description: EvaluateVariant a single feature flag for a given key
+ operationId: evaluateFlag
+ parameters:
+ - in: path
+ name: key
+ required: true
+ schema:
+ type: string
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/EvaluationRequest"
+ description: Evaluation context
+ required: true
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/EvaluationSuccess"
+ description: Flag evaluated successfully
+ "400":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/EvaluationFailure"
+ description: Evaluation failed
+ "500":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/EvaluationFailure"
+ description: Internal error
+ security:
+ - bearerAuth:
+ - read:feature_flags
+ summary: OFREP single flag evaluation contract
+ tags:
+ - OFREP Core
+ /openfeature/v0/manifest:
+ get:
+ description: Returns a manifest of all feature flags that have a fallback_value set in their metadata
+ operationId: get-openfeature-manifest
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/ManifestEnvelope"
+ description: OK
+ headers:
+ X-Manifest-Capabilities:
+ schema:
+ type: string
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - read:feature_flags
+ summary: Get OpenFeature manifest
+ tags:
+ - OpenFeature
+ /public/catalog:
+ get:
+ description: "What a pricing page shows: for each licence family the organization made public, its default version with its ACTIVE prices and user-facing entitlements, and the same for public add-on families. Read with a publishable key (X-Kaiten-Publishable-Key); the organization is the key's. Amounts are in minor units; never sum metered prices into a headline. Cacheable for 60 seconds."
+ operationId: getPublicCatalog
+ parameters:
+ - description: Only this licence family
+ explode: false
+ in: query
+ name: familySlug
+ schema:
+ description: Only this licence family
+ examples:
+ - pro
+ type: string
+ - description: addons (the default) lists the public add-ons too; none leaves them out
+ explode: false
+ in: query
+ name: include
+ schema:
+ description: addons (the default) lists the public add-ons too; none leaves them out
+ items:
+ enum:
+ - addons
+ - none
+ type: string
+ type:
+ - array
+ - "null"
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/PublicCatalog"
+ description: OK
+ headers:
+ Cache-Control:
+ schema:
+ type: string
+ Vary:
+ schema:
+ type: string
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "429":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Too Many Requests
+ headers:
+ Retry-After:
+ description: Seconds to wait before the next attempt
+ schema:
+ minimum: 0
+ type: integer
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - publishableKey: []
+ summary: Get the public catalogue
+ tags:
+ - public
+ /public/checkout-sessions:
+ post:
+ description: "Starts a checkout for a visitor the vendor's backend does not know yet, with the publishable key and a Turnstile token. Writes one checkout session and nothing else: the customer, the instance and the subscription are created by the completion. Answers 201 status=open with the session's ksc_ token, returned once; with dryRun, 200 status=preview, writing and emitting nothing. dueToday is computed by the server. 403 .AnonymousCheckoutDisabled; 422 .TurnstileNotConfigured, .TurnstileFailed, .InvalidPriceSelection, .PriceNotPublic, .AddonNotPublic, .PriceNotFlatFee, .NotSelfServe, .ProviderCannotCapturePayment, .QuantityExceedsMax, .CurrencyMismatch, .InvalidEmail, .InvalidReturnUrl, .VoucherInvalid, .FallbackRequired; 429 .RateLimited (Retry-After): 10 starts per 10 minutes and 30 an hour per visitor address, dryRun included, 1,000 an hour per key; 503 .TurnstileUnavailable (Retry-After)."
+ operationId: createCheckoutSession
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/CheckoutSessionDraft"
+ required: true
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/CheckoutSessionStart"
+ description: "A preview (dryRun): nothing was written"
+ "201":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/CheckoutSessionStart"
+ description: Created
+ headers:
+ Cache-Control:
+ schema:
+ type: string
+ Vary:
+ schema:
+ type: string
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "429":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Too Many Requests
+ headers:
+ Retry-After:
+ description: Seconds to wait before the next attempt
+ schema:
+ minimum: 0
+ type: integer
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - publishableKey: []
+ summary: Start an anonymous checkout
+ tags:
+ - public
+ /public/checkout-sessions/{sessionId}:
+ get:
+ description: "The session its ksc_ was minted for: its status, what it bought and what is due, and, once completed, the customer and instance created. A session still OPEN after expiresAt reads ABANDONED. The token keeps reading it for 24 h after it closed, then answers 401 PublicAuth.InvalidCredential."
+ operationId: getCheckoutSession
parameters:
- - description: Opaque pagination cursor from a previous response's nextCursor
- explode: false
- in: query
- name: cursor
+ - description: The session the ksc_ was minted for; another one answers 401
+ in: path
+ name: sessionId
+ required: true
schema:
- description: Opaque pagination cursor from a previous response's nextCursor
+ description: The session the ksc_ was minted for; another one answers 401
type: string
- - description: Maximum number of entries to return (default 50, max 200)
- explode: false
- in: query
- name: limit
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/CheckoutSession"
+ description: OK
+ headers:
+ Cache-Control:
+ schema:
+ type: string
+ Vary:
+ schema:
+ type: string
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "429":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Too Many Requests
+ headers:
+ Retry-After:
+ description: Seconds to wait before the next attempt
+ schema:
+ minimum: 0
+ type: integer
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - checkoutSession: []
+ summary: Read an anonymous checkout session
+ tags:
+ - public
+ /public/checkout-sessions/{sessionId}/confirm:
+ post:
+ description: "Completes the session when nothing has to be captured -- a FREE plan, a trial on a plan that needs no payment method, nothing due -- creating the customer, the instance and the subscription in one transaction: status=completed. Otherwise answers status=requires_payment_method with a hosted setup page, keeps the ksc_ across its redirect, and completes on the next confirm: awaiting_payment while the first invoice is charged, completed once paid. No body. 403 .AnonymousCheckoutDisabled; 409 .SessionClosed (errors[0].value.status), .DueTodayChanged, .EntitlementLimitReached; 422 .PriceNotPublic, .AddonNotPublic, .FallbackRequired, .ProviderCannotCapturePayment, .VoucherInvalid; 429 .RateLimited (Retry-After): 30 confirms per 10 minutes per visitor address, 1,000 an hour per key; 503 .ProviderUnavailable (Retry-After), Entitlement.VerificationUnavailable."
+ operationId: confirmCheckoutSession
+ parameters:
+ - description: The session the ksc_ was minted for; another one answers 401
+ in: path
+ name: sessionId
+ required: true
schema:
- description: Maximum number of entries to return (default 50, max 200)
- format: int32
- maximum: 200
- minimum: 1
- type: integer
+ description: The session the ksc_ was minted for; another one answers 401
+ type: string
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/CheckoutConfirmation"
+ description: OK
+ headers:
+ Cache-Control:
+ schema:
+ type: string
+ Vary:
+ schema:
+ type: string
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "429":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Too Many Requests
+ headers:
+ Retry-After:
+ description: Seconds to wait before the next attempt
+ schema:
+ minimum: 0
+ type: integer
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - checkoutSession: []
+ summary: Confirm an anonymous checkout
+ tags:
+ - public
+ /public/session/addons/{addonSlug}:
+ put:
+ description: "Attaches the add-on to the session's instance, changes its quantity, or removes it with 0. An attach or an increase changes the entitlements at once. With a live subscription a lower quantity, or 0, is scheduled for the next renewal: pendingQuantity shows it, and the quantity held, its entitlements and its billing stay until then; the quantity held cancels it, and so does an increase. Without a live subscription every change applies at once. The subscription bills the quantity held at each renewal, with no proration. Setting the quantity held, with nothing scheduled, changes nothing. 409 .OtherVersionAttached when another version of the same add-on is held, .BoundaryPending while the period that ended is being closed (Retry-After); 422 .AddonNotPublic, .Incompatible, .QuantityExceedsMax, .CurrencyMismatch, .InstanceRequired."
+ operationId: setSessionAddonQuantity
+ parameters:
+ - description: "An add-on version: the public one, or the one the instance holds"
+ in: path
+ name: addonSlug
+ required: true
+ schema:
+ description: "An add-on version: the public one, or the one the instance holds"
+ examples:
+ - extra-seats-v1
+ type: string
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/SessionAddonQuantity"
+ required: true
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/SessionAddon"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ headers:
+ Retry-After:
+ description: "On .BoundaryPending: seconds until the period's close is expected to have run"
+ schema:
+ minimum: 0
+ type: integer
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "429":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Too Many Requests
+ headers:
+ Retry-After:
+ description: Seconds to wait before the next attempt
+ schema:
+ minimum: 0
+ type: integer
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - customerSession: []
+ summary: Set how many of an add-on the instance holds
+ tags:
+ - public
+ /public/session/billing/cancel:
+ post:
+ description: "Cancels the subscription of the session's instance at the end of the period paid for: it keeps billing and granting until then, and reactivating before then keeps it. A trial ends at once, with nothing to pay. Repeating it changes nothing. 409 .NotActive when there is no live subscription, .BoundaryPending while the period that ended is being closed (Retry-After); 422 .InstanceRequired for a session bound to no instance."
+ operationId: cancelSessionSubscription
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/SessionCancellation"
+ required: true
responses:
"200":
content:
application/json:
schema:
- $ref: "#/components/schemas/PageComponent"
+ $ref: "#/components/schemas/SessionSubscription"
description: OK
"400":
content:
@@ -3268,40 +30167,76 @@ paths:
schema:
$ref: "#/components/schemas/Problem"
description: Forbidden
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ headers:
+ Retry-After:
+ description: "On .BoundaryPending: seconds until the period's close is expected to have run"
+ schema:
+ minimum: 0
+ type: integer
"422":
content:
application/problem+json:
schema:
$ref: "#/components/schemas/Problem"
description: Unprocessable Entity
+ "429":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Too Many Requests
+ headers:
+ Retry-After:
+ description: Seconds to wait before the next attempt
+ schema:
+ minimum: 0
+ type: integer
"500":
content:
application/problem+json:
schema:
$ref: "#/components/schemas/Problem"
description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
security:
- - bearerAuth:
- - read:components
- summary: List components
+ - customerSession: []
+ summary: Cancel the subscription at the period's end
tags:
- - components
+ - public
+ /public/session/billing/payment-method-session:
post:
- description: Create a new standalone component. Optionally derive it from a previous component version.
- operationId: create-component
+ description: Opens the payment provider's hosted page where the session's customer saves a payment method, for its invoices to be charged automatically. Send the customer to url; when it comes back, with kaiten_setup_session in the query, complete the session. Kaiten never sees card data.
+ operationId: createSessionPaymentMethodSession
requestBody:
content:
application/json:
schema:
- $ref: "#/components/schemas/Component"
+ $ref: "#/components/schemas/NewSessionPaymentMethodSession"
required: true
responses:
- "201":
+ "200":
content:
application/json:
schema:
- $ref: "#/components/schemas/Component"
- description: Created
+ $ref: "#/components/schemas/PaymentMethodSession"
+ description: OK
"400":
content:
application/problem+json:
@@ -3338,6 +30273,18 @@ paths:
schema:
$ref: "#/components/schemas/Problem"
description: Unprocessable Entity
+ "429":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Too Many Requests
+ headers:
+ Retry-After:
+ description: Seconds to wait before the next attempt
+ schema:
+ minimum: 0
+ type: integer
"500":
content:
application/problem+json:
@@ -3350,29 +30297,38 @@ paths:
schema:
$ref: "#/components/schemas/Problem"
description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
security:
- - bearerAuth:
- - write:components
- summary: Create a new component
+ - customerSession: []
+ summary: Open a page to save a payment method
tags:
- - components
- /components/{componentSlug}:
- delete:
- description: Delete a component by slug if it is not linked to any release.
- operationId: delete-component
+ - public
+ /public/session/billing/payment-method-session/{sessionId}/complete:
+ post:
+ description: Checks the setup session with the payment provider and makes its payment method the one the customer's invoices are charged to. Safe to repeat. A session of another customer answers not found; one the customer has not completed, a conflict. When the browser never comes back, the provider sync adopts the payment method within one interval.
+ operationId: completeSessionPaymentMethodSession
parameters:
- - description: Component slug
+ - description: "The setup session's id: the kaiten_setup_session the customer came back with"
in: path
- name: componentSlug
+ name: sessionId
required: true
schema:
- description: Component slug
- examples:
- - api-gateway-v1-2-3
+ description: "The setup session's id: the kaiten_setup_session the customer came back with"
+ maxLength: 255
+ minLength: 1
type: string
responses:
- "204":
- description: No Content
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/CompletedPaymentMethodSession"
+ description: OK
"400":
content:
application/problem+json:
@@ -3409,38 +30365,64 @@ paths:
schema:
$ref: "#/components/schemas/Problem"
description: Unprocessable Entity
+ "429":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Too Many Requests
+ headers:
+ Retry-After:
+ description: Seconds to wait before the next attempt
+ schema:
+ minimum: 0
+ type: integer
"500":
content:
application/problem+json:
schema:
$ref: "#/components/schemas/Problem"
description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
security:
- - bearerAuth:
- - write:components
- summary: Delete a component
+ - customerSession: []
+ summary: Complete a payment method page
tags:
- - components
- get:
- description: Returns a single component by its slug.
- operationId: get-component
- parameters:
- - description: Component slug
- in: path
- name: componentSlug
- required: true
- schema:
- description: Component slug
- examples:
- - api-gateway-v1-2-3
- type: string
+ - public
+ /public/session/billing/portal-session:
+ post:
+ description: Opens the payment provider's hosted portal, where the session's customer manages its payment methods and billing details. What it changes there reaches Kaiten through the provider sync.
+ operationId: createSessionPortalSession
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/NewSessionPortalSession"
+ required: true
responses:
"200":
content:
application/json:
schema:
- $ref: "#/components/schemas/Component"
+ $ref: "#/components/schemas/PortalSession"
description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
"401":
content:
application/problem+json:
@@ -3459,49 +30441,63 @@ paths:
schema:
$ref: "#/components/schemas/Problem"
description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
"422":
content:
application/problem+json:
schema:
$ref: "#/components/schemas/Problem"
description: Unprocessable Entity
+ "429":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Too Many Requests
+ headers:
+ Retry-After:
+ description: Seconds to wait before the next attempt
+ schema:
+ minimum: 0
+ type: integer
"500":
content:
application/problem+json:
schema:
$ref: "#/components/schemas/Problem"
description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
security:
- - bearerAuth:
- - read:components
- summary: Get a component by slug
+ - customerSession: []
+ summary: Open the payment provider's customer portal
tags:
- - components
- put:
- description: Update a component in place when unlinked, or create a new version when it is already linked to releases.
- operationId: update-component
- parameters:
- - description: Component slug
- in: path
- name: componentSlug
- required: true
- schema:
- description: Component slug
- examples:
- - api-gateway-v1-2-3
- type: string
- requestBody:
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/Component"
- required: true
+ - public
+ /public/session/billing/reactivate:
+ post:
+ description: "Takes back a cancellation scheduled for the period's end, before it: the subscription renews as before. 409 .NotScheduledForCancellation when none is scheduled (or the subscription has already ended), .BoundaryPending while the period that ended is being closed (Retry-After); 422 .InstanceRequired for a session bound to no instance."
+ operationId: reactivateSessionSubscription
responses:
"200":
content:
application/json:
schema:
- $ref: "#/components/schemas/Component"
+ $ref: "#/components/schemas/SessionSubscription"
description: OK
"400":
content:
@@ -3521,51 +30517,88 @@ paths:
schema:
$ref: "#/components/schemas/Problem"
description: Forbidden
- "404":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Not Found
"409":
content:
application/problem+json:
schema:
$ref: "#/components/schemas/Problem"
description: Conflict
+ headers:
+ Retry-After:
+ description: "On .BoundaryPending: seconds until the period's close is expected to have run"
+ schema:
+ minimum: 0
+ type: integer
"422":
content:
application/problem+json:
schema:
$ref: "#/components/schemas/Problem"
description: Unprocessable Entity
+ "429":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Too Many Requests
+ headers:
+ Retry-After:
+ description: Seconds to wait before the next attempt
+ schema:
+ minimum: 0
+ type: integer
"500":
content:
application/problem+json:
schema:
$ref: "#/components/schemas/Problem"
description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
security:
- - bearerAuth:
- - write:components
- summary: Update a component
+ - customerSession: []
+ summary: Keep a subscription scheduled for cancellation
tags:
- - components
- /connectors:
- get:
- description: Returns every registered connector with its version and settings schema, ordered by name. The registry is platform-wide rather than per-organization and holds one row per connector, so the response is not paginated.
- operationId: list-connectors
+ - public
+ /public/session/checkout:
+ post:
+ description: "Subscribes the session's instance to a price of the public catalogue, with add-ons and a voucher, in one call. With dryRun, answers 200 status=preview and what would be billed today, changing nothing. When a payment method has to be saved first, answers 200 status=requires_payment_method with a setup page: send the customer there, then call again with its setupSessionId. Otherwise answers 201 status=subscribed: the invoice of the first period has been pushed and charged, and payment.status says how that went -- poll GET /public/session/invoices on processing or requires_action. Needs a session bound to an instance. An instance whose live subscription is free (a FREE licence family, no add-on price above 0) is checked out too: that subscription is canceled at once, with no invoice, in the same transaction. 409 .AlreadySubscribed (any other live subscription), .SetupSessionIncomplete (no payment method was saved on the setup page); 422 .InstanceRequired, .NotSelfServe, .PriceNotPublic, .PriceNotFlatFee, .BillingEmailRequired, .InvalidReturnUrl, .ProviderCannotCapturePayment, .VoucherInvalid, .AddonInvalid, .AddonNotPublic, .QuantityExceedsMax, .CurrencyMismatch, .QuotaExceeded (the instance uses more than the plan allows: errors[0].value.entitlementSlugs, location body.licensePriceId); 503 .ProviderUnavailable (Retry-After)."
+ operationId: createSessionCheckout
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/SessionCheckoutOrder"
+ required: true
responses:
"200":
content:
application/json:
schema:
- items:
- $ref: "#/components/schemas/Connector"
- type:
- - array
- - "null"
- description: OK
+ $ref: "#/components/schemas/SessionCheckout"
+ description: "A preview (dryRun), or requires_payment_method with the hosted setup page: nothing was subscribed"
+ "201":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/SessionCheckout"
+ description: Created
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
"401":
content:
application/problem+json:
@@ -3578,38 +30611,81 @@ paths:
schema:
$ref: "#/components/schemas/Problem"
description: Forbidden
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "429":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Too Many Requests
+ headers:
+ Retry-After:
+ description: Seconds to wait before the next attempt
+ schema:
+ minimum: 0
+ type: integer
"500":
content:
application/problem+json:
schema:
$ref: "#/components/schemas/Problem"
- description: Internal Server Error
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
security:
- - bearerAuth:
- - read:organizations
- summary: List registered connectors
+ - customerSession: []
+ summary: Check out
tags:
- - connectors
- /connectors/{connectorName}:
+ - public
+ /public/session/invoices:
get:
- description: Retrieves a registered connector resource including version and settings schema
- operationId: get-connector
+ description: "The session's customer's issued invoices -- its instance's, for a session bound to one -- newest first, with how to pay each. An invoice still awaiting the outcome of its payment is read from the payment provider first, at most every 30 seconds: poll this after a checkout answered processing or requires_action."
+ operationId: listSessionInvoices
parameters:
- - description: Stable connector name
- in: path
- name: connectorName
- required: true
+ - description: Opaque cursor from the previous page's nextCursor
+ explode: false
+ in: query
+ name: cursor
schema:
- description: Stable connector name
- examples:
- - kaiten.integration.crm.attio
+ description: Opaque cursor from the previous page's nextCursor
type: string
+ - description: Page size, 50 by default, 200 at most
+ explode: false
+ in: query
+ name: limit
+ schema:
+ description: Page size, 50 by default, 200 at most
+ format: int32
+ maximum: 200
+ minimum: 0
+ type: integer
responses:
"200":
content:
application/json:
schema:
- $ref: "#/components/schemas/Connector"
+ $ref: "#/components/schemas/PageSessionInvoice"
description: OK
"400":
content:
@@ -3629,18 +30705,24 @@ paths:
schema:
$ref: "#/components/schemas/Problem"
description: Forbidden
- "404":
+ "422":
content:
application/problem+json:
schema:
$ref: "#/components/schemas/Problem"
- description: Not Found
- "422":
+ description: Unprocessable Entity
+ "429":
content:
application/problem+json:
schema:
$ref: "#/components/schemas/Problem"
- description: Unprocessable Entity
+ description: Too Many Requests
+ headers:
+ Retry-After:
+ description: Seconds to wait before the next attempt
+ schema:
+ minimum: 0
+ type: integer
"500":
content:
application/problem+json:
@@ -3648,34 +30730,21 @@ paths:
$ref: "#/components/schemas/Problem"
description: Internal Server Error
security:
- - bearerAuth:
- - read:organizations
- summary: Get connector registration
+ - customerSession: []
+ summary: List the customer's invoices
tags:
- - connectors
- /connectors/{connectorName}/activation:
- delete:
- description: "Deactivates a connector for the authenticated organization. Idempotent, and never refused for an unregistered or unlicensed connector: an organization must always be able to stop using something."
- operationId: deactivate-connector
- parameters:
- - description: Stable connector name
- in: path
- name: connectorName
- required: true
- schema:
- description: Stable connector name
- examples:
- - kaiten.integration.crm.attio
- type: string
+ - public
+ /public/session/portal:
+ get:
+ description: "Everything the customer's portal shows, in one read: the customer, and for a session bound to an instance its subscription, effective quotas with where they come from, add-ons, vouchers and next invoice; the payment method; and what the portal may offer. A session bound to the customer only gets the customer, its instances and the payment method."
+ operationId: getSessionPortal
responses:
- "204":
- description: No Content
- "400":
+ "200":
content:
- application/problem+json:
+ application/json:
schema:
- $ref: "#/components/schemas/Problem"
- description: Bad Request
+ $ref: "#/components/schemas/SessionPortal"
+ description: OK
"401":
content:
application/problem+json:
@@ -3688,44 +30757,58 @@ paths:
schema:
$ref: "#/components/schemas/Problem"
description: Forbidden
- "422":
+ "429":
content:
application/problem+json:
schema:
$ref: "#/components/schemas/Problem"
- description: Unprocessable Entity
+ description: Too Many Requests
+ headers:
+ Retry-After:
+ description: Seconds to wait before the next attempt
+ schema:
+ minimum: 0
+ type: integer
"500":
content:
application/problem+json:
schema:
$ref: "#/components/schemas/Problem"
description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
security:
- - bearerAuth:
- - write:organizations
- summary: Deactivate connector
+ - customerSession: []
+ summary: Read the customer portal
tags:
- - connectors
- put:
- description: "Activates a registered connector for the authenticated organization. Idempotent: activating an already-active connector succeeds without changing when it was first activated. Refused when the organization's licence does not include the connector."
- operationId: activate-connector
- parameters:
- - description: Stable connector name
- in: path
- name: connectorName
- required: true
- schema:
- description: Stable connector name
- examples:
- - kaiten.integration.crm.attio
- type: string
+ - public
+ /public/session/vouchers/redeem:
+ post:
+ description: "Redeems a code on the session's instance, whose subscription is TRIAL, ACTIVE or PAST_DUE: 201 with the redemption, never the code; dryRun answers 200 with the redemption it would make and writes nothing. A PRICE voucher discounts from the next invoice composed (appliesFromInvoice: next), never one already issued; a boost applies at once. Every refusal of the code is the same 422 .VoucherInvalid. 409 .SubscriptionIncomplete (a checkout waits for its payment), .NoLiveSubscription; 422 .InstanceRequired (a session bound to the customer only); 429 .RateLimited, with Retry-After, past 10 attempts per 10 minutes per session or 30 an hour per customer, validate's included."
+ operationId: redeemSessionVoucher
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/SessionRedeem"
+ required: true
responses:
- "200":
+ "201":
content:
application/json:
schema:
- $ref: "#/components/schemas/ConnectorActivation"
- description: OK
+ $ref: "#/components/schemas/SessionRedemption"
+ description: Created
"400":
content:
application/problem+json:
@@ -3744,18 +30827,30 @@ paths:
schema:
$ref: "#/components/schemas/Problem"
description: Forbidden
- "404":
+ "409":
content:
application/problem+json:
schema:
$ref: "#/components/schemas/Problem"
- description: Not Found
+ description: Conflict
"422":
content:
application/problem+json:
schema:
$ref: "#/components/schemas/Problem"
description: Unprocessable Entity
+ "429":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Too Many Requests
+ headers:
+ Retry-After:
+ description: Seconds to wait before the next attempt
+ schema:
+ minimum: 0
+ type: integer
"500":
content:
application/problem+json:
@@ -3768,29 +30863,34 @@ paths:
schema:
$ref: "#/components/schemas/Problem"
description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
security:
- - bearerAuth:
- - write:organizations
- summary: Activate connector
+ - customerSession: []
+ summary: Redeem a voucher code
tags:
- - connectors
- /connectors/{connectorName}/settings:
- delete:
- description: Delete connector settings for the authenticated organization
- operationId: delete-connector-settings
- parameters:
- - description: Stable connector name
- in: path
- name: connectorName
- required: true
- schema:
- description: Stable connector name
- examples:
- - kaiten.integration.crm.attio
- type: string
+ - public
+ /public/session/vouchers/validate:
+ post:
+ description: "Says whether a code would apply to the session's customer -- and its instance, and the price about to be checked out when given -- with what it gives. Every reason it would not gets the same {valid: false}. Nothing is redeemed: checkout redeems. 429 .RateLimited, with Retry-After, past 10 checks per 10 minutes per session or 30 an hour per customer."
+ operationId: validateSessionVoucher
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/SessionVoucherCheck"
+ required: true
responses:
- "204":
- description: No Content
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/SessionVoucherValidity"
+ description: OK
"400":
content:
application/problem+json:
@@ -3815,37 +30915,76 @@ paths:
schema:
$ref: "#/components/schemas/Problem"
description: Unprocessable Entity
+ "429":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Too Many Requests
+ headers:
+ Retry-After:
+ description: Seconds to wait before the next attempt
+ schema:
+ minimum: 0
+ type: integer
"500":
content:
application/problem+json:
schema:
$ref: "#/components/schemas/Problem"
description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
security:
- - bearerAuth:
- - write:organizations
- summary: Delete connector settings
+ - customerSession: []
+ summary: Check a voucher code
tags:
- - connectors
+ - public
+ /publishable-keys:
get:
- description: Get settings for a connector scoped to the authenticated organization. Write-only fields (flagged `writeOnly` in the connector settings schema) are returned redacted as `***`.
- operationId: get-connector-settings
+ description: The organization's publishable keys, newest first. The keys themselves are never returned, only their last four characters. Requires billing to be enabled for the organization.
+ operationId: listPublishableKeys
parameters:
- - description: Stable connector name
- in: path
- name: connectorName
- required: true
+ - description: Also list revoked keys
+ explode: false
+ in: query
+ name: includeRevoked
schema:
- description: Stable connector name
- examples:
- - kaiten.integration.crm.attio
+ description: Also list revoked keys
+ type: boolean
+ - description: Opaque pagination cursor from a previous response's nextCursor
+ explode: false
+ in: query
+ name: cursor
+ schema:
+ description: Opaque pagination cursor from a previous response's nextCursor
type: string
+ - description: Maximum number of entries to return (default 50, max 200)
+ explode: false
+ in: query
+ name: limit
+ schema:
+ description: Maximum number of entries to return (default 50, max 200)
+ format: int32
+ maximum: 200
+ minimum: 1
+ type: integer
responses:
"200":
content:
application/json:
schema:
- $ref: "#/components/schemas/ConnectorSettings"
+ $ref: "#/components/schemas/PagePublishableKey"
description: OK
"400":
content:
@@ -3865,12 +31004,6 @@ paths:
schema:
$ref: "#/components/schemas/Problem"
description: Forbidden
- "404":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Not Found
"422":
content:
application/problem+json:
@@ -3883,39 +31016,40 @@ paths:
schema:
$ref: "#/components/schemas/Problem"
description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
security:
- bearerAuth:
- - read:organizations
- summary: Get connector settings
+ - read:publishable_keys
+ summary: List publishable keys
tags:
- - connectors
- put:
- description: Create or update connector settings for the authenticated organization. Write-only fields (flagged `writeOnly` in the connector settings schema) may be omitted or sent as `***` to keep the stored value; they are returned redacted.
- operationId: update-connector-settings
- parameters:
- - description: Stable connector name
- in: path
- name: connectorName
- required: true
- schema:
- description: Stable connector name
- examples:
- - kaiten.integration.crm.attio
- type: string
+ - publishable-keys
+ post:
+ description: Issues a pk_ key for a web page to read the organization's public catalogue (GET /api/public/catalog) and, with anonymousCheckout, to start anonymous checkouts (POST /api/public/checkout-sessions). The key is returned once; only its digest is stored. anonymousCheckout needs a Turnstile site key and secret (422 .TurnstileNotConfigured); the secret is stored in Vault (422 .VaultNotConfigured without one) and never returned. Requires billing to be enabled for the organization.
+ operationId: createPublishableKey
requestBody:
content:
application/json:
schema:
- $ref: "#/components/schemas/ConnectorSettings"
- description: Connector settings payload to upsert
+ $ref: "#/components/schemas/PublishableKeyDraft"
required: true
responses:
- "200":
+ "201":
content:
application/json:
schema:
- $ref: "#/components/schemas/ConnectorSettings"
- description: OK
+ $ref: "#/components/schemas/PublishableKeyCreated"
+ description: Created
"400":
content:
application/problem+json:
@@ -3934,50 +31068,60 @@ paths:
schema:
$ref: "#/components/schemas/Problem"
description: Forbidden
- "404":
+ "422":
content:
application/problem+json:
schema:
$ref: "#/components/schemas/Problem"
- description: Not Found
- "422":
+ description: Unprocessable Entity
+ "500":
content:
application/problem+json:
schema:
$ref: "#/components/schemas/Problem"
- description: Unprocessable Entity
- "500":
+ description: Internal Server Error
+ "503":
content:
application/problem+json:
schema:
$ref: "#/components/schemas/Problem"
- description: Internal Server Error
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
security:
- bearerAuth:
- - write:organizations
- summary: Upsert connector settings
+ - write:publishable_keys
+ summary: Create a publishable key
tags:
- - connectors
- /connectors/{connectorName}/settings/schema:
- get:
- description: Get the JSON schema used to validate connector settings payloads
- operationId: get-connector-settings-schema
+ - publishable-keys
+ /publishable-keys/{keyId}:
+ patch:
+ description: Changes a live key's label, allowed origins or anonymous checkout settings. An absent member keeps the stored value; an empty Turnstile value clears it. After the write, a key with anonymousCheckout must hold a Turnstile site key and secret, else 422 .TurnstileNotConfigured; a secret on a deployment without Vault answers 422 .VaultNotConfigured. A revoked key answers 409 UpdatePublishableKey.Revoked. Requires billing to be enabled for the organization.
+ operationId: updatePublishableKey
parameters:
- - description: Stable connector name
+ - description: Publishable key id
in: path
- name: connectorName
+ name: keyId
required: true
schema:
- description: Stable connector name
- examples:
- - kaiten.integration.crm.attio
+ description: Publishable key id
type: string
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/PublishableKeyPatch"
+ required: true
responses:
"200":
content:
application/json:
schema:
- $ref: "#/components/schemas/ConnectorSettingsSchema"
+ $ref: "#/components/schemas/PublishableKey"
description: OK
"400":
content:
@@ -4003,6 +31147,12 @@ paths:
schema:
$ref: "#/components/schemas/Problem"
description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
"422":
content:
application/problem+json:
@@ -4015,32 +31165,42 @@ paths:
schema:
$ref: "#/components/schemas/Problem"
description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
security:
- bearerAuth:
- - read:organizations
- summary: Get connector settings schema
+ - write:publishable_keys
+ summary: Update a publishable key
tags:
- - connectors
- /connectors/{connectorName}/state:
- get:
- description: "Returns whether the connector is registered in this deployment, whether the authenticated organization's licence includes it, and whether the organization has activated it. Answers 200 with `available: false` for a connector this deployment does not have."
- operationId: get-connector-state
+ - publishable-keys
+ /publishable-keys/{keyId}/revoke:
+ post:
+ description: The key stops authenticating on the next request, its Turnstile secret is deleted, and its anonymous checkout sessions stop authenticating. Idempotent; revocation is final. Requires billing to be enabled for the organization.
+ operationId: revokePublishableKey
parameters:
- - description: Stable connector name
+ - description: Publishable key id
in: path
- name: connectorName
+ name: keyId
required: true
schema:
- description: Stable connector name
- examples:
- - kaiten.integration.crm.attio
+ description: Publishable key id
type: string
responses:
"200":
content:
application/json:
schema:
- $ref: "#/components/schemas/ConnectorState"
+ $ref: "#/components/schemas/PublishableKey"
description: OK
"400":
content:
@@ -4060,6 +31220,12 @@ paths:
schema:
$ref: "#/components/schemas/Problem"
description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
"422":
content:
application/problem+json:
@@ -4078,16 +31244,22 @@ paths:
schema:
$ref: "#/components/schemas/Problem"
description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
security:
- bearerAuth:
- - read:organizations
- summary: Get connector state for this organization
+ - write:publishable_keys
+ summary: Revoke a publishable key
tags:
- - connectors
- /customers:
+ - publishable-keys
+ /releases:
get:
- description: Returns a cursor-paginated page of customers for the current organization.
- operationId: list-customers
+ description: Returns a cursor-paginated page of releases for the current organization.
+ operationId: list-releases
parameters:
- description: Opaque pagination cursor from a previous response's nextCursor
explode: false
@@ -4111,7 +31283,7 @@ paths:
content:
application/json:
schema:
- $ref: "#/components/schemas/PageCustomer"
+ $ref: "#/components/schemas/PageRelease"
description: OK
"400":
content:
@@ -4145,25 +31317,25 @@ paths:
description: Internal Server Error
security:
- bearerAuth:
- - read:customers
- summary: List customers
+ - read:releases
+ summary: List releases
tags:
- - customers
+ - releases
post:
- description: Create a new customer with the provided details.
- operationId: create-customer
+ description: "Create a new release with the provided details and link the requested components. A release is immutable: there is no update endpoint, and the component list cannot change afterwards. Correcting one means deleting it and creating a new one, which starts a new deployment history."
+ operationId: create-release
requestBody:
content:
application/json:
schema:
- $ref: "#/components/schemas/Customer"
+ $ref: "#/components/schemas/Release"
required: true
responses:
"201":
content:
application/json:
schema:
- $ref: "#/components/schemas/Customer"
+ $ref: "#/components/schemas/Release"
description: Created
"400":
content:
@@ -4213,21 +31385,31 @@ paths:
schema:
$ref: "#/components/schemas/Problem"
description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
security:
- bearerAuth:
- - write:customers
- summary: Create a new customer
+ - write:releases
+ summary: Create a new release
tags:
- - customers
- /customers/{customerSlug}:
+ - releases
+ /releases/{releaseSlug}:
delete:
- description: Delete a customer by their slug.
- operationId: delete-customer
+ description: Delete a release by their slug. Since a release is immutable, this plus a fresh create is the only way to change one -- and it discards the deployment history recorded against the old release. A release still deployed to a zone cannot be deleted (409).
+ operationId: delete-release
parameters:
- - in: path
- name: customerSlug
+ - description: Release slug
+ in: path
+ name: releaseSlug
required: true
schema:
+ description: Release slug
+ examples:
+ - release-slug
type: string
responses:
"204":
@@ -4256,118 +31438,12 @@ paths:
schema:
$ref: "#/components/schemas/Problem"
description: Not Found
- "422":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unprocessable Entity
- "500":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Internal Server Error
- security:
- - bearerAuth:
- - write:customers
- summary: Delete a customer
- tags:
- - customers
- get:
- description: Returns a single customer by their slug.
- operationId: get-customer
- parameters:
- - in: path
- name: customerSlug
- required: true
- schema:
- type: string
- responses:
- "200":
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/Customer"
- description: OK
- "401":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unauthorized
- "403":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Forbidden
- "404":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Not Found
- "422":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unprocessable Entity
- "500":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Internal Server Error
- security:
- - bearerAuth:
- - read:customers
- summary: Get a customer by slug
- tags:
- - customers
- put:
- description: Update a customer's details. Slug and integrations are not renameable/settable through this endpoint -- omit them, or send the values already on record.
- operationId: update-customer
- parameters:
- - in: path
- name: customerSlug
- required: true
- schema:
- type: string
- requestBody:
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/Customer"
- required: true
- responses:
- "204":
- description: No Content
- "400":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Bad Request
- "401":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unauthorized
- "403":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Forbidden
- "404":
+ "409":
content:
application/problem+json:
schema:
$ref: "#/components/schemas/Problem"
- description: Not Found
+ description: Conflict
"422":
content:
application/problem+json:
@@ -4382,42 +31458,30 @@ paths:
description: Internal Server Error
security:
- bearerAuth:
- - write:customers
- summary: Update a customer
+ - write:releases
+ summary: Delete a release
tags:
- - customers
- /customers/{customerSlug}/integrations/{integrationName}:
- delete:
- description: Delete a single integration for a customer
- operationId: delete-customer-integration
+ - releases
+ get:
+ description: Returns a single release by their slug.
+ operationId: get-release-by-slug
parameters:
- - description: Customer slug
- in: path
- name: customerSlug
- required: true
- schema:
- description: Customer slug
- examples:
- - acme-corp
- type: string
- - description: Integration adapter name
+ - description: Release slug
in: path
- name: integrationName
+ name: releaseSlug
required: true
schema:
- description: Integration adapter name
+ description: Release slug
examples:
- - kaiten.integration.crm.attio
- type: string
- responses:
- "204":
- description: No Content
- "400":
+ - release-slug
+ type: string
+ responses:
+ "200":
content:
- application/problem+json:
+ application/json:
schema:
- $ref: "#/components/schemas/Problem"
- description: Bad Request
+ $ref: "#/components/schemas/Release"
+ description: OK
"401":
content:
application/problem+json:
@@ -4450,39 +31514,31 @@ paths:
description: Internal Server Error
security:
- bearerAuth:
- - write:customers
- summary: Delete a customer integration
+ - read:releases
+ summary: Get a release by slug
tags:
- - customers
+ - deploymentZones
+ /roles:
get:
- description: Retrieve a single integration for a customer
- operationId: get-customer-integration
- parameters:
- - description: Customer slug
- in: path
- name: customerSlug
- required: true
- schema:
- description: Customer slug
- examples:
- - acme-corp
- type: string
- - description: Integration adapter name
- in: path
- name: integrationName
- required: true
- schema:
- description: Integration adapter name
- examples:
- - kaiten.integration.crm.attio
- type: string
+ description: Requires a live human member with stored read:users (write:users implies read). Lists immutable built-in templates and current organization roles.
+ operationId: ListRoles
responses:
"200":
content:
application/json:
schema:
- $ref: "#/components/schemas/CustomerIntegration"
+ items:
+ $ref: "#/components/schemas/Role"
+ type:
+ - array
+ - "null"
description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
"401":
content:
application/problem+json:
@@ -4501,6 +31557,12 @@ paths:
schema:
$ref: "#/components/schemas/Problem"
description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
"422":
content:
application/problem+json:
@@ -4515,45 +31577,25 @@ paths:
description: Internal Server Error
security:
- bearerAuth:
- - read:customers
- summary: Get a customer integration
+ - read:users
+ summary: List organization roles
tags:
- - customers
+ - users
post:
- description: Create a single integration for a customer
- operationId: create-customer-integration
- parameters:
- - description: Customer slug
- in: path
- name: customerSlug
- required: true
- schema:
- description: Customer slug
- examples:
- - acme-corp
- type: string
- - description: Integration adapter name
- in: path
- name: integrationName
- required: true
- schema:
- description: Integration adapter name
- examples:
- - kaiten.integration.crm.attio
- type: string
+ description: Requires a live human member with stored write:users who covers current and proposed permissions. Built-in roles are immutable but assignable within caller authority; the last enabled administrator is protected.
+ operationId: CreateRole
requestBody:
content:
application/json:
schema:
- $ref: "#/components/schemas/CustomerIntegration"
- description: Integration payload
+ $ref: "#/components/schemas/CreateRole"
required: true
responses:
"201":
content:
application/json:
schema:
- $ref: "#/components/schemas/CustomerIntegration"
+ $ref: "#/components/schemas/Role"
description: Created
"400":
content:
@@ -4599,45 +31641,92 @@ paths:
description: Internal Server Error
security:
- bearerAuth:
- - write:customers
- summary: Create a customer integration
+ - write:users
+ summary: Create a custom organization role
tags:
- - customers
- put:
- description: Update a single integration for a customer
- operationId: update-customer-integration
+ - users
+ /roles/{slug}:
+ delete:
+ description: Requires a live human member with stored write:users who covers current and proposed permissions. Built-in roles are immutable but assignable within caller authority; the last enabled administrator is protected.
+ operationId: DeleteRole
parameters:
- - description: Customer slug
- in: path
- name: customerSlug
+ - in: path
+ name: slug
required: true
schema:
- description: Customer slug
- examples:
- - acme-corp
type: string
- - description: Integration adapter name
- in: path
- name: integrationName
+ responses:
+ "204":
+ description: No Content
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ security:
+ - bearerAuth:
+ - write:users
+ summary: Delete an unassigned custom organization role
+ tags:
+ - users
+ put:
+ description: Requires a live human member with stored write:users who covers current and proposed permissions. Built-in roles are immutable but assignable within caller authority; the last enabled administrator is protected.
+ operationId: UpdateRole
+ parameters:
+ - in: path
+ name: slug
required: true
schema:
- description: Integration adapter name
- examples:
- - kaiten.integration.crm.attio
type: string
requestBody:
content:
application/json:
schema:
- $ref: "#/components/schemas/CustomerIntegration"
- description: Integration payload
+ $ref: "#/components/schemas/RoleDefinition"
required: true
responses:
"200":
content:
application/json:
schema:
- $ref: "#/components/schemas/CustomerIntegration"
+ $ref: "#/components/schemas/Role"
description: OK
"400":
content:
@@ -4683,14 +31772,14 @@ paths:
description: Internal Server Error
security:
- bearerAuth:
- - write:customers
- summary: Update a customer integration
+ - write:users
+ summary: Replace a custom organization role
tags:
- - customers
- /deployment-zones:
+ - users
+ /service-accounts:
get:
- description: Returns a cursor-paginated page of deployment zones for the current organization. A deployment zone is one place a customer runs a release -- a target they name and classify themselves, holding the release currently on it. It is not an installation of Kaiten; that sense of the term belongs to operations and is not reachable through this API.
- operationId: list-deployment-zones
+ description: Returns a cursor-paginated page of service accounts (machine users) for the current organization.
+ operationId: get-service-accounts
parameters:
- description: Opaque pagination cursor from a previous response's nextCursor
explode: false
@@ -4714,7 +31803,7 @@ paths:
content:
application/json:
schema:
- $ref: "#/components/schemas/PageDeploymentZone"
+ $ref: "#/components/schemas/PageServiceAccount"
description: OK
"400":
content:
@@ -4734,6 +31823,12 @@ paths:
schema:
$ref: "#/components/schemas/Problem"
description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
"422":
content:
application/problem+json:
@@ -4748,25 +31843,27 @@ paths:
description: Internal Server Error
security:
- bearerAuth:
- - read:deployment_zones
- summary: List deployment zones
+ - read:tokens
+ summary: List all service accounts
tags:
- - deploymentZones
+ - service-accounts
post:
- description: Create a new deployment zone with the provided details. Supplying releaseId records the zone's first deployment. A deployment zone is one place a customer runs a release -- a target they name and classify themselves, holding the release currently on it. It is not an installation of Kaiten; that sense of the term belongs to operations and is not reachable through this API.
- operationId: create-deployment-zone
+ description: Create a new service account (machine user) with the provided name. Service accounts can be used to generate API tokens for programmatic access.
+ operationId: create-service-account
requestBody:
content:
application/json:
schema:
- $ref: "#/components/schemas/DeploymentZone"
+ $ref: "#/components/schemas/ServiceAccount"
+ description: Service account details
required: true
responses:
"201":
content:
application/json:
schema:
- $ref: "#/components/schemas/DeploymentZone"
+ $ref: "#/components/schemas/ServiceAccount"
+ description: Created service account
description: Created
"400":
content:
@@ -4786,6 +31883,12 @@ paths:
schema:
$ref: "#/components/schemas/Problem"
description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
"409":
content:
application/problem+json:
@@ -4810,83 +31913,46 @@ paths:
schema:
$ref: "#/components/schemas/Problem"
description: Service Unavailable
- security:
- - bearerAuth:
- - write:deployment_zones
- summary: Create a new deployment zone
- tags:
- - deploymentZones
- /deployment-zones/{deploymentZoneSlug}:
- delete:
- description: Delete a deployment zone by their slug.
- operationId: delete-deployment-zone
- parameters:
- - in: path
- name: deploymentZoneSlug
- required: true
- schema:
- type: string
- responses:
- "204":
- description: No Content
- "400":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Bad Request
- "401":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unauthorized
- "403":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Forbidden
- "404":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Not Found
- "422":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unprocessable Entity
- "500":
- content:
- application/problem+json:
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
schema:
- $ref: "#/components/schemas/Problem"
- description: Internal Server Error
+ minimum: 0
+ type: integer
security:
- bearerAuth:
- - write:deployment_zones
- summary: Delete a deployment zone
+ - write:tokens
+ summary: Create a new service account
tags:
- - deploymentZones
+ - service-accounts
+ /service-accounts/{serviceAccountSlug}:
get:
- description: Returns a single deployment zone by their slug.
- operationId: get-deployment-zone-by-slug
+ description: Retrieve details of a specific service account (machine user) by its slug.
+ operationId: get-service-account
parameters:
- - in: path
- name: deploymentZoneSlug
+ - description: Service account slug
+ in: path
+ name: serviceAccountSlug
required: true
schema:
+ description: Service account slug
+ examples:
+ - service-account-slug
type: string
responses:
"200":
content:
application/json:
schema:
- $ref: "#/components/schemas/DeploymentZone"
+ $ref: "#/components/schemas/ServiceAccount"
+ description: Service account details
description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
"401":
content:
application/problem+json:
@@ -4919,24 +31985,29 @@ paths:
description: Internal Server Error
security:
- bearerAuth:
- - read:deployment_zones
- summary: Get a deployment zone by slug
+ - read:tokens
+ summary: Get a service account by slug
tags:
- - deploymentZones
+ - service-accounts
put:
- description: "Update a deployment zone's details. Changing releaseId records a deployment: the zone's current release becomes the new one and the change is appended to its deployment history, including when the new release is one the zone already ran (a rollback). Omitting releaseId, or echoing back the current one, leaves the history untouched."
- operationId: update-deploymentZone
+ description: Update the name of a service account (machine user) by its slug.
+ operationId: update-service-account
parameters:
- - in: path
- name: deploymentZoneSlug
+ - description: Service account slug
+ in: path
+ name: serviceAccountSlug
required: true
schema:
+ description: Service account slug
+ examples:
+ - service-account-slug
type: string
requestBody:
content:
application/json:
schema:
- $ref: "#/components/schemas/DeploymentZone"
+ $ref: "#/components/schemas/ServiceAccount"
+ description: Service account update details
required: true
responses:
"204":
@@ -4979,15 +32050,24 @@ paths:
description: Internal Server Error
security:
- bearerAuth:
- - write:deployment_zones
- summary: Update a deployment zone
+ - write:tokens
+ summary: Update a service account
tags:
- - deploymentZones
- /entitlement-groups:
+ - service-accounts
+ /service-accounts/{serviceAccountSlug}/tokens:
get:
- description: Returns a cursor-paginated page of entitlement groups for the current organization.
- operationId: list-entitlement-groups
+ description: Returns a cursor-paginated page of API tokens associated with a service account. The organization context is derived from the authenticated user's JWT token. Token secret values are never returned, only metadata.
+ operationId: get-service-account-tokens
parameters:
+ - description: Service account slug
+ in: path
+ name: serviceAccountSlug
+ required: true
+ schema:
+ description: Service account slug
+ examples:
+ - service-account-slug
+ type: string
- description: Opaque pagination cursor from a previous response's nextCursor
explode: false
in: query
@@ -5010,7 +32090,7 @@ paths:
content:
application/json:
schema:
- $ref: "#/components/schemas/PageEntitlementGroup"
+ $ref: "#/components/schemas/PageToken"
description: OK
"400":
content:
@@ -5030,6 +32110,12 @@ paths:
schema:
$ref: "#/components/schemas/Problem"
description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
"422":
content:
application/problem+json:
@@ -5044,25 +32130,38 @@ paths:
description: Internal Server Error
security:
- bearerAuth:
- - read:entitlements
- summary: List all entitlement groups
+ - read:tokens
+ summary: List all tokens for a service account
tags:
- - entitlement-groups
+ - service-accounts
post:
- description: Create a new entitlement group to aggregate entitlements
- operationId: create-entitlement-group
+ description: Create a new API token for a service account. The token value is only returned once upon creation and cannot be retrieved again. The organization context is derived from the authenticated user's JWT token.
+ operationId: create-service-account-token
+ parameters:
+ - description: Service account slug
+ in: path
+ name: serviceAccountSlug
+ required: true
+ schema:
+ description: Service account slug
+ examples:
+ - service-account-slug
+ format: text
+ type: string
requestBody:
content:
application/json:
schema:
- $ref: "#/components/schemas/EntitlementGroup"
+ $ref: "#/components/schemas/PlainToken"
+ description: Token creation details
required: true
responses:
"201":
content:
application/json:
schema:
- $ref: "#/components/schemas/EntitlementGroup"
+ $ref: "#/components/schemas/PlainToken"
+ description: Created token with secret value (only shown once)
description: Created
"400":
content:
@@ -5082,6 +32181,12 @@ paths:
schema:
$ref: "#/components/schemas/Problem"
description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
"409":
content:
application/problem+json:
@@ -5106,21 +32211,42 @@ paths:
schema:
$ref: "#/components/schemas/Problem"
description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
security:
- bearerAuth:
- - write:entitlements
- summary: Create a new entitlement group
+ - write:tokens
+ summary: Create a new token for a service account
tags:
- - entitlement-groups
- /entitlement-groups/{entitlementGroupSlug}:
+ - service-accounts
+ /service-accounts/{serviceAccountSlug}/tokens/{tokenSlug}:
delete:
- description: Delete an entitlement group by its slug. This does not delete the member entitlements.
- operationId: delete-entitlement-group
+ description: Revoke an API token for a service account. Once revoked, the token can no longer be used for authentication. The organization context is derived from the authenticated user's JWT token.
+ operationId: delete-service-account-token
parameters:
- - in: path
- name: entitlementGroupSlug
+ - description: Service account slug
+ in: path
+ name: serviceAccountSlug
+ required: true
+ schema:
+ description: Service account slug
+ examples:
+ - service-account-slug
+ format: text
+ type: string
+ - description: Token slug to revoke
+ in: path
+ name: tokenSlug
required: true
schema:
+ description: Token slug to revoke
+ examples:
+ - my-token-slug
+ format: text
type: string
responses:
"204":
@@ -5163,26 +32289,91 @@ paths:
description: Internal Server Error
security:
- bearerAuth:
- - write:entitlements
- summary: Delete an entitlement group
+ - write:tokens
+ summary: Revoke a token for a service account
tags:
- - entitlement-groups
+ - service-accounts
+ /usage/reports/export:
get:
- description: Retrieve details of a specific entitlement group by its slug
- operationId: get-entitlement-group
+ description: Streams every usage report of the organization in the range, across instances and entitlements, as CSV or NDJSON, ordered by reportedAt. Filters narrow it to one instance or one entitlement, including deleted ones by ID. Use it to keep the usage history before deleting the organization.
+ operationId: exportOrganizationUsageReports
parameters:
- - in: path
- name: entitlementGroupSlug
- required: true
+ - description: Start of the range, inclusive (RFC 3339). Defaults to 30 days before to, moved up to the start of the organization's usage history when that is later. An explicit from before it answers 422 ExportUsageReports.OutsideRetention.
+ explode: false
+ in: query
+ name: from
+ schema:
+ description: Start of the range, inclusive (RFC 3339). Defaults to 30 days before to, moved up to the start of the organization's usage history when that is later. An explicit from before it answers 422 ExportUsageReports.OutsideRetention.
+ format: date-time
+ type: string
+ - description: End of the range, exclusive (RFC 3339). Defaults to now. At most 31 days after from.
+ explode: false
+ in: query
+ name: to
+ schema:
+ description: End of the range, exclusive (RFC 3339). Defaults to now. At most 31 days after from.
+ format: date-time
+ type: string
+ - description: "csv (the default): RFC 4180 with a header row. json: NDJSON, one report per line. Anything else answers 422 ExportUsageReports.InvalidFormat."
+ explode: false
+ in: query
+ name: format
+ schema:
+ description: "csv (the default): RFC 4180 with a header row. json: NDJSON, one report per line. Anything else answers 422 ExportUsageReports.InvalidFormat."
+ examples:
+ - csv
+ type: string
+ - description: Only this instance's reports
+ explode: false
+ in: query
+ name: instanceSlug
+ schema:
+ description: Only this instance's reports
+ type: string
+ - description: Only this instance's reports. Reaches a deleted instance, whose reports are kept.
+ explode: false
+ in: query
+ name: instanceId
+ schema:
+ description: Only this instance's reports. Reaches a deleted instance, whose reports are kept.
+ format: uuid
+ type: string
+ - description: Only this entitlement's reports
+ explode: false
+ in: query
+ name: entitlementSlug
+ schema:
+ description: Only this entitlement's reports
+ type: string
+ - description: Only this entitlement's reports. Reaches a deleted entitlement, whose reports are kept.
+ explode: false
+ in: query
+ name: entitlementId
schema:
+ description: Only this entitlement's reports. Reaches a deleted entitlement, whose reports are kept.
+ format: uuid
type: string
responses:
"200":
content:
- application/json:
+ application/x-ndjson:
schema:
- $ref: "#/components/schemas/EntitlementGroup"
- description: OK
+ type: string
+ text/csv:
+ schema:
+ type: string
+ description: "The reports, as an attachment: CSV with a header row for format=csv, one JSON object per line for format=json"
+ headers:
+ Content-Disposition:
+ description: attachment, with a file name naming the range
+ schema:
+ type: string
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
"401":
content:
application/problem+json:
@@ -5215,28 +32406,36 @@ paths:
description: Internal Server Error
security:
- bearerAuth:
- - read:entitlements
- summary: Get entitlement group by slug
+ - read:instances
+ summary: Export the organization's usage reports
tags:
- - entitlement-groups
- put:
- description: Update an existing entitlement group with the provided details
- operationId: update-entitlement-group
+ - instances
+ /users:
+ get:
+ description: Requires a live human member with stored read:users (write:users implies read). Provider authorization claims are ignored.
+ operationId: ListUsers
parameters:
- - in: path
- name: entitlementGroupSlug
- required: true
+ - explode: false
+ in: query
+ name: limit
+ schema:
+ default: 50
+ format: int32
+ maximum: 200
+ minimum: 1
+ type: integer
+ - explode: false
+ in: query
+ name: cursor
schema:
type: string
- requestBody:
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/EntitlementGroup"
- required: true
responses:
- "204":
- description: No Content
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/PageManagedUser"
+ description: OK
"400":
content:
application/problem+json:
@@ -5261,6 +32460,12 @@ paths:
schema:
$ref: "#/components/schemas/Problem"
description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
"422":
content:
application/problem+json:
@@ -5275,29 +32480,28 @@ paths:
description: Internal Server Error
security:
- bearerAuth:
- - write:entitlements
- summary: Update an entitlement group
+ - read:users
+ summary: List human users
tags:
- - entitlement-groups
- /entitlement-groups/{entitlementGroupSlug}/entitlements:
- post:
- description: Add an existing entitlement as a member of an entitlement group
- operationId: add-entitlement-to-group
+ - users
+ /users/{id}:
+ get:
+ description: Requires a live human member with stored read:users (write:users implies read). Provider authorization claims are ignored.
+ operationId: GetUser
parameters:
- in: path
- name: entitlementGroupSlug
+ name: id
required: true
schema:
+ format: uuid
type: string
- requestBody:
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/Command"
- required: true
responses:
- "201":
- description: Created
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/ManagedUser"
+ description: OK
"400":
content:
application/problem+json:
@@ -5342,25 +32546,26 @@ paths:
description: Internal Server Error
security:
- bearerAuth:
- - write:entitlements
- summary: Add an entitlement to a group
+ - read:users
+ summary: Get a human user
tags:
- - entitlement-groups
- /entitlement-groups/{entitlementGroupSlug}/entitlements/{entitlementSlug}:
- delete:
- description: Remove an entitlement from an entitlement group. The entitlement itself is not deleted.
- operationId: remove-entitlement-from-group
+ - users
+ put:
+ description: Requires a live human member with stored write:users who covers the target's current authority. Profile name and email are global across memberships and become locally managed.
+ operationId: UpdateUser
parameters:
- in: path
- name: entitlementGroupSlug
- required: true
- schema:
- type: string
- - in: path
- name: entitlementSlug
+ name: id
required: true
schema:
+ format: uuid
type: string
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Profile"
+ required: true
responses:
"204":
description: No Content
@@ -5388,6 +32593,12 @@ paths:
schema:
$ref: "#/components/schemas/Problem"
description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
"422":
content:
application/problem+json:
@@ -5402,38 +32613,33 @@ paths:
description: Internal Server Error
security:
- bearerAuth:
- - write:entitlements
- summary: Remove an entitlement from a group
+ - write:users
+ summary: Update a human profile
tags:
- - entitlement-groups
- /entitlement-groups/{entitlementGroupSlug}/usage:
- get:
- description: Retrieve the aggregated usage metrics of all member entitlements for a given instance
- operationId: get-entitlement-group-usage
+ - users
+ /users/{id}/assignments:
+ patch:
+ description: Requires a live human member with stored write:users who covers current and proposed permissions. Built-in roles are immutable but assignable within caller authority; the last enabled administrator is protected.
+ operationId: SetUserAssignments
parameters:
- in: path
- name: entitlementGroupSlug
- required: true
- schema:
- type: string
- - description: Instance slug to get usage for
- explode: false
- in: query
- name: instance
+ name: id
required: true
schema:
- description: Instance slug to get usage for
+ format: uuid
type: string
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/UserRoleAssignments"
+ required: true
responses:
"200":
content:
application/json:
schema:
- items:
- $ref: "#/components/schemas/EntitlementGroupUsage"
- type:
- - array
- - "null"
+ $ref: "#/components/schemas/UserAuthorization"
description: OK
"400":
content:
@@ -5459,6 +32665,12 @@ paths:
schema:
$ref: "#/components/schemas/Problem"
description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
"422":
content:
application/problem+json:
@@ -5473,38 +32685,33 @@ paths:
description: Internal Server Error
security:
- bearerAuth:
- - read:entitlements
- summary: Get aggregated usage for an entitlement group
+ - write:users
+ summary: Set user roles and direct scopes atomically
tags:
- - entitlement-groups
- /entitlements:
- get:
- description: Returns a cursor-paginated page of entitlements for the current organization.
- operationId: list-entitlements
+ - users
+ /users/{id}/authorization:
+ put:
+ description: Requires a live human member with stored write:users who covers current and proposed target authority. The last enabled administrator is protected. Provider authorization claims are ignored.
+ operationId: PutUserAuthorization
parameters:
- - description: Opaque pagination cursor from a previous response's nextCursor
- explode: false
- in: query
- name: cursor
+ - in: path
+ name: id
+ required: true
schema:
- description: Opaque pagination cursor from a previous response's nextCursor
+ format: uuid
type: string
- - description: Maximum number of entries to return (default 50, max 200)
- explode: false
- in: query
- name: limit
- schema:
- description: Maximum number of entries to return (default 50, max 200)
- format: int32
- maximum: 200
- minimum: 1
- type: integer
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/UserAuthorization"
+ required: true
responses:
"200":
content:
application/json:
schema:
- $ref: "#/components/schemas/PageEntitlement"
+ $ref: "#/components/schemas/UserAuthorization"
description: OK
"400":
content:
@@ -5524,6 +32731,18 @@ paths:
schema:
$ref: "#/components/schemas/Problem"
description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
"422":
content:
application/problem+json:
@@ -5538,26 +32757,34 @@ paths:
description: Internal Server Error
security:
- bearerAuth:
- - read:entitlements
- summary: List all entitlements
+ - write:users
+ summary: Replace user roles, permissions and enabled state
tags:
- - entitlements
- post:
- description: Create a new entitlement with the provided details
- operationId: create-entitlement
+ - users
+ /users/{id}/enabled:
+ patch:
+ description: Requires a live human member with stored write:users who covers current and proposed permissions. Built-in roles are immutable but assignable within caller authority; the last enabled administrator is protected.
+ operationId: SetUserEnabled
+ parameters:
+ - in: path
+ name: id
+ required: true
+ schema:
+ format: uuid
+ type: string
requestBody:
content:
application/json:
schema:
- $ref: "#/components/schemas/Entitlement"
+ $ref: "#/components/schemas/UserEnablement"
required: true
responses:
- "201":
+ "200":
content:
application/json:
schema:
- $ref: "#/components/schemas/Entitlement"
- description: Created
+ $ref: "#/components/schemas/UserAuthorization"
+ description: OK
"400":
content:
application/problem+json:
@@ -5600,31 +32827,63 @@ paths:
schema:
$ref: "#/components/schemas/Problem"
description: Internal Server Error
- "503":
+ security:
+ - bearerAuth:
+ - write:users
+ summary: Set user enabled state atomically
+ tags:
+ - users
+ /v1/notification-preferences:
+ get:
+ description: "Every notifiable event, with this user's effective value per channel: their own choice where they made one, the catalog default otherwise. Events absent from the catalog of an installation never appear, which is what keeps high-volume system events out of the feed."
+ operationId: getNotificationPreferences
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/PreferenceMatrix"
+ description: OK
+ "401":
content:
application/problem+json:
schema:
$ref: "#/components/schemas/Problem"
- description: Service Unavailable
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
security:
- bearerAuth:
- - write:entitlements
- summary: Create a new entitlement
+ - read:notifications
+ summary: Read the caller's notification preferences
tags:
- - entitlements
- /entitlements/{entitlementSlug}:
- delete:
- description: Delete an entitlement by its slug
- operationId: delete-entitlement
- parameters:
- - in: path
- name: entitlementSlug
- required: true
- schema:
- type: string
+ - notifications
+ put:
+ description: Stores the signed-in user's choices and answers with the whole matrix. A value that equals the catalog default is stored as no row at all, so a later change to that default still reaches this user.
+ operationId: putNotificationPreferences
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/PreferenceChoices"
+ required: true
responses:
- "204":
- description: No Content
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/PreferenceMatrix"
+ description: OK
"400":
content:
application/problem+json:
@@ -5643,12 +32902,6 @@ paths:
schema:
$ref: "#/components/schemas/Problem"
description: Forbidden
- "404":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Not Found
"422":
content:
application/problem+json:
@@ -5663,26 +32916,74 @@ paths:
description: Internal Server Error
security:
- bearerAuth:
- - write:entitlements
- summary: Delete an entitlement
+ - write:notifications
+ summary: Set the caller's notification preferences
tags:
- - entitlements
+ - notifications
+ /v1/notifications:
get:
- description: Retrieve details of a specific entitlement by its slug
- operationId: get-entitlement
+ description: Returns the signed-in user's notification feed, newest first, with the unread count. Notifications are a view over this organization's audit trail, narrowed to the events the user subscribes to; the id of a notification is the id of the audit trail entry it reports.
+ operationId: listNotifications
parameters:
- - in: path
- name: entitlementSlug
- required: true
+ - description: Which notifications to return (default all)
+ explode: false
+ in: query
+ name: status
+ schema:
+ description: Which notifications to return (default all)
+ enum:
+ - all
+ - unread
+ type: string
+ - description: "Only notifications about these kinds of object; repeat the parameter for several (default: all)"
+ explode: true
+ in: query
+ name: objectType
+ schema:
+ description: "Only notifications about these kinds of object; repeat the parameter for several (default: all)"
+ items:
+ enum:
+ - instance
+ - customer
+ - release
+ - deployment_zone
+ - component
+ - license
+ - token
+ type: string
+ type:
+ - array
+ - "null"
+ - description: Opaque cursor from a previous response's next_cursor
+ explode: false
+ in: query
+ name: cursor
schema:
+ description: Opaque cursor from a previous response's next_cursor
type: string
+ - description: Page size (default 20, max 50)
+ explode: false
+ in: query
+ name: limit
+ schema:
+ description: Page size (default 20, max 50)
+ format: int32
+ maximum: 50
+ minimum: 1
+ type: integer
responses:
"200":
content:
application/json:
schema:
- $ref: "#/components/schemas/Entitlement"
+ $ref: "#/components/schemas/List"
description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
"401":
content:
application/problem+json:
@@ -5695,12 +32996,6 @@ paths:
schema:
$ref: "#/components/schemas/Problem"
description: Forbidden
- "404":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Not Found
"422":
content:
application/problem+json:
@@ -5715,28 +33010,27 @@ paths:
description: Internal Server Error
security:
- bearerAuth:
- - read:entitlements
- summary: Get entitlement by slug
+ - read:notifications
+ summary: List the caller's notifications
tags:
- - entitlements
- put:
- description: Update an existing entitlement with the provided details
- operationId: update-entitlement
- parameters:
- - in: path
- name: entitlementSlug
- required: true
- schema:
- type: string
+ - notifications
+ /v1/notifications/mark-read:
+ post:
+ description: "Marks the given notifications read for the signed-in user, or all of them with all: true. Ids belonging to another organization are ignored rather than rejected, so a retry of a partially applied call is safe."
+ operationId: markNotificationsRead
requestBody:
content:
application/json:
schema:
- $ref: "#/components/schemas/Entitlement"
+ $ref: "#/components/schemas/ReadSelection"
required: true
responses:
- "204":
- description: No Content
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/MarkReadResult"
+ description: OK
"400":
content:
application/problem+json:
@@ -5755,18 +33049,6 @@ paths:
schema:
$ref: "#/components/schemas/Problem"
description: Forbidden
- "404":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Not Found
- "409":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Conflict
"422":
content:
application/problem+json:
@@ -5781,15 +33063,39 @@ paths:
description: Internal Server Error
security:
- bearerAuth:
- - write:entitlements
- summary: Update an entitlement
+ - write:notifications
+ summary: Mark notifications read
tags:
- - entitlements
- /feature-flags:
+ - notifications
+ /vouchers:
get:
- description: Returns a cursor-paginated page of feature flags for the given organization.
- operationId: get-feature-flags
+ description: The organization's vouchers, newest first, with their codes. Requires billing to be enabled for the organization.
+ operationId: listVouchers
parameters:
+ - explode: false
+ in: query
+ name: status
+ schema:
+ enum:
+ - DRAFT
+ - ACTIVE
+ - EXPIRED
+ - EXHAUSTED
+ - ARCHIVED
+ type: string
+ - explode: false
+ in: query
+ name: voucherType
+ schema:
+ enum:
+ - PRICE
+ - ENTITLEMENT_BOOST
+ type: string
+ - explode: false
+ in: query
+ name: restrictedCustomerSlug
+ schema:
+ type: string
- description: Opaque pagination cursor from a previous response's nextCursor
explode: false
in: query
@@ -5812,7 +33118,7 @@ paths:
content:
application/json:
schema:
- $ref: "#/components/schemas/PageFeatureFlag"
+ $ref: "#/components/schemas/PageVoucher"
description: OK
"400":
content:
@@ -5850,29 +33156,39 @@ paths:
schema:
$ref: "#/components/schemas/Problem"
description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
security:
- bearerAuth:
- - read:feature_flags
- summary: Get a list of feature flags
+ - read:vouchers
+ summary: List vouchers
tags:
- - featureflags
+ - vouchers
post:
- description: Create a new featureflag with the provided details
- operationId: create-feature-flag
+ description: "Creates a DRAFT voucher: a PRICE discount or an ENTITLEMENT_BOOST. The code is generated when none is given, and returned. Emits VOUCHER_CREATED, without the code. Requires billing to be enabled for the organization."
+ operationId: createVoucher
requestBody:
content:
application/json:
schema:
- $ref: "#/components/schemas/FeatureFlag"
- description: Feature flag details
+ $ref: "#/components/schemas/VoucherDraft"
required: true
responses:
"201":
content:
application/json:
schema:
- $ref: "#/components/schemas/FeatureFlag"
- description: Created feature flag
+ $ref: "#/components/schemas/Voucher"
description: Created
"400":
content:
@@ -5892,6 +33208,12 @@ paths:
schema:
$ref: "#/components/schemas/Problem"
description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
"409":
content:
application/problem+json:
@@ -5916,63 +33238,34 @@ paths:
schema:
$ref: "#/components/schemas/Problem"
description: Service Unavailable
- security:
- - bearerAuth:
- - write:feature_flags
- summary: Create a new featureflag
- tags:
- - featureflags
- /feature-flags/targeting/context:
- get:
- description: "Returns the identifiers a targeting rule may read, with their types and descriptions, including the organization's own entitlement slugs. Intended for an editor: it is what autocomplete and hover documentation are built from. The set is not closed — a rule may also target on attributes the caller supplies in its own evaluation context."
- operationId: get-targeting-context
- responses:
- "200":
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/TargetingContext"
- description: OK
- "401":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unauthorized
- "403":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Forbidden
- "500":
- content:
- application/problem+json:
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
schema:
- $ref: "#/components/schemas/Problem"
- description: Internal Server Error
+ minimum: 0
+ type: integer
security:
- bearerAuth:
- - read:feature_flags
- summary: Get the targeting context schema
+ - write:vouchers
+ summary: Create a voucher
tags:
- - featureflags
- /feature-flags/targeting/lint:
+ - vouchers
+ /vouchers/lookup:
post:
- description: "Runs the same check create and update run before accepting a targeting rule, and reports what it found without writing anything. A rule that references a fact or an entitlement that does not exist does not fail loudly at evaluation — it simply never matches — so this is what lets an editor say so while the rule is being written. A rejected rule is a 200 carrying `valid: false`, not a client error."
- operationId: lint-targeting-rule
+ description: Finds the voucher a code names, matched without case or separators. The code travels in the body, never in a URL. Requires billing to be enabled for the organization.
+ operationId: lookupVoucher
requestBody:
content:
application/json:
schema:
- $ref: "#/components/schemas/TargetingRuleDraft"
+ $ref: "#/components/schemas/VoucherCode"
required: true
responses:
"200":
content:
application/json:
schema:
- $ref: "#/components/schemas/TargetingRuleVerdict"
+ $ref: "#/components/schemas/Voucher"
description: OK
"400":
content:
@@ -5992,6 +33285,12 @@ paths:
schema:
$ref: "#/components/schemas/Problem"
description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
"422":
content:
application/problem+json:
@@ -6004,28 +33303,40 @@ paths:
schema:
$ref: "#/components/schemas/Problem"
description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
security:
- bearerAuth:
- - write:feature_flags
- summary: Check a targeting rule
+ - read:vouchers
+ summary: Find a voucher by code
tags:
- - featureflags
- /feature-flags/targeting/test:
+ - vouchers
+ /vouchers/validate:
post:
- description: "Runs a rule the way an evaluation would, without a flag and without writing anything: the context is enriched from the same sources (license, entitlements, instance, customer, deployment zone), anything the caller forged under __kaiten is discarded the same way, and the same engine answers. The response carries the enriched facts so an author can see exactly what the rule read — which is usually the answer to why it did or did not match. Nothing is metered or audited: this is authoring, not serving."
- operationId: test-targeting-rule
+ description: "Runs the redemption checks without redeeming: the voucher's own; with instanceSlug, the instance's; with licensePriceId, those that read the subscription, on the subscription that price would start. Answers 200 with valid, and the first failing reason (and eligibility rule) otherwise. 404 .InstanceNotFound, .PriceNotFound; 429 .RateLimited past 60 checks a minute per principal, with Retry-After. Requires billing to be enabled for the organization."
+ operationId: validateVoucher
requestBody:
content:
application/json:
schema:
- $ref: "#/components/schemas/TargetingRuleTrial"
+ $ref: "#/components/schemas/VoucherCheck"
required: true
responses:
"200":
content:
application/json:
schema:
- $ref: "#/components/schemas/TargetingRuleRehearsal"
+ $ref: "#/components/schemas/Validity"
description: OK
"400":
content:
@@ -6045,49 +33356,6 @@ paths:
schema:
$ref: "#/components/schemas/Problem"
description: Forbidden
- "422":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unprocessable Entity
- "500":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Internal Server Error
- security:
- - bearerAuth:
- - write:feature_flags
- summary: Rehearse a targeting rule against a context
- tags:
- - featureflags
- /feature-flags/{featureFlagSlug}:
- delete:
- description: Delete a featureflag with the provided details
- operationId: delete-feature-flag
- parameters:
- - in: path
- name: featureFlagSlug
- required: true
- schema:
- type: string
- responses:
- "204":
- description: No Content
- "401":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unauthorized
- "403":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Forbidden
"404":
content:
application/problem+json:
@@ -6100,86 +33368,54 @@ paths:
schema:
$ref: "#/components/schemas/Problem"
description: Unprocessable Entity
- "500":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Internal Server Error
- security:
- - bearerAuth:
- - write:feature_flags
- summary: Delete a featureflag
- tags:
- - featureflags
- get:
- description: Get a feature flag with the provided slug
- operationId: get-feature-flag
- parameters:
- - in: path
- name: featureFlagSlug
- required: true
- schema:
- type: string
- responses:
- "200":
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/FeatureFlag"
- description: OK
- "401":
+ "429":
content:
application/problem+json:
schema:
$ref: "#/components/schemas/Problem"
- description: Unauthorized
- "403":
- content:
- application/problem+json:
+ description: Too Many Requests
+ headers:
+ Retry-After:
+ description: Seconds to wait before the next attempt
schema:
- $ref: "#/components/schemas/Problem"
- description: Forbidden
- "404":
+ minimum: 0
+ type: integer
+ "500":
content:
application/problem+json:
schema:
$ref: "#/components/schemas/Problem"
- description: Not Found
- "422":
+ description: Internal Server Error
+ "503":
content:
application/problem+json:
schema:
$ref: "#/components/schemas/Problem"
- description: Unprocessable Entity
- "500":
- content:
- application/problem+json:
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
schema:
- $ref: "#/components/schemas/Problem"
- description: Internal Server Error
+ minimum: 0
+ type: integer
security:
- bearerAuth:
- - read:feature_flags
- summary: Get a feature flag by slug
+ - read:voucher_redemptions
+ summary: Validate a voucher code
tags:
- - featureflags
- put:
- description: Update a featureflag with the provided details
- operationId: update-feature-flag
+ - vouchers
+ /vouchers/{voucherId}:
+ delete:
+ description: "Deletes a voucher no instance ever redeemed, whatever its status, with its grants and flag grants: the entitlements and flags they held can be deleted again, and its code can be given to another voucher. A voucher redeemed at least once, or one a checkout waiting for its payment applies, is history (409 DeleteVoucher.Redeemed): archive it instead. Emits VOUCHER_DELETED. Requires billing to be enabled for the organization."
+ operationId: deleteVoucher
parameters:
- - in: path
- name: featureFlagSlug
+ - description: Voucher id
+ in: path
+ name: voucherId
required: true
schema:
+ description: Voucher id
type: string
- requestBody:
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/FeatureFlag"
- description: Feature flag details
- required: true
responses:
"204":
description: No Content
@@ -6207,6 +33443,12 @@ paths:
schema:
$ref: "#/components/schemas/Problem"
description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
"422":
content:
application/problem+json:
@@ -6219,40 +33461,41 @@ paths:
schema:
$ref: "#/components/schemas/Problem"
description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
security:
- bearerAuth:
- - write:feature_flags
- summary: Update a featureflag
+ - write:vouchers
+ summary: Delete a voucher
tags:
- - featureflags
- /instances:
+ - vouchers
get:
- description: Returns a cursor-paginated page of instances for the current organization.
- operationId: getInstances
+ description: One voucher, with its code. Requires billing to be enabled for the organization.
+ operationId: getVoucher
parameters:
- - description: Opaque pagination cursor from a previous response's nextCursor
- explode: false
- in: query
- name: cursor
+ - description: Voucher id
+ in: path
+ name: voucherId
+ required: true
schema:
- description: Opaque pagination cursor from a previous response's nextCursor
+ description: Voucher id
type: string
- - description: Maximum number of entries to return (default 50, max 200)
- explode: false
- in: query
- name: limit
- schema:
- description: Maximum number of entries to return (default 50, max 200)
- format: int32
- maximum: 200
- minimum: 1
- type: integer
responses:
"200":
content:
application/json:
schema:
- $ref: "#/components/schemas/PageInstance"
+ $ref: "#/components/schemas/Voucher"
description: OK
"400":
content:
@@ -6272,6 +33515,12 @@ paths:
schema:
$ref: "#/components/schemas/Problem"
description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
"422":
content:
application/problem+json:
@@ -6284,28 +33533,48 @@ paths:
schema:
$ref: "#/components/schemas/Problem"
description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
security:
- bearerAuth:
- - read:instances
- summary: List all instances
+ - read:vouchers
+ summary: Get a voucher
tags:
- - instances
- post:
- description: Create a new instance with the provided details
- operationId: createInstance
+ - vouchers
+ put:
+ description: Replaces a DRAFT voucher, but its type. An ACTIVE voucher takes a new name, description, expiresAt and maxRedemptions (at least redemptionsCount); its other members must stay as they are (409 UpdateVoucher.NotEditable). Emits VOUCHER_UPDATED. Requires billing to be enabled for the organization.
+ operationId: updateVoucher
+ parameters:
+ - description: Voucher id
+ in: path
+ name: voucherId
+ required: true
+ schema:
+ description: Voucher id
+ type: string
requestBody:
content:
application/json:
schema:
- $ref: "#/components/schemas/Instance"
+ $ref: "#/components/schemas/VoucherDraft"
required: true
responses:
- "201":
+ "200":
content:
application/json:
schema:
- $ref: "#/components/schemas/Instance"
- description: Created
+ $ref: "#/components/schemas/Voucher"
+ description: OK
"400":
content:
application/problem+json:
@@ -6354,29 +33623,37 @@ paths:
schema:
$ref: "#/components/schemas/Problem"
description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
security:
- bearerAuth:
- - write:instances
- summary: Create a new instance
+ - write:vouchers
+ summary: Update a voucher
tags:
- - instances
- /instances/{instanceSlug}:
- delete:
- description: Delete an instance with the provided slug
- operationId: deleteInstance
+ - vouchers
+ /vouchers/{voucherId}/archive:
+ post:
+ description: Stops a voucher from being redeemed. Redemptions already made keep applying until revoked. Emits VOUCHER_ARCHIVED. Requires billing to be enabled for the organization.
+ operationId: archiveVoucher
parameters:
- - description: Instance slug
+ - description: Voucher id
in: path
- name: instanceSlug
+ name: voucherId
required: true
schema:
- description: Instance slug
- examples:
- - instance-slug
+ description: Voucher id
type: string
responses:
- "204":
- description: No Content
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Voucher"
+ description: OK
"400":
content:
application/problem+json:
@@ -6401,102 +33678,61 @@ paths:
schema:
$ref: "#/components/schemas/Problem"
description: Not Found
- "422":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unprocessable Entity
- "500":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Internal Server Error
- security:
- - bearerAuth:
- - write:instances
- summary: Delete an instance
- tags:
- - instances
- get:
- description: Retrieve an instance by their unique slug
- operationId: getInstance
- parameters:
- - description: Instance slug
- in: path
- name: instanceSlug
- required: true
- schema:
- description: Instance slug
- examples:
- - instance-slug
- type: string
- responses:
- "200":
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/Instance"
- description: OK
- "401":
+ "409":
content:
application/problem+json:
schema:
$ref: "#/components/schemas/Problem"
- description: Unauthorized
- "403":
+ description: Conflict
+ "422":
content:
application/problem+json:
schema:
$ref: "#/components/schemas/Problem"
- description: Forbidden
- "404":
+ description: Unprocessable Entity
+ "500":
content:
application/problem+json:
schema:
$ref: "#/components/schemas/Problem"
- description: Not Found
- "422":
+ description: Internal Server Error
+ "503":
content:
application/problem+json:
schema:
$ref: "#/components/schemas/Problem"
- description: Unprocessable Entity
- "500":
- content:
- application/problem+json:
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
schema:
- $ref: "#/components/schemas/Problem"
- description: Internal Server Error
+ minimum: 0
+ type: integer
security:
- bearerAuth:
- - read:instances
- summary: Get an instance by slug
+ - write:vouchers
+ summary: Archive a voucher
tags:
- - instances
- patch:
- description: Partially update an instance. Currently supports the operational status and the commercial lifecycle stage.
- operationId: patchInstance
+ - vouchers
+ /vouchers/{voucherId}/publish:
+ post:
+ description: Makes a DRAFT voucher redeemable (409 PublishVoucher.NotADraft otherwise). Emits VOUCHER_PUBLISHED. Requires billing to be enabled for the organization.
+ operationId: publishVoucher
parameters:
- - description: Instance slug
+ - description: Voucher id
in: path
- name: instanceSlug
+ name: voucherId
required: true
schema:
- description: Instance slug
- examples:
- - instance-slug
+ description: Voucher id
type: string
- requestBody:
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/PatchInstanceBody"
- required: true
responses:
- "204":
- description: No Content
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Voucher"
+ description: OK
"400":
content:
application/problem+json:
@@ -6521,6 +33757,12 @@ paths:
schema:
$ref: "#/components/schemas/Problem"
description: Not Found
+ "409":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Conflict
"422":
content:
application/problem+json:
@@ -6533,34 +33775,60 @@ paths:
schema:
$ref: "#/components/schemas/Problem"
description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
security:
- bearerAuth:
- - write:instances
- summary: Patch an instance
+ - write:vouchers
+ summary: Publish a voucher
tags:
- - instances
- put:
- description: Update an instance with the provided details
- operationId: updateInstance
+ - vouchers
+ /vouchers/{voucherId}/redemptions:
+ get:
+ description: Every redemption of a voucher, newest first. Requires billing to be enabled for the organization.
+ operationId: listVoucherRedemptions
parameters:
- - description: Instance slug
+ - description: Voucher id
in: path
- name: instanceSlug
+ name: voucherId
required: true
schema:
- description: Instance slug
- examples:
- - instance-slug
+ description: Voucher id
type: string
- requestBody:
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/Instance"
- required: true
+ - description: Opaque pagination cursor from a previous response's nextCursor
+ explode: false
+ in: query
+ name: cursor
+ schema:
+ description: Opaque pagination cursor from a previous response's nextCursor
+ type: string
+ - description: Maximum number of entries to return (default 50, max 200)
+ explode: false
+ in: query
+ name: limit
+ schema:
+ description: Maximum number of entries to return (default 50, max 200)
+ format: int32
+ maximum: 200
+ minimum: 1
+ type: integer
responses:
- "204":
- description: No Content
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/PageRedemption"
+ description: OK
"400":
content:
application/problem+json:
@@ -6585,12 +33853,6 @@ paths:
schema:
$ref: "#/components/schemas/Problem"
description: Not Found
- "409":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Conflict
"422":
content:
application/problem+json:
@@ -6603,3585 +33865,2575 @@ paths:
schema:
$ref: "#/components/schemas/Problem"
description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
security:
- bearerAuth:
- - write:instances
- summary: Update an instance
+ - read:vouchers
+ summary: List a voucher's redemptions
tags:
- - instances
- /instances/{instanceSlug}/audit-trails:
- get:
- description: "Returns audit trail events for a given instance, optionally filtered by event name and time range. Results are cursor-paginated (default limit: 50, max: 200) — pass the previous response's nextCursor as the cursor parameter to fetch the next page."
- operationId: getAuditTrails
- parameters:
- - description: Instance slug
- in: path
- name: instanceSlug
- required: true
- schema:
- description: Instance slug
- examples:
- - instance-slug
- type: string
- - description: Filter by exact event name (e.g. ENTITLEMENT_VALUE_GET)
- explode: false
- in: query
- name: event_name
- schema:
- description: Filter by exact event name (e.g. ENTITLEMENT_VALUE_GET)
- type: string
- - description: RFC3339 timestamp — include entries with timestamp ≥ this value
- explode: false
- in: query
- name: after
- schema:
- description: RFC3339 timestamp — include entries with timestamp ≥ this value
- type: string
- - description: RFC3339 timestamp — include entries with timestamp ≤ this value
- explode: false
- in: query
- name: before
- schema:
- description: RFC3339 timestamp — include entries with timestamp ≤ this value
- type: string
- - description: Maximum number of entries to return (default 50, max 200)
- explode: false
- in: query
- name: limit
- schema:
- description: Maximum number of entries to return (default 50, max 200)
- format: int32
- maximum: 200
- minimum: 1
- type: integer
- - description: Opaque pagination cursor from a previous response's nextCursor
- explode: false
- in: query
- name: cursor
- schema:
- description: Opaque pagination cursor from a previous response's nextCursor
- type: string
+ - vouchers
+security:
+ - bearerAuth: []
+servers:
+ - url: /api
+webhooks:
+ com.kaiten.addon.entitlement.v1.assigned:
+ post:
+ description: Triggered when an add-on version grants an entitlement.
+ operationId: onAddonEntitlementAssigned
+ requestBody:
+ content:
+ application/json:
+ schema:
+ additionalProperties: false
+ properties:
+ data:
+ $ref: "#/components/schemas/AddonGrantUpdateDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
+ name:
+ description: Event name
+ enum:
+ - ADDON_ENTITLEMENT_ASSIGNED
+ examples:
+ - ADDON_ENTITLEMENT_ASSIGNED
+ type: string
+ type:
+ description: Event type
+ enum:
+ - com.kaiten.addon.entitlement.v1.assigned
+ examples:
+ - com.kaiten.addon.entitlement.v1.assigned
+ type: string
+ required:
+ - name
+ - type
+ - data
+ type: object
+ required: true
+ responses:
+ "200":
+ description: Webhook processed successfully
+ summary: Add-on Entitlement Assigned Webhook
+ tags:
+ - webhooks
+ - addons
+ com.kaiten.addon.entitlement.v1.unassigned:
+ post:
+ description: Triggered when an add-on grant is removed.
+ operationId: onAddonEntitlementUnassigned
+ requestBody:
+ content:
+ application/json:
+ schema:
+ additionalProperties: false
+ properties:
+ data:
+ $ref: "#/components/schemas/AddonGrantUpdateDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
+ name:
+ description: Event name
+ enum:
+ - ADDON_ENTITLEMENT_UNASSIGNED
+ examples:
+ - ADDON_ENTITLEMENT_UNASSIGNED
+ type: string
+ type:
+ description: Event type
+ enum:
+ - com.kaiten.addon.entitlement.v1.unassigned
+ examples:
+ - com.kaiten.addon.entitlement.v1.unassigned
+ type: string
+ required:
+ - name
+ - type
+ - data
+ type: object
+ required: true
+ responses:
+ "200":
+ description: Webhook processed successfully
+ summary: Add-on Entitlement Unassigned Webhook
+ tags:
+ - webhooks
+ - addons
+ com.kaiten.addon.entitlement.v1.updated:
+ post:
+ description: Triggered when an add-on grant changes.
+ operationId: onAddonEntitlementUpdated
+ requestBody:
+ content:
+ application/json:
+ schema:
+ additionalProperties: false
+ properties:
+ data:
+ $ref: "#/components/schemas/AddonGrantUpdateDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
+ name:
+ description: Event name
+ enum:
+ - ADDON_ENTITLEMENT_UPDATED
+ examples:
+ - ADDON_ENTITLEMENT_UPDATED
+ type: string
+ type:
+ description: Event type
+ enum:
+ - com.kaiten.addon.entitlement.v1.updated
+ examples:
+ - com.kaiten.addon.entitlement.v1.updated
+ type: string
+ required:
+ - name
+ - type
+ - data
+ type: object
+ required: true
+ responses:
+ "200":
+ description: Webhook processed successfully
+ summary: Add-on Entitlement Updated Webhook
+ tags:
+ - webhooks
+ - addons
+ com.kaiten.addon.price.v1.created:
+ post:
+ description: Triggered when a price is added to an add-on version.
+ operationId: onAddonPriceCreated
+ requestBody:
+ content:
+ application/json:
+ schema:
+ additionalProperties: false
+ properties:
+ data:
+ $ref: "#/components/schemas/AddonPriceUpdateDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
+ name:
+ description: Event name
+ enum:
+ - ADDON_PRICE_CREATED
+ examples:
+ - ADDON_PRICE_CREATED
+ type: string
+ type:
+ description: Event type
+ enum:
+ - com.kaiten.addon.price.v1.created
+ examples:
+ - com.kaiten.addon.price.v1.created
+ type: string
+ required:
+ - name
+ - type
+ - data
+ type: object
+ required: true
+ responses:
+ "200":
+ description: Webhook processed successfully
+ summary: Add-on Price Created Webhook
+ tags:
+ - webhooks
+ - addons
+ com.kaiten.addon.price.v1.deprecated:
+ post:
+ description: Triggered when an add-on price stops being offered.
+ operationId: onAddonPriceDeprecated
+ requestBody:
+ content:
+ application/json:
+ schema:
+ additionalProperties: false
+ properties:
+ data:
+ $ref: "#/components/schemas/AddonPriceUpdateDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
+ name:
+ description: Event name
+ enum:
+ - ADDON_PRICE_DEPRECATED
+ examples:
+ - ADDON_PRICE_DEPRECATED
+ type: string
+ type:
+ description: Event type
+ enum:
+ - com.kaiten.addon.price.v1.deprecated
+ examples:
+ - com.kaiten.addon.price.v1.deprecated
+ type: string
+ required:
+ - name
+ - type
+ - data
+ type: object
+ required: true
+ responses:
+ "200":
+ description: Webhook processed successfully
+ summary: Add-on Price Deprecated Webhook
+ tags:
+ - webhooks
+ - addons
+ com.kaiten.addon.v1.archived:
+ post:
+ description: Triggered when an add-on version is withdrawn from sale.
+ operationId: onAddonArchived
+ requestBody:
+ content:
+ application/json:
+ schema:
+ additionalProperties: false
+ properties:
+ data:
+ $ref: "#/components/schemas/AddonDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
+ name:
+ description: Event name
+ enum:
+ - ADDON_ARCHIVED
+ examples:
+ - ADDON_ARCHIVED
+ type: string
+ type:
+ description: Event type
+ enum:
+ - com.kaiten.addon.v1.archived
+ examples:
+ - com.kaiten.addon.v1.archived
+ type: string
+ required:
+ - name
+ - type
+ - data
+ type: object
+ required: true
+ responses:
+ "200":
+ description: Webhook processed successfully
+ summary: Add-on Archived Webhook
+ tags:
+ - webhooks
+ - addons
+ com.kaiten.addon.v1.created:
+ post:
+ description: Triggered when an add-on version is created.
+ operationId: onAddonCreated
+ requestBody:
+ content:
+ application/json:
+ schema:
+ additionalProperties: false
+ properties:
+ data:
+ $ref: "#/components/schemas/AddonDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
+ name:
+ description: Event name
+ enum:
+ - ADDON_CREATED
+ examples:
+ - ADDON_CREATED
+ type: string
+ type:
+ description: Event type
+ enum:
+ - com.kaiten.addon.v1.created
+ examples:
+ - com.kaiten.addon.v1.created
+ type: string
+ required:
+ - name
+ - type
+ - data
+ type: object
+ required: true
responses:
"200":
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/PageAuditTrail"
- description: OK
- "400":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Bad Request
- "401":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unauthorized
- "403":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Forbidden
- "404":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Not Found
- "422":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unprocessable Entity
- "500":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Internal Server Error
- security:
- - bearerAuth:
- - read:instances
- summary: List audit trail entries for an instance
+ description: Webhook processed successfully
+ summary: Add-on Created Webhook
tags:
- - instances
- /instances/{instanceSlug}/entitlements/usage:
- get:
- description: Retrieve a list of all entitlements usage metrics for a specific instance
- operationId: getEntitlementsUsageMetrics
- parameters:
- - description: Instance slug
- in: path
- name: instanceSlug
- required: true
- schema:
- description: Instance slug
- examples:
- - instance-slug
- type: string
+ - webhooks
+ - addons
+ com.kaiten.addon.v1.deleted:
+ post:
+ description: Triggered when an add-on version is deleted.
+ operationId: onAddonDeleted
+ requestBody:
+ content:
+ application/json:
+ schema:
+ additionalProperties: false
+ properties:
+ data:
+ $ref: "#/components/schemas/DeletedAddonDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
+ name:
+ description: Event name
+ enum:
+ - ADDON_DELETED
+ examples:
+ - ADDON_DELETED
+ type: string
+ type:
+ description: Event type
+ enum:
+ - com.kaiten.addon.v1.deleted
+ examples:
+ - com.kaiten.addon.v1.deleted
+ type: string
+ required:
+ - name
+ - type
+ - data
+ type: object
+ required: true
responses:
"200":
- content:
- application/json:
- schema:
- items:
- $ref: "#/components/schemas/EntitlementUsage"
- type:
- - array
- - "null"
- description: OK
- "400":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Bad Request
- "401":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unauthorized
- "403":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Forbidden
- "404":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Not Found
- "422":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unprocessable Entity
- "500":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Internal Server Error
- security:
- - bearerAuth:
- - read:instances
- summary: List all entitlements usage metrics
+ description: Webhook processed successfully
+ summary: Add-on Deleted Webhook
tags:
- - instances
- /instances/{instanceSlug}/entitlements/{entitlementSlug}/usage:
- get:
- description: Retrieve the usage metrics for a specific entitlement in a given instance
- operationId: getEntitlementUsageMetrics
- parameters:
- - description: Instance slug
- in: path
- name: instanceSlug
- required: true
- schema:
- description: Instance slug
- examples:
- - instance-slug
- type: string
- - description: Entitlement slug
- in: path
- name: entitlementSlug
- required: true
- schema:
- description: Entitlement slug
- examples:
- - entitlement-slug
- type: string
+ - webhooks
+ - addons
+ com.kaiten.addon.v1.published:
+ post:
+ description: Triggered when a draft add-on version goes on sale.
+ operationId: onAddonPublished
+ requestBody:
+ content:
+ application/json:
+ schema:
+ additionalProperties: false
+ properties:
+ data:
+ $ref: "#/components/schemas/AddonDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
+ name:
+ description: Event name
+ enum:
+ - ADDON_PUBLISHED
+ examples:
+ - ADDON_PUBLISHED
+ type: string
+ type:
+ description: Event type
+ enum:
+ - com.kaiten.addon.v1.published
+ examples:
+ - com.kaiten.addon.v1.published
+ type: string
+ required:
+ - name
+ - type
+ - data
+ type: object
+ required: true
responses:
"200":
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/EntitlementUsage"
- description: OK
- "400":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Bad Request
- "401":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unauthorized
- "403":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Forbidden
- "404":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Not Found
- "422":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unprocessable Entity
- "500":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Internal Server Error
- security:
- - bearerAuth:
- - read:instances
- summary: Get entitlement usage metrics for an instance
+ description: Webhook processed successfully
+ summary: Add-on Published Webhook
+ tags:
+ - webhooks
+ - addons
+ com.kaiten.addon.v1.unarchived:
+ post:
+ description: Triggered when an archived add-on version goes back on sale.
+ operationId: onAddonUnarchived
+ requestBody:
+ content:
+ application/json:
+ schema:
+ additionalProperties: false
+ properties:
+ data:
+ $ref: "#/components/schemas/AddonDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
+ name:
+ description: Event name
+ enum:
+ - ADDON_UNARCHIVED
+ examples:
+ - ADDON_UNARCHIVED
+ type: string
+ type:
+ description: Event type
+ enum:
+ - com.kaiten.addon.v1.unarchived
+ examples:
+ - com.kaiten.addon.v1.unarchived
+ type: string
+ required:
+ - name
+ - type
+ - data
+ type: object
+ required: true
+ responses:
+ "200":
+ description: Webhook processed successfully
+ summary: Add-on Unarchived Webhook
tags:
- - instances
+ - webhooks
+ - addons
+ com.kaiten.addon.v1.updated:
post:
- description: Report a usage metric for a specific entitlement in a given instance. This endpoint allows you to report the usage of an entitlement, including optional metadata and a timestamp.
- operationId: reportEntitlementUsageMetric
- parameters:
- - description: Instance slug
- in: path
- name: instanceSlug
- required: true
- schema:
- description: Instance slug
- examples:
- - instance-slug
- type: string
- - description: Entitlement slug
- in: path
- name: entitlementSlug
- required: true
- schema:
- description: Entitlement slug
- examples:
- - entitlement-slug
- type: string
+ description: Triggered when an add-on version, its compatibility or its family's visibility changes.
+ operationId: onAddonUpdated
requestBody:
content:
application/json:
schema:
- $ref: "#/components/schemas/ReportEntitlementUsageBody"
+ additionalProperties: false
+ properties:
+ data:
+ $ref: "#/components/schemas/AddonUpdateDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
+ name:
+ description: Event name
+ enum:
+ - ADDON_UPDATED
+ examples:
+ - ADDON_UPDATED
+ type: string
+ type:
+ description: Event type
+ enum:
+ - com.kaiten.addon.v1.updated
+ examples:
+ - com.kaiten.addon.v1.updated
+ type: string
+ required:
+ - name
+ - type
+ - data
+ type: object
required: true
responses:
"200":
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/EntitlementUsage"
- description: OK
- "400":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Bad Request
- "401":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unauthorized
- "403":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Forbidden
- "404":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Not Found
- "409":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Conflict
- "422":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unprocessable Entity
- "500":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Internal Server Error
- security:
- - bearerAuth:
- - write:instances
- summary: Report entitlement usage metric for an instance
+ description: Webhook processed successfully
+ summary: Add-on Updated Webhook
tags:
- - instances
- /instances/{instanceSlug}/integrations/{integrationName}:
- delete:
- description: Delete a single integration for an instance
- operationId: delete-instance-integration
- parameters:
- - description: Instance slug
- in: path
- name: instanceSlug
- required: true
- schema:
- description: Instance slug
- examples:
- - instance-slug
- type: string
- - description: Integration adapter name
- in: path
- name: integrationName
- required: true
- schema:
- description: Integration adapter name
- examples:
- - kaiten.integration.crm.attio
- type: string
+ - webhooks
+ - addons
+ com.kaiten.billing_provider.v1.connected:
+ post:
+ description: Triggered when an organization connects a payment provider (activates its connector).
+ operationId: onBillingProviderConnected
+ requestBody:
+ content:
+ application/json:
+ schema:
+ additionalProperties: false
+ properties:
+ data:
+ $ref: "#/components/schemas/ConnectionDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
+ name:
+ description: Event name
+ enum:
+ - BILLING_PROVIDER_CONNECTED
+ examples:
+ - BILLING_PROVIDER_CONNECTED
+ type: string
+ type:
+ description: Event type
+ enum:
+ - com.kaiten.billing_provider.v1.connected
+ examples:
+ - com.kaiten.billing_provider.v1.connected
+ type: string
+ required:
+ - name
+ - type
+ - data
+ type: object
+ required: true
responses:
- "204":
- description: No Content
- "400":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Bad Request
- "401":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unauthorized
- "403":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Forbidden
- "404":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Not Found
- "422":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unprocessable Entity
- "500":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Internal Server Error
- security:
- - bearerAuth:
- - write:instances
- summary: Delete an instance integration
+ "200":
+ description: Webhook processed successfully
+ summary: Billing Provider Connected Webhook
tags:
- - instances
- get:
- description: Retrieve a single integration for an instance
- operationId: get-instance-integration
- parameters:
- - description: Instance slug
- in: path
- name: instanceSlug
- required: true
- schema:
- description: Instance slug
- examples:
- - instance-slug
- type: string
- - description: Integration adapter name
- in: path
- name: integrationName
- required: true
- schema:
- description: Integration adapter name
- examples:
- - kaiten.integration.crm.attio
- type: string
+ - webhooks
+ - billing
+ com.kaiten.billing_provider.v1.disconnected:
+ post:
+ description: Triggered when an organization disconnects a payment provider (deactivates its connector).
+ operationId: onBillingProviderDisconnected
+ requestBody:
+ content:
+ application/json:
+ schema:
+ additionalProperties: false
+ properties:
+ data:
+ $ref: "#/components/schemas/ConnectionDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
+ name:
+ description: Event name
+ enum:
+ - BILLING_PROVIDER_DISCONNECTED
+ examples:
+ - BILLING_PROVIDER_DISCONNECTED
+ type: string
+ type:
+ description: Event type
+ enum:
+ - com.kaiten.billing_provider.v1.disconnected
+ examples:
+ - com.kaiten.billing_provider.v1.disconnected
+ type: string
+ required:
+ - name
+ - type
+ - data
+ type: object
+ required: true
responses:
"200":
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/InstanceIntegration"
- description: OK
- "401":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unauthorized
- "403":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Forbidden
- "404":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Not Found
- "422":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unprocessable Entity
- "500":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Internal Server Error
- security:
- - bearerAuth:
- - read:instances
- summary: Get an instance integration
+ description: Webhook processed successfully
+ summary: Billing Provider Disconnected Webhook
tags:
- - instances
+ - webhooks
+ - billing
+ com.kaiten.billing_provider.v1.sync_failed:
post:
- description: Create a single integration for an instance
- operationId: create-instance-integration
- parameters:
- - description: Instance slug
- in: path
- name: instanceSlug
- required: true
- schema:
- description: Instance slug
- examples:
- - instance-slug
- type: string
- - description: Integration adapter name
- in: path
- name: integrationName
- required: true
- schema:
- description: Integration adapter name
- examples:
- - kaiten.integration.crm.attio
- type: string
+ description: Triggered when syncing with a payment provider has failed three passes in a row, once per streak.
+ operationId: onBillingProviderSyncFailed
requestBody:
content:
application/json:
schema:
- $ref: "#/components/schemas/InstanceIntegration"
- description: Integration payload
+ additionalProperties: false
+ properties:
+ data:
+ $ref: "#/components/schemas/SyncFailureDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
+ name:
+ description: Event name
+ enum:
+ - BILLING_PROVIDER_SYNC_FAILED
+ examples:
+ - BILLING_PROVIDER_SYNC_FAILED
+ type: string
+ type:
+ description: Event type
+ enum:
+ - com.kaiten.billing_provider.v1.sync_failed
+ examples:
+ - com.kaiten.billing_provider.v1.sync_failed
+ type: string
+ required:
+ - name
+ - type
+ - data
+ type: object
required: true
responses:
- "201":
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/InstanceIntegration"
- description: Created
- "400":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Bad Request
- "401":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unauthorized
- "403":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Forbidden
- "404":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Not Found
- "409":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Conflict
- "422":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unprocessable Entity
- "500":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Internal Server Error
- security:
- - bearerAuth:
- - write:instances
- summary: Create an instance integration
+ "200":
+ description: Webhook processed successfully
+ summary: Billing Provider Sync Failed Webhook
tags:
- - instances
- put:
- description: Update a single integration for an instance
- operationId: update-instance-integration
- parameters:
- - description: Instance slug
- in: path
- name: instanceSlug
- required: true
- schema:
- description: Instance slug
- examples:
- - instance-slug
- type: string
- - description: Integration adapter name
- in: path
- name: integrationName
- required: true
- schema:
- description: Integration adapter name
- examples:
- - kaiten.integration.crm.attio
- type: string
+ - webhooks
+ - billing
+ com.kaiten.checkout_session.v1.abandoned:
+ post:
+ description: "Triggered when an anonymous checkout ends unpaid: still OPEN 24 h after its start (status ABANDONED), or its INCOMPLETE subscription expired or was canceled (status EXPIRED, with the customer and instance to clean up)."
+ operationId: onCheckoutSessionAbandoned
requestBody:
content:
application/json:
schema:
- $ref: "#/components/schemas/InstanceIntegration"
- description: Integration payload
+ additionalProperties: false
+ properties:
+ data:
+ $ref: "#/components/schemas/SessionEventDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
+ name:
+ description: Event name
+ enum:
+ - CHECKOUT_SESSION_ABANDONED
+ examples:
+ - CHECKOUT_SESSION_ABANDONED
+ type: string
+ type:
+ description: Event type
+ enum:
+ - com.kaiten.checkout_session.v1.abandoned
+ examples:
+ - com.kaiten.checkout_session.v1.abandoned
+ type: string
+ required:
+ - name
+ - type
+ - data
+ type: object
required: true
responses:
"200":
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/InstanceIntegration"
- description: OK
- "400":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Bad Request
- "401":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unauthorized
- "403":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Forbidden
- "404":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Not Found
- "409":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Conflict
- "422":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unprocessable Entity
- "500":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Internal Server Error
- security:
- - bearerAuth:
- - write:instances
- summary: Update an instance integration
+ description: Webhook processed successfully
+ summary: Checkout Session Abandoned Webhook
+ tags:
+ - webhooks
+ - checkout-sessions
+ com.kaiten.checkout_session.v1.completed:
+ post:
+ description: "Triggered when an anonymous checkout completes: its ACTIVATION invoice is paid, or nothing was due. Carries the customer and instance it created; provision on this event, never on instance.v1.created."
+ operationId: onCheckoutSessionCompleted
+ requestBody:
+ content:
+ application/json:
+ schema:
+ additionalProperties: false
+ properties:
+ data:
+ $ref: "#/components/schemas/SessionEventDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
+ name:
+ description: Event name
+ enum:
+ - CHECKOUT_SESSION_COMPLETED
+ examples:
+ - CHECKOUT_SESSION_COMPLETED
+ type: string
+ type:
+ description: Event type
+ enum:
+ - com.kaiten.checkout_session.v1.completed
+ examples:
+ - com.kaiten.checkout_session.v1.completed
+ type: string
+ required:
+ - name
+ - type
+ - data
+ type: object
+ required: true
+ responses:
+ "200":
+ description: Webhook processed successfully
+ summary: Checkout Session Completed Webhook
tags:
- - instances
- /integration/{adapter}/customer/{externalId}:
- get:
- description: Resolve a customer through one of its integrations using adapter + external ID.
- operationId: get-customer-integration-by-external-id
- parameters:
- - description: Integration adapter without the kaiten.integration. prefix
- in: path
- name: adapter
- required: true
- schema:
- description: Integration adapter without the kaiten.integration. prefix
- examples:
- - crm.attio
- type: string
- - description: External identifier in the third-party system
- in: path
- name: externalId
- required: true
- schema:
- description: External identifier in the third-party system
- examples:
- - rec_12345
- type: string
+ - webhooks
+ - checkout-sessions
+ com.kaiten.checkout_session.v1.started:
+ post:
+ description: Triggered when an anonymous checkout starts (POST /public/checkout-sessions). A dryRun emits nothing.
+ operationId: onCheckoutSessionStarted
+ requestBody:
+ content:
+ application/json:
+ schema:
+ additionalProperties: false
+ properties:
+ data:
+ $ref: "#/components/schemas/SessionEventDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
+ name:
+ description: Event name
+ enum:
+ - CHECKOUT_SESSION_STARTED
+ examples:
+ - CHECKOUT_SESSION_STARTED
+ type: string
+ type:
+ description: Event type
+ enum:
+ - com.kaiten.checkout_session.v1.started
+ examples:
+ - com.kaiten.checkout_session.v1.started
+ type: string
+ required:
+ - name
+ - type
+ - data
+ type: object
+ required: true
responses:
"200":
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/CustomerIntegrationResource"
- description: OK
- "401":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unauthorized
- "403":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Forbidden
- "404":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Not Found
- "422":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unprocessable Entity
- "500":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Internal Server Error
- security:
- - bearerAuth:
- - read:customers
- summary: Get a customer by integration external ID
+ description: Webhook processed successfully
+ summary: Checkout Session Started Webhook
tags:
- - integrations
- - customers
- patch:
- description: Create or partially update a customer resolved through adapter + external ID. Missing fields are left unchanged.
- operationId: upsert-customer-integration-by-external-id
- parameters:
- - description: Integration adapter without the kaiten.integration. prefix
- in: path
- name: adapter
- required: true
- schema:
- description: Integration adapter without the kaiten.integration. prefix
- examples:
- - crm.attio
- type: string
- - description: External identifier in the third-party system
- in: path
- name: externalId
- required: true
- schema:
- description: External identifier in the third-party system
- examples:
- - rec_12345
- type: string
+ - webhooks
+ - checkout-sessions
+ com.kaiten.component.v1.created:
+ post:
+ description: Triggered when a new component is created.
+ operationId: onComponentCreated
requestBody:
content:
application/json:
schema:
- $ref: "#/components/schemas/CustomerBody"
+ additionalProperties: false
+ properties:
+ data:
+ $ref: "#/components/schemas/ComponentDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
+ name:
+ description: Event name
+ enum:
+ - COMPONENT_CREATED
+ examples:
+ - COMPONENT_CREATED
+ type: string
+ type:
+ description: Event type
+ enum:
+ - com.kaiten.component.v1.created
+ examples:
+ - com.kaiten.component.v1.created
+ type: string
+ required:
+ - name
+ - type
+ - data
+ type: object
required: true
responses:
"200":
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/CustomerIntegrationResource"
- description: OK
- "400":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Bad Request
- "401":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unauthorized
- "403":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Forbidden
- "404":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Not Found
- "409":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Conflict
- "422":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unprocessable Entity
- "500":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Internal Server Error
- "503":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Service Unavailable
- security:
- - bearerAuth:
- - write:customers
- summary: Upsert a customer by integration external ID
+ description: Webhook processed successfully
+ summary: Component Created Webhook
tags:
- - integrations
- - customers
- /integration/{adapter}/instance/{externalId}:
- get:
- description: Resolve an instance through one of its integrations using adapter + external ID.
- operationId: get-instance-integration-by-external-id
- parameters:
- - description: Integration adapter without the kaiten.integration. prefix
- in: path
- name: adapter
- required: true
- schema:
- description: Integration adapter without the kaiten.integration. prefix
- examples:
- - crm.attio
- type: string
- - description: External identifier in the third-party system
- in: path
- name: externalId
- required: true
- schema:
- description: External identifier in the third-party system
- examples:
- - rec_12345
- type: string
+ - webhooks
+ - components
+ com.kaiten.component.v1.deleted:
+ post:
+ description: Triggered when a component is deleted.
+ operationId: onComponentDeleted
+ requestBody:
+ content:
+ application/json:
+ schema:
+ additionalProperties: false
+ properties:
+ data:
+ $ref: "#/components/schemas/ComponentDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
+ name:
+ description: Event name
+ enum:
+ - COMPONENT_DELETED
+ examples:
+ - COMPONENT_DELETED
+ type: string
+ type:
+ description: Event type
+ enum:
+ - com.kaiten.component.v1.deleted
+ examples:
+ - com.kaiten.component.v1.deleted
+ type: string
+ required:
+ - name
+ - type
+ - data
+ type: object
+ required: true
responses:
"200":
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/InstanceIntegrationResource"
- description: OK
- "401":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unauthorized
- "403":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Forbidden
- "404":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Not Found
- "422":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unprocessable Entity
- "500":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Internal Server Error
- security:
- - bearerAuth:
- - read:instances
- summary: Get an instance by integration external ID
+ description: Webhook processed successfully
+ summary: Component Deleted Webhook
tags:
- - integrations
- - instances
- patch:
- description: Create or partially update an instance resolved through adapter + external ID. Missing fields are left unchanged.
- operationId: upsert-instance-integration-by-external-id
- parameters:
- - description: Integration adapter without the kaiten.integration. prefix
- in: path
- name: adapter
- required: true
- schema:
- description: Integration adapter without the kaiten.integration. prefix
- examples:
- - crm.attio
- type: string
- - description: External identifier in the third-party system
- in: path
- name: externalId
- required: true
- schema:
- description: External identifier in the third-party system
- examples:
- - rec_12345
- type: string
+ - webhooks
+ - components
+ com.kaiten.component.v1.updated:
+ post:
+ description: Triggered when a component is updated or auto-versioned.
+ operationId: onComponentUpdated
requestBody:
content:
application/json:
schema:
- $ref: "#/components/schemas/InstanceBody"
+ additionalProperties: false
+ properties:
+ data:
+ $ref: "#/components/schemas/ComponentDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
+ name:
+ description: Event name
+ enum:
+ - COMPONENT_UPDATED
+ examples:
+ - COMPONENT_UPDATED
+ type: string
+ type:
+ description: Event type
+ enum:
+ - com.kaiten.component.v1.updated
+ examples:
+ - com.kaiten.component.v1.updated
+ type: string
+ required:
+ - name
+ - type
+ - data
+ type: object
required: true
responses:
"200":
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/InstanceIntegrationResource"
- description: OK
- "400":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Bad Request
- "401":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unauthorized
- "403":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Forbidden
- "404":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Not Found
- "409":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Conflict
- "422":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unprocessable Entity
- "500":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Internal Server Error
- "503":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Service Unavailable
- security:
- - bearerAuth:
- - write:instances
- summary: Upsert an instance by integration external ID
+ description: Webhook processed successfully
+ summary: Component Updated Webhook
tags:
- - integrations
- - instances
- /license-families:
- get:
- description: "Returns a cursor-paginated page of license families for the current organization, each with the version it currently resolves to: the family's default version, or its highest-numbered published version. A family whose versions are all drafts or all archived is still listed, with no current version."
- operationId: list-license-families
- parameters:
- - description: Opaque pagination cursor from a previous response's nextCursor
- explode: false
- in: query
- name: cursor
- schema:
- description: Opaque pagination cursor from a previous response's nextCursor
- type: string
- - description: Maximum number of entries to return (default 50, max 200)
- explode: false
- in: query
- name: limit
- schema:
- description: Maximum number of entries to return (default 50, max 200)
- format: int32
- maximum: 200
- minimum: 1
- type: integer
+ - webhooks
+ - components
+ com.kaiten.customer.payment_method.v1.attached:
+ post:
+ description: Triggered when a customer's default payment method in a provider is set or replaced (a completed setup session, or a change in the provider's portal). Carries no card data.
+ operationId: onCustomerPaymentMethodAttached
+ requestBody:
+ content:
+ application/json:
+ schema:
+ additionalProperties: false
+ properties:
+ data:
+ $ref: "#/components/schemas/PaymentMethodAttachedDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
+ name:
+ description: Event name
+ enum:
+ - CUSTOMER_PAYMENT_METHOD_ATTACHED
+ examples:
+ - CUSTOMER_PAYMENT_METHOD_ATTACHED
+ type: string
+ type:
+ description: Event type
+ enum:
+ - com.kaiten.customer.payment_method.v1.attached
+ examples:
+ - com.kaiten.customer.payment_method.v1.attached
+ type: string
+ required:
+ - name
+ - type
+ - data
+ type: object
+ required: true
responses:
"200":
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/PageLicenseFamilyView"
- description: OK
- "400":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Bad Request
- "401":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unauthorized
- "403":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Forbidden
- "422":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unprocessable Entity
- "500":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Internal Server Error
- security:
- - bearerAuth:
- - read:licenses
- summary: List license families
+ description: Webhook processed successfully
+ summary: Customer Payment Method Attached Webhook
+ tags:
+ - webhooks
+ - billing
+ com.kaiten.customer.payment_method.v1.detached:
+ post:
+ description: Triggered when a customer no longer has a payment method in a provider.
+ operationId: onCustomerPaymentMethodDetached
+ requestBody:
+ content:
+ application/json:
+ schema:
+ additionalProperties: false
+ properties:
+ data:
+ $ref: "#/components/schemas/PaymentMethodDetachedDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
+ name:
+ description: Event name
+ enum:
+ - CUSTOMER_PAYMENT_METHOD_DETACHED
+ examples:
+ - CUSTOMER_PAYMENT_METHOD_DETACHED
+ type: string
+ type:
+ description: Event type
+ enum:
+ - com.kaiten.customer.payment_method.v1.detached
+ examples:
+ - com.kaiten.customer.payment_method.v1.detached
+ type: string
+ required:
+ - name
+ - type
+ - data
+ type: object
+ required: true
+ responses:
+ "200":
+ description: Webhook processed successfully
+ summary: Customer Payment Method Detached Webhook
tags:
- - license-families
- /license-families/{familySlug}:
- get:
- description: |-
- Resolves a license family to the version it currently serves, returned as currentVersion: the family's default version if it has one, otherwise its highest-numbered PUBLISHED version. This is the stable address for "the current version of this product", so a caller never has to name a version slug.
-
- A family with a default keeps serving it when a newer version is published -- the default is what the vendor puts forward, and superseding it is an explicit act (make another version the default, or unset it). A family without a default follows publication: the newest PUBLISHED version wins.
-
- Returns 404 GetLicenseFamily.NoPublishedVersion when the family exists but has no PUBLISHED version, since there is nothing to resolve to. Use ?include=versions to read such a family anyway: with the history requested, the family is returned with its versions and no currentVersion. Use ?version=N for explicit access to one version, including draft and archived ones.
- operationId: get-license-family
- parameters:
- - description: License family slug
- in: path
- name: familySlug
- required: true
- schema:
- description: License family slug
- examples:
- - enterprise-license
- type: string
- - description: "Address this version number instead of resolving the current one. Explicit historical access: the version is returned in currentVersion whatever its lifecycle state, draft and archived included, and the resolution rule does not apply."
- explode: false
- in: query
- name: version
- schema:
- description: "Address this version number instead of resolving the current one. Explicit historical access: the version is returned in currentVersion whatever its lifecycle state, draft and archived included, and the resolution rule does not apply."
- format: int32
- minimum: 1
- type: integer
- - description: "Extra representations to add, comma-separated. Only 'versions' exists today: it adds every version of the family, oldest first, and makes a family with no PUBLISHED version readable instead of a 404. A list, so that another representation can be added later without changing the parameter's type."
- explode: false
- in: query
- name: include
- schema:
- description: "Extra representations to add, comma-separated. Only 'versions' exists today: it adds every version of the family, oldest first, and makes a family with no PUBLISHED version readable instead of a 404. A list, so that another representation can be added later without changing the parameter's type."
- items:
- enum:
- - versions
- type: string
- type: array
+ - webhooks
+ - billing
+ com.kaiten.customer.payment_method.v1.expiring:
+ post:
+ description: Triggered once, 30 days before the end of the expiry month of a customer's payment method.
+ operationId: onCustomerPaymentMethodExpiring
+ requestBody:
+ content:
+ application/json:
+ schema:
+ additionalProperties: false
+ properties:
+ data:
+ $ref: "#/components/schemas/PaymentMethodExpiringDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
+ name:
+ description: Event name
+ enum:
+ - CUSTOMER_PAYMENT_METHOD_EXPIRING
+ examples:
+ - CUSTOMER_PAYMENT_METHOD_EXPIRING
+ type: string
+ type:
+ description: Event type
+ enum:
+ - com.kaiten.customer.payment_method.v1.expiring
+ examples:
+ - com.kaiten.customer.payment_method.v1.expiring
+ type: string
+ required:
+ - name
+ - type
+ - data
+ type: object
+ required: true
responses:
"200":
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/LicenseFamilyView"
- description: OK
- "400":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Bad Request
- "401":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unauthorized
- "403":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Forbidden
- "404":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Not Found
- "422":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unprocessable Entity
- "500":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Internal Server Error
- security:
- - bearerAuth:
- - read:licenses
- summary: Get a license family by slug
+ description: Webhook processed successfully
+ summary: Customer Payment Method Expiring Webhook
tags:
- - license-families
- /licenses:
- get:
- description: Returns a cursor-paginated page of licenses for the current organization.
- operationId: get-licenses
- parameters:
- - description: Opaque pagination cursor from a previous response's nextCursor
- explode: false
- in: query
- name: cursor
- schema:
- description: Opaque pagination cursor from a previous response's nextCursor
- type: string
- - description: Maximum number of entries to return (default 50, max 200)
- explode: false
- in: query
- name: limit
- schema:
- description: Maximum number of entries to return (default 50, max 200)
- format: int32
- maximum: 200
- minimum: 1
- type: integer
+ - webhooks
+ - billing
+ com.kaiten.customer.v1.created:
+ post:
+ description: Triggered when a new customer is created.
+ operationId: onCustomerCreated
+ requestBody:
+ content:
+ application/json:
+ schema:
+ additionalProperties: false
+ properties:
+ data:
+ $ref: "#/components/schemas/CustomerEventDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
+ name:
+ description: Event name
+ enum:
+ - CUSTOMER_CREATED
+ examples:
+ - CUSTOMER_CREATED
+ type: string
+ type:
+ description: Event type
+ enum:
+ - com.kaiten.customer.v1.created
+ examples:
+ - com.kaiten.customer.v1.created
+ type: string
+ required:
+ - name
+ - type
+ - data
+ type: object
+ required: true
responses:
"200":
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/PageLicense"
- description: OK
- "400":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Bad Request
- "401":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unauthorized
- "403":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Forbidden
- "422":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unprocessable Entity
- "500":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Internal Server Error
- security:
- - bearerAuth:
- - read:licenses
- summary: GetUser all licenses
+ description: Webhook processed successfully
+ summary: Customer Created Webhook
+ tags:
+ - webhooks
+ - customers
+ com.kaiten.customer.v1.creation_rejected:
+ post:
+ description: Triggered when a customer creation is refused because the organization has reached its customer entitlement cap. No customer is created.
+ operationId: onCustomerCreationRejected
+ requestBody:
+ content:
+ application/json:
+ schema:
+ additionalProperties: false
+ properties:
+ data:
+ $ref: "#/components/schemas/CustomerCreationRejectedDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
+ name:
+ description: Event name
+ enum:
+ - CUSTOMER_CREATION_REJECTED
+ examples:
+ - CUSTOMER_CREATION_REJECTED
+ type: string
+ type:
+ description: Event type
+ enum:
+ - com.kaiten.customer.v1.creation_rejected
+ examples:
+ - com.kaiten.customer.v1.creation_rejected
+ type: string
+ required:
+ - name
+ - type
+ - data
+ type: object
+ required: true
+ responses:
+ "200":
+ description: Webhook processed successfully
+ summary: Customer Creation Rejected Webhook
tags:
- - licenses
+ - webhooks
+ - customers
+ com.kaiten.customer.v1.deleted:
post:
- description: |-
- Create a new license with the provided details. Without familySlug or familyId this creates a new license family and the license becomes its version 1. With one of them it adds the next version to that family (familySlug names it by slug, familyId by identifier; sent together they must agree), and the version number is assigned by the server as the next one in the family's sequence. The version starts PUBLISHED, or DRAFT when lifecycleState says so; ARCHIVED is refused (422 CreateLicense.LifecycleStateNotSettable), because a version is archived with archive-license once it has been on sale.
-
- Where the deployment limits how many licenses an organization may have, only a new family counts against that limit (409 CreateLicense.EntitlementLimitReached once it is reached): a new version of an existing family never does.
- operationId: create-license
+ description: Triggered when a customer is deleted.
+ operationId: onCustomerDeleted
requestBody:
content:
application/json:
schema:
- $ref: "#/components/schemas/License"
+ additionalProperties: false
+ properties:
+ data:
+ $ref: "#/components/schemas/CustomerEventDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
+ name:
+ description: Event name
+ enum:
+ - CUSTOMER_DELETED
+ examples:
+ - CUSTOMER_DELETED
+ type: string
+ type:
+ description: Event type
+ enum:
+ - com.kaiten.customer.v1.deleted
+ examples:
+ - com.kaiten.customer.v1.deleted
+ type: string
+ required:
+ - name
+ - type
+ - data
+ type: object
required: true
- responses:
- "201":
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/License"
- description: Created
- "400":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Bad Request
- "401":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unauthorized
- "403":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Forbidden
- "404":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Not Found
- "409":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Conflict
- "422":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unprocessable Entity
- "500":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Internal Server Error
- "503":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Service Unavailable
- security:
- - bearerAuth:
- - write:licenses
- summary: Create a new license
- tags:
- - licenses
- /licenses/{licenseSlug}:
- delete:
- description: Delete a license with the provided details. Deleting the last version of a license family also deletes the family, and frees its slug for a new product to take. A deleted version's number is not reused by the next version of its family.
- operationId: delete-license
- parameters:
- - description: License slug
- in: path
- name: licenseSlug
- required: true
- schema:
- description: License slug
- examples:
- - license-slug
- type: string
- responses:
- "204":
- description: No Content
- "400":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Bad Request
- "401":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unauthorized
- "403":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Forbidden
- "404":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Not Found
- "409":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Conflict
- "422":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unprocessable Entity
- "500":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Internal Server Error
- security:
- - bearerAuth:
- - write:licenses
- summary: Delete a license
- tags:
- - licenses
- get:
- description: Retrieve a license by their unique slug
- operationId: get-license
- parameters:
- - description: License slug
- in: path
- name: licenseSlug
- required: true
- schema:
- description: License slug
- examples:
- - license-slug
- type: string
responses:
"200":
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/License"
- description: OK
- "401":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unauthorized
- "403":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Forbidden
- "404":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Not Found
- "422":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unprocessable Entity
- "500":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Internal Server Error
- security:
- - bearerAuth:
- - read:licenses
- summary: Get a license by slug
+ description: Webhook processed successfully
+ summary: Customer Deleted Webhook
+ tags:
+ - webhooks
+ - customers
+ com.kaiten.customer.v1.updated:
+ post:
+ description: Triggered when a customer is updated.
+ operationId: onCustomerUpdated
+ requestBody:
+ content:
+ application/json:
+ schema:
+ additionalProperties: false
+ properties:
+ data:
+ $ref: "#/components/schemas/CustomerEventDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
+ name:
+ description: Event name
+ enum:
+ - CUSTOMER_UPDATED
+ examples:
+ - CUSTOMER_UPDATED
+ type: string
+ type:
+ description: Event type
+ enum:
+ - com.kaiten.customer.v1.updated
+ examples:
+ - com.kaiten.customer.v1.updated
+ type: string
+ required:
+ - name
+ - type
+ - data
+ type: object
+ required: true
+ responses:
+ "200":
+ description: Webhook processed successfully
+ summary: Customer Updated Webhook
tags:
- - licenses
- put:
- description: Update a license with the provided details
- operationId: update-license
- parameters:
- - description: License slug
- in: path
- name: licenseSlug
- required: true
- schema:
- description: License slug
- examples:
- - license-slug
- type: string
+ - webhooks
+ - customers
+ com.kaiten.deployment_zone.v1.created:
+ post:
+ description: Triggered when a new deployment zone is created.
+ operationId: onDeploymentZoneCreated
requestBody:
content:
application/json:
schema:
- $ref: "#/components/schemas/License"
+ additionalProperties: false
+ properties:
+ data:
+ $ref: "#/components/schemas/DeploymentZoneDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
+ name:
+ description: Event name
+ enum:
+ - DEPLOYMENT_ZONE_CREATED
+ examples:
+ - DEPLOYMENT_ZONE_CREATED
+ type: string
+ type:
+ description: Event type
+ enum:
+ - com.kaiten.deployment_zone.v1.created
+ examples:
+ - com.kaiten.deployment_zone.v1.created
+ type: string
+ required:
+ - name
+ - type
+ - data
+ type: object
required: true
responses:
- "204":
- description: No Content
- "400":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Bad Request
- "401":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unauthorized
- "403":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Forbidden
- "404":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Not Found
- "409":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Conflict
- "422":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unprocessable Entity
- "500":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Internal Server Error
- security:
- - bearerAuth:
- - write:licenses
- summary: Update a license
+ "200":
+ description: Webhook processed successfully
+ summary: Deployment Zone Created Webhook
tags:
- - licenses
- /licenses/{licenseSlug}/archive:
+ - webhooks
+ - deploymentZones
+ com.kaiten.deployment_zone.v1.deleted:
post:
- description: "Withdraws a PUBLISHED license version from sale: its family stops resolving to it and no instance can be assigned to it any more, while instances already on it keep it. Only a published version can be archived (409 ArchiveLicense.NotPublished) -- a draft that was never on sale is deleted instead -- and not while it is its family's default (409 ArchiveLicense.DefaultMustBePublished): make another version the default, or unset it, first. Emits LICENSE_ARCHIVED."
- operationId: archive-license
- parameters:
- - description: License version slug
- in: path
- name: licenseSlug
- required: true
- schema:
- description: License version slug
- examples:
- - enterprise-license-v2
- type: string
+ description: Triggered when a deployment zone is deleted.
+ operationId: onDeploymentZoneDeleted
+ requestBody:
+ content:
+ application/json:
+ schema:
+ additionalProperties: false
+ properties:
+ data:
+ $ref: "#/components/schemas/DeploymentZoneDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
+ name:
+ description: Event name
+ enum:
+ - DEPLOYMENT_ZONE_DELETED
+ examples:
+ - DEPLOYMENT_ZONE_DELETED
+ type: string
+ type:
+ description: Event type
+ enum:
+ - com.kaiten.deployment_zone.v1.deleted
+ examples:
+ - com.kaiten.deployment_zone.v1.deleted
+ type: string
+ required:
+ - name
+ - type
+ - data
+ type: object
+ required: true
responses:
"200":
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/License"
- description: OK
- "400":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Bad Request
- "401":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unauthorized
- "403":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Forbidden
- "404":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Not Found
- "409":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Conflict
- "422":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unprocessable Entity
- "500":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Internal Server Error
- security:
- - bearerAuth:
- - write:licenses
- summary: Archive a license version
+ description: Webhook processed successfully
+ summary: Deployment Zone Deleted Webhook
tags:
- - licenses
- /licenses/{licenseSlug}/entitlements:
- get:
- description: Returns a cursor-paginated page of entitlements granted by a license, identified by its unique slug.
- operationId: get-license-entitlements
- parameters:
- - description: License slug
- in: path
- name: licenseSlug
- required: true
- schema:
- description: License slug
- examples:
- - license-slug
- type: string
- - description: Opaque pagination cursor from a previous response's nextCursor
- explode: false
- in: query
- name: cursor
- schema:
- description: Opaque pagination cursor from a previous response's nextCursor
- type: string
- - description: Maximum number of entries to return (default 50, max 200)
- explode: false
- in: query
- name: limit
- schema:
- description: Maximum number of entries to return (default 50, max 200)
- format: int32
- maximum: 200
- minimum: 1
- type: integer
+ - webhooks
+ - deploymentZones
+ com.kaiten.deployment_zone.v1.release_deployed:
+ post:
+ description: Triggered when a release is deployed to a deployment zone, either at zone creation or when an existing zone's release changes.
+ operationId: onReleaseDeployed
+ requestBody:
+ content:
+ application/json:
+ schema:
+ additionalProperties: false
+ properties:
+ data:
+ $ref: "#/components/schemas/DeploymentDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
+ name:
+ description: Event name
+ enum:
+ - RELEASE_DEPLOYED
+ examples:
+ - RELEASE_DEPLOYED
+ type: string
+ type:
+ description: Event type
+ enum:
+ - com.kaiten.deployment_zone.v1.release_deployed
+ examples:
+ - com.kaiten.deployment_zone.v1.release_deployed
+ type: string
+ required:
+ - name
+ - type
+ - data
+ type: object
+ required: true
responses:
"200":
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/PageLicenseEntitlement"
- description: OK
- "400":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Bad Request
- "401":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unauthorized
- "403":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Forbidden
- "404":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Not Found
- "422":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unprocessable Entity
- "500":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Internal Server Error
- security:
- - bearerAuth:
- - read:licenses
- summary: Get all entitlements for a license
+ description: Webhook processed successfully
+ summary: Release Deployed Webhook
tags:
- - licenses
+ - webhooks
+ - deploymentZones
+ com.kaiten.deployment_zone.v1.updated:
post:
- description: Associate an entitlement with a license by providing the license slug and entitlement details
- operationId: associate-entitlement-with-license
- parameters:
- - description: License slug
- in: path
- name: licenseSlug
- required: true
- schema:
- description: License slug
- examples:
- - license-slug
- type: string
+ description: Triggered when a deployment zone is updated.
+ operationId: onDeploymentZoneUpdated
requestBody:
content:
application/json:
schema:
- $ref: "#/components/schemas/LicenseEntitlement"
+ additionalProperties: false
+ properties:
+ data:
+ $ref: "#/components/schemas/DeploymentZoneDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
+ name:
+ description: Event name
+ enum:
+ - DEPLOYMENT_ZONE_UPDATED
+ examples:
+ - DEPLOYMENT_ZONE_UPDATED
+ type: string
+ type:
+ description: Event type
+ enum:
+ - com.kaiten.deployment_zone.v1.updated
+ examples:
+ - com.kaiten.deployment_zone.v1.updated
+ type: string
+ required:
+ - name
+ - type
+ - data
+ type: object
required: true
responses:
- "204":
- description: No Content
- "400":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Bad Request
- "401":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unauthorized
- "403":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Forbidden
- "404":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Not Found
- "409":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Conflict
- "422":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unprocessable Entity
- "500":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Internal Server Error
- "503":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Service Unavailable
- security:
- - bearerAuth:
- - write:licenses
- summary: Associate an entitlement with a license
+ "200":
+ description: Webhook processed successfully
+ summary: Deployment Zone Updated Webhook
tags:
- - licenses
- /licenses/{licenseSlug}/entitlements/{entitlementSlug}:
- delete:
- description: Delete a license entitlement with the provided details
- operationId: delete-license-entitlement
- parameters:
- - in: path
- name: licenseSlug
- required: true
- schema:
- type: string
- - in: path
- name: entitlementSlug
- required: true
- schema:
- type: string
+ - webhooks
+ - deploymentZones
+ com.kaiten.entitlement.v1.created:
+ post:
+ description: Triggered when a new entitlement is created.
+ operationId: onEntitlementCreated
+ requestBody:
+ content:
+ application/json:
+ schema:
+ additionalProperties: false
+ properties:
+ data:
+ $ref: "#/components/schemas/EntitlementDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
+ name:
+ description: Event name
+ enum:
+ - ENTITLEMENT_CREATED
+ examples:
+ - ENTITLEMENT_CREATED
+ type: string
+ type:
+ description: Event type
+ enum:
+ - com.kaiten.entitlement.v1.created
+ examples:
+ - com.kaiten.entitlement.v1.created
+ type: string
+ required:
+ - name
+ - type
+ - data
+ type: object
+ required: true
responses:
- "204":
- description: No Content
- "400":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Bad Request
- "401":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unauthorized
- "403":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Forbidden
- "404":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Not Found
- "422":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unprocessable Entity
- "500":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Internal Server Error
- security:
- - bearerAuth:
- - write:licenses
- summary: Delete a license entitlement
+ "200":
+ description: Webhook processed successfully
+ summary: Entitlement Created Webhook
+ tags:
+ - webhooks
+ - entitlements
+ com.kaiten.entitlement.v1.deleted:
+ post:
+ description: Triggered when an entitlement is deleted.
+ operationId: onEntitlementDeleted
+ requestBody:
+ content:
+ application/json:
+ schema:
+ additionalProperties: false
+ properties:
+ data:
+ $ref: "#/components/schemas/EntitlementDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
+ name:
+ description: Event name
+ enum:
+ - ENTITLEMENT_DELETED
+ examples:
+ - ENTITLEMENT_DELETED
+ type: string
+ type:
+ description: Event type
+ enum:
+ - com.kaiten.entitlement.v1.deleted
+ examples:
+ - com.kaiten.entitlement.v1.deleted
+ type: string
+ required:
+ - name
+ - type
+ - data
+ type: object
+ required: true
+ responses:
+ "200":
+ description: Webhook processed successfully
+ summary: Entitlement Deleted Webhook
tags:
- - licenses
- get:
- description: Retrieve a entitlement for a license by their unique slugs
- operationId: get-license-entitlement
- parameters:
- - in: path
- name: licenseSlug
- required: true
- schema:
- type: string
- - in: path
- name: entitlementSlug
- required: true
- schema:
- type: string
+ - webhooks
+ - entitlements
+ com.kaiten.entitlement.v1.updated:
+ post:
+ description: Triggered when an entitlement is updated.
+ operationId: onEntitlementUpdated
+ requestBody:
+ content:
+ application/json:
+ schema:
+ additionalProperties: false
+ properties:
+ data:
+ $ref: "#/components/schemas/EntitlementDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
+ name:
+ description: Event name
+ enum:
+ - ENTITLEMENT_UPDATED
+ examples:
+ - ENTITLEMENT_UPDATED
+ type: string
+ type:
+ description: Event type
+ enum:
+ - com.kaiten.entitlement.v1.updated
+ examples:
+ - com.kaiten.entitlement.v1.updated
+ type: string
+ required:
+ - name
+ - type
+ - data
+ type: object
+ required: true
responses:
"200":
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/LicenseEntitlement"
- description: OK
- "401":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unauthorized
- "403":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Forbidden
- "404":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Not Found
- "422":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unprocessable Entity
- "500":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Internal Server Error
- security:
- - bearerAuth:
- - read:licenses
- summary: Get a license entitlement
+ description: Webhook processed successfully
+ summary: Entitlement Updated Webhook
tags:
- - licenses
- put:
- description: Update an license entitlement with the provided details
- operationId: update-license-entitlement
- parameters:
- - in: path
- name: licenseSlug
- required: true
- schema:
- type: string
- - in: path
- name: entitlementSlug
- required: true
- schema:
- type: string
+ - webhooks
+ - entitlements
+ com.kaiten.entitlement_group.v1.created:
+ post:
+ description: Triggered when a new entitlement group is created.
+ operationId: onEntitlementGroupCreated
requestBody:
content:
application/json:
schema:
- $ref: "#/components/schemas/LicenseEntitlement"
+ additionalProperties: false
+ properties:
+ data:
+ $ref: "#/components/schemas/EntitlementGroupDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
+ name:
+ description: Event name
+ enum:
+ - ENTITLEMENT_GROUP_CREATED
+ examples:
+ - ENTITLEMENT_GROUP_CREATED
+ type: string
+ type:
+ description: Event type
+ enum:
+ - com.kaiten.entitlement_group.v1.created
+ examples:
+ - com.kaiten.entitlement_group.v1.created
+ type: string
+ required:
+ - name
+ - type
+ - data
+ type: object
required: true
responses:
- "204":
- description: No Content
- "400":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Bad Request
- "401":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unauthorized
- "403":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Forbidden
- "404":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Not Found
- "422":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unprocessable Entity
- "500":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Internal Server Error
- security:
- - bearerAuth:
- - write:licenses
- summary: Update an license entitlement
+ "200":
+ description: Webhook processed successfully
+ summary: Entitlement Group Created Webhook
tags:
- - licenses
- /licenses/{licenseSlug}/publish:
+ - webhooks
+ - entitlement-groups
+ com.kaiten.entitlement_group.v1.deleted:
post:
- description: "Puts a DRAFT license version on sale: it becomes PUBLISHED, so it can be made its family's default, and its family resolves to it when the family has no default and this is its highest published version. Only a draft can be published (409 PublishLicense.NotADraft); an archived version goes back on sale with unarchive-license. Emits LICENSE_PUBLISHED."
- operationId: publish-license
- parameters:
- - description: License version slug
- in: path
- name: licenseSlug
- required: true
- schema:
- description: License version slug
- examples:
- - enterprise-license-v2
- type: string
+ description: Triggered when an entitlement group is deleted.
+ operationId: onEntitlementGroupDeleted
+ requestBody:
+ content:
+ application/json:
+ schema:
+ additionalProperties: false
+ properties:
+ data:
+ $ref: "#/components/schemas/EntitlementGroupDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
+ name:
+ description: Event name
+ enum:
+ - ENTITLEMENT_GROUP_DELETED
+ examples:
+ - ENTITLEMENT_GROUP_DELETED
+ type: string
+ type:
+ description: Event type
+ enum:
+ - com.kaiten.entitlement_group.v1.deleted
+ examples:
+ - com.kaiten.entitlement_group.v1.deleted
+ type: string
+ required:
+ - name
+ - type
+ - data
+ type: object
+ required: true
responses:
"200":
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/License"
- description: OK
- "400":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Bad Request
- "401":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unauthorized
- "403":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Forbidden
- "404":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Not Found
- "409":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Conflict
- "422":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unprocessable Entity
- "500":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Internal Server Error
- security:
- - bearerAuth:
- - write:licenses
- summary: Publish a license version
+ description: Webhook processed successfully
+ summary: Entitlement Group Deleted Webhook
tags:
- - licenses
- /licenses/{licenseSlug}/unarchive:
+ - webhooks
+ - entitlement-groups
+ com.kaiten.entitlement_group.v1.updated:
post:
- description: "Puts an ARCHIVED license version back on sale: it is PUBLISHED again, can be assigned to instances, and can be what its family resolves to. Only an archived version can be unarchived (409 UnarchiveLicense.NotArchived). Emits LICENSE_UNARCHIVED."
- operationId: unarchive-license
- parameters:
- - description: License version slug
- in: path
- name: licenseSlug
- required: true
- schema:
- description: License version slug
- examples:
- - enterprise-license-v2
- type: string
+ description: Triggered when an entitlement group is updated.
+ operationId: onEntitlementGroupUpdated
+ requestBody:
+ content:
+ application/json:
+ schema:
+ additionalProperties: false
+ properties:
+ data:
+ $ref: "#/components/schemas/EntitlementGroupDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
+ name:
+ description: Event name
+ enum:
+ - ENTITLEMENT_GROUP_UPDATED
+ examples:
+ - ENTITLEMENT_GROUP_UPDATED
+ type: string
+ type:
+ description: Event type
+ enum:
+ - com.kaiten.entitlement_group.v1.updated
+ examples:
+ - com.kaiten.entitlement_group.v1.updated
+ type: string
+ required:
+ - name
+ - type
+ - data
+ type: object
+ required: true
responses:
"200":
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/License"
- description: OK
- "400":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Bad Request
- "401":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unauthorized
- "403":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Forbidden
- "404":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Not Found
- "409":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Conflict
- "422":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unprocessable Entity
- "500":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Internal Server Error
- security:
- - bearerAuth:
- - write:licenses
- summary: Unarchive a license version
+ description: Webhook processed successfully
+ summary: Entitlement Group Updated Webhook
tags:
- - licenses
- /metadata-fields:
- get:
- description: Returns a cursor-paginated page of the active (non-archived) metadata fields declared for the org and the given resource type, ordered by displayOrder then createdAt.
- operationId: list-metadata-fields
- parameters:
- - explode: false
- in: query
- name: resourceType
- required: true
- schema:
- enum:
- - DEPLOYMENT_ZONE
- - INSTANCE
- type: string
- - description: Opaque pagination cursor from a previous response's nextCursor
- explode: false
- in: query
- name: cursor
- schema:
- description: Opaque pagination cursor from a previous response's nextCursor
- type: string
- - description: Maximum number of entries to return (default 50, max 200)
- explode: false
- in: query
- name: limit
- schema:
- description: Maximum number of entries to return (default 50, max 200)
- format: int32
- maximum: 200
- minimum: 1
- type: integer
+ - webhooks
+ - entitlement-groups
+ com.kaiten.feature_flag.v1.created:
+ post:
+ description: Triggered when a new feature flag is created.
+ operationId: onFeatureFlagCreated
+ requestBody:
+ content:
+ application/json:
+ schema:
+ additionalProperties: false
+ properties:
+ data:
+ $ref: "#/components/schemas/FeatureFlagDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
+ name:
+ description: Event name
+ enum:
+ - FEATURE_FLAG_CREATED
+ examples:
+ - FEATURE_FLAG_CREATED
+ type: string
+ type:
+ description: Event type
+ enum:
+ - com.kaiten.feature_flag.v1.created
+ examples:
+ - com.kaiten.feature_flag.v1.created
+ type: string
+ required:
+ - name
+ - type
+ - data
+ type: object
+ required: true
responses:
"200":
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/PageMetadataField"
- description: OK
- "400":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Bad Request
- "401":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unauthorized
- "403":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Forbidden
- "422":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unprocessable Entity
- "500":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Internal Server Error
- security:
- - bearerAuth:
- - read:metadata_fields
- summary: List active metadata fields for a resource type
+ description: Webhook processed successfully
+ summary: Feature Flag Created Webhook
tags:
- - metadataFields
+ - webhooks
+ - featureflags
+ com.kaiten.feature_flag.v1.deleted:
post:
- description: Declares a typed metadata field for a given resource type (DEPLOYMENT_ZONE, INSTANCE). The json_schema is validated against JSON Schema 2020-12 before insertion. Restricted to write:metadata_fields scope (admin-only policy).
- operationId: create-metadata-field
+ description: Triggered when a feature flag is deleted.
+ operationId: onFeatureFlagDeleted
requestBody:
content:
application/json:
schema:
- $ref: "#/components/schemas/MetadataField"
+ additionalProperties: false
+ properties:
+ data:
+ $ref: "#/components/schemas/FeatureFlagDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
+ name:
+ description: Event name
+ enum:
+ - FEATURE_FLAG_DELETED
+ examples:
+ - FEATURE_FLAG_DELETED
+ type: string
+ type:
+ description: Event type
+ enum:
+ - com.kaiten.feature_flag.v1.deleted
+ examples:
+ - com.kaiten.feature_flag.v1.deleted
+ type: string
+ required:
+ - name
+ - type
+ - data
+ type: object
required: true
responses:
- "201":
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/MetadataField"
- description: Created
- "400":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Bad Request
- "401":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unauthorized
- "403":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Forbidden
- "409":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Conflict
- "422":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unprocessable Entity
- "500":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Internal Server Error
- "503":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Service Unavailable
- security:
- - bearerAuth:
- - write:metadata_fields
- summary: Create a metadata field
+ "200":
+ description: Webhook processed successfully
+ summary: Feature Flag Deleted Webhook
tags:
- - metadataFields
- /metadata-fields/reorder:
+ - webhooks
+ - featureflags
+ com.kaiten.feature_flag.v1.evaluated:
post:
- description: Sets the displayOrder of the listed fields to their position in the array (0-indexed). All ids must belong to the same organization, resource type, and be non-archived; the entire reorder is applied in a single transaction.
- operationId: reorder-metadata-fields
+ description: Triggered on every flag evaluation. The payload carries no evaluation context, so no targeting key or user attribute is exposed.
+ operationId: onFeatureFlagEvaluated
requestBody:
content:
application/json:
schema:
- $ref: "#/components/schemas/ReorderMetadataFieldsInput"
+ additionalProperties: false
+ properties:
+ data:
+ $ref: "#/components/schemas/FeatureFlagEvaluatedDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
+ name:
+ description: Event name
+ enum:
+ - FEATURE_FLAG_EVALUATED
+ examples:
+ - FEATURE_FLAG_EVALUATED
+ type: string
+ type:
+ description: Event type
+ enum:
+ - com.kaiten.feature_flag.v1.evaluated
+ examples:
+ - com.kaiten.feature_flag.v1.evaluated
+ type: string
+ required:
+ - name
+ - type
+ - data
+ type: object
required: true
responses:
- "204":
- description: No Content
- "400":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Bad Request
- "401":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unauthorized
- "403":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Forbidden
- "404":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Not Found
- "422":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unprocessable Entity
- "500":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Internal Server Error
- security:
- - bearerAuth:
- - write:metadata_fields
- summary: Reorder metadata fields
+ "200":
+ description: Webhook processed successfully
+ summary: Feature Flag Evaluated Webhook
tags:
- - metadataFields
- /metadata-fields/{id}:
- patch:
- description: "Updates label and json_schema. `key` and `resourceType` are immutable, and `displayOrder` belongs to POST /metadata-fields/reorder. The new json_schema is validated against JSON Schema 2020-12 and the transition rules: type changes, cardinality changes, and free-string ↔ enum toggles are rejected with 422."
- operationId: update-metadata-field
- parameters:
- - in: path
- name: id
- required: true
- schema:
- type: string
+ - webhooks
+ - featureflags
+ com.kaiten.feature_flag.v1.updated:
+ post:
+ description: Triggered when a feature flag is updated.
+ operationId: onFeatureFlagUpdated
requestBody:
content:
application/json:
schema:
- $ref: "#/components/schemas/MetadataField"
+ additionalProperties: false
+ properties:
+ data:
+ $ref: "#/components/schemas/FeatureFlagDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
+ name:
+ description: Event name
+ enum:
+ - FEATURE_FLAG_UPDATED
+ examples:
+ - FEATURE_FLAG_UPDATED
+ type: string
+ type:
+ description: Event type
+ enum:
+ - com.kaiten.feature_flag.v1.updated
+ examples:
+ - com.kaiten.feature_flag.v1.updated
+ type: string
+ required:
+ - name
+ - type
+ - data
+ type: object
required: true
responses:
"200":
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/MetadataField"
- description: OK
- "400":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Bad Request
- "401":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unauthorized
- "403":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Forbidden
- "404":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Not Found
- "409":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Conflict
- "422":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unprocessable Entity
- "500":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Internal Server Error
- security:
- - bearerAuth:
- - write:metadata_fields
- summary: Update a metadata field
+ description: Webhook processed successfully
+ summary: Feature Flag Updated Webhook
tags:
- - metadataFields
- /metadata-fields/{id}/archive:
+ - webhooks
+ - featureflags
+ com.kaiten.identity.v1.system_token_issued:
post:
- description: Soft-deletes a metadata field. The row stays in the DB (its archived_at is set) so historic metadata is still interpretable; the field stops appearing in the active schema composition.
- operationId: archive-metadata-field
- parameters:
- - in: path
- name: id
- required: true
- schema:
- type: string
+ description: "Triggered when a platform credential mints an organization-scoped credential for the `system:kaiten` platform identity inside this organization. Carries the platform credential that performed the mint and the scopes granted, never the credential value. Expect this at the rate an integration refreshes: 96 a day per process at a 15-minute lifetime, more if it refreshes before expiry."
+ operationId: onSystemOrganizationTokenIssued
+ requestBody:
+ content:
+ application/json:
+ schema:
+ additionalProperties: false
+ properties:
+ data:
+ $ref: "#/components/schemas/SystemTokenIssuanceDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
+ name:
+ description: Event name
+ enum:
+ - SYSTEM_ORGANIZATION_TOKEN_ISSUED
+ examples:
+ - SYSTEM_ORGANIZATION_TOKEN_ISSUED
+ type: string
+ type:
+ description: Event type
+ enum:
+ - com.kaiten.identity.v1.system_token_issued
+ examples:
+ - com.kaiten.identity.v1.system_token_issued
+ type: string
+ required:
+ - name
+ - type
+ - data
+ type: object
+ required: true
responses:
"200":
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/MetadataField"
- description: OK
- "400":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Bad Request
- "401":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unauthorized
- "403":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Forbidden
- "404":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Not Found
- "422":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unprocessable Entity
- "500":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Internal Server Error
- security:
- - bearerAuth:
- - write:metadata_fields
- summary: Archive a metadata field
+ description: Webhook processed successfully
+ summary: System Organization Token Issued Webhook
tags:
- - metadataFields
- /metadata-fields/{id}/dry-run:
+ - webhooks
+ - identity
+ com.kaiten.instance.addon.v1.added:
post:
- description: Computes, server-side, how many existing resources of this field's resource type carry a value under this field's key that would NOT satisfy the supplied candidate JSON Schema, plus a small sample. Read-only — does not mutate the field. Replaces the former client-side dry-run that pulled every resource's metadata.
- operationId: dry-run-metadata-field
- parameters:
- - in: path
- name: id
- required: true
- schema:
- type: string
+ description: Triggered when an add-on is attached to an instance.
+ operationId: onInstanceAddonAdded
requestBody:
content:
application/json:
schema:
- $ref: "#/components/schemas/MetadataFieldSchemaProposal"
+ additionalProperties: false
+ properties:
+ data:
+ $ref: "#/components/schemas/InstanceAddonUpdateDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
+ name:
+ description: Event name
+ enum:
+ - INSTANCE_ADDON_ADDED
+ examples:
+ - INSTANCE_ADDON_ADDED
+ type: string
+ type:
+ description: Event type
+ enum:
+ - com.kaiten.instance.addon.v1.added
+ examples:
+ - com.kaiten.instance.addon.v1.added
+ type: string
+ required:
+ - name
+ - type
+ - data
+ type: object
required: true
responses:
"200":
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/Impact"
- description: OK
- "400":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Bad Request
- "401":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unauthorized
- "403":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Forbidden
- "404":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Not Found
- "422":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unprocessable Entity
- "500":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Internal Server Error
- security:
- - bearerAuth:
- - write:metadata_fields
- summary: Preview the impact of a metadata field schema change
+ description: Webhook processed successfully
+ summary: Instance Add-on Added Webhook
tags:
- - metadataFields
- /metadata-fields/{id}/unarchive:
+ - webhooks
+ - instances
+ com.kaiten.instance.addon.v1.quantity_changed:
post:
- description: Restores a previously archived metadata field (clears its archived_at). The field re-enters the active schema composition. Returns 409 if an active field already uses the same key for this resource type, or 404 if the field does not exist or is not archived.
- operationId: unarchive-metadata-field
- parameters:
- - in: path
- name: id
- required: true
- schema:
- type: string
+ description: Triggered when the quantity of an instance's add-on changes.
+ operationId: onInstanceAddonQuantityChanged
+ requestBody:
+ content:
+ application/json:
+ schema:
+ additionalProperties: false
+ properties:
+ data:
+ $ref: "#/components/schemas/InstanceAddonUpdateDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
+ name:
+ description: Event name
+ enum:
+ - INSTANCE_ADDON_QUANTITY_CHANGED
+ examples:
+ - INSTANCE_ADDON_QUANTITY_CHANGED
+ type: string
+ type:
+ description: Event type
+ enum:
+ - com.kaiten.instance.addon.v1.quantity_changed
+ examples:
+ - com.kaiten.instance.addon.v1.quantity_changed
+ type: string
+ required:
+ - name
+ - type
+ - data
+ type: object
+ required: true
responses:
"200":
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/MetadataField"
- description: OK
- "400":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Bad Request
- "401":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unauthorized
- "403":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Forbidden
- "404":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Not Found
- "409":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Conflict
- "422":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unprocessable Entity
- "500":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Internal Server Error
- security:
- - bearerAuth:
- - write:metadata_fields
- summary: Unarchive a metadata field
+ description: Webhook processed successfully
+ summary: Instance Add-on Quantity Changed Webhook
tags:
- - metadataFields
- /ofrep/v1/evaluate/flags:
+ - webhooks
+ - instances
+ com.kaiten.instance.addon.v1.removed:
post:
- description: |-
- OFREP bulk evaluation request.
- The endpoint is called by the client providers to evaluate all flags at once.
- operationId: evaluateFlagsBulk
- parameters:
- - description: The request will be processed only if ETag doesn't match any of the values listed.
- in: header
- name: If-None-Match
- schema:
- type: string
+ description: Triggered when an add-on is removed from an instance.
+ operationId: onInstanceAddonRemoved
requestBody:
content:
application/json:
schema:
- $ref: "#/components/schemas/BulkEvaluationRequest"
- description: EvaluateVariant multiple flags in one request
+ additionalProperties: false
+ properties:
+ data:
+ $ref: "#/components/schemas/InstanceAddonUpdateDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
+ name:
+ description: Event name
+ enum:
+ - INSTANCE_ADDON_REMOVED
+ examples:
+ - INSTANCE_ADDON_REMOVED
+ type: string
+ type:
+ description: Event type
+ enum:
+ - com.kaiten.instance.addon.v1.removed
+ examples:
+ - com.kaiten.instance.addon.v1.removed
+ type: string
+ required:
+ - name
+ - type
+ - data
+ type: object
+ required: true
responses:
"200":
- content:
- application/json:
- schema:
- oneOf:
- - properties:
- flags:
- description: Array of evaluation results
- items:
- oneOf:
- - $ref: "#/components/schemas/EvaluationSuccess"
- - $ref: "#/components/schemas/EvaluationFailure"
- type: array
- - $ref: "#/components/schemas/BulkEvaluationFailure"
- description: OFREP successful evaluation response
- headers:
- ETag:
- description: Entity tag used for cache validation
- schema:
- type: string
- "304":
- description: Bulk evaluation is not modified
- "400":
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/BulkEvaluationFailure"
- description: Bad evaluation request
- "401":
- description: Unauthorized - You need credentials to access the API
- "403":
- description: Forbidden - You are not authorized to access the API
- "429":
- description: Rate limit reached on the Flag Management System
- headers:
- Retry-After:
- description: Indicates when to retry the request again
- schema:
- type: string
- "500":
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/BulkEvaluationFailure"
- description: Internal server error
- security:
- - bearerAuth:
- - read:feature_flags
- summary: OFREP bulk flag evaluation contract
+ description: Webhook processed successfully
+ summary: Instance Add-on Removed Webhook
tags:
- - OFREP Core
- /ofrep/v1/evaluate/flags/{key}:
+ - webhooks
+ - instances
+ com.kaiten.instance.billing.v1.canceled:
post:
- description: EvaluateVariant a single feature flag for a given key
- operationId: evaluateFlag
- parameters:
- - in: path
- name: key
- required: true
- schema:
- type: string
+ description: "Triggered when a subscription is canceled: at its period's end, immediately, or during its trial."
+ operationId: onInstanceBillingCanceled
requestBody:
content:
application/json:
schema:
- $ref: "#/components/schemas/EvaluationRequest"
- description: Evaluation context
+ additionalProperties: false
+ properties:
+ data:
+ $ref: "#/components/schemas/CancellationDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
+ name:
+ description: Event name
+ enum:
+ - INSTANCE_BILLING_CANCELED
+ examples:
+ - INSTANCE_BILLING_CANCELED
+ type: string
+ type:
+ description: Event type
+ enum:
+ - com.kaiten.instance.billing.v1.canceled
+ examples:
+ - com.kaiten.instance.billing.v1.canceled
+ type: string
+ required:
+ - name
+ - type
+ - data
+ type: object
required: true
responses:
"200":
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/EvaluationSuccess"
- description: Flag evaluated successfully
- "400":
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/EvaluationFailure"
- description: Evaluation failed
- "500":
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/EvaluationFailure"
- description: Internal error
- security:
- - bearerAuth:
- - read:feature_flags
- summary: OFREP single flag evaluation contract
- tags:
- - OFREP Core
- /openfeature/v0/manifest:
- get:
- description: Returns a manifest of all feature flags that have a fallback_value set in their metadata
- operationId: get-openfeature-manifest
- responses:
- "200":
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/ManifestEnvelope"
- description: OK
- headers:
- X-Manifest-Capabilities:
- schema:
- type: string
- "401":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unauthorized
- "403":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Forbidden
- "500":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Internal Server Error
- security:
- - bearerAuth:
- - read:feature_flags
- summary: Get OpenFeature manifest
+ description: Webhook processed successfully
+ summary: Instance Billing Canceled Webhook
tags:
- - OpenFeature
- /releases:
- get:
- description: Returns a cursor-paginated page of releases for the current organization.
- operationId: list-releases
- parameters:
- - description: Opaque pagination cursor from a previous response's nextCursor
- explode: false
- in: query
- name: cursor
- schema:
- description: Opaque pagination cursor from a previous response's nextCursor
- type: string
- - description: Maximum number of entries to return (default 50, max 200)
- explode: false
- in: query
- name: limit
- schema:
- description: Maximum number of entries to return (default 50, max 200)
- format: int32
- maximum: 200
- minimum: 1
- type: integer
- responses:
- "200":
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/PageRelease"
- description: OK
- "400":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Bad Request
- "401":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unauthorized
- "403":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Forbidden
- "422":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unprocessable Entity
- "500":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Internal Server Error
- security:
- - bearerAuth:
- - read:releases
- summary: List releases
+ - webhooks
+ - billing
+ com.kaiten.instance.billing.v1.cancellation_reverted:
+ post:
+ description: Triggered when a scheduled cancellation is reverted before the boundary.
+ operationId: onInstanceBillingCancellationReverted
+ requestBody:
+ content:
+ application/json:
+ schema:
+ additionalProperties: false
+ properties:
+ data:
+ $ref: "#/components/schemas/CancellationChangeDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
+ name:
+ description: Event name
+ enum:
+ - INSTANCE_BILLING_CANCELLATION_REVERTED
+ examples:
+ - INSTANCE_BILLING_CANCELLATION_REVERTED
+ type: string
+ type:
+ description: Event type
+ enum:
+ - com.kaiten.instance.billing.v1.cancellation_reverted
+ examples:
+ - com.kaiten.instance.billing.v1.cancellation_reverted
+ type: string
+ required:
+ - name
+ - type
+ - data
+ type: object
+ required: true
+ responses:
+ "200":
+ description: Webhook processed successfully
+ summary: Instance Billing Cancellation Reverted Webhook
tags:
- - releases
+ - webhooks
+ - billing
+ com.kaiten.instance.billing.v1.cancellation_scheduled:
post:
- description: "Create a new release with the provided details and link the requested components. A release is immutable: there is no update endpoint, and the component list cannot change afterwards. Correcting one means deleting it and creating a new one, which starts a new deployment history."
- operationId: create-release
+ description: Triggered when a subscription is set to cancel at the end of its period.
+ operationId: onInstanceBillingCancellationScheduled
requestBody:
content:
application/json:
schema:
- $ref: "#/components/schemas/Release"
+ additionalProperties: false
+ properties:
+ data:
+ $ref: "#/components/schemas/CancellationChangeDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
+ name:
+ description: Event name
+ enum:
+ - INSTANCE_BILLING_CANCELLATION_SCHEDULED
+ examples:
+ - INSTANCE_BILLING_CANCELLATION_SCHEDULED
+ type: string
+ type:
+ description: Event type
+ enum:
+ - com.kaiten.instance.billing.v1.cancellation_scheduled
+ examples:
+ - com.kaiten.instance.billing.v1.cancellation_scheduled
+ type: string
+ required:
+ - name
+ - type
+ - data
+ type: object
required: true
responses:
- "201":
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/Release"
- description: Created
- "400":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Bad Request
- "401":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unauthorized
- "403":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Forbidden
- "404":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Not Found
- "409":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Conflict
- "422":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unprocessable Entity
- "500":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Internal Server Error
- "503":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Service Unavailable
- security:
- - bearerAuth:
- - write:releases
- summary: Create a new release
- tags:
- - releases
- /releases/{releaseSlug}:
- delete:
- description: Delete a release by their slug. Since a release is immutable, this plus a fresh create is the only way to change one -- and it discards the deployment history recorded against the old release. A release still deployed to a zone cannot be deleted (409).
- operationId: delete-release
- parameters:
- - description: Release slug
- in: path
- name: releaseSlug
- required: true
- schema:
- description: Release slug
- examples:
- - release-slug
- type: string
- responses:
- "204":
- description: No Content
- "400":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Bad Request
- "401":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unauthorized
- "403":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Forbidden
- "404":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Not Found
- "409":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Conflict
- "422":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unprocessable Entity
- "500":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Internal Server Error
- security:
- - bearerAuth:
- - write:releases
- summary: Delete a release
+ "200":
+ description: Webhook processed successfully
+ summary: Instance Billing Cancellation Scheduled Webhook
tags:
- - releases
- get:
- description: Returns a single release by their slug.
- operationId: get-release-by-slug
- parameters:
- - description: Release slug
- in: path
- name: releaseSlug
- required: true
- schema:
- description: Release slug
- examples:
- - release-slug
- type: string
+ - webhooks
+ - billing
+ com.kaiten.instance.billing.v1.plan_change_canceled:
+ post:
+ description: "Triggered when a scheduled plan change is dropped: by request, or by a cancellation."
+ operationId: onInstanceBillingPlanChangeCanceled
+ requestBody:
+ content:
+ application/json:
+ schema:
+ additionalProperties: false
+ properties:
+ data:
+ $ref: "#/components/schemas/PlanChangeScheduleDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
+ name:
+ description: Event name
+ enum:
+ - INSTANCE_BILLING_PLAN_CHANGE_CANCELED
+ examples:
+ - INSTANCE_BILLING_PLAN_CHANGE_CANCELED
+ type: string
+ type:
+ description: Event type
+ enum:
+ - com.kaiten.instance.billing.v1.plan_change_canceled
+ examples:
+ - com.kaiten.instance.billing.v1.plan_change_canceled
+ type: string
+ required:
+ - name
+ - type
+ - data
+ type: object
+ required: true
responses:
"200":
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/Release"
- description: OK
- "401":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unauthorized
- "403":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Forbidden
- "404":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Not Found
- "422":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unprocessable Entity
- "500":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Internal Server Error
- security:
- - bearerAuth:
- - read:releases
- summary: Get a release by slug
+ description: Webhook processed successfully
+ summary: Instance Billing Plan Change Canceled Webhook
+ tags:
+ - webhooks
+ - billing
+ com.kaiten.instance.billing.v1.plan_change_scheduled:
+ post:
+ description: Triggered when a plan change is scheduled for the next boundary, or replaced.
+ operationId: onInstanceBillingPlanChangeScheduled
+ requestBody:
+ content:
+ application/json:
+ schema:
+ additionalProperties: false
+ properties:
+ data:
+ $ref: "#/components/schemas/PlanChangeScheduleDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
+ name:
+ description: Event name
+ enum:
+ - INSTANCE_BILLING_PLAN_CHANGE_SCHEDULED
+ examples:
+ - INSTANCE_BILLING_PLAN_CHANGE_SCHEDULED
+ type: string
+ type:
+ description: Event type
+ enum:
+ - com.kaiten.instance.billing.v1.plan_change_scheduled
+ examples:
+ - com.kaiten.instance.billing.v1.plan_change_scheduled
+ type: string
+ required:
+ - name
+ - type
+ - data
+ type: object
+ required: true
+ responses:
+ "200":
+ description: Webhook processed successfully
+ summary: Instance Billing Plan Change Scheduled Webhook
tags:
- - deploymentZones
- /service-accounts:
- get:
- description: Returns a cursor-paginated page of service accounts (machine users) for the current organization.
- operationId: get-service-accounts
- parameters:
- - description: Opaque pagination cursor from a previous response's nextCursor
- explode: false
- in: query
- name: cursor
- schema:
- description: Opaque pagination cursor from a previous response's nextCursor
- type: string
- - description: Maximum number of entries to return (default 50, max 200)
- explode: false
- in: query
- name: limit
- schema:
- description: Maximum number of entries to return (default 50, max 200)
- format: int32
- maximum: 200
- minimum: 1
- type: integer
+ - webhooks
+ - billing
+ com.kaiten.instance.billing.v1.plan_changed:
+ post:
+ description: Triggered when a scheduled plan change is applied at a boundary.
+ operationId: onInstanceBillingPlanChanged
+ requestBody:
+ content:
+ application/json:
+ schema:
+ additionalProperties: false
+ properties:
+ data:
+ $ref: "#/components/schemas/PlanChangeDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
+ name:
+ description: Event name
+ enum:
+ - INSTANCE_BILLING_PLAN_CHANGED
+ examples:
+ - INSTANCE_BILLING_PLAN_CHANGED
+ type: string
+ type:
+ description: Event type
+ enum:
+ - com.kaiten.instance.billing.v1.plan_changed
+ examples:
+ - com.kaiten.instance.billing.v1.plan_changed
+ type: string
+ required:
+ - name
+ - type
+ - data
+ type: object
+ required: true
responses:
"200":
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/PageServiceAccount"
- description: OK
- "400":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Bad Request
- "401":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unauthorized
- "403":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Forbidden
- "404":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Not Found
- "422":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unprocessable Entity
- "500":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Internal Server Error
- security:
- - bearerAuth:
- - read:tokens
- summary: List all service accounts
+ description: Webhook processed successfully
+ summary: Instance Billing Plan Changed Webhook
tags:
- - service-accounts
+ - webhooks
+ - billing
+ com.kaiten.instance.billing.v1.provider_changed:
post:
- description: Create a new service account (machine user) with the provided name. Service accounts can be used to generate API tokens for programmatic access.
- operationId: create-service-account
+ description: Triggered when a subscription moves to another payment provider, from its next invoice on.
+ operationId: onInstanceBillingProviderChanged
requestBody:
content:
application/json:
schema:
- $ref: "#/components/schemas/ServiceAccount"
- description: Service account details
+ additionalProperties: false
+ properties:
+ data:
+ $ref: "#/components/schemas/ProviderChangeDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
+ name:
+ description: Event name
+ enum:
+ - INSTANCE_BILLING_PROVIDER_CHANGED
+ examples:
+ - INSTANCE_BILLING_PROVIDER_CHANGED
+ type: string
+ type:
+ description: Event type
+ enum:
+ - com.kaiten.instance.billing.v1.provider_changed
+ examples:
+ - com.kaiten.instance.billing.v1.provider_changed
+ type: string
+ required:
+ - name
+ - type
+ - data
+ type: object
required: true
responses:
- "201":
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/ServiceAccount"
- description: Created service account
- description: Created
- "400":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Bad Request
- "401":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unauthorized
- "403":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Forbidden
- "404":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Not Found
- "409":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Conflict
- "422":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unprocessable Entity
- "500":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Internal Server Error
- "503":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Service Unavailable
- security:
- - bearerAuth:
- - write:tokens
- summary: Create a new service account
+ "200":
+ description: Webhook processed successfully
+ summary: Instance Billing Provider Changed Webhook
tags:
- - service-accounts
- /service-accounts/{serviceAccountSlug}:
- get:
- description: Retrieve details of a specific service account (machine user) by its slug.
- operationId: get-service-account
- parameters:
- - description: Service account slug
- in: path
- name: serviceAccountSlug
- required: true
- schema:
- description: Service account slug
- examples:
- - service-account-slug
- type: string
+ - webhooks
+ - billing
+ com.kaiten.instance.billing.v1.started:
+ post:
+ description: Triggered when an instance is subscribed, or a CANCELED subscription subscribed again.
+ operationId: onInstanceBillingStarted
+ requestBody:
+ content:
+ application/json:
+ schema:
+ additionalProperties: false
+ properties:
+ data:
+ $ref: "#/components/schemas/BillingStartedDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
+ name:
+ description: Event name
+ enum:
+ - INSTANCE_BILLING_STARTED
+ examples:
+ - INSTANCE_BILLING_STARTED
+ type: string
+ type:
+ description: Event type
+ enum:
+ - com.kaiten.instance.billing.v1.started
+ examples:
+ - com.kaiten.instance.billing.v1.started
+ type: string
+ required:
+ - name
+ - type
+ - data
+ type: object
+ required: true
+ responses:
+ "200":
+ description: Webhook processed successfully
+ summary: Instance Billing Started Webhook
+ tags:
+ - webhooks
+ - billing
+ com.kaiten.instance.billing.v1.status_changed:
+ post:
+ description: Triggered when a trial converts, or a subscription becomes PAST_DUE or leaves it.
+ operationId: onInstanceBillingStatusChanged
+ requestBody:
+ content:
+ application/json:
+ schema:
+ additionalProperties: false
+ properties:
+ data:
+ $ref: "#/components/schemas/StatusChangeDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
+ name:
+ description: Event name
+ enum:
+ - INSTANCE_BILLING_STATUS_CHANGED
+ examples:
+ - INSTANCE_BILLING_STATUS_CHANGED
+ type: string
+ type:
+ description: Event type
+ enum:
+ - com.kaiten.instance.billing.v1.status_changed
+ examples:
+ - com.kaiten.instance.billing.v1.status_changed
+ type: string
+ required:
+ - name
+ - type
+ - data
+ type: object
+ required: true
responses:
"200":
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/ServiceAccount"
- description: Service account details
- description: OK
- "400":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Bad Request
- "401":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unauthorized
- "403":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Forbidden
- "404":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Not Found
- "422":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unprocessable Entity
- "500":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Internal Server Error
- security:
- - bearerAuth:
- - read:tokens
- summary: Get a service account by slug
+ description: Webhook processed successfully
+ summary: Instance Billing Status Changed Webhook
tags:
- - service-accounts
- put:
- description: Update the name of a service account (machine user) by its slug.
- operationId: update-service-account
- parameters:
- - description: Service account slug
- in: path
- name: serviceAccountSlug
- required: true
- schema:
- description: Service account slug
- examples:
- - service-account-slug
- type: string
+ - webhooks
+ - billing
+ com.kaiten.instance.billing.v1.trial_will_end:
+ post:
+ description: "Triggered once per trial, at reminderAt: its family's trialReminderDays before trialEndsAt (3 by default), or at once when the trial is shorter. Carries what a reminder e-mail needs, read when it is emitted: the customer and its billing e-mail, the instance, the plan, daysLeft, the charge the conversion would make and the card it would be made to."
+ operationId: onInstanceBillingTrialWillEnd
requestBody:
content:
application/json:
schema:
- $ref: "#/components/schemas/ServiceAccount"
- description: Service account update details
+ additionalProperties: false
+ properties:
+ data:
+ $ref: "#/components/schemas/TrialWillEndDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
+ name:
+ description: Event name
+ enum:
+ - INSTANCE_BILLING_TRIAL_WILL_END
+ examples:
+ - INSTANCE_BILLING_TRIAL_WILL_END
+ type: string
+ type:
+ description: Event type
+ enum:
+ - com.kaiten.instance.billing.v1.trial_will_end
+ examples:
+ - com.kaiten.instance.billing.v1.trial_will_end
+ type: string
+ required:
+ - name
+ - type
+ - data
+ type: object
required: true
responses:
- "204":
- description: No Content
- "400":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Bad Request
- "401":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unauthorized
- "403":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Forbidden
- "404":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Not Found
- "422":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unprocessable Entity
- "500":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Internal Server Error
- security:
- - bearerAuth:
- - write:tokens
- summary: Update a service account
+ "200":
+ description: Webhook processed successfully
+ summary: Instance Billing Trial Will End Webhook
tags:
- - service-accounts
- /service-accounts/{serviceAccountSlug}/tokens:
- get:
- description: Returns a cursor-paginated page of API tokens associated with a service account. The organization context is derived from the authenticated user's JWT token. Token secret values are never returned, only metadata.
- operationId: get-service-account-tokens
- parameters:
- - description: Service account slug
- in: path
- name: serviceAccountSlug
- required: true
- schema:
- description: Service account slug
- examples:
- - service-account-slug
- type: string
- - description: Opaque pagination cursor from a previous response's nextCursor
- explode: false
- in: query
- name: cursor
- schema:
- description: Opaque pagination cursor from a previous response's nextCursor
- type: string
- - description: Maximum number of entries to return (default 50, max 200)
- explode: false
- in: query
- name: limit
- schema:
- description: Maximum number of entries to return (default 50, max 200)
- format: int32
- maximum: 200
- minimum: 1
- type: integer
+ - webhooks
+ - billing
+ com.kaiten.instance.entitlement.v1.cap_exceeded:
+ post:
+ description: Triggered when a SOFT-enforcement entitlement's usage crosses from below the cap to above it. Fires once per crossing.
+ operationId: onInstanceEntitlementCapExceeded
+ requestBody:
+ content:
+ application/json:
+ schema:
+ additionalProperties: false
+ properties:
+ data:
+ $ref: "#/components/schemas/InstanceEntitlementCapExceededDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
+ name:
+ description: Event name
+ enum:
+ - INSTANCE_ENTITLEMENT_CAP_EXCEEDED
+ examples:
+ - INSTANCE_ENTITLEMENT_CAP_EXCEEDED
+ type: string
+ type:
+ description: Event type
+ enum:
+ - com.kaiten.instance.entitlement.v1.cap_exceeded
+ examples:
+ - com.kaiten.instance.entitlement.v1.cap_exceeded
+ type: string
+ required:
+ - name
+ - type
+ - data
+ type: object
+ required: true
responses:
"200":
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/PageToken"
- description: OK
- "400":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Bad Request
- "401":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unauthorized
- "403":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Forbidden
- "404":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Not Found
- "422":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unprocessable Entity
- "500":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Internal Server Error
- security:
- - bearerAuth:
- - read:tokens
- summary: List all tokens for a service account
+ description: Webhook processed successfully
+ summary: Instance Entitlement Cap Exceeded Webhook
tags:
- - service-accounts
- post:
- description: Create a new API token for a service account. The token value is only returned once upon creation and cannot be retrieved again. The organization context is derived from the authenticated user's JWT token.
- operationId: create-service-account-token
- parameters:
- - description: Service account slug
- in: path
- name: serviceAccountSlug
- required: true
- schema:
- description: Service account slug
- examples:
- - service-account-slug
- format: text
- type: string
+ - webhooks
+ - instances
+ com.kaiten.instance.entitlement.v1.usage_period_rolled_over:
+ post:
+ description: "Triggered once per usage window closed by a report: the entitlement's own stored bucket, and one synthetic event per window skipped entirely since the last report."
+ operationId: onInstanceEntitlementUsagePeriodRolledOver
requestBody:
content:
application/json:
schema:
- $ref: "#/components/schemas/PlainToken"
- description: Token creation details
+ additionalProperties: false
+ properties:
+ data:
+ $ref: "#/components/schemas/InstanceEntitlementUsagePeriodRolledOverDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
+ name:
+ description: Event name
+ enum:
+ - INSTANCE_ENTITLEMENT_USAGE_PERIOD_ROLLED_OVER
+ examples:
+ - INSTANCE_ENTITLEMENT_USAGE_PERIOD_ROLLED_OVER
+ type: string
+ type:
+ description: Event type
+ enum:
+ - com.kaiten.instance.entitlement.v1.usage_period_rolled_over
+ examples:
+ - com.kaiten.instance.entitlement.v1.usage_period_rolled_over
+ type: string
+ required:
+ - name
+ - type
+ - data
+ type: object
required: true
responses:
- "201":
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/PlainToken"
- description: Created token with secret value (only shown once)
- description: Created
- "400":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Bad Request
- "401":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unauthorized
- "403":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Forbidden
- "404":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Not Found
- "409":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Conflict
- "422":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unprocessable Entity
- "500":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Internal Server Error
- "503":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Service Unavailable
- security:
- - bearerAuth:
- - write:tokens
- summary: Create a new token for a service account
- tags:
- - service-accounts
- /service-accounts/{serviceAccountSlug}/tokens/{tokenSlug}:
- delete:
- description: Revoke an API token for a service account. Once revoked, the token can no longer be used for authentication. The organization context is derived from the authenticated user's JWT token.
- operationId: delete-service-account-token
- parameters:
- - description: Service account slug
- in: path
- name: serviceAccountSlug
- required: true
- schema:
- description: Service account slug
- examples:
- - service-account-slug
- format: text
- type: string
- - description: Token slug to revoke
- in: path
- name: tokenSlug
- required: true
- schema:
- description: Token slug to revoke
- examples:
- - my-token-slug
- format: text
- type: string
- responses:
- "204":
- description: No Content
- "400":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Bad Request
- "401":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unauthorized
- "403":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Forbidden
- "404":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Not Found
- "422":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unprocessable Entity
- "500":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Internal Server Error
- security:
- - bearerAuth:
- - write:tokens
- summary: Revoke a token for a service account
+ "200":
+ description: Webhook processed successfully
+ summary: Entitlement Usage Period Rolled Over Webhook
tags:
- - service-accounts
- /v1/notification-preferences:
- get:
- description: "Every notifiable event, with this user's effective value per channel: their own choice where they made one, the catalogue default otherwise. Events absent from the catalogue of a deployment never appear, which is what keeps high-volume system events out of the feed."
- operationId: getNotificationPreferences
+ - webhooks
+ - instances
+ com.kaiten.instance.entitlement.v1.usage_reached:
+ post:
+ description: Triggered when an instance's entitlement usage reaches its threshold.
+ operationId: onInstanceEntitlementUsageReached
+ requestBody:
+ content:
+ application/json:
+ schema:
+ additionalProperties: false
+ properties:
+ data:
+ $ref: "#/components/schemas/EntitlementUsageDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
+ name:
+ description: Event name
+ enum:
+ - INSTANCE_ENTITLEMENT_USAGE_REACHED
+ examples:
+ - INSTANCE_ENTITLEMENT_USAGE_REACHED
+ type: string
+ type:
+ description: Event type
+ enum:
+ - com.kaiten.instance.entitlement.v1.usage_reached
+ examples:
+ - com.kaiten.instance.entitlement.v1.usage_reached
+ type: string
+ required:
+ - name
+ - type
+ - data
+ type: object
+ required: true
responses:
"200":
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/PreferenceMatrix"
- description: OK
- "401":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unauthorized
- "403":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Forbidden
- "500":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Internal Server Error
- security:
- - bearerAuth:
- - read:notifications
- summary: Read the caller's notification preferences
+ description: Webhook processed successfully
+ summary: Instance Entitlement Usage Reached Webhook
+ tags:
+ - webhooks
+ - instances
+ com.kaiten.instance.entitlement.v1.usage_report_accepted:
+ post:
+ description: Triggered when a usage report is persisted, including a SOFT-enforcement overage.
+ operationId: onInstanceEntitlementUsageReportAccepted
+ requestBody:
+ content:
+ application/json:
+ schema:
+ additionalProperties: false
+ properties:
+ data:
+ $ref: "#/components/schemas/InstanceEntitlementUsageReportAcceptedDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
+ name:
+ description: Event name
+ enum:
+ - ENTITLEMENT_USAGE_REPORT_ACCEPTED
+ examples:
+ - ENTITLEMENT_USAGE_REPORT_ACCEPTED
+ type: string
+ type:
+ description: Event type
+ enum:
+ - com.kaiten.instance.entitlement.v1.usage_report_accepted
+ examples:
+ - com.kaiten.instance.entitlement.v1.usage_report_accepted
+ type: string
+ required:
+ - name
+ - type
+ - data
+ type: object
+ required: true
+ responses:
+ "200":
+ description: Webhook processed successfully
+ summary: Entitlement Usage Report Accepted Webhook
tags:
- - notifications
- put:
- description: Stores the signed-in user's choices and answers with the whole matrix. A value that equals the catalogue default is stored as no row at all, so a later change to that default still reaches this user.
- operationId: putNotificationPreferences
+ - webhooks
+ - instances
+ com.kaiten.instance.entitlement.v1.usage_report_rejected:
+ post:
+ description: Triggered when a usage report is refused because it would take a HARD-enforcement entitlement past its cap. The usage is not persisted.
+ operationId: onInstanceEntitlementUsageReportRejected
requestBody:
content:
application/json:
schema:
- $ref: "#/components/schemas/PreferenceChoices"
+ additionalProperties: false
+ properties:
+ data:
+ $ref: "#/components/schemas/InstanceEntitlementUsageReportAcceptedDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
+ name:
+ description: Event name
+ enum:
+ - ENTITLEMENT_USAGE_REPORT_REJECTED
+ examples:
+ - ENTITLEMENT_USAGE_REPORT_REJECTED
+ type: string
+ type:
+ description: Event type
+ enum:
+ - com.kaiten.instance.entitlement.v1.usage_report_rejected
+ examples:
+ - com.kaiten.instance.entitlement.v1.usage_report_rejected
+ type: string
+ required:
+ - name
+ - type
+ - data
+ type: object
required: true
responses:
"200":
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/PreferenceMatrix"
- description: OK
- "400":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Bad Request
- "401":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unauthorized
- "403":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Forbidden
- "422":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unprocessable Entity
- "500":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Internal Server Error
- security:
- - bearerAuth:
- - write:notifications
- summary: Set the caller's notification preferences
- tags:
- - notifications
- /v1/notifications:
- get:
- description: Returns the signed-in user's notification feed, newest first, with the unread count. Notifications are a view over this organization's audit trail, narrowed to the events the user subscribes to; the id of a notification is the id of the audit trail entry it reports.
- operationId: listNotifications
- parameters:
- - description: Which notifications to return (default all)
- explode: false
- in: query
- name: status
- schema:
- description: Which notifications to return (default all)
- enum:
- - all
- - unread
- type: string
- - description: "Only notifications about these kinds of object; repeat the parameter for several (default: all)"
- explode: true
- in: query
- name: objectType
- schema:
- description: "Only notifications about these kinds of object; repeat the parameter for several (default: all)"
- items:
- enum:
- - instance
- - customer
- - release
- - deployment_zone
- - component
- - license
- - token
- type: string
- type:
- - array
- - "null"
- - description: Opaque cursor from a previous response's next_cursor
- explode: false
- in: query
- name: cursor
- schema:
- description: Opaque cursor from a previous response's next_cursor
- type: string
- - description: Page size (default 20, max 50)
- explode: false
- in: query
- name: limit
- schema:
- description: Page size (default 20, max 50)
- format: int32
- maximum: 50
- minimum: 1
- type: integer
- responses:
- "200":
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/List"
- description: OK
- "400":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Bad Request
- "401":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unauthorized
- "403":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Forbidden
- "422":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unprocessable Entity
- "500":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Internal Server Error
- security:
- - bearerAuth:
- - read:notifications
- summary: List the caller's notifications
+ description: Webhook processed successfully
+ summary: Entitlement Usage Report Rejected Webhook
tags:
- - notifications
- /v1/notifications/mark-read:
+ - webhooks
+ - instances
+ com.kaiten.instance.entitlement.v1.usage_warning_threshold_reached:
post:
- description: "Marks the given notifications read for the signed-in user, or all of them with all: true. Ids belonging to another organization are ignored rather than rejected, so a retry of a partially applied call is safe."
- operationId: markNotificationsRead
+ description: Triggered when an instance's entitlement usage crosses its configured early-warning percentage boundary, before the cap is reached. Fires once per crossing.
+ operationId: onInstanceEntitlementUsageWarningThresholdReached
requestBody:
content:
application/json:
schema:
- $ref: "#/components/schemas/ReadSelection"
+ additionalProperties: false
+ properties:
+ data:
+ $ref: "#/components/schemas/InstanceEntitlementUsageWarningThresholdReachedDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
+ name:
+ description: Event name
+ enum:
+ - INSTANCE_ENTITLEMENT_USAGE_WARNING_THRESHOLD_REACHED
+ examples:
+ - INSTANCE_ENTITLEMENT_USAGE_WARNING_THRESHOLD_REACHED
+ type: string
+ type:
+ description: Event type
+ enum:
+ - com.kaiten.instance.entitlement.v1.usage_warning_threshold_reached
+ examples:
+ - com.kaiten.instance.entitlement.v1.usage_warning_threshold_reached
+ type: string
+ required:
+ - name
+ - type
+ - data
+ type: object
required: true
responses:
"200":
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/MarkReadResult"
- description: OK
- "400":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Bad Request
- "401":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unauthorized
- "403":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Forbidden
- "422":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Unprocessable Entity
- "500":
- content:
- application/problem+json:
- schema:
- $ref: "#/components/schemas/Problem"
- description: Internal Server Error
- security:
- - bearerAuth:
- - write:notifications
- summary: Mark notifications read
+ description: Webhook processed successfully
+ summary: Instance Entitlement Usage Warning Threshold Reached Webhook
+ tags:
+ - webhooks
+ - instances
+ com.kaiten.instance.entitlement.v1.value_get:
+ post:
+ description: Triggered on every read of an instance entitlement's usage value. This is a read audit signal, not a state change.
+ operationId: onInstanceEntitlementValueGet
+ requestBody:
+ content:
+ application/json:
+ schema:
+ additionalProperties: false
+ properties:
+ data:
+ $ref: "#/components/schemas/InstanceEntitlementValueGetDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
+ name:
+ description: Event name
+ enum:
+ - ENTITLEMENT_VALUE_GET
+ examples:
+ - ENTITLEMENT_VALUE_GET
+ type: string
+ type:
+ description: Event type
+ enum:
+ - com.kaiten.instance.entitlement.v1.value_get
+ examples:
+ - com.kaiten.instance.entitlement.v1.value_get
+ type: string
+ required:
+ - name
+ - type
+ - data
+ type: object
+ required: true
+ responses:
+ "200":
+ description: Webhook processed successfully
+ summary: Instance Entitlement Value Read Webhook
tags:
- - notifications
-security:
- - bearerAuth: []
-servers:
- - url: /api
-webhooks:
- com.kaiten.component.v1.created:
+ - webhooks
+ - instances
+ com.kaiten.instance.entitlement_exception.v1.created:
post:
- description: Triggered when a new component is created.
- operationId: onComponentCreated
+ description: Triggered when an exception is created on one entitlement of an instance. Emitted whether or not billing is enabled.
+ operationId: onInstanceEntitlementExceptionCreated
requestBody:
content:
application/json:
@@ -10189,20 +36441,22 @@ webhooks:
additionalProperties: false
properties:
data:
- $ref: "#/components/schemas/Component"
+ $ref: "#/components/schemas/ExceptionEventDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
name:
description: Event name
enum:
- - COMPONENT_CREATED
+ - INSTANCE_ENTITLEMENT_EXCEPTION_CREATED
examples:
- - COMPONENT_CREATED
+ - INSTANCE_ENTITLEMENT_EXCEPTION_CREATED
type: string
type:
description: Event type
enum:
- - com.kaiten.component.v1.created
+ - com.kaiten.instance.entitlement_exception.v1.created
examples:
- - com.kaiten.component.v1.created
+ - com.kaiten.instance.entitlement_exception.v1.created
type: string
required:
- name
@@ -10213,14 +36467,14 @@ webhooks:
responses:
"200":
description: Webhook processed successfully
- summary: Component Created Webhook
+ summary: Instance Entitlement Exception Created Webhook
tags:
- webhooks
- - components
- com.kaiten.component.v1.deleted:
+ - entitlement-exceptions
+ com.kaiten.instance.entitlement_exception.v1.expired:
post:
- description: Triggered when a component is deleted.
- operationId: onComponentDeleted
+ description: Triggered once per exception, when its endsAt passes; never for a revoked one.
+ operationId: onInstanceEntitlementExceptionExpired
requestBody:
content:
application/json:
@@ -10228,20 +36482,22 @@ webhooks:
additionalProperties: false
properties:
data:
- $ref: "#/components/schemas/Component"
+ $ref: "#/components/schemas/ExceptionEventDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
name:
description: Event name
enum:
- - COMPONENT_DELETED
+ - INSTANCE_ENTITLEMENT_EXCEPTION_EXPIRED
examples:
- - COMPONENT_DELETED
+ - INSTANCE_ENTITLEMENT_EXCEPTION_EXPIRED
type: string
type:
description: Event type
enum:
- - com.kaiten.component.v1.deleted
+ - com.kaiten.instance.entitlement_exception.v1.expired
examples:
- - com.kaiten.component.v1.deleted
+ - com.kaiten.instance.entitlement_exception.v1.expired
type: string
required:
- name
@@ -10252,14 +36508,14 @@ webhooks:
responses:
"200":
description: Webhook processed successfully
- summary: Component Deleted Webhook
+ summary: Instance Entitlement Exception Expired Webhook
tags:
- webhooks
- - components
- com.kaiten.component.v1.updated:
+ - entitlement-exceptions
+ com.kaiten.instance.entitlement_exception.v1.revoked:
post:
- description: Triggered when a component is updated or auto-versioned.
- operationId: onComponentUpdated
+ description: Triggered when an exception is revoked. Revoking one already revoked or ended emits nothing.
+ operationId: onInstanceEntitlementExceptionRevoked
requestBody:
content:
application/json:
@@ -10267,20 +36523,22 @@ webhooks:
additionalProperties: false
properties:
data:
- $ref: "#/components/schemas/Component"
+ $ref: "#/components/schemas/ExceptionEventDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
name:
description: Event name
enum:
- - COMPONENT_UPDATED
+ - INSTANCE_ENTITLEMENT_EXCEPTION_REVOKED
examples:
- - COMPONENT_UPDATED
+ - INSTANCE_ENTITLEMENT_EXCEPTION_REVOKED
type: string
type:
description: Event type
enum:
- - com.kaiten.component.v1.updated
+ - com.kaiten.instance.entitlement_exception.v1.revoked
examples:
- - com.kaiten.component.v1.updated
+ - com.kaiten.instance.entitlement_exception.v1.revoked
type: string
required:
- name
@@ -10291,14 +36549,14 @@ webhooks:
responses:
"200":
description: Webhook processed successfully
- summary: Component Updated Webhook
+ summary: Instance Entitlement Exception Revoked Webhook
tags:
- webhooks
- - components
- com.kaiten.customer.v1.created:
+ - entitlement-exceptions
+ com.kaiten.instance.invoice.v1.handoff_acknowledged:
post:
- description: Triggered when a new customer is created.
- operationId: onCustomerCreated
+ description: Triggered when the organization's accounting system acknowledges an invoice from the handoff queue, or the invoice is marked paid while still waiting there.
+ operationId: onInstanceInvoiceHandoffAcknowledged
requestBody:
content:
application/json:
@@ -10306,20 +36564,22 @@ webhooks:
additionalProperties: false
properties:
data:
- $ref: "#/components/schemas/Customer"
+ $ref: "#/components/schemas/HandoffAcknowledgementDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
name:
description: Event name
enum:
- - CUSTOMER_CREATED
+ - INSTANCE_INVOICE_HANDOFF_ACKNOWLEDGED
examples:
- - CUSTOMER_CREATED
+ - INSTANCE_INVOICE_HANDOFF_ACKNOWLEDGED
type: string
type:
description: Event type
enum:
- - com.kaiten.customer.v1.created
+ - com.kaiten.instance.invoice.v1.handoff_acknowledged
examples:
- - com.kaiten.customer.v1.created
+ - com.kaiten.instance.invoice.v1.handoff_acknowledged
type: string
required:
- name
@@ -10330,14 +36590,14 @@ webhooks:
responses:
"200":
description: Webhook processed successfully
- summary: Customer Created Webhook
+ summary: Instance Invoice Handoff Acknowledged Webhook
tags:
- webhooks
- - customers
- com.kaiten.customer.v1.creation_rejected:
+ - billing
+ com.kaiten.instance.invoice.v1.held:
post:
- description: Triggered when a customer creation is refused because the organization has reached its customer entitlement cap. No customer is created.
- operationId: onCustomerCreationRejected
+ description: Triggered when a period closes into an invoice held as a DRAFT, because the usage journal it was measured from failed a consistency check. It is neither issued nor handed off until released or recomposed.
+ operationId: onInstanceInvoiceHeld
requestBody:
content:
application/json:
@@ -10345,20 +36605,22 @@ webhooks:
additionalProperties: false
properties:
data:
- $ref: "#/components/schemas/CustomerCreationRejected"
+ $ref: "#/components/schemas/HeldInvoiceDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
name:
description: Event name
enum:
- - CUSTOMER_CREATION_REJECTED
+ - INSTANCE_INVOICE_HELD
examples:
- - CUSTOMER_CREATION_REJECTED
+ - INSTANCE_INVOICE_HELD
type: string
type:
description: Event type
enum:
- - com.kaiten.customer.v1.creation_rejected
+ - com.kaiten.instance.invoice.v1.held
examples:
- - com.kaiten.customer.v1.creation_rejected
+ - com.kaiten.instance.invoice.v1.held
type: string
required:
- name
@@ -10369,14 +36631,14 @@ webhooks:
responses:
"200":
description: Webhook processed successfully
- summary: Customer Creation Rejected Webhook
+ summary: Instance Invoice Held Webhook
tags:
- webhooks
- - customers
- com.kaiten.customer.v1.deleted:
+ - billing
+ com.kaiten.instance.invoice.v1.issued:
post:
- description: Triggered when a customer is deleted.
- operationId: onCustomerDeleted
+ description: Triggered once per invoice with something owed, when it is issued. For an invoice the organization collects itself, it is the signal to claim it from the handoff queue, which stays the source of truth.
+ operationId: onInstanceInvoiceIssued
requestBody:
content:
application/json:
@@ -10384,20 +36646,22 @@ webhooks:
additionalProperties: false
properties:
data:
- $ref: "#/components/schemas/Customer"
+ $ref: "#/components/schemas/IssuedInvoiceDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
name:
description: Event name
enum:
- - CUSTOMER_DELETED
+ - INSTANCE_INVOICE_ISSUED
examples:
- - CUSTOMER_DELETED
+ - INSTANCE_INVOICE_ISSUED
type: string
type:
description: Event type
enum:
- - com.kaiten.customer.v1.deleted
+ - com.kaiten.instance.invoice.v1.issued
examples:
- - com.kaiten.customer.v1.deleted
+ - com.kaiten.instance.invoice.v1.issued
type: string
required:
- name
@@ -10408,14 +36672,14 @@ webhooks:
responses:
"200":
description: Webhook processed successfully
- summary: Customer Deleted Webhook
+ summary: Instance Invoice Issued Webhook
tags:
- webhooks
- - customers
- com.kaiten.customer.v1.updated:
+ - billing
+ com.kaiten.instance.invoice.v1.marked_uncollectible:
post:
- description: Triggered when a customer is updated.
- operationId: onCustomerUpdated
+ description: Triggered when an invoice is written off.
+ operationId: onInstanceInvoiceMarkedUncollectible
requestBody:
content:
application/json:
@@ -10423,20 +36687,22 @@ webhooks:
additionalProperties: false
properties:
data:
- $ref: "#/components/schemas/Customer"
+ $ref: "#/components/schemas/UncollectibleInvoiceDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
name:
description: Event name
enum:
- - CUSTOMER_UPDATED
+ - INSTANCE_INVOICE_MARKED_UNCOLLECTIBLE
examples:
- - CUSTOMER_UPDATED
+ - INSTANCE_INVOICE_MARKED_UNCOLLECTIBLE
type: string
type:
description: Event type
enum:
- - com.kaiten.customer.v1.updated
+ - com.kaiten.instance.invoice.v1.marked_uncollectible
examples:
- - com.kaiten.customer.v1.updated
+ - com.kaiten.instance.invoice.v1.marked_uncollectible
type: string
required:
- name
@@ -10447,14 +36713,14 @@ webhooks:
responses:
"200":
description: Webhook processed successfully
- summary: Customer Updated Webhook
+ summary: Instance Invoice Marked Uncollectible Webhook
tags:
- webhooks
- - customers
- com.kaiten.deployment_zone.v1.created:
+ - billing
+ com.kaiten.instance.invoice.v1.paid:
post:
- description: Triggered when a new deployment zone is created.
- operationId: onDeploymentZoneCreated
+ description: "Triggered when an invoice is paid: marked paid by the organization, or issued paid because nothing was owed."
+ operationId: onInstanceInvoicePaid
requestBody:
content:
application/json:
@@ -10462,20 +36728,22 @@ webhooks:
additionalProperties: false
properties:
data:
- $ref: "#/components/schemas/DeploymentZone"
+ $ref: "#/components/schemas/PaidInvoiceDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
name:
description: Event name
enum:
- - DEPLOYMENT_ZONE_CREATED
+ - INSTANCE_INVOICE_PAID
examples:
- - DEPLOYMENT_ZONE_CREATED
+ - INSTANCE_INVOICE_PAID
type: string
type:
description: Event type
enum:
- - com.kaiten.deployment_zone.v1.created
+ - com.kaiten.instance.invoice.v1.paid
examples:
- - com.kaiten.deployment_zone.v1.created
+ - com.kaiten.instance.invoice.v1.paid
type: string
required:
- name
@@ -10486,14 +36754,14 @@ webhooks:
responses:
"200":
description: Webhook processed successfully
- summary: Deployment Zone Created Webhook
+ summary: Instance Invoice Paid Webhook
tags:
- webhooks
- - deploymentZones
- com.kaiten.deployment_zone.v1.deleted:
+ - billing
+ com.kaiten.instance.invoice.v1.payment_failed:
post:
- description: Triggered when a deployment zone is deleted.
- operationId: onDeploymentZoneDeleted
+ description: Triggered when the automatic charge of an invoice is refused, or needs the customer to authenticate it on the invoice's hosted page.
+ operationId: onInstanceInvoicePaymentFailed
requestBody:
content:
application/json:
@@ -10501,20 +36769,22 @@ webhooks:
additionalProperties: false
properties:
data:
- $ref: "#/components/schemas/DeploymentZone"
+ $ref: "#/components/schemas/PaymentFailedInvoiceDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
name:
description: Event name
enum:
- - DEPLOYMENT_ZONE_DELETED
+ - INSTANCE_INVOICE_PAYMENT_FAILED
examples:
- - DEPLOYMENT_ZONE_DELETED
+ - INSTANCE_INVOICE_PAYMENT_FAILED
type: string
type:
description: Event type
enum:
- - com.kaiten.deployment_zone.v1.deleted
+ - com.kaiten.instance.invoice.v1.payment_failed
examples:
- - com.kaiten.deployment_zone.v1.deleted
+ - com.kaiten.instance.invoice.v1.payment_failed
type: string
required:
- name
@@ -10525,14 +36795,14 @@ webhooks:
responses:
"200":
description: Webhook processed successfully
- summary: Deployment Zone Deleted Webhook
+ summary: Instance Invoice Payment Failed Webhook
tags:
- webhooks
- - deploymentZones
- com.kaiten.deployment_zone.v1.release_deployed:
+ - billing
+ com.kaiten.instance.invoice.v1.push_failed:
post:
- description: Triggered when a release is deployed to a deployment zone, either at zone creation or when an existing zone's release changes.
- operationId: onReleaseDeployed
+ description: Triggered when pushing an invoice to its payment provider has failed as many times as the alert threshold, then once a day while it keeps failing.
+ operationId: onInstanceInvoicePushFailed
requestBody:
content:
application/json:
@@ -10540,20 +36810,22 @@ webhooks:
additionalProperties: false
properties:
data:
- $ref: "#/components/schemas/Deployment"
+ $ref: "#/components/schemas/PushFailedInvoiceDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
name:
description: Event name
enum:
- - RELEASE_DEPLOYED
+ - INSTANCE_INVOICE_PUSH_FAILED
examples:
- - RELEASE_DEPLOYED
+ - INSTANCE_INVOICE_PUSH_FAILED
type: string
type:
description: Event type
enum:
- - com.kaiten.deployment_zone.v1.release_deployed
+ - com.kaiten.instance.invoice.v1.push_failed
examples:
- - com.kaiten.deployment_zone.v1.release_deployed
+ - com.kaiten.instance.invoice.v1.push_failed
type: string
required:
- name
@@ -10564,14 +36836,14 @@ webhooks:
responses:
"200":
description: Webhook processed successfully
- summary: Release Deployed Webhook
+ summary: Instance Invoice Push Failed Webhook
tags:
- webhooks
- - deploymentZones
- com.kaiten.deployment_zone.v1.updated:
+ - billing
+ com.kaiten.instance.invoice.v1.pushed:
post:
- description: Triggered when a deployment zone is updated.
- operationId: onDeploymentZoneUpdated
+ description: Triggered when an invoice's payment provider has issued it, right after INSTANCE_INVOICE_ISSUED.
+ operationId: onInstanceInvoicePushed
requestBody:
content:
application/json:
@@ -10579,20 +36851,22 @@ webhooks:
additionalProperties: false
properties:
data:
- $ref: "#/components/schemas/DeploymentZone"
+ $ref: "#/components/schemas/PushedInvoiceDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
name:
description: Event name
enum:
- - DEPLOYMENT_ZONE_UPDATED
+ - INSTANCE_INVOICE_PUSHED
examples:
- - DEPLOYMENT_ZONE_UPDATED
+ - INSTANCE_INVOICE_PUSHED
type: string
type:
description: Event type
enum:
- - com.kaiten.deployment_zone.v1.updated
+ - com.kaiten.instance.invoice.v1.pushed
examples:
- - com.kaiten.deployment_zone.v1.updated
+ - com.kaiten.instance.invoice.v1.pushed
type: string
required:
- name
@@ -10603,14 +36877,14 @@ webhooks:
responses:
"200":
description: Webhook processed successfully
- summary: Deployment Zone Updated Webhook
+ summary: Instance Invoice Pushed Webhook
tags:
- webhooks
- - deploymentZones
- com.kaiten.entitlement.v1.created:
+ - billing
+ com.kaiten.instance.invoice.v1.reconciliation_mismatch:
post:
- description: Triggered when a new entitlement is created.
- operationId: onEntitlementCreated
+ description: "Triggered when an invoice's payment provider holds amounts that differ from Kaiten's. Kaiten never corrects itself: void and recompose the invoice."
+ operationId: onInstanceInvoiceReconciliationMismatch
requestBody:
content:
application/json:
@@ -10618,20 +36892,22 @@ webhooks:
additionalProperties: false
properties:
data:
- $ref: "#/components/schemas/Entitlement"
+ $ref: "#/components/schemas/MismatchedInvoiceDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
name:
description: Event name
enum:
- - ENTITLEMENT_CREATED
+ - INSTANCE_INVOICE_RECONCILIATION_MISMATCH
examples:
- - ENTITLEMENT_CREATED
+ - INSTANCE_INVOICE_RECONCILIATION_MISMATCH
type: string
type:
description: Event type
enum:
- - com.kaiten.entitlement.v1.created
+ - com.kaiten.instance.invoice.v1.reconciliation_mismatch
examples:
- - com.kaiten.entitlement.v1.created
+ - com.kaiten.instance.invoice.v1.reconciliation_mismatch
type: string
required:
- name
@@ -10642,14 +36918,14 @@ webhooks:
responses:
"200":
description: Webhook processed successfully
- summary: Entitlement Created Webhook
+ summary: Instance Invoice Reconciliation Mismatch Webhook
tags:
- webhooks
- - entitlements
- com.kaiten.entitlement.v1.deleted:
+ - billing
+ com.kaiten.instance.invoice.v1.released:
post:
- description: Triggered when an entitlement is deleted.
- operationId: onEntitlementDeleted
+ description: "Triggered when a held invoice leaves its hold: released by the organization, recomposed from a sound journal, or found sound by a later check. It is issued in the same transaction."
+ operationId: onInstanceInvoiceReleased
requestBody:
content:
application/json:
@@ -10657,20 +36933,22 @@ webhooks:
additionalProperties: false
properties:
data:
- $ref: "#/components/schemas/Entitlement"
+ $ref: "#/components/schemas/ReleasedInvoiceDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
name:
description: Event name
enum:
- - ENTITLEMENT_DELETED
+ - INSTANCE_INVOICE_RELEASED
examples:
- - ENTITLEMENT_DELETED
+ - INSTANCE_INVOICE_RELEASED
type: string
type:
description: Event type
enum:
- - com.kaiten.entitlement.v1.deleted
+ - com.kaiten.instance.invoice.v1.released
examples:
- - com.kaiten.entitlement.v1.deleted
+ - com.kaiten.instance.invoice.v1.released
type: string
required:
- name
@@ -10681,14 +36959,14 @@ webhooks:
responses:
"200":
description: Webhook processed successfully
- summary: Entitlement Deleted Webhook
+ summary: Instance Invoice Released Webhook
tags:
- webhooks
- - entitlements
- com.kaiten.entitlement.v1.updated:
+ - billing
+ com.kaiten.instance.invoice.v1.voided:
post:
- description: Triggered when an entitlement is updated.
- operationId: onEntitlementUpdated
+ description: Triggered when an invoice is voided. Its boundary can then be recomposed into a replacement.
+ operationId: onInstanceInvoiceVoided
requestBody:
content:
application/json:
@@ -10696,20 +36974,22 @@ webhooks:
additionalProperties: false
properties:
data:
- $ref: "#/components/schemas/Entitlement"
+ $ref: "#/components/schemas/VoidedInvoiceDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
name:
description: Event name
enum:
- - ENTITLEMENT_UPDATED
+ - INSTANCE_INVOICE_VOIDED
examples:
- - ENTITLEMENT_UPDATED
+ - INSTANCE_INVOICE_VOIDED
type: string
type:
description: Event type
enum:
- - com.kaiten.entitlement.v1.updated
+ - com.kaiten.instance.invoice.v1.voided
examples:
- - com.kaiten.entitlement.v1.updated
+ - com.kaiten.instance.invoice.v1.voided
type: string
required:
- name
@@ -10720,14 +37000,14 @@ webhooks:
responses:
"200":
description: Webhook processed successfully
- summary: Entitlement Updated Webhook
+ summary: Instance Invoice Voided Webhook
tags:
- webhooks
- - entitlements
- com.kaiten.entitlement_group.v1.created:
+ - billing
+ com.kaiten.instance.v1.created:
post:
- description: Triggered when a new entitlement group is created.
- operationId: onEntitlementGroupCreated
+ description: Triggered when a new instance is created.
+ operationId: onInstanceCreated
requestBody:
content:
application/json:
@@ -10735,20 +37015,22 @@ webhooks:
additionalProperties: false
properties:
data:
- $ref: "#/components/schemas/EntitlementGroup"
+ $ref: "#/components/schemas/InstanceDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
name:
description: Event name
enum:
- - ENTITLEMENT_GROUP_CREATED
+ - INSTANCE_CREATED
examples:
- - ENTITLEMENT_GROUP_CREATED
+ - INSTANCE_CREATED
type: string
type:
description: Event type
enum:
- - com.kaiten.entitlement_group.v1.created
+ - com.kaiten.instance.v1.created
examples:
- - com.kaiten.entitlement_group.v1.created
+ - com.kaiten.instance.v1.created
type: string
required:
- name
@@ -10759,14 +37041,14 @@ webhooks:
responses:
"200":
description: Webhook processed successfully
- summary: Entitlement Group Created Webhook
+ summary: Instance Created Webhook
tags:
- webhooks
- - entitlement-groups
- com.kaiten.entitlement_group.v1.deleted:
+ - instances
+ com.kaiten.instance.v1.deleted:
post:
- description: Triggered when an entitlement group is deleted.
- operationId: onEntitlementGroupDeleted
+ description: Triggered when an instance is deleted.
+ operationId: onInstanceDeleted
requestBody:
content:
application/json:
@@ -10774,20 +37056,22 @@ webhooks:
additionalProperties: false
properties:
data:
- $ref: "#/components/schemas/EntitlementGroup"
+ $ref: "#/components/schemas/InstanceDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
name:
description: Event name
enum:
- - ENTITLEMENT_GROUP_DELETED
+ - INSTANCE_DELETED
examples:
- - ENTITLEMENT_GROUP_DELETED
+ - INSTANCE_DELETED
type: string
type:
description: Event type
enum:
- - com.kaiten.entitlement_group.v1.deleted
+ - com.kaiten.instance.v1.deleted
examples:
- - com.kaiten.entitlement_group.v1.deleted
+ - com.kaiten.instance.v1.deleted
type: string
required:
- name
@@ -10798,14 +37082,14 @@ webhooks:
responses:
"200":
description: Webhook processed successfully
- summary: Entitlement Group Deleted Webhook
+ summary: Instance Deleted Webhook
tags:
- webhooks
- - entitlement-groups
- com.kaiten.entitlement_group.v1.updated:
+ - instances
+ com.kaiten.instance.v1.deployed:
post:
- description: Triggered when an entitlement group is updated.
- operationId: onEntitlementGroupUpdated
+ description: Triggered when an instance is deployed to a deployment zone.
+ operationId: onInstanceDeployed
requestBody:
content:
application/json:
@@ -10813,20 +37097,22 @@ webhooks:
additionalProperties: false
properties:
data:
- $ref: "#/components/schemas/EntitlementGroup"
+ $ref: "#/components/schemas/InstanceDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
name:
description: Event name
enum:
- - ENTITLEMENT_GROUP_UPDATED
+ - INSTANCE_DEPLOYED
examples:
- - ENTITLEMENT_GROUP_UPDATED
+ - INSTANCE_DEPLOYED
type: string
type:
description: Event type
enum:
- - com.kaiten.entitlement_group.v1.updated
+ - com.kaiten.instance.v1.deployed
examples:
- - com.kaiten.entitlement_group.v1.updated
+ - com.kaiten.instance.v1.deployed
type: string
required:
- name
@@ -10837,14 +37123,14 @@ webhooks:
responses:
"200":
description: Webhook processed successfully
- summary: Entitlement Group Updated Webhook
+ summary: Instance Deployed Webhook
tags:
- webhooks
- - entitlement-groups
- com.kaiten.feature_flag.v1.created:
+ - instances
+ com.kaiten.instance.v1.lifecycle_stage_changed:
post:
- description: Triggered when a new feature flag is created.
- operationId: onFeatureFlagCreated
+ description: Triggered when an instance commercial lifecycle stage changes.
+ operationId: onInstanceLifecycleStageChanged
requestBody:
content:
application/json:
@@ -10852,20 +37138,22 @@ webhooks:
additionalProperties: false
properties:
data:
- $ref: "#/components/schemas/FeatureFlag"
+ $ref: "#/components/schemas/InstanceLifecycleStageChangedDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
name:
description: Event name
enum:
- - FEATURE_FLAG_CREATED
+ - INSTANCE_LIFECYCLE_STAGE_CHANGED
examples:
- - FEATURE_FLAG_CREATED
+ - INSTANCE_LIFECYCLE_STAGE_CHANGED
type: string
type:
description: Event type
enum:
- - com.kaiten.feature_flag.v1.created
+ - com.kaiten.instance.v1.lifecycle_stage_changed
examples:
- - com.kaiten.feature_flag.v1.created
+ - com.kaiten.instance.v1.lifecycle_stage_changed
type: string
required:
- name
@@ -10876,14 +37164,14 @@ webhooks:
responses:
"200":
description: Webhook processed successfully
- summary: Feature Flag Created Webhook
+ summary: Instance Lifecycle Stage Changed Webhook
tags:
- webhooks
- - featureflags
- com.kaiten.feature_flag.v1.deleted:
+ - instances
+ com.kaiten.instance.v1.migrated:
post:
- description: Triggered when a feature flag is deleted.
- operationId: onFeatureFlagDeleted
+ description: Triggered when an instance is migrated from one deployment zone to another.
+ operationId: onInstanceMigrated
requestBody:
content:
application/json:
@@ -10891,20 +37179,22 @@ webhooks:
additionalProperties: false
properties:
data:
- $ref: "#/components/schemas/FeatureFlag"
+ $ref: "#/components/schemas/InstanceDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
name:
description: Event name
enum:
- - FEATURE_FLAG_DELETED
+ - INSTANCE_MIGRATED
examples:
- - FEATURE_FLAG_DELETED
+ - INSTANCE_MIGRATED
type: string
type:
description: Event type
enum:
- - com.kaiten.feature_flag.v1.deleted
+ - com.kaiten.instance.v1.migrated
examples:
- - com.kaiten.feature_flag.v1.deleted
+ - com.kaiten.instance.v1.migrated
type: string
required:
- name
@@ -10915,14 +37205,14 @@ webhooks:
responses:
"200":
description: Webhook processed successfully
- summary: Feature Flag Deleted Webhook
+ summary: Instance Migrated Webhook
tags:
- webhooks
- - featureflags
- com.kaiten.feature_flag.v1.evaluated:
+ - instances
+ com.kaiten.instance.v1.status_changed:
post:
- description: Triggered on every flag evaluation. The payload carries no evaluation context, so no targeting key or user attribute is exposed.
- operationId: onFeatureFlagEvaluated
+ description: Triggered when an instance operational status changes.
+ operationId: onInstanceStatusChanged
requestBody:
content:
application/json:
@@ -10930,20 +37220,22 @@ webhooks:
additionalProperties: false
properties:
data:
- $ref: "#/components/schemas/FeatureFlagEvaluated"
+ $ref: "#/components/schemas/InstanceStatusChangedDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
name:
description: Event name
enum:
- - FEATURE_FLAG_EVALUATED
+ - INSTANCE_STATUS_CHANGED
examples:
- - FEATURE_FLAG_EVALUATED
+ - INSTANCE_STATUS_CHANGED
type: string
type:
description: Event type
enum:
- - com.kaiten.feature_flag.v1.evaluated
+ - com.kaiten.instance.v1.status_changed
examples:
- - com.kaiten.feature_flag.v1.evaluated
+ - com.kaiten.instance.v1.status_changed
type: string
required:
- name
@@ -10954,14 +37246,14 @@ webhooks:
responses:
"200":
description: Webhook processed successfully
- summary: Feature Flag Evaluated Webhook
+ summary: Instance Status Changed Webhook
tags:
- webhooks
- - featureflags
- com.kaiten.feature_flag.v1.updated:
+ - instances
+ com.kaiten.instance.v1.updated:
post:
- description: Triggered when a feature flag is updated.
- operationId: onFeatureFlagUpdated
+ description: "Triggered when an instance is updated, by a write or by billing: the licence version a paid checkout bought, or the fallback a subscription that ended moves it to (licenseChangeReason BILLING_FALLBACK)."
+ operationId: onInstanceUpdated
requestBody:
content:
application/json:
@@ -10969,20 +37261,22 @@ webhooks:
additionalProperties: false
properties:
data:
- $ref: "#/components/schemas/FeatureFlag"
+ $ref: "#/components/schemas/InstanceUpdateDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
name:
description: Event name
enum:
- - FEATURE_FLAG_UPDATED
+ - INSTANCE_UPDATED
examples:
- - FEATURE_FLAG_UPDATED
+ - INSTANCE_UPDATED
type: string
type:
description: Event type
enum:
- - com.kaiten.feature_flag.v1.updated
+ - com.kaiten.instance.v1.updated
examples:
- - com.kaiten.feature_flag.v1.updated
+ - com.kaiten.instance.v1.updated
type: string
required:
- name
@@ -10993,14 +37287,14 @@ webhooks:
responses:
"200":
description: Webhook processed successfully
- summary: Feature Flag Updated Webhook
+ summary: Instance Updated Webhook
tags:
- webhooks
- - featureflags
- com.kaiten.identity.v1.system_token_issued:
+ - instances
+ com.kaiten.instance.voucher.v1.expired:
post:
- description: "Triggered when a platform credential mints an organization-scoped credential for the `system:kaiten` platform identity inside this organization. Carries the platform credential that performed the mint and the scopes granted, never the credential value. Expect this at the rate an integration refreshes: roughly 120 per day per process at a 15-minute lifetime."
- operationId: onSystemOrganizationTokenIssued
+ description: Triggered when a PRICE redemption has discounted its last invoice, or a boost's window ends.
+ operationId: onInstanceVoucherExpired
requestBody:
content:
application/json:
@@ -11008,20 +37302,22 @@ webhooks:
additionalProperties: false
properties:
data:
- $ref: "#/components/schemas/SystemTokenIssuance"
+ $ref: "#/components/schemas/RedemptionDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
name:
description: Event name
enum:
- - SYSTEM_ORGANIZATION_TOKEN_ISSUED
+ - INSTANCE_VOUCHER_EXPIRED
examples:
- - SYSTEM_ORGANIZATION_TOKEN_ISSUED
+ - INSTANCE_VOUCHER_EXPIRED
type: string
type:
description: Event type
enum:
- - com.kaiten.identity.v1.system_token_issued
+ - com.kaiten.instance.voucher.v1.expired
examples:
- - com.kaiten.identity.v1.system_token_issued
+ - com.kaiten.instance.voucher.v1.expired
type: string
required:
- name
@@ -11032,14 +37328,14 @@ webhooks:
responses:
"200":
description: Webhook processed successfully
- summary: System Organization Token Issued Webhook
+ summary: Instance Voucher Expired Webhook
tags:
- webhooks
- - identity
- com.kaiten.instance.entitlement.v1.cap_exceeded:
+ - vouchers
+ com.kaiten.instance.voucher.v1.redeemed:
post:
- description: Triggered when a SOFT-enforcement entitlement's usage crosses from below the cap to above it. Fires once per crossing.
- operationId: onInstanceEntitlementCapExceeded
+ description: Triggered when an instance redeems a voucher.
+ operationId: onInstanceVoucherRedeemed
requestBody:
content:
application/json:
@@ -11047,20 +37343,22 @@ webhooks:
additionalProperties: false
properties:
data:
- $ref: "#/components/schemas/InstanceEntitlementCapExceeded"
+ $ref: "#/components/schemas/RedemptionDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
name:
description: Event name
enum:
- - INSTANCE_ENTITLEMENT_CAP_EXCEEDED
+ - INSTANCE_VOUCHER_REDEEMED
examples:
- - INSTANCE_ENTITLEMENT_CAP_EXCEEDED
+ - INSTANCE_VOUCHER_REDEEMED
type: string
type:
description: Event type
enum:
- - com.kaiten.instance.entitlement.v1.cap_exceeded
+ - com.kaiten.instance.voucher.v1.redeemed
examples:
- - com.kaiten.instance.entitlement.v1.cap_exceeded
+ - com.kaiten.instance.voucher.v1.redeemed
type: string
required:
- name
@@ -11071,14 +37369,14 @@ webhooks:
responses:
"200":
description: Webhook processed successfully
- summary: Instance Entitlement Cap Exceeded Webhook
+ summary: Instance Voucher Redeemed Webhook
tags:
- webhooks
- - instances
- com.kaiten.instance.entitlement.v1.usage_period_rolled_over:
+ - vouchers
+ com.kaiten.instance.voucher.v1.revoked:
post:
- description: "Triggered once per usage window closed by a report: the entitlement's own stored bucket, and one synthetic event per window skipped entirely since the last report."
- operationId: onInstanceEntitlementUsagePeriodRolledOver
+ description: Triggered when a redemption is revoked.
+ operationId: onInstanceVoucherRevoked
requestBody:
content:
application/json:
@@ -11086,20 +37384,22 @@ webhooks:
additionalProperties: false
properties:
data:
- $ref: "#/components/schemas/InstanceEntitlementUsagePeriodRolledOver"
+ $ref: "#/components/schemas/RedemptionDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
name:
description: Event name
enum:
- - INSTANCE_ENTITLEMENT_USAGE_PERIOD_ROLLED_OVER
+ - INSTANCE_VOUCHER_REVOKED
examples:
- - INSTANCE_ENTITLEMENT_USAGE_PERIOD_ROLLED_OVER
+ - INSTANCE_VOUCHER_REVOKED
type: string
type:
description: Event type
enum:
- - com.kaiten.instance.entitlement.v1.usage_period_rolled_over
+ - com.kaiten.instance.voucher.v1.revoked
examples:
- - com.kaiten.instance.entitlement.v1.usage_period_rolled_over
+ - com.kaiten.instance.voucher.v1.revoked
type: string
required:
- name
@@ -11110,14 +37410,14 @@ webhooks:
responses:
"200":
description: Webhook processed successfully
- summary: Entitlement Usage Period Rolled Over Webhook
+ summary: Instance Voucher Revoked Webhook
tags:
- webhooks
- - instances
- com.kaiten.instance.entitlement.v1.usage_reached:
+ - vouchers
+ com.kaiten.license.entitlement.v1.assigned:
post:
- description: Triggered when an instance's entitlement usage reaches its threshold.
- operationId: onInstanceEntitlementUsageReached
+ description: Triggered when an entitlement is assigned to a license.
+ operationId: onLicenseEntitlementAssigned
requestBody:
content:
application/json:
@@ -11125,20 +37425,22 @@ webhooks:
additionalProperties: false
properties:
data:
- $ref: "#/components/schemas/EntitlementUsage"
+ $ref: "#/components/schemas/LicenseEntitlementDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
name:
description: Event name
enum:
- - INSTANCE_ENTITLEMENT_USAGE_REACHED
+ - LICENSE_ENTITLEMENT_ASSIGNED
examples:
- - INSTANCE_ENTITLEMENT_USAGE_REACHED
+ - LICENSE_ENTITLEMENT_ASSIGNED
type: string
type:
description: Event type
enum:
- - com.kaiten.instance.entitlement.v1.usage_reached
+ - com.kaiten.license.entitlement.v1.assigned
examples:
- - com.kaiten.instance.entitlement.v1.usage_reached
+ - com.kaiten.license.entitlement.v1.assigned
type: string
required:
- name
@@ -11149,14 +37451,14 @@ webhooks:
responses:
"200":
description: Webhook processed successfully
- summary: Instance Entitlement Usage Reached Webhook
+ summary: License Entitlement Assigned Webhook
tags:
- webhooks
- - instances
- com.kaiten.instance.entitlement.v1.usage_report_accepted:
+ - licenses
+ com.kaiten.license.entitlement.v1.unassigned:
post:
- description: Triggered when a usage report is persisted, including a SOFT-enforcement overage.
- operationId: onInstanceEntitlementUsageReportAccepted
+ description: Triggered when an entitlement is unassigned from a license.
+ operationId: onLicenseEntitlementUnassigned
requestBody:
content:
application/json:
@@ -11164,20 +37466,22 @@ webhooks:
additionalProperties: false
properties:
data:
- $ref: "#/components/schemas/InstanceEntitlementUsageReportAccepted"
+ $ref: "#/components/schemas/LicenseEntitlementDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
name:
description: Event name
enum:
- - ENTITLEMENT_USAGE_REPORT_ACCEPTED
+ - LICENSE_ENTITLEMENT_UNASSIGNED
examples:
- - ENTITLEMENT_USAGE_REPORT_ACCEPTED
+ - LICENSE_ENTITLEMENT_UNASSIGNED
type: string
type:
description: Event type
enum:
- - com.kaiten.instance.entitlement.v1.usage_report_accepted
+ - com.kaiten.license.entitlement.v1.unassigned
examples:
- - com.kaiten.instance.entitlement.v1.usage_report_accepted
+ - com.kaiten.license.entitlement.v1.unassigned
type: string
required:
- name
@@ -11188,14 +37492,14 @@ webhooks:
responses:
"200":
description: Webhook processed successfully
- summary: Entitlement Usage Report Accepted Webhook
+ summary: License Entitlement Unassigned Webhook
tags:
- webhooks
- - instances
- com.kaiten.instance.entitlement.v1.usage_report_rejected:
+ - licenses
+ com.kaiten.license.entitlement.v1.updated:
post:
- description: Triggered when a usage report is refused because it would take a HARD-enforcement entitlement past its cap. The usage is not persisted.
- operationId: onInstanceEntitlementUsageReportRejected
+ description: Triggered when a license entitlement is updated.
+ operationId: onLicenseEntitlementUpdated
requestBody:
content:
application/json:
@@ -11203,20 +37507,22 @@ webhooks:
additionalProperties: false
properties:
data:
- $ref: "#/components/schemas/InstanceEntitlementUsageReportAccepted"
+ $ref: "#/components/schemas/LicenseEntitlementDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
name:
description: Event name
enum:
- - ENTITLEMENT_USAGE_REPORT_REJECTED
+ - LICENSE_ENTITLEMENT_UPDATED
examples:
- - ENTITLEMENT_USAGE_REPORT_REJECTED
+ - LICENSE_ENTITLEMENT_UPDATED
type: string
type:
description: Event type
enum:
- - com.kaiten.instance.entitlement.v1.usage_report_rejected
+ - com.kaiten.license.entitlement.v1.updated
examples:
- - com.kaiten.instance.entitlement.v1.usage_report_rejected
+ - com.kaiten.license.entitlement.v1.updated
type: string
required:
- name
@@ -11227,14 +37533,14 @@ webhooks:
responses:
"200":
description: Webhook processed successfully
- summary: Entitlement Usage Report Rejected Webhook
+ summary: License Entitlement Updated Webhook
tags:
- webhooks
- - instances
- com.kaiten.instance.entitlement.v1.usage_warning_threshold_reached:
+ - licenses
+ com.kaiten.license.price.v1.created:
post:
- description: Triggered when an instance's entitlement usage crosses its configured early-warning percentage boundary, before the cap is reached. Fires once per crossing.
- operationId: onInstanceEntitlementUsageWarningThresholdReached
+ description: Triggered when a price is added to a licence version.
+ operationId: onLicensePriceCreated
requestBody:
content:
application/json:
@@ -11242,20 +37548,22 @@ webhooks:
additionalProperties: false
properties:
data:
- $ref: "#/components/schemas/InstanceEntitlementUsageWarningThresholdReached"
+ $ref: "#/components/schemas/LicensePriceEventDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
name:
description: Event name
enum:
- - INSTANCE_ENTITLEMENT_USAGE_WARNING_THRESHOLD_REACHED
+ - LICENSE_PRICE_CREATED
examples:
- - INSTANCE_ENTITLEMENT_USAGE_WARNING_THRESHOLD_REACHED
+ - LICENSE_PRICE_CREATED
type: string
type:
description: Event type
enum:
- - com.kaiten.instance.entitlement.v1.usage_warning_threshold_reached
+ - com.kaiten.license.price.v1.created
examples:
- - com.kaiten.instance.entitlement.v1.usage_warning_threshold_reached
+ - com.kaiten.license.price.v1.created
type: string
required:
- name
@@ -11266,14 +37574,14 @@ webhooks:
responses:
"200":
description: Webhook processed successfully
- summary: Instance Entitlement Usage Warning Threshold Reached Webhook
+ summary: License Price Created Webhook
tags:
- webhooks
- - instances
- com.kaiten.instance.entitlement.v1.value_get:
+ - licenses
+ com.kaiten.license.price.v1.deprecated:
post:
- description: Triggered on every read of an instance entitlement's usage value. This is a read audit signal, not a state change.
- operationId: onInstanceEntitlementValueGet
+ description: Triggered when a licence price is deprecated.
+ operationId: onLicensePriceDeprecated
requestBody:
content:
application/json:
@@ -11281,20 +37589,22 @@ webhooks:
additionalProperties: false
properties:
data:
- $ref: "#/components/schemas/InstanceEntitlementValueGet"
+ $ref: "#/components/schemas/LicensePriceEventDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
name:
description: Event name
enum:
- - ENTITLEMENT_VALUE_GET
+ - LICENSE_PRICE_DEPRECATED
examples:
- - ENTITLEMENT_VALUE_GET
+ - LICENSE_PRICE_DEPRECATED
type: string
type:
description: Event type
enum:
- - com.kaiten.instance.entitlement.v1.value_get
+ - com.kaiten.license.price.v1.deprecated
examples:
- - com.kaiten.instance.entitlement.v1.value_get
+ - com.kaiten.license.price.v1.deprecated
type: string
required:
- name
@@ -11305,14 +37615,14 @@ webhooks:
responses:
"200":
description: Webhook processed successfully
- summary: Instance Entitlement Value Read Webhook
+ summary: License Price Deprecated Webhook
tags:
- webhooks
- - instances
- com.kaiten.instance.v1.created:
+ - licenses
+ com.kaiten.license.price.v1.updated:
post:
- description: Triggered when a new instance is created.
- operationId: onInstanceCreated
+ description: Triggered when a price of a DRAFT licence version is edited.
+ operationId: onLicensePriceUpdated
requestBody:
content:
application/json:
@@ -11320,20 +37630,22 @@ webhooks:
additionalProperties: false
properties:
data:
- $ref: "#/components/schemas/Instance"
+ $ref: "#/components/schemas/LicensePriceUpdatedEventDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
name:
description: Event name
enum:
- - INSTANCE_CREATED
+ - LICENSE_PRICE_UPDATED
examples:
- - INSTANCE_CREATED
+ - LICENSE_PRICE_UPDATED
type: string
type:
description: Event type
enum:
- - com.kaiten.instance.v1.created
+ - com.kaiten.license.price.v1.updated
examples:
- - com.kaiten.instance.v1.created
+ - com.kaiten.license.price.v1.updated
type: string
required:
- name
@@ -11344,14 +37656,14 @@ webhooks:
responses:
"200":
description: Webhook processed successfully
- summary: Instance Created Webhook
+ summary: License Price Updated Webhook
tags:
- webhooks
- - instances
- com.kaiten.instance.v1.deleted:
+ - licenses
+ com.kaiten.license.v1.archived:
post:
- description: Triggered when an instance is deleted.
- operationId: onInstanceDeleted
+ description: Triggered when a published license version is archived and withdrawn from sale.
+ operationId: onLicenseArchived
requestBody:
content:
application/json:
@@ -11359,20 +37671,22 @@ webhooks:
additionalProperties: false
properties:
data:
- $ref: "#/components/schemas/Instance"
+ $ref: "#/components/schemas/LicenseDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
name:
description: Event name
enum:
- - INSTANCE_DELETED
+ - LICENSE_ARCHIVED
examples:
- - INSTANCE_DELETED
+ - LICENSE_ARCHIVED
type: string
type:
description: Event type
enum:
- - com.kaiten.instance.v1.deleted
+ - com.kaiten.license.v1.archived
examples:
- - com.kaiten.instance.v1.deleted
+ - com.kaiten.license.v1.archived
type: string
required:
- name
@@ -11383,14 +37697,14 @@ webhooks:
responses:
"200":
description: Webhook processed successfully
- summary: Instance Deleted Webhook
+ summary: License Archived Webhook
tags:
- webhooks
- - instances
- com.kaiten.instance.v1.deployed:
+ - licenses
+ com.kaiten.license.v1.created:
post:
- description: Triggered when an instance is deployed to a deployment zone.
- operationId: onInstanceDeployed
+ description: Triggered when a new license is created.
+ operationId: onLicenseCreated
requestBody:
content:
application/json:
@@ -11398,20 +37712,22 @@ webhooks:
additionalProperties: false
properties:
data:
- $ref: "#/components/schemas/Instance"
+ $ref: "#/components/schemas/LicenseDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
name:
description: Event name
enum:
- - INSTANCE_DEPLOYED
+ - LICENSE_CREATED
examples:
- - INSTANCE_DEPLOYED
+ - LICENSE_CREATED
type: string
type:
description: Event type
enum:
- - com.kaiten.instance.v1.deployed
+ - com.kaiten.license.v1.created
examples:
- - com.kaiten.instance.v1.deployed
+ - com.kaiten.license.v1.created
type: string
required:
- name
@@ -11422,14 +37738,14 @@ webhooks:
responses:
"200":
description: Webhook processed successfully
- summary: Instance Deployed Webhook
+ summary: License Created Webhook
tags:
- webhooks
- - instances
- com.kaiten.instance.v1.lifecycle_stage_changed:
+ - licenses
+ com.kaiten.license.v1.deleted:
post:
- description: Triggered when an instance commercial lifecycle stage changes.
- operationId: onInstanceLifecycleStageChanged
+ description: Triggered when a license is deleted.
+ operationId: onLicenseDeleted
requestBody:
content:
application/json:
@@ -11437,20 +37753,22 @@ webhooks:
additionalProperties: false
properties:
data:
- $ref: "#/components/schemas/InstanceLifecycleStageChanged"
+ $ref: "#/components/schemas/LicenseDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
name:
description: Event name
enum:
- - INSTANCE_LIFECYCLE_STAGE_CHANGED
+ - LICENSE_DELETED
examples:
- - INSTANCE_LIFECYCLE_STAGE_CHANGED
+ - LICENSE_DELETED
type: string
type:
description: Event type
enum:
- - com.kaiten.instance.v1.lifecycle_stage_changed
+ - com.kaiten.license.v1.deleted
examples:
- - com.kaiten.instance.v1.lifecycle_stage_changed
+ - com.kaiten.license.v1.deleted
type: string
required:
- name
@@ -11461,14 +37779,14 @@ webhooks:
responses:
"200":
description: Webhook processed successfully
- summary: Instance Lifecycle Stage Changed Webhook
+ summary: License Deleted Webhook
tags:
- webhooks
- - instances
- com.kaiten.instance.v1.migrated:
+ - licenses
+ com.kaiten.license.v1.published:
post:
- description: Triggered when an instance is migrated from one deployment zone to another.
- operationId: onInstanceMigrated
+ description: Triggered when a draft license version is published and goes on sale.
+ operationId: onLicensePublished
requestBody:
content:
application/json:
@@ -11476,20 +37794,22 @@ webhooks:
additionalProperties: false
properties:
data:
- $ref: "#/components/schemas/Instance"
+ $ref: "#/components/schemas/LicenseDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
name:
description: Event name
enum:
- - INSTANCE_MIGRATED
+ - LICENSE_PUBLISHED
examples:
- - INSTANCE_MIGRATED
+ - LICENSE_PUBLISHED
type: string
type:
description: Event type
enum:
- - com.kaiten.instance.v1.migrated
+ - com.kaiten.license.v1.published
examples:
- - com.kaiten.instance.v1.migrated
+ - com.kaiten.license.v1.published
type: string
required:
- name
@@ -11500,14 +37820,14 @@ webhooks:
responses:
"200":
description: Webhook processed successfully
- summary: Instance Migrated Webhook
+ summary: License Published Webhook
tags:
- webhooks
- - instances
- com.kaiten.instance.v1.status_changed:
+ - licenses
+ com.kaiten.license.v1.unarchived:
post:
- description: Triggered when an instance operational status changes.
- operationId: onInstanceStatusChanged
+ description: Triggered when an archived license version is put back on sale.
+ operationId: onLicenseUnarchived
requestBody:
content:
application/json:
@@ -11515,20 +37835,22 @@ webhooks:
additionalProperties: false
properties:
data:
- $ref: "#/components/schemas/InstanceStatusChanged"
+ $ref: "#/components/schemas/LicenseDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
name:
description: Event name
enum:
- - INSTANCE_STATUS_CHANGED
+ - LICENSE_UNARCHIVED
examples:
- - INSTANCE_STATUS_CHANGED
+ - LICENSE_UNARCHIVED
type: string
type:
description: Event type
enum:
- - com.kaiten.instance.v1.status_changed
+ - com.kaiten.license.v1.unarchived
examples:
- - com.kaiten.instance.v1.status_changed
+ - com.kaiten.license.v1.unarchived
type: string
required:
- name
@@ -11539,14 +37861,14 @@ webhooks:
responses:
"200":
description: Webhook processed successfully
- summary: Instance Status Changed Webhook
+ summary: License Unarchived Webhook
tags:
- webhooks
- - instances
- com.kaiten.instance.v1.updated:
+ - licenses
+ com.kaiten.license.v1.updated:
post:
- description: Triggered when an instance is updated.
- operationId: onInstanceUpdated
+ description: Triggered when a license is updated.
+ operationId: onLicenseUpdated
requestBody:
content:
application/json:
@@ -11554,20 +37876,22 @@ webhooks:
additionalProperties: false
properties:
data:
- $ref: "#/components/schemas/Instance"
+ $ref: "#/components/schemas/LicenseDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
name:
description: Event name
enum:
- - INSTANCE_UPDATED
+ - LICENSE_UPDATED
examples:
- - INSTANCE_UPDATED
+ - LICENSE_UPDATED
type: string
type:
description: Event type
enum:
- - com.kaiten.instance.v1.updated
+ - com.kaiten.license.v1.updated
examples:
- - com.kaiten.instance.v1.updated
+ - com.kaiten.license.v1.updated
type: string
required:
- name
@@ -11578,14 +37902,14 @@ webhooks:
responses:
"200":
description: Webhook processed successfully
- summary: Instance Updated Webhook
+ summary: License Updated Webhook
tags:
- webhooks
- - instances
- com.kaiten.license.entitlement.v1.assigned:
+ - licenses
+ com.kaiten.license_family.v1.created:
post:
- description: Triggered when an entitlement is assigned to a license.
- operationId: onLicenseEntitlementAssigned
+ description: "Triggered when a license is created without naming a family: its version 1 opens a new family, which this event carries, with the version it serves -- none if that version is a draft. It is recorded in the same transaction as that version's LICENSE_CREATED."
+ operationId: onLicenseFamilyCreated
requestBody:
content:
application/json:
@@ -11593,20 +37917,22 @@ webhooks:
additionalProperties: false
properties:
data:
- $ref: "#/components/schemas/LicenseEntitlement"
+ $ref: "#/components/schemas/LicenseFamilyViewDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
name:
description: Event name
enum:
- - LICENSE_ENTITLEMENT_ASSIGNED
+ - LICENSE_FAMILY_CREATED
examples:
- - LICENSE_ENTITLEMENT_ASSIGNED
+ - LICENSE_FAMILY_CREATED
type: string
type:
description: Event type
enum:
- - com.kaiten.license.entitlement.v1.assigned
+ - com.kaiten.license_family.v1.created
examples:
- - com.kaiten.license.entitlement.v1.assigned
+ - com.kaiten.license_family.v1.created
type: string
required:
- name
@@ -11617,14 +37943,14 @@ webhooks:
responses:
"200":
description: Webhook processed successfully
- summary: License Entitlement Assigned Webhook
+ summary: License Family Created Webhook
tags:
- webhooks
- - licenses
- com.kaiten.license.entitlement.v1.unassigned:
+ - license-families
+ com.kaiten.license_family.v1.deleted:
post:
- description: Triggered when an entitlement is unassigned from a license.
- operationId: onLicenseEntitlementUnassigned
+ description: Triggered when a license family's last version is deleted, which deletes the family and frees its slug. It carries the family as it stood before that version went, and is recorded in the same transaction as that version's LICENSE_DELETED.
+ operationId: onLicenseFamilyDeleted
requestBody:
content:
application/json:
@@ -11632,20 +37958,22 @@ webhooks:
additionalProperties: false
properties:
data:
- $ref: "#/components/schemas/LicenseEntitlement"
+ $ref: "#/components/schemas/LicenseFamilyViewDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
name:
description: Event name
enum:
- - LICENSE_ENTITLEMENT_UNASSIGNED
+ - LICENSE_FAMILY_DELETED
examples:
- - LICENSE_ENTITLEMENT_UNASSIGNED
+ - LICENSE_FAMILY_DELETED
type: string
type:
description: Event type
enum:
- - com.kaiten.license.entitlement.v1.unassigned
+ - com.kaiten.license_family.v1.deleted
examples:
- - com.kaiten.license.entitlement.v1.unassigned
+ - com.kaiten.license_family.v1.deleted
type: string
required:
- name
@@ -11656,14 +37984,14 @@ webhooks:
responses:
"200":
description: Webhook processed successfully
- summary: License Entitlement Unassigned Webhook
+ summary: License Family Deleted Webhook
tags:
- webhooks
- - licenses
- com.kaiten.license.entitlement.v1.updated:
+ - license-families
+ com.kaiten.license_family.v1.updated:
post:
- description: Triggered when a license entitlement is updated.
- operationId: onLicenseEntitlementUpdated
+ description: "Triggered when a license family starts serving another version, or none: a newer version published, a version published, archived or unarchived, the family's default taken or given up, or the served version deleted. It carries the family with the version it serves now, and is recorded in the same transaction as the version event that caused the change. A change that leaves the served version where it was -- a draft added, a version renamed, a default restated -- triggers none."
+ operationId: onLicenseFamilyUpdated
requestBody:
content:
application/json:
@@ -11671,20 +37999,22 @@ webhooks:
additionalProperties: false
properties:
data:
- $ref: "#/components/schemas/LicenseEntitlement"
+ $ref: "#/components/schemas/LicenseFamilyViewDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
name:
description: Event name
enum:
- - LICENSE_ENTITLEMENT_UPDATED
+ - LICENSE_FAMILY_UPDATED
examples:
- - LICENSE_ENTITLEMENT_UPDATED
+ - LICENSE_FAMILY_UPDATED
type: string
type:
description: Event type
enum:
- - com.kaiten.license.entitlement.v1.updated
+ - com.kaiten.license_family.v1.updated
examples:
- - com.kaiten.license.entitlement.v1.updated
+ - com.kaiten.license_family.v1.updated
type: string
required:
- name
@@ -11695,14 +38025,14 @@ webhooks:
responses:
"200":
description: Webhook processed successfully
- summary: License Entitlement Updated Webhook
+ summary: License Family Updated Webhook
tags:
- webhooks
- - licenses
- com.kaiten.license.v1.archived:
+ - license-families
+ com.kaiten.metadata_field.v1.archived:
post:
- description: Triggered when a published license version is archived and withdrawn from sale.
- operationId: onLicenseArchived
+ description: Triggered when a metadata field is archived and leaves the active schema composition.
+ operationId: onMetadataFieldArchived
requestBody:
content:
application/json:
@@ -11710,20 +38040,22 @@ webhooks:
additionalProperties: false
properties:
data:
- $ref: "#/components/schemas/License"
+ $ref: "#/components/schemas/MetadataFieldDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
name:
description: Event name
enum:
- - LICENSE_ARCHIVED
+ - METADATA_FIELD_ARCHIVED
examples:
- - LICENSE_ARCHIVED
+ - METADATA_FIELD_ARCHIVED
type: string
type:
description: Event type
enum:
- - com.kaiten.license.v1.archived
+ - com.kaiten.metadata_field.v1.archived
examples:
- - com.kaiten.license.v1.archived
+ - com.kaiten.metadata_field.v1.archived
type: string
required:
- name
@@ -11734,14 +38066,14 @@ webhooks:
responses:
"200":
description: Webhook processed successfully
- summary: License Archived Webhook
+ summary: Metadata Field Archived Webhook
tags:
- webhooks
- - licenses
- com.kaiten.license.v1.created:
+ - metadataFields
+ com.kaiten.metadata_field.v1.created:
post:
- description: Triggered when a new license is created.
- operationId: onLicenseCreated
+ description: Triggered when a metadata field is declared for a resource type.
+ operationId: onMetadataFieldCreated
requestBody:
content:
application/json:
@@ -11749,20 +38081,22 @@ webhooks:
additionalProperties: false
properties:
data:
- $ref: "#/components/schemas/License"
+ $ref: "#/components/schemas/MetadataFieldDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
name:
description: Event name
enum:
- - LICENSE_CREATED
+ - METADATA_FIELD_CREATED
examples:
- - LICENSE_CREATED
+ - METADATA_FIELD_CREATED
type: string
type:
description: Event type
enum:
- - com.kaiten.license.v1.created
+ - com.kaiten.metadata_field.v1.created
examples:
- - com.kaiten.license.v1.created
+ - com.kaiten.metadata_field.v1.created
type: string
required:
- name
@@ -11773,14 +38107,14 @@ webhooks:
responses:
"200":
description: Webhook processed successfully
- summary: License Created Webhook
+ summary: Metadata Field Created Webhook
tags:
- webhooks
- - licenses
- com.kaiten.license.v1.deleted:
+ - metadataFields
+ com.kaiten.metadata_field.v1.reordered:
post:
- description: Triggered when a license is deleted.
- operationId: onLicenseDeleted
+ description: Triggered when the display order of a resource type's metadata fields is rewritten.
+ operationId: onMetadataFieldReordered
requestBody:
content:
application/json:
@@ -11788,20 +38122,22 @@ webhooks:
additionalProperties: false
properties:
data:
- $ref: "#/components/schemas/License"
+ $ref: "#/components/schemas/MetadataFieldReorderedDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
name:
description: Event name
enum:
- - LICENSE_DELETED
+ - METADATA_FIELD_REORDERED
examples:
- - LICENSE_DELETED
+ - METADATA_FIELD_REORDERED
type: string
type:
description: Event type
enum:
- - com.kaiten.license.v1.deleted
+ - com.kaiten.metadata_field.v1.reordered
examples:
- - com.kaiten.license.v1.deleted
+ - com.kaiten.metadata_field.v1.reordered
type: string
required:
- name
@@ -11812,14 +38148,14 @@ webhooks:
responses:
"200":
description: Webhook processed successfully
- summary: License Deleted Webhook
+ summary: Metadata Fields Reordered Webhook
tags:
- webhooks
- - licenses
- com.kaiten.license.v1.published:
+ - metadataFields
+ com.kaiten.metadata_field.v1.unarchived:
post:
- description: Triggered when a draft license version is published and goes on sale.
- operationId: onLicensePublished
+ description: Triggered when an archived metadata field is restored to the active schema composition.
+ operationId: onMetadataFieldUnarchived
requestBody:
content:
application/json:
@@ -11827,20 +38163,22 @@ webhooks:
additionalProperties: false
properties:
data:
- $ref: "#/components/schemas/License"
+ $ref: "#/components/schemas/MetadataFieldDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
name:
description: Event name
enum:
- - LICENSE_PUBLISHED
+ - METADATA_FIELD_UNARCHIVED
examples:
- - LICENSE_PUBLISHED
+ - METADATA_FIELD_UNARCHIVED
type: string
type:
description: Event type
enum:
- - com.kaiten.license.v1.published
+ - com.kaiten.metadata_field.v1.unarchived
examples:
- - com.kaiten.license.v1.published
+ - com.kaiten.metadata_field.v1.unarchived
type: string
required:
- name
@@ -11851,14 +38189,14 @@ webhooks:
responses:
"200":
description: Webhook processed successfully
- summary: License Published Webhook
+ summary: Metadata Field Unarchived Webhook
tags:
- webhooks
- - licenses
- com.kaiten.license.v1.unarchived:
+ - metadataFields
+ com.kaiten.metadata_field.v1.updated:
post:
- description: Triggered when an archived license version is put back on sale.
- operationId: onLicenseUnarchived
+ description: Triggered when a metadata field's label or JSON schema changes. A reordering emits METADATA_FIELD_REORDERED instead.
+ operationId: onMetadataFieldUpdated
requestBody:
content:
application/json:
@@ -11866,20 +38204,22 @@ webhooks:
additionalProperties: false
properties:
data:
- $ref: "#/components/schemas/License"
+ $ref: "#/components/schemas/MetadataFieldDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
name:
description: Event name
enum:
- - LICENSE_UNARCHIVED
+ - METADATA_FIELD_UPDATED
examples:
- - LICENSE_UNARCHIVED
+ - METADATA_FIELD_UPDATED
type: string
type:
description: Event type
enum:
- - com.kaiten.license.v1.unarchived
+ - com.kaiten.metadata_field.v1.updated
examples:
- - com.kaiten.license.v1.unarchived
+ - com.kaiten.metadata_field.v1.updated
type: string
required:
- name
@@ -11890,14 +38230,14 @@ webhooks:
responses:
"200":
description: Webhook processed successfully
- summary: License Unarchived Webhook
+ summary: Metadata Field Updated Webhook
tags:
- webhooks
- - licenses
- com.kaiten.license.v1.updated:
+ - metadataFields
+ com.kaiten.publishable_key.v1.created:
post:
- description: Triggered when a license is updated.
- operationId: onLicenseUpdated
+ description: Triggered when a publishable key is issued. The key is never in the payload, only its hint.
+ operationId: onPublishableKeyCreated
requestBody:
content:
application/json:
@@ -11905,20 +38245,22 @@ webhooks:
additionalProperties: false
properties:
data:
- $ref: "#/components/schemas/License"
+ $ref: "#/components/schemas/PublishableKeyEventDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
name:
description: Event name
enum:
- - LICENSE_UPDATED
+ - PUBLISHABLE_KEY_CREATED
examples:
- - LICENSE_UPDATED
+ - PUBLISHABLE_KEY_CREATED
type: string
type:
description: Event type
enum:
- - com.kaiten.license.v1.updated
+ - com.kaiten.publishable_key.v1.created
examples:
- - com.kaiten.license.v1.updated
+ - com.kaiten.publishable_key.v1.created
type: string
required:
- name
@@ -11929,14 +38271,14 @@ webhooks:
responses:
"200":
description: Webhook processed successfully
- summary: License Updated Webhook
+ summary: Publishable Key Created Webhook
tags:
- webhooks
- - licenses
- com.kaiten.license_family.v1.created:
+ - publishable-keys
+ com.kaiten.publishable_key.v1.revoked:
post:
- description: "Triggered when a license is created without naming a family: its version 1 opens a new family, which this event carries, with the version it serves -- none if that version is a draft. It is recorded in the same transaction as that version's LICENSE_CREATED."
- operationId: onLicenseFamilyCreated
+ description: Triggered once, when a publishable key is revoked.
+ operationId: onPublishableKeyRevoked
requestBody:
content:
application/json:
@@ -11944,20 +38286,22 @@ webhooks:
additionalProperties: false
properties:
data:
- $ref: "#/components/schemas/LicenseFamilyView"
+ $ref: "#/components/schemas/PublishableKeyEventDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
name:
description: Event name
enum:
- - LICENSE_FAMILY_CREATED
+ - PUBLISHABLE_KEY_REVOKED
examples:
- - LICENSE_FAMILY_CREATED
+ - PUBLISHABLE_KEY_REVOKED
type: string
type:
description: Event type
enum:
- - com.kaiten.license_family.v1.created
+ - com.kaiten.publishable_key.v1.revoked
examples:
- - com.kaiten.license_family.v1.created
+ - com.kaiten.publishable_key.v1.revoked
type: string
required:
- name
@@ -11968,14 +38312,14 @@ webhooks:
responses:
"200":
description: Webhook processed successfully
- summary: License Family Created Webhook
+ summary: Publishable Key Revoked Webhook
tags:
- webhooks
- - license-families
- com.kaiten.license_family.v1.deleted:
+ - publishable-keys
+ com.kaiten.release.v1.created:
post:
- description: Triggered when a license family's last version is deleted, which deletes the family and frees its slug. It carries the family as it stood before that version went, and is recorded in the same transaction as that version's LICENSE_DELETED.
- operationId: onLicenseFamilyDeleted
+ description: Triggered when a new release is created.
+ operationId: onReleaseCreated
requestBody:
content:
application/json:
@@ -11983,20 +38327,22 @@ webhooks:
additionalProperties: false
properties:
data:
- $ref: "#/components/schemas/LicenseFamilyView"
+ $ref: "#/components/schemas/ReleaseDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
name:
description: Event name
enum:
- - LICENSE_FAMILY_DELETED
+ - RELEASE_CREATED
examples:
- - LICENSE_FAMILY_DELETED
+ - RELEASE_CREATED
type: string
type:
description: Event type
enum:
- - com.kaiten.license_family.v1.deleted
+ - com.kaiten.release.v1.created
examples:
- - com.kaiten.license_family.v1.deleted
+ - com.kaiten.release.v1.created
type: string
required:
- name
@@ -12007,14 +38353,14 @@ webhooks:
responses:
"200":
description: Webhook processed successfully
- summary: License Family Deleted Webhook
+ summary: Release Created Webhook
tags:
- webhooks
- - license-families
- com.kaiten.license_family.v1.updated:
+ - releases
+ com.kaiten.release.v1.deleted:
post:
- description: "Triggered when a license family starts serving another version, or none: a newer version published, a version published, archived or unarchived, the family's default taken or given up, or the served version deleted. It carries the family with the version it serves now, and is recorded in the same transaction as the version event that caused the change. A change that leaves the served version where it was -- a draft added, a version renamed, a default restated -- triggers none."
- operationId: onLicenseFamilyUpdated
+ description: Triggered when a release is deleted.
+ operationId: onReleaseDeleted
requestBody:
content:
application/json:
@@ -12022,20 +38368,22 @@ webhooks:
additionalProperties: false
properties:
data:
- $ref: "#/components/schemas/LicenseFamilyView"
+ $ref: "#/components/schemas/ReleaseDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
name:
description: Event name
enum:
- - LICENSE_FAMILY_UPDATED
+ - RELEASE_DELETED
examples:
- - LICENSE_FAMILY_UPDATED
+ - RELEASE_DELETED
type: string
type:
description: Event type
enum:
- - com.kaiten.license_family.v1.updated
+ - com.kaiten.release.v1.deleted
examples:
- - com.kaiten.license_family.v1.updated
+ - com.kaiten.release.v1.deleted
type: string
required:
- name
@@ -12046,14 +38394,14 @@ webhooks:
responses:
"200":
description: Webhook processed successfully
- summary: License Family Updated Webhook
+ summary: Release Deleted Webhook
tags:
- webhooks
- - license-families
- com.kaiten.metadata_field.v1.archived:
+ - releases
+ com.kaiten.voucher.v1.archived:
post:
- description: Triggered when a metadata field is archived and leaves the active schema composition.
- operationId: onMetadataFieldArchived
+ description: Triggered when a voucher is archived.
+ operationId: onVoucherArchived
requestBody:
content:
application/json:
@@ -12061,20 +38409,22 @@ webhooks:
additionalProperties: false
properties:
data:
- $ref: "#/components/schemas/MetadataField"
+ $ref: "#/components/schemas/VoucherDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
name:
description: Event name
enum:
- - METADATA_FIELD_ARCHIVED
+ - VOUCHER_ARCHIVED
examples:
- - METADATA_FIELD_ARCHIVED
+ - VOUCHER_ARCHIVED
type: string
type:
description: Event type
enum:
- - com.kaiten.metadata_field.v1.archived
+ - com.kaiten.voucher.v1.archived
examples:
- - com.kaiten.metadata_field.v1.archived
+ - com.kaiten.voucher.v1.archived
type: string
required:
- name
@@ -12085,14 +38435,14 @@ webhooks:
responses:
"200":
description: Webhook processed successfully
- summary: Metadata Field Archived Webhook
+ summary: Voucher Archived Webhook
tags:
- webhooks
- - metadataFields
- com.kaiten.metadata_field.v1.created:
+ - vouchers
+ com.kaiten.voucher.v1.created:
post:
- description: Triggered when a metadata field is declared for a resource type.
- operationId: onMetadataFieldCreated
+ description: Triggered when a voucher is created. The code is never in the payload.
+ operationId: onVoucherCreated
requestBody:
content:
application/json:
@@ -12100,20 +38450,22 @@ webhooks:
additionalProperties: false
properties:
data:
- $ref: "#/components/schemas/MetadataField"
+ $ref: "#/components/schemas/VoucherDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
name:
description: Event name
enum:
- - METADATA_FIELD_CREATED
+ - VOUCHER_CREATED
examples:
- - METADATA_FIELD_CREATED
+ - VOUCHER_CREATED
type: string
type:
description: Event type
enum:
- - com.kaiten.metadata_field.v1.created
+ - com.kaiten.voucher.v1.created
examples:
- - com.kaiten.metadata_field.v1.created
+ - com.kaiten.voucher.v1.created
type: string
required:
- name
@@ -12124,14 +38476,14 @@ webhooks:
responses:
"200":
description: Webhook processed successfully
- summary: Metadata Field Created Webhook
+ summary: Voucher Created Webhook
tags:
- webhooks
- - metadataFields
- com.kaiten.metadata_field.v1.reordered:
+ - vouchers
+ com.kaiten.voucher.v1.deleted:
post:
- description: Triggered when the display order of a resource type's metadata fields is rewritten.
- operationId: onMetadataFieldReordered
+ description: Triggered when a voucher no instance ever redeemed is deleted, with its last state.
+ operationId: onVoucherDeleted
requestBody:
content:
application/json:
@@ -12139,20 +38491,22 @@ webhooks:
additionalProperties: false
properties:
data:
- $ref: "#/components/schemas/MetadataFieldReordered"
+ $ref: "#/components/schemas/VoucherDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
name:
description: Event name
enum:
- - METADATA_FIELD_REORDERED
+ - VOUCHER_DELETED
examples:
- - METADATA_FIELD_REORDERED
+ - VOUCHER_DELETED
type: string
type:
description: Event type
enum:
- - com.kaiten.metadata_field.v1.reordered
+ - com.kaiten.voucher.v1.deleted
examples:
- - com.kaiten.metadata_field.v1.reordered
+ - com.kaiten.voucher.v1.deleted
type: string
required:
- name
@@ -12163,14 +38517,14 @@ webhooks:
responses:
"200":
description: Webhook processed successfully
- summary: Metadata Fields Reordered Webhook
+ summary: Voucher Deleted Webhook
tags:
- webhooks
- - metadataFields
- com.kaiten.metadata_field.v1.unarchived:
+ - vouchers
+ com.kaiten.voucher.v1.exhausted:
post:
- description: Triggered when an archived metadata field is restored to the active schema composition.
- operationId: onMetadataFieldUnarchived
+ description: Triggered by the redemption that reaches maxRedemptions.
+ operationId: onVoucherExhausted
requestBody:
content:
application/json:
@@ -12178,20 +38532,22 @@ webhooks:
additionalProperties: false
properties:
data:
- $ref: "#/components/schemas/MetadataField"
+ $ref: "#/components/schemas/ExhaustedEventDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
name:
description: Event name
enum:
- - METADATA_FIELD_UNARCHIVED
+ - VOUCHER_EXHAUSTED
examples:
- - METADATA_FIELD_UNARCHIVED
+ - VOUCHER_EXHAUSTED
type: string
type:
description: Event type
enum:
- - com.kaiten.metadata_field.v1.unarchived
+ - com.kaiten.voucher.v1.exhausted
examples:
- - com.kaiten.metadata_field.v1.unarchived
+ - com.kaiten.voucher.v1.exhausted
type: string
required:
- name
@@ -12202,14 +38558,14 @@ webhooks:
responses:
"200":
description: Webhook processed successfully
- summary: Metadata Field Unarchived Webhook
+ summary: Voucher Exhausted Webhook
tags:
- webhooks
- - metadataFields
- com.kaiten.metadata_field.v1.updated:
+ - vouchers
+ com.kaiten.voucher.v1.expired:
post:
- description: Triggered when a metadata field's label or JSON schema changes. A reordering emits METADATA_FIELD_REORDERED instead.
- operationId: onMetadataFieldUpdated
+ description: Triggered when an active voucher reaches its expiresAt and can no longer be redeemed.
+ operationId: onVoucherExpired
requestBody:
content:
application/json:
@@ -12217,20 +38573,22 @@ webhooks:
additionalProperties: false
properties:
data:
- $ref: "#/components/schemas/MetadataField"
+ $ref: "#/components/schemas/ExpiredEventDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
name:
description: Event name
enum:
- - METADATA_FIELD_UPDATED
+ - VOUCHER_EXPIRED
examples:
- - METADATA_FIELD_UPDATED
+ - VOUCHER_EXPIRED
type: string
type:
description: Event type
enum:
- - com.kaiten.metadata_field.v1.updated
+ - com.kaiten.voucher.v1.expired
examples:
- - com.kaiten.metadata_field.v1.updated
+ - com.kaiten.voucher.v1.expired
type: string
required:
- name
@@ -12241,14 +38599,14 @@ webhooks:
responses:
"200":
description: Webhook processed successfully
- summary: Metadata Field Updated Webhook
+ summary: Voucher Expired Webhook
tags:
- webhooks
- - metadataFields
- com.kaiten.release.v1.created:
+ - vouchers
+ com.kaiten.voucher.v1.published:
post:
- description: Triggered when a new release is created.
- operationId: onReleaseCreated
+ description: Triggered when a draft voucher becomes redeemable.
+ operationId: onVoucherPublished
requestBody:
content:
application/json:
@@ -12256,20 +38614,22 @@ webhooks:
additionalProperties: false
properties:
data:
- $ref: "#/components/schemas/Release"
+ $ref: "#/components/schemas/VoucherDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
name:
description: Event name
enum:
- - RELEASE_CREATED
+ - VOUCHER_PUBLISHED
examples:
- - RELEASE_CREATED
+ - VOUCHER_PUBLISHED
type: string
type:
description: Event type
enum:
- - com.kaiten.release.v1.created
+ - com.kaiten.voucher.v1.published
examples:
- - com.kaiten.release.v1.created
+ - com.kaiten.voucher.v1.published
type: string
required:
- name
@@ -12280,14 +38640,14 @@ webhooks:
responses:
"200":
description: Webhook processed successfully
- summary: Release Created Webhook
+ summary: Voucher Published Webhook
tags:
- webhooks
- - releases
- com.kaiten.release.v1.deleted:
+ - vouchers
+ com.kaiten.voucher.v1.updated:
post:
- description: Triggered when a release is deleted.
- operationId: onReleaseDeleted
+ description: Triggered when a voucher changes.
+ operationId: onVoucherUpdated
requestBody:
content:
application/json:
@@ -12295,20 +38655,22 @@ webhooks:
additionalProperties: false
properties:
data:
- $ref: "#/components/schemas/Release"
+ $ref: "#/components/schemas/VoucherDelivered"
+ headers:
+ $ref: "#/components/schemas/EventHeaders"
name:
description: Event name
enum:
- - RELEASE_DELETED
+ - VOUCHER_UPDATED
examples:
- - RELEASE_DELETED
+ - VOUCHER_UPDATED
type: string
type:
description: Event type
enum:
- - com.kaiten.release.v1.deleted
+ - com.kaiten.voucher.v1.updated
examples:
- - com.kaiten.release.v1.deleted
+ - com.kaiten.voucher.v1.updated
type: string
required:
- name
@@ -12319,7 +38681,7 @@ webhooks:
responses:
"200":
description: Webhook processed successfully
- summary: Release Deleted Webhook
+ summary: Voucher Updated Webhook
tags:
- webhooks
- - releases
+ - vouchers
diff --git a/openapi/platform-openapi.yaml b/openapi/platform-openapi.yaml
index 38b05bc..71fc09d 100644
--- a/openapi/platform-openapi.yaml
+++ b/openapi/platform-openapi.yaml
@@ -1,5 +1,84 @@
components:
schemas:
+ ClosePeriodsReport:
+ additionalProperties: false
+ properties:
+ closed:
+ description: Periods closed into an invoice
+ format: int64
+ type: integer
+ examined:
+ description: Subscriptions the batch looked at
+ format: int64
+ type: integer
+ hasMore:
+ description: Set when due subscriptions are left beyond the batch
+ type: boolean
+ held:
+ description: Of those, invoices held as DRAFTs for their usage journal
+ format: int64
+ type: integer
+ invoices:
+ items:
+ $ref: "#/components/schemas/ClosedInvoice"
+ type: array
+ skipped:
+ description: "Subscriptions left for later: held by another transaction, their meters' clock not at the boundary, or failed (logged)"
+ format: int64
+ type: integer
+ required:
+ - examined
+ - closed
+ - held
+ - skipped
+ - hasMore
+ - invoices
+ type: object
+ ClosePeriodsScope:
+ additionalProperties: false
+ properties:
+ instanceSlug:
+ description: Close only this instance's subscription
+ type: string
+ type: object
+ ClosedInvoice:
+ additionalProperties: false
+ properties:
+ boundaryAt:
+ format: date-time
+ type: string
+ held:
+ description: Set when the invoice is a held DRAFT
+ type: boolean
+ id:
+ type: string
+ instanceSlug:
+ type: string
+ kind:
+ enum:
+ - ACTIVATION
+ - RENEWAL
+ - FINAL
+ type: string
+ status:
+ enum:
+ - DRAFT
+ - PUSHED
+ - PUSH_FAILED
+ - MANUAL
+ - PAID
+ - PAYMENT_FAILED
+ - UNCOLLECTIBLE
+ - VOID
+ type: string
+ required:
+ - id
+ - instanceSlug
+ - kind
+ - boundaryAt
+ - status
+ - held
+ type: object
Connector:
additionalProperties: false
properties:
@@ -365,7 +444,7 @@ openapi: 3.1.0
paths:
/platform/connectors:
post:
- description: Registers or updates connector metadata and settings schema for this Kaiten deployment
+ description: Registers or updates connector metadata and settings schema for this Kaiten installation
operationId: register-connector
requestBody:
content:
@@ -454,7 +533,7 @@ paths:
- platform
/platform/organizations:
post:
- description: "Converges on the organization an external identity provider describes, creating it if it is not already there and returning it either way. The internal id is derived from the external id, so the operation is idempotent: a re-run returns the same organization instead of creating a second tenant, and an organization that already exists -- including one provisioned on a first login -- is returned unchanged apart from acquiring a name if it had none. Platform API: requires a Kaiten platform token (`ksm_...`) with write:organizations."
+ description: "Converges on the organization an external identity provider describes, creating it if it is not already there and returning it either way. The internal id is derived from the external id, so the operation is idempotent: a re-run returns the same organization instead of creating a second one, and an organization that already exists -- including one provisioned on a first login -- is returned unchanged apart from acquiring a name if it had none. Platform API: requires a Kaiten platform token (`ksm_...`) with write:organizations."
operationId: ensure-organization
requestBody:
content:
@@ -612,6 +691,86 @@ paths:
summary: Get an organization by ID
tags:
- organizations
+ /platform/organizations/{orgId}/billing/close-periods:
+ post:
+ description: "Closes the subscriptions whose period has ended now, rather than at the period-close job's next pass: each one's RENEWAL invoice is composed and issued (or held, when its usage journal fails a check) and its period advances. A subscription several periods behind closes one period at a time until it is current. Only what is due closes, at most the configured batch size per call. Platform API: requires a Kaiten platform token (`ksm_...`) with write:billing; the closes are recorded under system:kaiten."
+ operationId: platform-close-billing-periods
+ parameters:
+ - description: Organization ID
+ in: path
+ name: orgId
+ required: true
+ schema:
+ description: Organization ID
+ format: uuid
+ type: string
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/ClosePeriodsScope"
+ required: true
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/ClosePeriodsReport"
+ description: OK
+ "400":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Bad Request
+ "401":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unauthorized
+ "403":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Forbidden
+ "404":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Not Found
+ "422":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Unprocessable Entity
+ "500":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Internal Server Error
+ "503":
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/Problem"
+ description: Service Unavailable
+ headers:
+ Retry-After:
+ description: Seconds to wait before retrying, when a dependency (a payment provider) could not be reached
+ schema:
+ minimum: 0
+ type: integer
+ security:
+ - platformAuth:
+ - write:billing
+ summary: Close an organization's due billing periods
+ tags:
+ - billing
/platform/organizations/{orgId}/memberships/{userId}:
delete:
description: "Soft-deletes a single membership. Platform API: requires a Kaiten platform token (`ksm_...`) with delete:memberships. The system:kaiten membership cannot be removed — the database refuses it, and this answers 403."
diff --git a/openapi/source.yaml b/openapi/source.yaml
index 3f7c144..19d4f94 100644
--- a/openapi/source.yaml
+++ b/openapi/source.yaml
@@ -1,9 +1,9 @@
# Where the two snapshots in this directory were copied from. Rewritten by
# `task sync:openapi`; never edit the snapshots themselves by hand.
repository: kaitencloud/kaiten
-ref: origin/main
-commit: fa7f554fb6464f52c2a3f1ed487f4c7383c8125d
-synced_at: "2026-09-30"
+ref: 26.10.8
+commit: f685ceb86bfeb5f6110663662fb955336ce1a1a4
+synced_at: "2026-11-01"
files:
openapi.yaml: app/openapi.yaml
platform-openapi.yaml: app/platform-openapi.yaml
diff --git a/pyproject.toml b/pyproject.toml
index e7a85b4..c54007e 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -92,6 +92,8 @@ ignore = [
# Docstrings and contract descriptions run long; the formatter owns code width.
"E501",
]
+# The contract's descriptions write their arithmetic with these, and the models keep them.
+allowed-confusables = ["×", "−"]
[tool.ruff.lint.isort]
known-first-party = ["kaitencloud"]
diff --git a/scripts/generate_models.py b/scripts/generate_models.py
index 441da06..240588d 100644
--- a/scripts/generate_models.py
+++ b/scripts/generate_models.py
@@ -13,7 +13,7 @@
uv run python scripts/generate_models.py # rewrite src/kaitencloud/types/
uv run python scripts/generate_models.py --check # exit 1 when the committed files are stale
-Three rules shape the output, each because the alternative breaks a caller:
+Four rules shape the output, each because the alternative breaks a caller:
- Only what the SDK can return is generated: the response schemas of the operations
``openapi/coverage.yaml`` maps, and the payloads of the published webhooks. Request bodies
@@ -24,6 +24,9 @@
- Nothing is guessed. A new multi-value enum or inline union must be named below, and an
attribute that would shadow a pydantic member must be renamed below; the generator stops
until it is.
+- A model keeps its class. A webhook payload the contract renamed keeps the name it was
+ published under, and one that is a resource plus who wrote the change subclasses that
+ resource's model, so an event's ``data`` stays an instance of the class it had.
"""
from __future__ import annotations
@@ -68,6 +71,66 @@
"ImpactSample": "MetadataFieldImpactSample",
"RolloutDate": "RolloutDateVariant",
"RolloutPercentage": "RolloutPercentageVariant",
+ # Webhook payloads the contract renamed when it named every delivered shape ...Delivered.
+ # They keep the name the SDK published them under.
+ "CustomerCreationRejectedDelivered": "CustomerCreationRejected",
+ "DeploymentDelivered": "Deployment",
+ "FeatureFlagEvaluatedDelivered": "FeatureFlagEvaluated",
+ "InstanceEntitlementCapExceededDelivered": "InstanceEntitlementCapExceeded",
+ "InstanceEntitlementUsagePeriodRolledOverDelivered": "InstanceEntitlementUsagePeriodRolledOver",
+ "InstanceEntitlementUsageReportAcceptedDelivered": "InstanceEntitlementUsageReportAccepted",
+ "InstanceEntitlementUsageWarningThresholdReachedDelivered": (
+ "InstanceEntitlementUsageWarningThresholdReached"
+ ),
+ "InstanceEntitlementValueGetDelivered": "InstanceEntitlementValueGet",
+ "InstanceLifecycleStageChangedDelivered": "InstanceLifecycleStageChanged",
+ "InstanceStatusChangedDelivered": "InstanceStatusChanged",
+ "MetadataFieldReorderedDelivered": "MetadataFieldReordered",
+ "SystemTokenIssuanceDelivered": "SystemTokenIssuance",
+}
+
+# Webhook payloads that are a resource plus who wrote the change, keyed to that resource's
+# schema. Each is generated as a subclass of the resource's model, so an event's ``data`` stays
+# an instance of the class it was before the contract gave it a delivered shape. The generator
+# checks the claim: every property of the resource must keep its type in the payload.
+EXTENDS: dict[str, str] = {
+ "AddonDelivered": "Addon",
+ "AddonGrantUpdateDelivered": "AddonEntitlement",
+ "AddonPriceUpdateDelivered": "Price",
+ "AddonUpdateDelivered": "Addon",
+ "BillingStartedDelivered": "InstanceBilling",
+ "CancellationChangeDelivered": "InstanceBilling",
+ "CancellationDelivered": "InstanceBilling",
+ "ComponentDelivered": "Component",
+ "CustomerEventDelivered": "Customer",
+ "DeploymentZoneDelivered": "DeploymentZone",
+ "EntitlementDelivered": "Entitlement",
+ "EntitlementGroupDelivered": "EntitlementGroup",
+ "EntitlementUsageDelivered": "EntitlementUsage",
+ "FeatureFlagDelivered": "FeatureFlag",
+ "HeldInvoiceDelivered": "InvoiceSummary",
+ "InstanceAddonUpdateDelivered": "InstanceAddon",
+ "InstanceDelivered": "Instance",
+ "InstanceUpdateDelivered": "Instance",
+ "IssuedInvoiceDelivered": "InvoiceSummary",
+ "LicenseDelivered": "License",
+ "LicenseEntitlementDelivered": "LicenseEntitlement",
+ "LicenseFamilyViewDelivered": "LicenseFamilyView",
+ "LicensePriceEventDelivered": "Price",
+ "LicensePriceUpdatedEventDelivered": "Price",
+ "MetadataFieldDelivered": "MetadataField",
+ "MismatchedInvoiceDelivered": "InvoiceSummary",
+ "PaidInvoiceDelivered": "InvoiceSummary",
+ "PaymentFailedInvoiceDelivered": "InvoiceSummary",
+ "PushFailedInvoiceDelivered": "InvoiceSummary",
+ "PushedInvoiceDelivered": "InvoiceSummary",
+ "RedemptionDelivered": "Redemption",
+ "ReleaseDelivered": "Release",
+ "ReleasedInvoiceDelivered": "InvoiceSummary",
+ "TrialWillEndDelivered": "InstanceBilling",
+ "UncollectibleInvoiceDelivered": "InvoiceSummary",
+ "VoidedInvoiceDelivered": "InvoiceSummary",
+ "VoucherDelivered": "Voucher",
}
# Names for inline unions, keyed by their members.
@@ -82,16 +145,13 @@
# Names for enums with more than one value, keyed by (schema, property).
ENUM_NAMES: dict[tuple[str, str], str] = {
+ ("Addon", "lifecycleState"): "AddonLifecycleState",
("Entitlement", "aggregationMethod"): "AggregationMethod",
("Entitlement", "resetAnchor"): "ResetAnchor",
("Entitlement", "resetPeriod"): "ResetPeriod",
("Entitlement", "type"): "EntitlementType",
("FeatureFlag", "type"): "FeatureFlagType",
("Instance", "status"): "InstanceStatus",
- ("InstanceEntitlementUsageReportAccepted", "behavior"): "UsageBehavior",
- ("InstanceEntitlementUsageReportAccepted", "status"): "UsageReportStatus",
- ("InstanceStatusChanged", "new_status"): "InstanceStatus",
- ("InstanceStatusChanged", "previous_status"): "InstanceStatus",
("License", "lifecycleState"): "LicenseLifecycleState",
("License", "type"): "LicenseType",
("LicenseEntitlement", "entitlementType"): "LicenseEntitlementType",
@@ -99,18 +159,246 @@
("TargetingContextNode", "type"): "TargetingFactType",
}
-# Attribute names that would shadow a pydantic.BaseModel member, keyed by (schema, property).
+# Names for the enums several schemas share -- an invoice's status, a billing period -- keyed by
+# (property, values). A property must match as well as the values for a name to apply.
+ENUM_VOCABULARY: dict[tuple[str, frozenset[str]], str] = {
+ ("aggregationMethod", frozenset({"COUNT", "SUM", "AVERAGE", "MIN", "MAX", "LATEST"})): (
+ "AggregationMethod"
+ ),
+ ("appliesTo", frozenset({"LICENSE_BASE", "ADDONS", "BOTH", "SELECTED_PRICES"})): (
+ "DiscountAppliesTo"
+ ),
+ ("behavior", frozenset({"append", "set"})): "UsageBehavior",
+ ("billingModel", frozenset({"FLAT_FEE", "USAGE_BASED", "OVERAGE"})): "BillingModel",
+ ("billingPeriod", frozenset({"MONTHLY", "QUARTERLY", "SEMI_ANNUAL", "ANNUAL"})): (
+ "BillingPeriod"
+ ),
+ ("billingStatus", frozenset({"INCOMPLETE", "TRIAL", "ACTIVE", "PAST_DUE", "CANCELED"})): (
+ "SubscriptionStatus"
+ ),
+ ("billingTiming", frozenset({"ADVANCE", "ARREARS"})): "BillingTiming",
+ (
+ "cancellationReason",
+ frozenset({"TRIAL_ENDED_WITHOUT_PAYMENT_METHOD", "INCOMPLETE_EXPIRED"}),
+ ): ("CancellationReason"),
+ ("collectionMethod", frozenset({"SEND_INVOICE", "CHARGE_AUTOMATICALLY"})): "CollectionMethod",
+ ("defaultCollectionMethod", frozenset({"SEND_INVOICE", "CHARGE_AUTOMATICALLY"})): (
+ "CollectionMethod"
+ ),
+ ("disabledReason", frozenset({"DEPLOYMENT_DISABLED", "NOT_ENTITLED"})): (
+ "BillingDisabledReason"
+ ),
+ ("discountType", frozenset({"PERCENTAGE", "FIXED_AMOUNT"})): "DiscountType",
+ ("duration", frozenset({"ONE_TIME", "REPEATING", "FOREVER"})): "VoucherDuration",
+ ("from", frozenset({"NOOP", "STRIPE"})): "BillingProviderKind",
+ ("from", frozenset({"INCOMPLETE", "TRIAL", "ACTIVE", "PAST_DUE", "CANCELED"})): (
+ "SubscriptionStatus"
+ ),
+ ("entitlementType", frozenset({"BOOLEAN", "NUMBER", "CONFIG", "NUMBER_AI_CREDIT"})): (
+ "EntitlementType"
+ ),
+ ("handoffStatus", frozenset({"NOT_REQUIRED", "PENDING", "ACKNOWLEDGED"})): "HandoffStatus",
+ (
+ "holdReason",
+ frozenset(
+ {
+ "LEDGER_SEQUENCE_GAP",
+ "LEDGER_CHAIN_BREAK",
+ "LEDGER_COUNTER_MISMATCH",
+ "PROVIDER_ITEM_LIMIT",
+ }
+ ),
+ ): "InvoiceHoldReason",
+ ("increaseBilling", frozenset({"TRUE_UP", "NEXT_BOUNDARY"})): "IncreaseBilling",
+ ("kind", frozenset({"ACTIVATION", "RENEWAL", "FINAL"})): "InvoiceKind",
+ ("kind", frozenset({"NOOP", "STRIPE"})): "BillingProviderKind",
+ ("kind", frozenset({"SET", "ADD", "UNLIMITED"})): "ExceptionKind",
+ ("kind", frozenset({"USER", "SERVICE", "SYSTEM"})): "ActorKind",
+ ("lastSyncStatus", frozenset({"SUCCESS", "PARTIAL", "FAILED"})): "SyncStatus",
+ ("mode", frozenset({"AT_PERIOD_END", "IMMEDIATE"})): "CancellationMode",
+ ("modifierType", frozenset({"SET", "ADD", "MULTIPLY", "UNLIMITED"})): "GrantModifier",
+ ("new_status", frozenset({"HEALTHY", "DEGRADED", "INCIDENT", "MAINTENANCE"})): "InstanceStatus",
+ ("overrideBehavior", frozenset({"ADD", "OVERRIDE", "MAX"})): "OverrideBehavior",
+ ("previous_status", frozenset({"HEALTHY", "DEGRADED", "INCIDENT", "MAINTENANCE"})): (
+ "InstanceStatus"
+ ),
+ ("priceAppliesTo", frozenset({"LICENSE_BASE", "ADDONS", "BOTH", "SELECTED_PRICES"})): (
+ "DiscountAppliesTo"
+ ),
+ ("priceDiscountType", frozenset({"PERCENTAGE", "FIXED_AMOUNT"})): "DiscountType",
+ ("pricingType", frozenset({"FREE", "PAID", "CUSTOM"})): "PricingType",
+ ("providerKind", frozenset({"NOOP", "STRIPE"})): "BillingProviderKind",
+ ("reason", frozenset({"PERIOD_ENDED", "ANCHOR_CHANGED"})): "PeriodRolloverReason",
+ ("reason", frozenset({"TRIAL_ENDED", "INVOICE_OVERDUE", "PAYMENT_FAILED", "SETTLED"})): (
+ "SubscriptionStatusChangeReason"
+ ),
+ (
+ "reason",
+ frozenset(
+ {
+ "NOT_FOUND",
+ "NOT_ACTIVE",
+ "NOT_YET_VALID",
+ "EXPIRED",
+ "EXHAUSTED",
+ "ALREADY_REDEEMED",
+ "NOT_ELIGIBLE",
+ "CURRENCY_MISMATCH",
+ "DISCOUNT_LIMIT_EXCEEDED",
+ }
+ ),
+ ): "VoucherInvalidReason",
+ ("reconciliationStatus", frozenset({"MATCHED", "MISMATCH"})): "ReconciliationStatus",
+ ("retentionSource", frozenset({"KBK", "ENV", "UNKNOWN"})): "RetentionSource",
+ (
+ "rule",
+ frozenset(
+ {
+ "RESTRICTED_CUSTOMER",
+ "LICENSE_NOT_APPLICABLE",
+ "ADDON_NOT_APPLICABLE",
+ "FIRST_TIME_ONLY",
+ "ANNUAL_ONLY",
+ "MINIMUM_SUBSCRIPTION_AMOUNT",
+ "NOTHING_TO_BOOST",
+ }
+ ),
+ ): "VoucherRule",
+ ("source", frozenset({"license", "addon", "exception"})): "EntitlementSource",
+ ("source", frozenset({"MARK_PAID", "PROVIDER", "ZERO_TOTAL"})): "PaymentSource",
+ ("status", frozenset({"ACCEPTED", "REJECTED"})): "UsageReportStatus",
+ ("status", frozenset({"ACTIVE", "DEPRECATED"})): "PriceStatus",
+ ("status", frozenset({"ACTIVE", "EXPIRED", "FAILED"})): "PaymentMethodStatus",
+ ("status", frozenset({"ACTIVE", "EXPIRED", "REVOKED"})): "RedemptionStatus",
+ ("status", frozenset({"DRAFT", "ACTIVE", "EXPIRED", "EXHAUSTED", "ARCHIVED"})): "VoucherStatus",
+ (
+ "status",
+ frozenset(
+ {
+ "DRAFT",
+ "PUSHED",
+ "PUSH_FAILED",
+ "MANUAL",
+ "PAID",
+ "PAYMENT_FAILED",
+ "UNCOLLECTIBLE",
+ "VOID",
+ }
+ ),
+ ): "InvoiceStatus",
+ ("status", frozenset({"HEALTHY", "DEGRADED", "INCIDENT", "MAINTENANCE"})): "InstanceStatus",
+ ("status", frozenset({"INCOMPLETE", "TRIAL", "ACTIVE", "PAST_DUE", "CANCELED"})): (
+ "SubscriptionStatus"
+ ),
+ (
+ "status",
+ frozenset({"MANUAL", "PUSHED", "PAID", "PAYMENT_FAILED", "UNCOLLECTIBLE", "VOID"}),
+ ): ("SessionInvoiceStatus"),
+ ("status", frozenset({"NOT_REQUIRED", "PENDING", "ACKNOWLEDGED"})): "HandoffStatus",
+ ("status", frozenset({"OPEN", "AWAITING_PAYMENT", "COMPLETED", "ABANDONED", "EXPIRED"})): (
+ "CheckoutSessionStatus"
+ ),
+ ("status", frozenset({"OPEN", "COMPLETED", "ABANDONED", "EXPIRED"})): (
+ "CheckoutSessionEventStatus"
+ ),
+ ("status", frozenset({"SCHEDULED", "ACTIVE", "EXPIRED", "REVOKED"})): "ExceptionStatus",
+ ("status", frozenset({"SUCCESS", "PARTIAL", "FAILED"})): "SyncStatus",
+ ("status", frozenset({"draft", "open", "paid", "uncollectible", "void"})): (
+ "ProviderInvoiceStatus"
+ ),
+ ("status", frozenset({"paid", "requires_action", "failed", "processing", "not_required"})): (
+ "PaymentStatus"
+ ),
+ ("status", frozenset({"preview", "open"})): "CheckoutStartStatus",
+ ("status", frozenset({"preview", "requires_payment_method", "subscribed"})): (
+ "SessionCheckoutStatus"
+ ),
+ ("status", frozenset({"requires_payment_method", "awaiting_payment", "completed"})): (
+ "CheckoutConfirmationStatus"
+ ),
+ ("to", frozenset({"NOOP", "STRIPE"})): "BillingProviderKind",
+ ("to", frozenset({"INCOMPLETE", "TRIAL", "ACTIVE", "PAST_DUE", "CANCELED"})): (
+ "SubscriptionStatus"
+ ),
+ ("trialEndWithoutPaymentMethod", frozenset({"CANCEL", "CONVERT"})): (
+ "TrialEndWithoutPaymentMethod"
+ ),
+ ("type", frozenset({"BASE", "ADDON", "USAGE", "OVERAGE", "DISCOUNT"})): "InvoiceLineType",
+ ("type", frozenset({"BOOLEAN", "NUMBER", "CONFIG", "NUMBER_AI_CREDIT"})): "EntitlementType",
+ ("type", frozenset({"PERCENTAGE", "FIXED_AMOUNT"})): "DiscountType",
+ ("unavailableReason", frozenset({"NOT_ENTITLED", "VAULT_NOT_CONFIGURED"})): (
+ "ProviderUnavailableReason"
+ ),
+ ("usageHistoryRetentionSource", frozenset({"KBK", "ENV", "UNKNOWN"})): "RetentionSource",
+ ("via", frozenset({"customer_session", "checkout_session"})): "EventVia",
+ ("voucherType", frozenset({"PRICE", "ENTITLEMENT_BOOST", "FLAG_GRANT", "COMPOSITE"})): (
+ "VoucherType"
+ ),
+}
+
+# Attribute names that would shadow a pydantic.BaseModel member or a Python keyword, keyed by
+# (schema, property).
FIELD_NAMES: dict[tuple[str, str], str] = {
("ConnectorSettingsSchema", "schema"): "json_schema",
+ ("ProviderChangeDelivered", "from"): "from_",
+ ("StatusChangeDelivered", "from"): "from_",
}
ALIAS_DOCS: dict[str, str] = {
+ "ActorKind": "Who acted: a person, a service account, or Kaiten itself.",
+ "AddonLifecycleState": (
+ "Whether an add-on version may be sold: ``DRAFT`` before it goes on sale, ``PUBLISHED`` "
+ "while it is on sale, ``ARCHIVED`` once it has been withdrawn."
+ ),
"AggregationMethod": "How each reported usage value folds into an entitlement's stored total.",
+ "BillingDisabledReason": "Why billing is off: the deployment disabled it, or the plan does not include it.",
+ "BillingModel": (
+ "How a price bills: ``FLAT_FEE`` per period, ``USAGE_BASED`` per sale unit of usage, "
+ "``OVERAGE`` per sale unit above the license's limit."
+ ),
+ "BillingPeriod": "How often a subscription bills its flat fees.",
+ "BillingProviderKind": (
+ "Who issues and collects an invoice: ``NOOP``, the organization itself through the "
+ "handoff queue, or ``STRIPE``."
+ ),
+ "BillingTiming": "When a price bills: in ``ADVANCE`` of its period, or in ``ARREARS`` after it.",
+ "CancellationMode": "When a cancellation takes effect: at the end of the period paid for, or now.",
+ "CancellationReason": "Why Kaiten canceled a subscription on its own.",
+ "CheckoutConfirmationStatus": "Where an anonymous checkout stands after a confirmation.",
+ "CheckoutSessionEventStatus": "The status of an anonymous checkout session, as its events report it.",
+ "CheckoutSessionStatus": "The status of an anonymous checkout session.",
+ "CheckoutStartStatus": "What starting an anonymous checkout did: ``preview`` for a dry run, ``open`` otherwise.",
+ "CollectionMethod": (
+ "How an invoice is collected: ``SEND_INVOICE`` sends it for payment, "
+ "``CHARGE_AUTOMATICALLY`` charges the customer's payment method."
+ ),
"DefaultVariant": "What a feature flag serves when no targeting rule matches, discriminated by ``type``.",
+ "DiscountAppliesTo": "What a price voucher discounts.",
+ "DiscountType": "How a discount is expressed: a percentage, or a fixed amount in minor units.",
+ "EntitlementSource": (
+ "Where an instance's entitlement comes from: its ``license``, an ``addon``, or an "
+ "``exception`` alone."
+ ),
"EntitlementType": "The value type of an entitlement in the catalog.",
"EntitlementValue": "A typed entitlement value, discriminated by ``type``: a number, a boolean or a config object.",
+ "EventVia": (
+ "What wrote the change an event reports, when it was not an organization credential: "
+ "a customer session, or an anonymous checkout."
+ ),
+ "ExceptionKind": "How an entitlement exception overrides a value: ``SET`` it, ``ADD`` to it, or lift it (``UNLIMITED``).",
+ "ExceptionStatus": "Where an entitlement exception stands.",
"FeatureFlagType": "The value type of a feature flag's variants.",
+ "GrantModifier": "How a voucher grant changes an entitlement: ``SET``, ``ADD``, ``MULTIPLY`` or ``UNLIMITED``.",
+ "HandoffStatus": "Where an invoice stands in the accounting handoff.",
+ "IncreaseBilling": (
+ "How a mid-period increase of an add-on billed in advance is billed: a ``TRUE_UP`` line, "
+ "or from the ``NEXT_BOUNDARY``."
+ ),
"InstanceStatus": "The operational status of an instance.",
+ "InvoiceHoldReason": "Why an invoice is held as a draft.",
+ "InvoiceKind": "What an invoice bills: a subscription's ``ACTIVATION``, a ``RENEWAL``, or its ``FINAL`` period.",
+ "InvoiceLineType": "What an invoice line bills.",
+ "InvoiceStatus": "Where an invoice stands, from ``DRAFT`` to ``PAID``, ``UNCOLLECTIBLE`` or ``VOID``.",
"LicenseEntitlementType": "The value type of an entitlement granted by a license.",
"LicenseLifecycleState": (
"Whether a license version may be served: ``DRAFT`` before it goes on sale, "
@@ -118,8 +406,25 @@
),
"LicenseType": "The kind of a license.",
"MetadataResourceType": "The resource a metadata field applies to.",
+ "OverrideBehavior": "How an add-on's number grant combines with the license's: ``ADD``, ``OVERRIDE`` or ``MAX``.",
+ "PaymentMethodStatus": "Whether a saved payment method can be charged.",
+ "PaymentSource": "What recorded an invoice as paid.",
+ "PaymentStatus": "How a checkout's first payment went.",
+ "PeriodRolloverReason": "Why a usage window closed: its period ended, or its anchor changed.",
+ "PriceStatus": "Whether a price is ``ACTIVE``, or ``DEPRECATED``: still billing the subscriptions on it, and no new one.",
+ "PricingType": "How a version is sold: ``FREE`` or ``PAID`` self-serve, or ``CUSTOM``, through a conversation.",
+ "ProviderInvoiceStatus": "An invoice's status at its payment provider.",
+ "ProviderUnavailableReason": "Why a payment provider cannot be connected.",
+ "ReconciliationStatus": "Whether an invoice matches its copy at the payment provider.",
+ "RedemptionStatus": "Where a voucher redemption stands.",
"ResetAnchor": "What a periodic usage window is aligned on.",
"ResetPeriod": "How often a periodic entitlement's usage resets.",
+ "RetentionSource": "Where the usage history's retention period comes from.",
+ "SessionCheckoutStatus": "What a customer session's checkout did.",
+ "SessionInvoiceStatus": "An issued invoice's status, as the customer's portal shows it.",
+ "SubscriptionStatus": "Where a subscription stands.",
+ "SubscriptionStatusChangeReason": "Why a subscription's status changed.",
+ "SyncStatus": "How a sync with a payment provider went.",
"TargetingFactType": "The shape of a fact a targeting rule can read.",
"Scope": (
"A permission an organization credential can carry, ``read:`` or "
@@ -127,8 +432,17 @@
),
"Targeting": "One targeting rule of a feature flag, discriminated by ``type``.",
"Targetings": "A feature flag's targeting rules, evaluated in order; the first match wins.",
+ "TrialEndWithoutPaymentMethod": (
+ "What a trial charged automatically becomes at its end without a payment method: "
+ "``CANCEL`` or ``CONVERT``."
+ ),
"UsageBehavior": "How a usage report applies: ``append`` folds the value in, ``set`` overwrites it.",
"UsageReportStatus": "Whether a usage report was persisted.",
+ "VoucherDuration": "How long a price voucher's discount lasts.",
+ "VoucherInvalidReason": "Why a voucher code would not apply.",
+ "VoucherRule": "The redemption rule a voucher code failed.",
+ "VoucherStatus": "Where a voucher stands.",
+ "VoucherType": "What a voucher gives: a price discount, an entitlement boost, flag grants, or several.",
}
# Docstrings for the schemas the contract leaves undescribed.
@@ -236,7 +550,8 @@ def core_module(
names = {name: CLASS_NAMES.get(name, name) for name in emitted}
module = ModelModule(source="openapi/openapi.yaml", schemas=schemas, names=names)
- for name in sorted(emitted, key=names.__getitem__):
+ # A subclass is defined after the class it extends.
+ for name in sorted(emitted, key=lambda name: (name in EXTENDS, names[name])):
module.add_schema(name)
module.enums["Scope"] = scopes(spec)
return module.render(), names, module.exports
@@ -333,10 +648,18 @@ def webhooks_module(spec: dict[str, Any], core_names: dict[str, str]) -> str:
]
events: list[tuple[str, str, str, str, str]] = []
+ headers: set[str] = set()
for key, item in sorted((spec.get("webhooks") or {}).items()):
operation = item["post"]
schema = operation["requestBody"]["content"]["application/json"]["schema"]
properties = schema["properties"]
+ unknown = set(properties) - {"name", "type", "data", "headers"}
+ if unknown:
+ raise SystemExit(f"webhook {key}: the envelope has unsupported properties {unknown}")
+ # Every envelope carries the same headers, or none does: one rule renders them all.
+ headers.add(
+ core_names[ref_name(properties["headers"]["$ref"])] if "headers" in properties else ""
+ )
event_type = only_enum_value(properties["type"], f"webhook {key}.type")
if event_type != key:
raise SystemExit(f"webhook {key}: its type enum says {event_type!r}")
@@ -346,7 +669,10 @@ def webhooks_module(spec: dict[str, Any], core_names: dict[str, str]) -> str:
summary = operation.get("description") or operation.get("summary") or ""
events.append((python, event_name, event_type, data, summary))
- data_types = sorted({data for _, _, _, data, _ in events})
+ if len(headers) > 1:
+ raise SystemExit(f"webhooks: the envelopes disagree on their headers: {sorted(headers)}")
+ header = next(iter(headers), "")
+ data_types = sorted({data for _, _, _, data, _ in events} | ({header} - {""}))
lines.append(f"from ._core import {', '.join(data_types)}")
lines += ["", "__all__ = [", ' "WEBHOOK_EVENTS",', ' "WebhookEvent",']
lines += [f' "{python}",' for python, *_ in sorted(events)]
@@ -367,6 +693,12 @@ def webhooks_module(spec: dict[str, Any], core_names: dict[str, str]) -> str:
' """What the event is about."""',
"",
]
+ if header:
+ lines += [
+ f" headers: {header} | None = None",
+ ' """The trace context of the request that produced the event."""',
+ "",
+ ]
members = "\n".join(f" | {python}" for python, *_ in events).lstrip(" |")
lines += [
@@ -418,15 +750,19 @@ def add_schema(self, name: str) -> None:
def render_class(self, name: str, schema: dict[str, Any]) -> list[str]:
python = self.names[name]
- lines = [f"class {python}(KaitenModel):"]
+ base = EXTENDS.get(name)
+ lines = [f"class {python}({self.names[base] if base else 'KaitenModel'}):"]
lines += docstring(schema.get("description") or CLASS_DOCS.get(python), " ")
required = set(schema.get("required") or [])
properties: dict[str, Any] = schema.get("properties") or {}
- if not properties and len(lines) == 1:
+ inherited = self.inherited(name, base) if base else set()
+ if not set(properties) - inherited and len(lines) == 1:
lines.append(" pass")
for wire, node in properties.items():
+ if wire in inherited:
+ continue
attribute = FIELD_NAMES.get((name, wire), snake_case(wire))
if keyword.iskeyword(attribute) or hasattr(BaseModel, attribute):
raise SystemExit(
@@ -450,6 +786,30 @@ def render_class(self, name: str, schema: dict[str, Any]) -> list[str]:
lines += docstring(node.get("description"), " ")
return lines
+ def inherited(self, name: str, base: str) -> set[str]:
+ """The properties ``name`` takes from ``base`` as they are, checking it really extends it.
+
+ Each property of the base must keep its type in the derived schema -- rendered as the
+ base's, so their enums get the base's names -- and stay required where the base requires
+ it. A property the base has and the derived schema lacks must be optional in the base.
+ """
+ own = self.schemas[name].get("properties") or {}
+ parent = self.schemas[base]
+ required = set(self.schemas[name].get("required") or [])
+ for wire, node in (parent.get("properties") or {}).items():
+ where = f"{name}.{wire}"
+ if wire not in own:
+ if wire in (parent.get("required") or []):
+ raise SystemExit(f"{where}: missing, but required by {base}, which it extends")
+ continue
+ if wire in (parent.get("required") or []) and wire not in required:
+ raise SystemExit(f"{where}: optional, but required by {base}, which it extends")
+ ours = self.annotation(own[wire], owner=base, prop=wire)
+ theirs = self.annotation(node, owner=base, prop=wire)
+ if ours != theirs:
+ raise SystemExit(f"{where}: {ours}, but {theirs} in {base}, which it extends")
+ return set(own) & set(parent.get("properties") or {})
+
def annotation(self, node: dict[str, Any], *, owner: str, prop: str | None) -> str:
where = f"{owner}.{prop}" if prop else owner
if "$ref" in node:
@@ -494,15 +854,20 @@ def annotation(self, node: dict[str, Any], *, owner: str, prop: str | None) -> s
return f"{text} | None" if nullable else text
def string(self, node: dict[str, Any], *, owner: str, prop: str | None) -> str:
- values = node.get("enum")
+ # A nullable enum lists null among its values; its type already says it is nullable.
+ values = [value for value in node.get("enum") or [] if value is not None]
if values:
if len(values) == 1:
return f"Literal[{json.dumps(values[0])}]"
- name = ENUM_NAMES.get((owner, prop or ""))
+ name = ENUM_NAMES.get((owner, prop or "")) or ENUM_VOCABULARY.get(
+ (prop or "", frozenset(values))
+ )
if name is None:
- raise SystemExit(f"{owner}.{prop}: name this enum in ENUM_NAMES ({values})")
+ raise SystemExit(
+ f"{owner}.{prop}: name this enum in ENUM_NAMES or ENUM_VOCABULARY ({values})"
+ )
known = self.enums.setdefault(name, tuple(values))
- if known != tuple(values):
+ if set(known) != set(values):
raise SystemExit(f"{name}: {owner}.{prop} has {values}, elsewhere {list(known)}")
return f"{name} | str"
if node.get("format") == "date-time":
@@ -512,17 +877,35 @@ def string(self, node: dict[str, Any], *, owner: str, prop: str | None) -> str:
def union(self, node: dict[str, Any], *, owner: str, name: str | None = None) -> str:
key = "oneOf" if "oneOf" in node else "anyOf"
members: list[str] = []
+ scalars: list[dict[str, Any]] = []
+ nullable = False
for member in node[key]:
- if "$ref" not in member:
+ if member.get("type") == "null":
+ # A null member makes the union nullable: `X | None`, not a model of its own.
+ nullable = True
+ elif "$ref" in member:
+ members.append(ref_name(member["$ref"]))
+ elif member.get("type") in ("boolean", "number", "integer", "string", "object") and (
+ not member.get("properties") and not member.get("enum")
+ ):
+ scalars.append(member)
+ else:
raise SystemExit(f"{owner}: inline union members are not supported")
- members.append(ref_name(member["$ref"]))
-
- name = name or UNION_NAMES.get(frozenset(members))
- if name is None:
- raise SystemExit(f"{owner}: name the union of {members} in UNION_NAMES")
- tag = (node.get("discriminator") or {}).get("propertyName") or self.shared_tag(members)
- self.unions[name] = (tag, [self.names[member] for member in members])
- return name
+
+ if scalars:
+ if members or name:
+ raise SystemExit(f"{owner}: a union mixes inline values and schemas")
+ text = " | ".join(self.annotation(member, owner=owner, prop=None) for member in scalars)
+ elif len(members) == 1 and name is None:
+ text = self.names[members[0]]
+ else:
+ name = name or UNION_NAMES.get(frozenset(members))
+ if name is None:
+ raise SystemExit(f"{owner}: name the union of {members} in UNION_NAMES")
+ tag = (node.get("discriminator") or {}).get("propertyName") or self.shared_tag(members)
+ self.unions[name] = (tag, [self.names[member] for member in members])
+ text = name
+ return f"{text} | None" if nullable else text
def shared_tag(self, members: Iterable[str]) -> str | None:
"""``type``, when every member pins it to its own single value."""
diff --git a/src/kaitencloud/__init__.py b/src/kaitencloud/__init__.py
index dd539e5..0699059 100644
--- a/src/kaitencloud/__init__.py
+++ b/src/kaitencloud/__init__.py
@@ -37,11 +37,12 @@
RateLimitError,
ServiceUnavailableError,
ThresholdExceededError,
+ TransactionIdReusedError,
UnprocessableEntityError,
WebhookPayloadError,
WebhookVerificationError,
)
-from ._models import KaitenModel
+from ._models import KaitenModel, UsageReportResult
from ._sync.client import KaitenClient, KaitenPlatformClient
from ._types import NOT_GIVEN, NotGiven
from ._version import __version__
@@ -77,7 +78,9 @@
"RateLimitError",
"ServiceUnavailableError",
"ThresholdExceededError",
+ "TransactionIdReusedError",
"UnprocessableEntityError",
+ "UsageReportResult",
"WebhookPayloadError",
"WebhookVerificationError",
"__version__",
diff --git a/src/kaitencloud/_async/_http.py b/src/kaitencloud/_async/_http.py
index b96196c..8faf1cc 100644
--- a/src/kaitencloud/_async/_http.py
+++ b/src/kaitencloud/_async/_http.py
@@ -12,7 +12,7 @@
import httpx
from pydantic import BaseModel, ValidationError
-from .._constants import DEFAULT_TIMEOUT, MAX_PAGE_SIZE
+from .._constants import DEFAULT_TIMEOUT, MAX_PAGE_SIZE, MAX_REPORT_PAGE_SIZE
from .._exceptions import (
APIConnectionError,
APIResponseValidationError,
@@ -22,7 +22,15 @@
make_status_error,
)
from .._types import NOT_GIVEN, AsyncCredential, NotGiven, TimeoutTypes
-from .._utils import USER_AGENT, Surface, async_resolve_token, async_sleep, check_token, retry_delay
+from .._utils import (
+ PUBLISHABLE_KEY_PREFIX,
+ USER_AGENT,
+ Surface,
+ async_resolve_token,
+ async_sleep,
+ check_token,
+ retry_delay,
+)
__all__ = ["AsyncAPIClient"]
@@ -96,6 +104,7 @@ async def request(
*,
params: Mapping[str, Any] | None = None,
body: Any = None,
+ headers: Mapping[str, str] | None = None,
idempotent: bool | None = None,
errors: Mapping[int, type[APIStatusError]] | None = None,
) -> httpx.Response:
@@ -113,7 +122,11 @@ async def request(
while True:
request = self._build(
- method, url, params=_query(params), body=body, headers=await self._headers()
+ method,
+ url,
+ params=_query(params),
+ body=body,
+ headers={**await self._headers(path), **(headers or {})},
)
try:
response = await self.http_client.send(request)
@@ -250,6 +263,43 @@ async def list_all(
rows.append(row)
return rows
+ async def list_reports(
+ self,
+ path: str,
+ model: type[ModelT],
+ *,
+ params: Mapping[str, Any] | None = None,
+ limit: int | None = None,
+ ) -> list[ModelT]:
+ """Every row of a usage report history, which pages by ``afterSeq`` rather than a cursor.
+
+ ``limit`` is a ceiling, as for :meth:`paginate`. A ``nextAfterSeq`` that does not move
+ past the page it follows raises :class:`PaginationError`.
+ """
+ rows: list[ModelT] = []
+ after: int | None = None
+ while True:
+ size = MAX_REPORT_PAGE_SIZE
+ if limit is not None:
+ size = min(size, limit - len(rows))
+ response = await self.request(
+ "GET", path, params={**(params or {}), "afterSeq": after, "limit": size}
+ )
+ page = self.decode(response)
+ if not isinstance(page, dict) or not isinstance(page.get("items"), list):
+ raise APIResponseValidationError(
+ f"GET {path}: expected a page of items", response=response
+ )
+ rows += [self.validate(model, item, response) for item in page["items"]]
+ following = page.get("nextAfterSeq")
+ if following is None or (limit is not None and len(rows) >= limit):
+ return rows[:limit]
+ if not isinstance(following, int) or (after is not None and following <= after):
+ raise PaginationError(
+ f"GET {path}: the API reported more rows but no afterSeq past {after}"
+ )
+ after = following
+
@staticmethod
def decode(response: httpx.Response) -> Any:
if not response.content:
@@ -291,14 +341,15 @@ def _build(
method, url, params=params, json=body, headers=headers, timeout=self.timeout
)
- async def _headers(self) -> dict[str, str]:
- token = check_token(await async_resolve_token(self.credential), self.surface)
- return {
- "Accept": "application/json",
- "User-Agent": USER_AGENT,
- **self.default_headers,
- "Authorization": f"Bearer {token}",
- }
+ async def _headers(self, path: str) -> dict[str, str]:
+ token = check_token(await async_resolve_token(self.credential), self.surface, path)
+ headers = {"Accept": "application/json", "User-Agent": USER_AGENT, **self.default_headers}
+ if token.startswith(PUBLISHABLE_KEY_PREFIX):
+ # Not a bearer credential: the API refuses a publishable key sent as Authorization.
+ headers["X-Kaiten-Publishable-Key"] = token
+ else:
+ headers["Authorization"] = f"Bearer {token}"
+ return headers
async def _back_off(
self, request: httpx.Request, attempt: int, reason: str, retry_after: str | None = None
diff --git a/src/kaitencloud/_async/client.py b/src/kaitencloud/_async/client.py
index 5b36901..0e5c6b2 100644
--- a/src/kaitencloud/_async/client.py
+++ b/src/kaitencloud/_async/client.py
@@ -26,6 +26,7 @@
from ..types import PlatformCredential
from ._http import AsyncAPIClient
from .resources import (
+ AsyncBilling,
AsyncComponents,
AsyncConnectors,
AsyncCustomers,
@@ -42,6 +43,7 @@
AsyncServiceAccounts,
)
from .resources.platform import (
+ AsyncPlatformBilling,
AsyncPlatformConnectors,
AsyncPlatformOrganizations,
AsyncPlatformTokens,
@@ -113,7 +115,7 @@ class AsyncKaitenClient(_AsyncBaseClient):
The Core API is what an organization's own credential talks to: an organization token
(``ksh_...``) or a JWT from your identity provider. It covers customers, instances,
- licenses, entitlements and usage, feature flags, releases and connectors::
+ licenses, entitlements and usage, billing, feature flags, releases and connectors::
async with AsyncKaitenClient(
base_url="https://kaiten.example.com", token="ksh_..."
@@ -136,6 +138,9 @@ class AsyncKaitenClient(_AsyncBaseClient):
TLS or transports. The Kaiten client never closes a client it did not create.
"""
+ billing: AsyncBilling
+ """Billing: prices, subscriptions, invoices, add-ons, vouchers and the public catalog."""
+
components: AsyncComponents
"""Components: the versioned building blocks releases are made of."""
@@ -210,6 +215,7 @@ def __init__(
def _bind(self) -> None:
api = self._api
+ self.billing = AsyncBilling(api)
self.components = AsyncComponents(api)
self.connectors = AsyncConnectors(api)
self.customers = AsyncCustomers(api)
@@ -251,6 +257,9 @@ class AsyncKaitenPlatformClient(_AsyncBaseClient):
http_client: An :class:`httpx.AsyncClient` to send requests with.
"""
+ billing: AsyncPlatformBilling
+ """Billing across organizations: closing their due periods."""
+
connectors: AsyncPlatformConnectors
"""The deployment-wide connector registry."""
@@ -306,6 +315,7 @@ async def me(self) -> PlatformCredential:
def _bind(self) -> None:
api = self._api
+ self.billing = AsyncPlatformBilling(api)
self.connectors = AsyncPlatformConnectors(api)
self.organizations = AsyncPlatformOrganizations(api)
self.tokens = AsyncPlatformTokens(api)
diff --git a/src/kaitencloud/_async/resources/__init__.py b/src/kaitencloud/_async/resources/__init__.py
index 8a40fa4..452395c 100644
--- a/src/kaitencloud/_async/resources/__init__.py
+++ b/src/kaitencloud/_async/resources/__init__.py
@@ -1,6 +1,7 @@
# Copyright 2026 KAITEN INC
# SPDX-License-Identifier: Apache-2.0
+from .billing import AsyncBilling
from .components import AsyncComponents
from .connectors import AsyncConnectors
from .customers import AsyncCustomers
@@ -17,6 +18,7 @@
from .service_accounts import AsyncServiceAccounts
__all__ = [
+ "AsyncBilling",
"AsyncComponents",
"AsyncConnectors",
"AsyncCustomers",
diff --git a/src/kaitencloud/_async/resources/billing/__init__.py b/src/kaitencloud/_async/resources/billing/__init__.py
new file mode 100644
index 0000000..76a462a
--- /dev/null
+++ b/src/kaitencloud/_async/resources/billing/__init__.py
@@ -0,0 +1,216 @@
+# Copyright 2026 KAITEN INC
+# SPDX-License-Identifier: Apache-2.0
+
+from __future__ import annotations
+
+from ...._utils import compact
+from ....types import (
+ BillingCapabilities,
+ BillingHealth,
+ BillingSettings,
+ ClosePeriodsReport,
+ CollectionMethod,
+ SyncReport,
+)
+from ..._http import AsyncAPIClient
+from .._base import AsyncResource
+from .addons import AsyncBillingAddons
+from .customers import AsyncBillingCustomers
+from .invoices import AsyncBillingHandoff, AsyncBillingInvoices
+from .license_prices import AsyncBillingLicensePrices
+from .public import AsyncBillingPublic, AsyncBillingSession
+from .sessions import (
+ AsyncBillingCheckoutSessions,
+ AsyncBillingCustomerSessions,
+ AsyncBillingPublishableKeys,
+)
+from .subscriptions import (
+ AsyncBillingInstanceAddons,
+ AsyncBillingInstanceVouchers,
+ AsyncBillingSubscriptions,
+)
+from .vouchers import AsyncBillingVouchers
+
+__all__ = [
+ "AsyncBilling",
+ "AsyncBillingAddons",
+ "AsyncBillingCheckoutSessions",
+ "AsyncBillingCustomerSessions",
+ "AsyncBillingCustomers",
+ "AsyncBillingHandoff",
+ "AsyncBillingInstanceAddons",
+ "AsyncBillingInstanceVouchers",
+ "AsyncBillingInvoices",
+ "AsyncBillingLicensePrices",
+ "AsyncBillingPublic",
+ "AsyncBillingPublishableKeys",
+ "AsyncBillingSession",
+ "AsyncBillingSubscriptions",
+ "AsyncBillingVouchers",
+]
+
+
+class AsyncBilling(AsyncResource):
+ """Billing: the organization's settings and payment providers, and a namespace per resource.
+
+ Kaiten composes every invoice from the prices of a license version, its add-ons and the
+ usage it metered. Each subscription names who issues and collects them: ``NOOP``, the
+ organization itself through :attr:`handoff`, or a payment provider. Most operations here
+ require billing to be enabled for the organization; :meth:`get_capabilities` says whether
+ it is.
+ """
+
+ __slots__ = (
+ "addons",
+ "checkout_sessions",
+ "customer_sessions",
+ "customers",
+ "handoff",
+ "instance_addons",
+ "instance_vouchers",
+ "invoices",
+ "license_prices",
+ "public",
+ "publishable_keys",
+ "session",
+ "subscriptions",
+ "vouchers",
+ )
+
+ addons: AsyncBillingAddons
+ """The add-on catalog: families, versions, grants, prices and the license families they fit."""
+
+ checkout_sessions: AsyncBillingCheckoutSessions
+ """Anonymous checkouts, as the organization reads them."""
+
+ customer_sessions: AsyncBillingCustomerSessions
+ """Customer sessions (``kst_...``), for a customer's browser to act on its own billing."""
+
+ customers: AsyncBillingCustomers
+ """A customer's billing: its payment method, and the payment provider's portal."""
+
+ handoff: AsyncBillingHandoff
+ """The accounting handoff: invoices claimed in leased batches and acknowledged one by one."""
+
+ instance_addons: AsyncBillingInstanceAddons
+ """The add-ons an instance holds."""
+
+ instance_vouchers: AsyncBillingInstanceVouchers
+ """The vouchers an instance redeemed."""
+
+ invoices: AsyncBillingInvoices
+ """The organization's invoices."""
+
+ license_prices: AsyncBillingLicensePrices
+ """The prices of license versions, and the invoice a subscription to one would get."""
+
+ public: AsyncBillingPublic
+ """What a publishable key (``pk_...``) reaches. Build the client with that key."""
+
+ publishable_keys: AsyncBillingPublishableKeys
+ """Publishable keys (``pk_...``), which a web page reads the public catalog with."""
+
+ session: AsyncBillingSession
+ """What a customer session (``kst_...``) reaches. Build the client with that session."""
+
+ subscriptions: AsyncBillingSubscriptions
+ """Instances' subscriptions."""
+
+ vouchers: AsyncBillingVouchers
+ """The organization's vouchers."""
+
+ def __init__(self, api: AsyncAPIClient) -> None:
+ super().__init__(api)
+ self.addons = AsyncBillingAddons(api)
+ self.checkout_sessions = AsyncBillingCheckoutSessions(api)
+ self.customer_sessions = AsyncBillingCustomerSessions(api)
+ self.customers = AsyncBillingCustomers(api)
+ self.handoff = AsyncBillingHandoff(api)
+ self.instance_addons = AsyncBillingInstanceAddons(api)
+ self.instance_vouchers = AsyncBillingInstanceVouchers(api)
+ self.invoices = AsyncBillingInvoices(api)
+ self.license_prices = AsyncBillingLicensePrices(api)
+ self.public = AsyncBillingPublic(api)
+ self.publishable_keys = AsyncBillingPublishableKeys(api)
+ self.session = AsyncBillingSession(api)
+ self.subscriptions = AsyncBillingSubscriptions(api)
+ self.vouchers = AsyncBillingVouchers(api)
+
+ async def get_settings(self) -> BillingSettings:
+ """Return the organization's billing defaults.
+
+ They are the collection method and payment terms a subscription gets when it names none
+ of its own. An organization that never wrote them has ``SEND_INVOICE`` and 30 days.
+ """
+ return await self._api.get("/billing/settings", BillingSettings)
+
+ async def update_settings(
+ self,
+ *,
+ default_collection_method: CollectionMethod,
+ default_days_until_due: int,
+ handoff_stripe_invoices: bool,
+ ) -> BillingSettings:
+ """Replace the organization's billing defaults, and return them.
+
+ A subscription that names its own terms keeps them; the others take these at their
+ next invoice.
+
+ Args:
+ default_collection_method: ``SEND_INVOICE`` sends an invoice for payment;
+ ``CHARGE_AUTOMATICALLY`` charges the customer's payment method, on a provider
+ that can.
+ default_days_until_due: Days an invoice is due after it is issued, 0 to 365.
+ handoff_stripe_invoices: Whether invoices a payment provider issues also enter the
+ handoff queue, for an accounting system that wants every invoice.
+ """
+ body = {
+ "defaultCollectionMethod": default_collection_method,
+ "defaultDaysUntilDue": default_days_until_due,
+ "handoffStripeInvoices": handoff_stripe_invoices,
+ }
+ return await self._api.send("PUT", "/billing/settings", BillingSettings, body=body)
+
+ async def get_capabilities(self) -> BillingCapabilities:
+ """Return whether billing is enabled -- and if not, why -- and what it can do here.
+
+ Who can collect the organization's invoices, and which parts of billing this release
+ ships, so a client shows only what works. It answers even when billing is disabled.
+ """
+ return await self._api.get("/billing/capabilities", BillingCapabilities)
+
+ async def get_health(self) -> BillingHealth:
+ """Return what the organization's billing needs attention for, computed when read.
+
+ Periods waiting for their close, held invoices, invoices a payment provider keeps
+ refusing, reconciliation mismatches, each provider's sync, overdue invoices and the
+ handoff backlog.
+ """
+ return await self._api.get("/billing/health", BillingHealth)
+
+ async def sync(self) -> SyncReport:
+ """Run one sync pass now with each payment provider the organization connected.
+
+ What was paid, voided or finalized in a provider is mirrored, as the periodic pass does.
+
+ Raises:
+ ConflictError: No provider is connected (``SyncProvider.NotConnected``).
+ """
+ return await self._api.send("POST", "/billing/sync", SyncReport)
+
+ async def close_periods(self, *, instance_slug: str | None = None) -> ClosePeriodsReport:
+ """Close the subscriptions whose period has ended, now rather than at the next pass.
+
+ Each one's ``RENEWAL`` invoice is composed and issued -- or held, when its usage journal
+ fails a check -- and its period advances. Only what is due closes, at most a batch per
+ call: ``has_more`` says when more is left.
+
+ Args:
+ instance_slug: Close only this instance's subscription.
+ """
+ return await self._api.send(
+ "POST",
+ "/billing/close-periods",
+ ClosePeriodsReport,
+ body=compact({"instanceSlug": instance_slug}),
+ )
diff --git a/src/kaitencloud/_async/resources/billing/addons.py b/src/kaitencloud/_async/resources/billing/addons.py
new file mode 100644
index 0000000..89c4bd9
--- /dev/null
+++ b/src/kaitencloud/_async/resources/billing/addons.py
@@ -0,0 +1,349 @@
+# Copyright 2026 KAITEN INC
+# SPDX-License-Identifier: Apache-2.0
+
+from __future__ import annotations
+
+import builtins
+from typing import Any, Literal
+
+from ...._types import EntitlementValueInput
+from ...._utils import api_path, compact, entitlement_value, price_body
+from ....types import (
+ Addon,
+ AddonEntitlement,
+ AddonFamily,
+ AddonLifecycleState,
+ BillingModel,
+ BillingPeriod,
+ BillingTiming,
+ CompatibleLicenseFamilies,
+ IncreaseBilling,
+ OverrideBehavior,
+ Price,
+ PriceStatus,
+ PricingType,
+)
+from .._base import AsyncResource
+
+__all__ = ["AsyncBillingAddons"]
+
+
+class AsyncBillingAddons(AsyncResource):
+ """The add-on catalog: what an instance can hold on top of its license, by the unit.
+
+ Add-ons are versioned like licenses: an add-on *family* is the product, each version has
+ its grants and its prices, and moves through ``DRAFT``, ``PUBLISHED`` and ``ARCHIVED``. A
+ version fits the license families :meth:`set_compatibility` names, and none until then.
+ """
+
+ async def list(
+ self,
+ *,
+ lifecycle_state: AddonLifecycleState | None = None,
+ family_slug: str | None = None,
+ ) -> builtins.list[Addon]:
+ """Return the organization's add-on versions, newest first, walking all pages.
+
+ Args:
+ lifecycle_state: Only versions in this state.
+ family_slug: Only versions of this family.
+ """
+ return await self._api.list_all(
+ "/addons",
+ Addon,
+ params={"lifecycleState": lifecycle_state, "familySlug": family_slug},
+ )
+
+ async def get(self, addon_slug: str) -> Addon:
+ """Return the add-on version identified by ``addon_slug``."""
+ return await self._api.get(api_path("addons", addon_slug), Addon)
+
+ async def create(
+ self,
+ *,
+ name: str,
+ pricing_type: PricingType,
+ description: str = "",
+ slug: str | None = None,
+ family_slug: str | None = None,
+ family_id: str | None = None,
+ version_name: str | None = None,
+ lifecycle_state: Literal["DRAFT", "PUBLISHED"] | None = None,
+ is_default: bool | None = None,
+ max_quantity: int | None = None,
+ increase_billing: IncreaseBilling | None = None,
+ ) -> Addon:
+ """Create an add-on version: the first of a new family, or the next of an existing one.
+
+ Args:
+ name: The add-on's name, e.g. ``"Extra seats"``.
+ pricing_type: ``FREE``, ``PAID`` or ``CUSTOM``.
+ description: A description.
+ slug: The version's slug. Defaults to ``-v`` for a new version of a
+ family, and to one generated from the name for a new family.
+ family_slug: Make this a new version of this family.
+ family_id: The same family, by identifier.
+ version_name: A label for the version. Defaults to ``"Version - "``.
+ lifecycle_state: ``DRAFT``, or ``PUBLISHED`` when omitted.
+ is_default: Make this version its family's default.
+ max_quantity: The largest quantity an instance can hold.
+ increase_billing: Read only when this opens a new family: how a mid-period increase
+ of its add-ons billed in advance is billed, ``TRUE_UP`` (the default) or
+ ``NEXT_BOUNDARY``.
+ """
+ body = {
+ "name": name,
+ "description": description,
+ "pricingType": pricing_type,
+ **compact(
+ {
+ "slug": slug,
+ "familySlug": family_slug,
+ "familyId": family_id,
+ "versionName": version_name,
+ "lifecycleState": lifecycle_state,
+ "isDefault": is_default,
+ "maxQuantity": max_quantity,
+ "increaseBilling": increase_billing,
+ }
+ ),
+ }
+ return await self._api.send("POST", "/addons", Addon, body=body)
+
+ async def update(
+ self,
+ addon_slug: str,
+ *,
+ name: str,
+ description: str = "",
+ is_default: bool = False,
+ version_name: str | None = None,
+ max_quantity: int | None = None,
+ ) -> Addon:
+ """Replace an add-on version's name, description, version name, default flag and maximum.
+
+ A full replacement, as for a license version: its family, pricing type and state are
+ not part of it.
+ """
+ body = {
+ "name": name,
+ "description": description,
+ "isDefault": is_default,
+ **compact({"versionName": version_name, "maxQuantity": max_quantity}),
+ }
+ return await self._api.send("PUT", api_path("addons", addon_slug), Addon, body=body)
+
+ async def delete(self, addon_slug: str) -> None:
+ """Delete an add-on version that was never attached and grants nothing.
+
+ Its family goes with its last version.
+
+ Raises:
+ ConflictError: The version is in use (``DeleteAddon.InUseConflict``).
+ """
+ await self._api.send_empty("DELETE", api_path("addons", addon_slug))
+
+ async def publish(self, addon_slug: str) -> Addon:
+ """Put a ``DRAFT`` version on sale, and return it ``PUBLISHED``."""
+ return await self._api.send("POST", api_path("addons", addon_slug, "publish"), Addon)
+
+ async def archive(self, addon_slug: str) -> Addon:
+ """Withdraw a ``PUBLISHED`` version from sale: instances holding it keep it."""
+ return await self._api.send("POST", api_path("addons", addon_slug, "archive"), Addon)
+
+ async def unarchive(self, addon_slug: str) -> Addon:
+ """Put an ``ARCHIVED`` version back on sale, and return it ``PUBLISHED``."""
+ return await self._api.send("POST", api_path("addons", addon_slug, "unarchive"), Addon)
+
+ async def list_compatibility(self, addon_slug: str) -> CompatibleLicenseFamilies:
+ """Return the license families an instance must be on to attach this version.
+
+ None means it cannot be attached at all.
+ """
+ return await self._api.get(
+ api_path("addons", addon_slug, "compatible-license-families"),
+ CompatibleLicenseFamilies,
+ )
+
+ async def set_compatibility(self, addon_slug: str, family_slug: str) -> None:
+ """Let instances on any version of the license family attach this add-on version."""
+ await self._api.send_empty(
+ "PUT", api_path("addons", addon_slug, "compatible-license-families", family_slug)
+ )
+
+ async def remove_compatibility(self, addon_slug: str, family_slug: str) -> None:
+ """Stop the license family attaching this version; instances holding it keep it."""
+ await self._api.send_empty(
+ "DELETE", api_path("addons", addon_slug, "compatible-license-families", family_slug)
+ )
+
+ async def list_entitlements(self, addon_slug: str) -> builtins.list[AddonEntitlement]:
+ """Return what the add-on version grants per unit of quantity."""
+ return await self._api.get_list(
+ api_path("addons", addon_slug, "entitlements"), AddonEntitlement
+ )
+
+ async def get_entitlement(self, addon_slug: str, entitlement_slug: str) -> AddonEntitlement:
+ """Return one grant of the add-on version."""
+ return await self._api.get(
+ api_path("addons", addon_slug, "entitlements", entitlement_slug), AddonEntitlement
+ )
+
+ async def assign_entitlement(
+ self,
+ addon_slug: str,
+ entitlement_slug: str,
+ value: EntitlementValueInput,
+ *,
+ override_behavior: OverrideBehavior | None = None,
+ overage_percent: int | None = None,
+ ) -> AddonEntitlement:
+ """Grant an entitlement per unit of the add-on version, and return the grant.
+
+ Args:
+ addon_slug: The add-on version that grants.
+ entitlement_slug: The entitlement granted.
+ value: What one unit grants, as for a license grant: a ``bool``, a number, or a
+ mapping for a CONFIG entitlement. A number counts once per unit held.
+ override_behavior: How a number grant combines with the license's: ``ADD``,
+ ``OVERRIDE`` or ``MAX``.
+ overage_percent: The overage the add-on allows: ``-1`` with an unlimited value,
+ ``0`` or more otherwise. Omitted, it inherits the license grant's.
+
+ Raises:
+ ConflictError: An instance with a live subscription holds the version
+ (``AssignAddonEntitlement.BillingActive``).
+ """
+ body = {
+ "entitlementSlug": entitlement_slug,
+ **_grant(value, override_behavior, overage_percent),
+ }
+ return await self._api.send(
+ "POST", api_path("addons", addon_slug, "entitlements"), AddonEntitlement, body=body
+ )
+
+ async def update_entitlement(
+ self,
+ addon_slug: str,
+ entitlement_slug: str,
+ value: EntitlementValueInput,
+ *,
+ override_behavior: OverrideBehavior | None = None,
+ overage_percent: int | None = None,
+ ) -> AddonEntitlement:
+ """Replace a grant's value, override behavior and overage: see :meth:`assign_entitlement`."""
+ return await self._api.send(
+ "PUT",
+ api_path("addons", addon_slug, "entitlements", entitlement_slug),
+ AddonEntitlement,
+ body=_grant(value, override_behavior, overage_percent),
+ )
+
+ async def unassign_entitlement(self, addon_slug: str, entitlement_slug: str) -> None:
+ """Remove a grant from the add-on version.
+
+ Raises:
+ ConflictError: An instance with a live subscription holds the version, or an
+ ``ACTIVE`` price of the version meters the entitlement.
+ """
+ await self._api.send_empty(
+ "DELETE", api_path("addons", addon_slug, "entitlements", entitlement_slug)
+ )
+
+ async def list_prices(
+ self, addon_slug: str, *, status: PriceStatus | None = None
+ ) -> builtins.list[Price]:
+ """Return the prices of the add-on version, in display order; both statuses when omitted."""
+ return await self._api.get_list(
+ api_path("addons", addon_slug, "prices"), Price, params={"status": status}
+ )
+
+ async def create_price(
+ self,
+ addon_slug: str,
+ *,
+ billing_model: BillingModel,
+ currency: str,
+ unit_amount_decimal: str,
+ billing_period: BillingPeriod | None = None,
+ billing_timing: BillingTiming | None = None,
+ metered_entitlement_slug: str | None = None,
+ is_default: bool | None = None,
+ display_label: str | None = None,
+ display_order: int | None = None,
+ ) -> Price:
+ """Add a price to the add-on version, under the rules of a license price.
+
+ A price never changes once created: create another and deprecate this one. See
+ :meth:`AsyncBillingLicensePrices.create` for the arguments.
+ """
+ body = price_body(
+ billing_model=billing_model,
+ currency=currency,
+ unit_amount_decimal=unit_amount_decimal,
+ billing_period=billing_period,
+ billing_timing=billing_timing,
+ metered_entitlement_slug=metered_entitlement_slug,
+ is_default=is_default,
+ display_label=display_label,
+ display_order=display_order,
+ )
+ return await self._api.send(
+ "POST", api_path("addons", addon_slug, "prices"), Price, body=body
+ )
+
+ async def deprecate_price(self, addon_slug: str, price_id: str) -> Price:
+ """Stop offering a price of the add-on version.
+
+ Raises:
+ ConflictError: It is a default price (``DeprecateAddonPrice.IsDefault``), which bills
+ every instance holding the version: retire it through a new version.
+ """
+ return await self._api.send(
+ "POST", api_path("addons", addon_slug, "prices", price_id, "deprecate"), Price
+ )
+
+ async def list_families(self) -> builtins.list[AddonFamily]:
+ """Return every add-on family with its versions, newest family first."""
+ return await self._api.get_list("/addon-families", AddonFamily)
+
+ async def get_family(self, family_slug: str) -> AddonFamily:
+ """Return an add-on family with its versions and its current version."""
+ return await self._api.get(api_path("addon-families", family_slug), AddonFamily)
+
+ async def update_family(
+ self,
+ family_slug: str,
+ *,
+ is_public: bool | None = None,
+ increase_billing: IncreaseBilling | None = None,
+ ) -> AddonFamily:
+ """Change an add-on family's own settings; what is omitted keeps its value.
+
+ Args:
+ family_slug: The family.
+ is_public: List the family's default ``PUBLISHED`` version, with its ``ACTIVE``
+ prices, in the public catalog. Families are private until made public.
+ increase_billing: How a mid-period increase of its add-ons billed in advance is
+ billed, from the next composition: ``TRUE_UP`` or ``NEXT_BOUNDARY``.
+ """
+ body = compact({"isPublic": is_public, "increaseBilling": increase_billing})
+ return await self._api.send(
+ "PATCH", api_path("addon-families", family_slug), AddonFamily, body=body
+ )
+
+
+def _grant(
+ value: EntitlementValueInput,
+ override_behavior: OverrideBehavior | None,
+ overage_percent: int | None,
+) -> dict[str, Any]:
+ return {
+ "value": entitlement_value(value),
+ **compact(
+ {
+ "overrideBehavior": override_behavior,
+ "limitCapExceededOveragePercent": overage_percent,
+ }
+ ),
+ }
diff --git a/src/kaitencloud/_async/resources/billing/customers.py b/src/kaitencloud/_async/resources/billing/customers.py
new file mode 100644
index 0000000..8cc833a
--- /dev/null
+++ b/src/kaitencloud/_async/resources/billing/customers.py
@@ -0,0 +1,102 @@
+# Copyright 2026 KAITEN INC
+# SPDX-License-Identifier: Apache-2.0
+
+from __future__ import annotations
+
+from ...._utils import api_path, compact
+from ....types import (
+ CompletedPaymentMethodSession,
+ CustomerBilling,
+ PaymentMethodSession,
+ PortalSession,
+)
+from .._base import AsyncResource
+
+__all__ = ["AsyncBillingCustomers"]
+
+
+class AsyncBillingCustomers(AsyncResource):
+ """A customer's billing: its payment method, and its payment provider's portal.
+
+ The pages it opens are the provider's own: Kaiten never sees card data.
+ """
+
+ async def get(self, customer_slug: str) -> CustomerBilling:
+ """Return the customer's billing e-mail, and its side in each payment provider.
+
+ Its id there and the labels of its default payment method -- never card data.
+ """
+ return await self._api.get(api_path("customers", customer_slug, "billing"), CustomerBilling)
+
+ async def detach_payment_method(self, customer_slug: str) -> None:
+ """Remove the customer's payment method from its payment provider.
+
+ Raises:
+ ConflictError: A live subscription of the customer is charged automatically
+ (``.InUseByAutomaticCollection``), or there is no payment method.
+ """
+ await self._api.send_empty(
+ "DELETE", api_path("customers", customer_slug, "billing", "payment-method")
+ )
+
+ async def create_payment_method_session(
+ self, customer_slug: str, *, return_url: str, currency: str | None = None
+ ) -> PaymentMethodSession:
+ """Open the payment provider's page where the customer saves a payment method.
+
+ Send the customer to its ``url``. When it comes back to ``return_url``, with
+ ``kaiten_setup_session`` in the query, call :meth:`complete_payment_method_session`.
+
+ Args:
+ customer_slug: The customer.
+ return_url: Where the customer comes back to: https, or http on localhost.
+ currency: The currency the payment method is set up in; required when the customer
+ has no live subscription, whose currency is used otherwise.
+ """
+ return await self._api.send(
+ "POST",
+ api_path("customers", customer_slug, "billing", "payment-method-session"),
+ PaymentMethodSession,
+ body={"returnUrl": return_url, **compact({"currency": currency})},
+ )
+
+ async def complete_payment_method_session(
+ self, customer_slug: str, session_id: str
+ ) -> CompletedPaymentMethodSession:
+ """Check a setup session with the provider, and charge the customer's invoices to it.
+
+ The redirect is never trusted: the provider is asked. Idempotent.
+
+ Raises:
+ NotFoundError: The session is unknown, or another customer's.
+ ConflictError: The customer did not complete it (``.SessionNotComplete``).
+ """
+ return await self._api.send(
+ "POST",
+ api_path(
+ "customers",
+ customer_slug,
+ "billing",
+ "payment-method-session",
+ session_id,
+ "complete",
+ ),
+ CompletedPaymentMethodSession,
+ idempotent=True,
+ )
+
+ async def create_portal_session(self, customer_slug: str, *, return_url: str) -> PortalSession:
+ """Open the payment provider's portal, where the customer manages its payment methods.
+
+ What it changes there reaches Kaiten through the provider sync.
+
+ Args:
+ customer_slug: The customer.
+ return_url: Where the portal sends the customer back: https, or http on localhost.
+ """
+ return await self._api.send(
+ "POST",
+ api_path("customers", customer_slug, "billing", "portal-session"),
+ PortalSession,
+ body={"returnUrl": return_url},
+ )
diff --git a/src/kaitencloud/_async/resources/billing/invoices.py b/src/kaitencloud/_async/resources/billing/invoices.py
new file mode 100644
index 0000000..dc08466
--- /dev/null
+++ b/src/kaitencloud/_async/resources/billing/invoices.py
@@ -0,0 +1,379 @@
+# Copyright 2026 KAITEN INC
+# SPDX-License-Identifier: Apache-2.0
+
+from __future__ import annotations
+
+import builtins
+from collections.abc import Sequence
+from datetime import datetime
+from typing import Any, Literal
+
+from ...._constants import EXPORT_MEDIA_TYPES
+from ...._utils import api_path, check_limit, compact, optional_datetime
+from ....types import (
+ BillingProviderKind,
+ HandoffClaim,
+ HandoffStatus,
+ Invoice,
+ InvoiceKind,
+ InvoiceStatus,
+ InvoiceSummary,
+ QueuedInvoice,
+ UsageReport,
+)
+from .._base import AsyncResource
+
+__all__ = ["AsyncBillingHandoff", "AsyncBillingInvoices"]
+
+
+class AsyncBillingInvoices(AsyncResource):
+ """The organization's invoices, addressed by id: an invoice outlives its instance and customer.
+
+ Amounts are integer minor units, as strings. An invoice is a ``DRAFT`` until it is issued:
+ ``MANUAL`` when the organization collects it, ``PUSHED`` to a payment provider otherwise.
+ """
+
+ async def list(
+ self,
+ *,
+ instance_slug: str | None = None,
+ customer_slug: str | None = None,
+ status: Sequence[InvoiceStatus] | None = None,
+ kind: InvoiceKind | None = None,
+ provider_kind: BillingProviderKind | None = None,
+ overdue: bool | None = None,
+ held: bool | None = None,
+ handoff_status: HandoffStatus | None = None,
+ issued_from: datetime | str | None = None,
+ issued_to: datetime | str | None = None,
+ boundary_from: datetime | str | None = None,
+ boundary_to: datetime | str | None = None,
+ updated_since: datetime | str | None = None,
+ limit: int | None = None,
+ ) -> builtins.list[InvoiceSummary]:
+ """Return the organization's invoices, newest first.
+
+ With ``updated_since``, the invoices changed since then, oldest change first: store the
+ last ``updated_at`` you read and pass it back for an incremental sync.
+
+ Args:
+ instance_slug: Only this instance's invoices.
+ customer_slug: Only this customer's invoices.
+ status: Only invoices in these statuses.
+ kind: ``ACTIVATION``, ``RENEWAL`` or ``FINAL``.
+ provider_kind: Only invoices issued by ``NOOP`` or by a payment provider.
+ overdue: Only issued invoices still unpaid past their due date.
+ held: Only drafts held for their usage journal.
+ handoff_status: ``NOT_REQUIRED``, ``PENDING`` or ``ACKNOWLEDGED``.
+ issued_from: Issued at or after this moment.
+ issued_to: Issued before this moment.
+ boundary_from: Billing a boundary at or after this moment.
+ boundary_to: Billing a boundary before this moment.
+ updated_since: Only invoices changed at or after this moment.
+ limit: The most invoices to return. Every page is walked when omitted.
+ """
+ return await self._api.list_all(
+ "/invoices",
+ InvoiceSummary,
+ params={
+ "instanceSlug": instance_slug,
+ **invoice_query(
+ customer_slug=customer_slug,
+ status=status,
+ kind=kind,
+ provider_kind=provider_kind,
+ overdue=overdue,
+ held=held,
+ handoff_status=handoff_status,
+ issued_from=issued_from,
+ issued_to=issued_to,
+ boundary_from=boundary_from,
+ boundary_to=boundary_to,
+ updated_since=updated_since,
+ ),
+ },
+ limit=check_limit(limit),
+ )
+
+ async def get(self, invoice_id: str) -> Invoice:
+ """Return an invoice with its lines.
+
+ It carries its hold and its handoff, and its customer and instance as they were when it
+ was composed.
+ """
+ return await self._api.get(api_path("invoices", invoice_id), Invoice)
+
+ async def export(
+ self,
+ *,
+ format: Literal["csv", "json"] = "csv",
+ granularity: Literal["line", "invoice"] | None = None,
+ instance_slug: str | None = None,
+ customer_slug: str | None = None,
+ status: Sequence[InvoiceStatus] | None = None,
+ kind: InvoiceKind | None = None,
+ provider_kind: BillingProviderKind | None = None,
+ overdue: bool | None = None,
+ held: bool | None = None,
+ handoff_status: HandoffStatus | None = None,
+ issued_from: datetime | str | None = None,
+ issued_to: datetime | str | None = None,
+ boundary_from: datetime | str | None = None,
+ boundary_to: datetime | str | None = None,
+ updated_since: datetime | str | None = None,
+ ) -> bytes:
+ """Return the invoices the filters of :meth:`list` select, exported.
+
+ Amounts are integer minor units with the currency's exponent beside them. The export
+ carries billing e-mails: treat it as the accounting read it is.
+
+ Args:
+ format: ``"csv"`` (the default), RFC 4180 with a header row, or ``"json"`` for
+ NDJSON, one invoice with its lines per line.
+ granularity: For CSV, ``"line"`` (the default) -- one row per invoice line -- or
+ ``"invoice"``, one row per invoice.
+ instance_slug: Only this instance's invoices. The other filters are
+ :meth:`list`'s.
+ """
+ response = await self._api.request(
+ "GET",
+ "/invoices/export",
+ params={
+ "format": format,
+ "granularity": granularity,
+ "instanceSlug": instance_slug,
+ **invoice_query(
+ customer_slug=customer_slug,
+ status=status,
+ kind=kind,
+ provider_kind=provider_kind,
+ overdue=overdue,
+ held=held,
+ handoff_status=handoff_status,
+ issued_from=issued_from,
+ issued_to=issued_to,
+ boundary_from=boundary_from,
+ boundary_to=boundary_to,
+ updated_since=updated_since,
+ ),
+ },
+ headers={"Accept": EXPORT_MEDIA_TYPES[format]},
+ )
+ return response.content
+
+ async def void(self, invoice_id: str, *, reason: str) -> Invoice:
+ """Void an invoice not yet settled, which frees its boundary for a recompose.
+
+ A payment provider's invoice is voided there first, and here only once it confirms.
+ Voiding a ``VOID`` invoice again answers it unchanged.
+
+ Args:
+ invoice_id: The invoice.
+ reason: Why, 1 to 500 characters.
+ """
+ return await self._api.send(
+ "POST",
+ api_path("invoices", invoice_id, "void"),
+ Invoice,
+ body={"reason": reason},
+ idempotent=True,
+ )
+
+ async def recompose(self, invoice_id: str) -> Invoice:
+ """Compose an invoice again from the usage journal as it is now, keeping its base lines.
+
+ A held draft is rewritten in place; a ``VOID`` invoice gets a replacement for the same
+ boundary, which is returned. Any other invoice must be voided first.
+ """
+ return await self._api.send("POST", api_path("invoices", invoice_id, "recompose"), Invoice)
+
+ async def mark_paid(
+ self,
+ invoice_id: str,
+ *,
+ paid_at: datetime | str | None = None,
+ external_reference: str | None = None,
+ note: str | None = None,
+ ) -> Invoice:
+ """Record that a ``MANUAL`` invoice was paid.
+
+ An invoice still waiting in the handoff queue is acknowledged by the same write. An
+ invoice a payment provider collects is settled there instead.
+
+ Args:
+ invoice_id: The invoice.
+ paid_at: When it was paid, now or earlier. Now when omitted.
+ external_reference: Its number in the accounting system, 1 to 255 characters.
+ note: A note, such as the transfer's reference, kept only in the paid event.
+ """
+ body = compact(
+ {
+ "paidAt": optional_datetime(paid_at),
+ "externalReference": external_reference,
+ "note": note,
+ }
+ )
+ return await self._api.send(
+ "POST", api_path("invoices", invoice_id, "mark-paid"), Invoice, body=body
+ )
+
+ async def write_off(self, invoice_id: str, *, reason: str) -> Invoice:
+ """Give up collecting a ``MANUAL`` invoice: it becomes ``UNCOLLECTIBLE``.
+
+ Args:
+ invoice_id: The invoice.
+ reason: Why, 1 to 500 characters.
+ """
+ return await self._api.send(
+ "POST",
+ api_path("invoices", invoice_id, "write-off"),
+ Invoice,
+ body={"reason": reason},
+ idempotent=True,
+ )
+
+ async def release_hold(self, invoice_id: str, *, reason: str) -> Invoice:
+ """Accept a held invoice as composed, and issue it.
+
+ Args:
+ invoice_id: The invoice.
+ reason: Why, 1 to 500 characters. The release keeps it, with who released it.
+
+ Raises:
+ ConflictError: The hold is ``PROVIDER_ITEM_LIMIT``, which the provider would refuse
+ (``ReleaseInvoiceHold.ProviderItemLimit``).
+ """
+ return await self._api.send(
+ "POST",
+ api_path("invoices", invoice_id, "release-hold"),
+ Invoice,
+ body={"reason": reason},
+ )
+
+ async def retry_push(self, invoice_id: str) -> Invoice:
+ """Put a ``DRAFT`` or ``PUSH_FAILED`` invoice of a payment provider back in the push queue."""
+ return await self._api.send("POST", api_path("invoices", invoice_id, "retry-push"), Invoice)
+
+ async def sync(self, invoice_id: str) -> Invoice:
+ """Read an invoice from its payment provider now, and apply what the provider says."""
+ return await self._api.send("POST", api_path("invoices", invoice_id, "sync"), Invoice)
+
+ async def list_line_reports(
+ self, invoice_id: str, line_id: str, *, limit: int | None = None
+ ) -> builtins.list[UsageReport]:
+ """Return the usage reports a ``USAGE`` or ``OVERAGE`` line was measured from.
+
+ Summed by reset window and floored at 0, they give the line's measured quantity. It
+ answers after the instance was deleted.
+
+ Args:
+ invoice_id: The invoice.
+ line_id: The line, as the invoice's lines carry it.
+ limit: The most reports to return. Every page is walked when omitted.
+ """
+ return await self._api.list_reports(
+ api_path("invoices", invoice_id, "lines", line_id, "reports"),
+ UsageReport,
+ limit=check_limit(limit),
+ )
+
+
+class AsyncBillingHandoff(AsyncResource):
+ """The accounting handoff: the invoices waiting for the organization's accounting system.
+
+ Claim a batch under a lease, book each invoice, then acknowledge it. An invoice not
+ acknowledged before its lease expires is claimed again: the queue delivers at least once,
+ so deduplicate on the invoice id.
+ """
+
+ async def list(
+ self,
+ *,
+ status: Literal["PENDING", "ACKNOWLEDGED"] | None = None,
+ limit: int | None = None,
+ ) -> builtins.list[QueuedInvoice]:
+ """Return the queue, oldest issue first, without leasing anything.
+
+ Args:
+ status: ``PENDING`` (the default) or ``ACKNOWLEDGED``.
+ limit: The most invoices to return. Every page is walked when omitted.
+ """
+ return await self._api.list_all(
+ "/billing/handoff",
+ QueuedInvoice,
+ params={"status": status},
+ limit=check_limit(limit),
+ )
+
+ async def claim(
+ self, *, limit: int | None = None, lease_seconds: int | None = None
+ ) -> HandoffClaim:
+ """Lease the oldest invoices waiting, under one lease, and return them.
+
+ Two consumers claiming at once get different invoices. A claim is not idempotent: a lost
+ response leaves its invoices leased until ``leased_until``, so it is never retried.
+
+ Args:
+ limit: How many invoices to lease, 1 to 100. 25 when omitted.
+ lease_seconds: How long the lease lasts, 60 to 3600 seconds. 900 when omitted.
+ """
+ body = compact({"limit": limit, "leaseSeconds": lease_seconds})
+ return await self._api.send("POST", "/billing/handoff/claim", HandoffClaim, body=body)
+
+ async def ack(
+ self,
+ invoice_id: str,
+ *,
+ lease_id: str | None = None,
+ external_reference: str | None = None,
+ ) -> Invoice:
+ """Record that the accounting system booked an invoice of the queue. Safe to repeat.
+
+ Args:
+ invoice_id: The invoice.
+ lease_id: The claim's lease. When given, an invoice another claim took since is
+ refused (``AckHandoff.LeaseMismatch``).
+ external_reference: The invoice's number in the accounting system. Another
+ reference than the one it was acknowledged under is refused
+ (``AckHandoff.ReferenceMismatch``): take it as already handed off.
+ """
+ body = compact({"leaseId": lease_id, "externalReference": external_reference})
+ return await self._api.send(
+ "POST",
+ api_path("billing", "handoff", invoice_id, "ack"),
+ Invoice,
+ body=body,
+ idempotent=True,
+ )
+
+
+def invoice_query(
+ *,
+ customer_slug: str | None,
+ status: Sequence[InvoiceStatus] | None,
+ kind: InvoiceKind | None,
+ provider_kind: BillingProviderKind | None,
+ overdue: bool | None,
+ held: bool | None,
+ handoff_status: HandoffStatus | None,
+ issued_from: datetime | str | None,
+ issued_to: datetime | str | None,
+ boundary_from: datetime | str | None,
+ boundary_to: datetime | str | None,
+ updated_since: datetime | str | None,
+) -> dict[str, Any]:
+ """The filters every invoice listing takes, as query parameters."""
+ return {
+ "customerSlug": customer_slug,
+ "status": list(status) if status is not None else None,
+ "kind": kind,
+ "providerKind": provider_kind,
+ "overdue": overdue,
+ "held": held,
+ "handoffStatus": handoff_status,
+ "issuedFrom": optional_datetime(issued_from),
+ "issuedTo": optional_datetime(issued_to),
+ "boundaryFrom": optional_datetime(boundary_from),
+ "boundaryTo": optional_datetime(boundary_to),
+ "updatedSince": optional_datetime(updated_since),
+ }
diff --git a/src/kaitencloud/_async/resources/billing/license_prices.py b/src/kaitencloud/_async/resources/billing/license_prices.py
new file mode 100644
index 0000000..c522c70
--- /dev/null
+++ b/src/kaitencloud/_async/resources/billing/license_prices.py
@@ -0,0 +1,192 @@
+# Copyright 2026 KAITEN INC
+# SPDX-License-Identifier: Apache-2.0
+
+from __future__ import annotations
+
+import builtins
+from collections.abc import Mapping
+
+from ...._utils import addon_quantities, api_path, compact, price_body
+from ....types import (
+ BillingModel,
+ BillingPeriod,
+ BillingTiming,
+ InvoicePreview,
+ Price,
+ PriceStatus,
+)
+from .._base import AsyncResource
+
+__all__ = ["AsyncBillingLicensePrices"]
+
+
+class AsyncBillingLicensePrices(AsyncResource):
+ """The prices of license versions.
+
+ A ``FLAT_FEE`` price bills a subscription each period; a ``USAGE_BASED`` price bills the
+ usage of an entitlement, and an ``OVERAGE`` price the usage above the license's limit, per
+ sale unit. Amounts are decimal strings in minor units: ``"4900"`` is 49.00 in a currency
+ with two decimals. The prices of a published version never change.
+ """
+
+ async def list(
+ self,
+ license_slug: str,
+ *,
+ status: PriceStatus | None = None,
+ billing_model: BillingModel | None = None,
+ ) -> builtins.list[Price]:
+ """Return the prices of the license version, in display order; both statuses when omitted."""
+ return await self._api.get_list(
+ api_path("licenses", license_slug, "prices"),
+ Price,
+ params={"status": status, "billingModel": billing_model},
+ )
+
+ async def get(self, license_slug: str, price_id: str) -> Price:
+ """Return one price of the license version."""
+ return await self._api.get(api_path("licenses", license_slug, "prices", price_id), Price)
+
+ async def create(
+ self,
+ license_slug: str,
+ *,
+ billing_model: BillingModel,
+ currency: str,
+ unit_amount_decimal: str,
+ billing_period: BillingPeriod | None = None,
+ billing_timing: BillingTiming | None = None,
+ metered_entitlement_slug: str | None = None,
+ is_default: bool | None = None,
+ display_label: str | None = None,
+ display_order: int | None = None,
+ ) -> Price:
+ """Add a price to the license version, and return it.
+
+ Args:
+ license_slug: The license version.
+ billing_model: ``FLAT_FEE``, ``USAGE_BASED`` or ``OVERAGE``.
+ currency: An ISO 4217 code, upper case. One currency per version.
+ unit_amount_decimal: The amount in minor units, as a decimal string: per period for
+ ``FLAT_FEE``, per sale unit for a metered price.
+ billing_period: ``MONTHLY``, ``QUARTERLY``, ``SEMI_ANNUAL`` or ``ANNUAL``. Required
+ on a ``FLAT_FEE`` price, refused on a metered one.
+ billing_timing: ``ADVANCE`` (the default for ``FLAT_FEE``) or ``ARREARS``, which a
+ metered price must be.
+ metered_entitlement_slug: The entitlement a metered price measures: granted by the
+ version, with a reset period, aggregated by ``SUM`` or ``COUNT``.
+ is_default: Make this ``FLAT_FEE`` price the default of its billing period.
+ display_label: The invoice line's label.
+ display_order: Its position among the version's prices.
+
+ Raises:
+ ConflictError: Another price is already the default of the period
+ (``CreateLicensePrice.DefaultConflict``).
+ """
+ body = price_body(
+ billing_model=billing_model,
+ currency=currency,
+ unit_amount_decimal=unit_amount_decimal,
+ billing_period=billing_period,
+ billing_timing=billing_timing,
+ metered_entitlement_slug=metered_entitlement_slug,
+ is_default=is_default,
+ display_label=display_label,
+ display_order=display_order,
+ )
+ return await self._api.send(
+ "POST", api_path("licenses", license_slug, "prices"), Price, body=body
+ )
+
+ async def update(
+ self,
+ license_slug: str,
+ price_id: str,
+ *,
+ unit_amount_decimal: str | None = None,
+ billing_period: BillingPeriod | None = None,
+ billing_timing: BillingTiming | None = None,
+ metered_entitlement_slug: str | None = None,
+ is_default: bool | None = None,
+ display_label: str | None = None,
+ display_order: int | None = None,
+ ) -> Price:
+ """Edit a price of a ``DRAFT`` version; what is omitted keeps its value.
+
+ Its currency and billing model never change, and the prices of a published version are
+ immutable (``UpdateLicensePrice.VersionNotDraft``).
+ """
+ body = compact(
+ {
+ "unitAmountDecimal": unit_amount_decimal,
+ "billingPeriod": billing_period,
+ "billingTiming": billing_timing,
+ "meteredEntitlementSlug": metered_entitlement_slug,
+ "isDefault": is_default,
+ "displayLabel": display_label,
+ "displayOrder": display_order,
+ }
+ )
+ return await self._api.send(
+ "PATCH", api_path("licenses", license_slug, "prices", price_id), Price, body=body
+ )
+
+ async def deprecate(self, license_slug: str, price_id: str) -> Price:
+ """Retire a price from the catalog, for good.
+
+ It keeps billing the subscriptions pinned to it, is no longer offered, and stops being a
+ default.
+ """
+ return await self._api.send(
+ "POST", api_path("licenses", license_slug, "prices", price_id, "deprecate"), Price
+ )
+
+ async def preview_invoice(
+ self,
+ license_slug: str,
+ *,
+ base_price_id: str | None = None,
+ add_ons: Mapping[str, int] | None = None,
+ sample_usage: Mapping[str, str] | None = None,
+ instance_slug: str | None = None,
+ voucher_code: str | None = None,
+ ) -> InvoicePreview:
+ """Compose the ``RENEWAL`` invoice a subscription to the version would get now.
+
+ Nothing is written, and it works on a ``DRAFT`` version, to check its prices before
+ publishing it.
+
+ Args:
+ license_slug: The license version.
+ base_price_id: The ``FLAT_FEE`` price billed as the base. Defaults to the version's
+ default one.
+ add_ons: Add-on versions priced in, as ``{addon_slug: quantity}``.
+ sample_usage: Usage of the metered entitlements over the period that ends, as
+ ``{entitlement_slug: quantity}`` in decimal strings. An entitlement left out
+ used nothing.
+ instance_slug: Rate this instance's own usage instead of a sample.
+ voucher_code: Apply a ``PRICE`` voucher's discount, consuming nothing.
+ """
+ body = compact(
+ {
+ "basePriceId": base_price_id,
+ "addOns": addon_quantities(add_ons),
+ "sampleUsage": (
+ [
+ {"entitlementSlug": slug, "quantity": quantity}
+ for slug, quantity in sample_usage.items()
+ ]
+ if sample_usage is not None
+ else None
+ ),
+ "instanceSlug": instance_slug,
+ "voucherCode": voucher_code,
+ }
+ )
+ return await self._api.send(
+ "POST",
+ api_path("licenses", license_slug, "invoice-preview"),
+ InvoicePreview,
+ body=body,
+ idempotent=True,
+ )
diff --git a/src/kaitencloud/_async/resources/billing/public.py b/src/kaitencloud/_async/resources/billing/public.py
new file mode 100644
index 0000000..a9ddf13
--- /dev/null
+++ b/src/kaitencloud/_async/resources/billing/public.py
@@ -0,0 +1,330 @@
+# Copyright 2026 KAITEN INC
+# SPDX-License-Identifier: Apache-2.0
+
+from __future__ import annotations
+
+import builtins
+from collections.abc import Mapping
+
+from ...._utils import addon_quantities, api_path, check_limit, compact
+from ....types import (
+ BillingPeriod,
+ CheckoutConfirmation,
+ CheckoutSession,
+ CheckoutSessionStart,
+ CompletedPaymentMethodSession,
+ PaymentMethodSession,
+ PortalSession,
+ PublicCatalog,
+ SessionAddon,
+ SessionCheckout,
+ SessionInvoice,
+ SessionPortal,
+ SessionRedemption,
+ SessionSubscription,
+ SessionVoucherValidity,
+)
+from .._base import AsyncResource
+
+__all__ = ["AsyncBillingPublic", "AsyncBillingSession"]
+
+
+class AsyncBillingPublic(AsyncResource):
+ """What a publishable key (``pk_...``) reaches: the public catalog and anonymous checkouts.
+
+ Build the client with the key as its token -- the SDK sends it as the API requires, in
+ ``X-Kaiten-Publishable-Key`` and never as a bearer token::
+
+ public = AsyncKaitenClient(base_url="https://kaiten.example.com", token="pk_...")
+ catalog = await public.billing.public.get_catalog()
+
+ An anonymous checkout's own routes take the token it was started with (``ksc_...``)
+ instead: build a client with that token to read or confirm it.
+ """
+
+ async def get_catalog(
+ self, *, family_slug: str | None = None, include_addons: bool = True
+ ) -> PublicCatalog:
+ """Return what a pricing page shows: the public plans, their prices and entitlements.
+
+ For each license family the organization made public, its default version with its
+ ``ACTIVE`` prices and user-facing entitlements, and the same for public add-on
+ families. Amounts are in minor units: never sum metered prices into a headline.
+
+ Args:
+ family_slug: Only this license family.
+ include_addons: Also list the public add-ons. True by default.
+ """
+ return await self._api.get(
+ "/public/catalog",
+ PublicCatalog,
+ params={"familySlug": family_slug, "include": None if include_addons else ["none"]},
+ )
+
+ async def create_checkout_session(
+ self,
+ *,
+ turnstile_token: str,
+ price_id: str | None = None,
+ license_slug: str | None = None,
+ billing_period: BillingPeriod | None = None,
+ add_ons: Mapping[str, int] | None = None,
+ email: str | None = None,
+ return_url: str | None = None,
+ voucher_code: str | None = None,
+ trial: bool | None = None,
+ client_reference_id: str | None = None,
+ dry_run: bool | None = None,
+ ) -> CheckoutSessionStart:
+ """Start a checkout for a visitor your backend does not know yet.
+
+ It writes one checkout session and nothing else: the customer, the instance and the
+ subscription are created when it completes. The session's token (``ksc_...``) is in
+ this response only. The key must allow anonymous checkouts.
+
+ Args:
+ turnstile_token: The token the Turnstile widget gave the page. Single-use.
+ price_id: A ``FLAT_FEE`` price of a public plan. Not with ``license_slug``.
+ license_slug: A public plan's version, with ``billing_period``: its default
+ ``ACTIVE`` ``FLAT_FEE`` price of that period.
+ billing_period: ``MONTHLY``, ``QUARTERLY``, ``SEMI_ANNUAL`` or ``ANNUAL``.
+ add_ons: Public add-ons, as ``{addon_slug: quantity}``.
+ email: The buyer's e-mail, which becomes the new customer's billing e-mail. Required
+ unless ``dry_run``.
+ return_url: Where the payment-method page sends the visitor back, on an origin the
+ key allows. Required unless ``dry_run``.
+ voucher_code: A code to apply to the selection.
+ trial: ``False`` declines the license's trial.
+ client_reference_id: Your own reference, echoed on the ``checkout_session`` events.
+ dry_run: Compute what would be due today, and write nothing.
+ """
+ body = {
+ "turnstileToken": turnstile_token,
+ **compact(
+ {
+ "priceId": price_id,
+ "licenseSlug": license_slug,
+ "billingPeriod": billing_period,
+ "addOns": addon_quantities(add_ons),
+ "email": email,
+ "returnUrl": return_url,
+ "voucherCode": voucher_code,
+ "trial": trial,
+ "clientReferenceId": client_reference_id,
+ "dryRun": dry_run,
+ }
+ ),
+ }
+ return await self._api.send(
+ "POST", "/public/checkout-sessions", CheckoutSessionStart, body=body
+ )
+
+ async def get_checkout_session(self, session_id: str) -> CheckoutSession:
+ """Return the checkout session the client's ``ksc_`` token was minted for.
+
+ Its status, what it bought and what is due, and once completed, the customer and
+ instance it created. The token reads it for 24 hours after it closed.
+ """
+ return await self._api.get(
+ api_path("public", "checkout-sessions", session_id), CheckoutSession
+ )
+
+ async def confirm_checkout_session(self, session_id: str) -> CheckoutConfirmation:
+ """Complete the checkout session when nothing has to be captured, or say what is missing.
+
+ A free plan, a trial that needs no payment method, or nothing due completes it at once:
+ ``completed``. Otherwise ``requires_payment_method`` comes with the page where the
+ visitor saves one, and the next confirmation completes it.
+ """
+ return await self._api.send(
+ "POST",
+ api_path("public", "checkout-sessions", session_id, "confirm"),
+ CheckoutConfirmation,
+ )
+
+
+class AsyncBillingSession(AsyncResource):
+ """What a customer session (``kst_...``) reaches: one customer's own billing.
+
+ These are the routes a customer's page calls with the session your backend minted with
+ :meth:`AsyncBillingCustomerSessions.create`. Build the client with the session's token to
+ call them from Python::
+
+ customer = AsyncKaitenClient(base_url="https://kaiten.example.com", token=session.token)
+ portal = await customer.billing.session.get_portal()
+
+ The subscription routes need a session bound to an instance.
+ """
+
+ async def get_portal(self) -> SessionPortal:
+ """Return everything the customer's portal shows, in one read.
+
+ The customer, and for a session bound to an instance its subscription, effective quotas
+ with where they come from, add-ons, vouchers and next invoice; the payment method; and
+ what the portal may offer.
+ """
+ return await self._api.get("/public/session/portal", SessionPortal)
+
+ async def list_invoices(self, *, limit: int | None = None) -> builtins.list[SessionInvoice]:
+ """Return the customer's issued invoices, newest first, with how to pay each.
+
+ Args:
+ limit: The most invoices to return. Every page is walked when omitted.
+ """
+ return await self._api.list_all(
+ "/public/session/invoices", SessionInvoice, limit=check_limit(limit)
+ )
+
+ async def checkout(
+ self,
+ *,
+ license_price_id: str,
+ add_ons: Mapping[str, int] | None = None,
+ voucher_code: str | None = None,
+ billing_email: str | None = None,
+ return_url: str | None = None,
+ setup_session_id: str | None = None,
+ trial: bool | None = None,
+ dry_run: bool | None = None,
+ ) -> SessionCheckout:
+ """Subscribe the session's instance to a price of the public catalog, in one call.
+
+ ``status`` says what happened: ``preview`` for a dry run, ``requires_payment_method``
+ with the page where the customer saves one -- call again with its ``setup_session_id``
+ afterwards -- or ``subscribed``.
+
+ Args:
+ license_price_id: An ``ACTIVE`` flat-fee price of a public plan.
+ add_ons: Public add-ons that fit the plan, as ``{addon_slug: quantity}``.
+ voucher_code: A code to redeem with the checkout.
+ billing_email: Where invoices go; required when the customer has none.
+ return_url: Where the payment-method page sends the customer back.
+ setup_session_id: The setup session a ``requires_payment_method`` answer gave.
+ trial: ``False`` skips the plan's trial.
+ dry_run: Answer what the checkout would bill today, and change nothing.
+ """
+ body = {
+ "licensePriceId": license_price_id,
+ **compact(
+ {
+ "addOns": addon_quantities(add_ons),
+ "voucherCode": voucher_code,
+ "billingEmail": billing_email,
+ "returnUrl": return_url,
+ "setupSessionId": setup_session_id,
+ "trial": trial,
+ "dryRun": dry_run,
+ }
+ ),
+ }
+ return await self._api.send("POST", "/public/session/checkout", SessionCheckout, body=body)
+
+ async def cancel(self, *, reason: str | None = None) -> SessionSubscription:
+ """Cancel the instance's subscription at the end of the period paid for.
+
+ It keeps billing and granting until then. A trial ends at once. Repeating it changes
+ nothing.
+
+ Args:
+ reason: Why the customer cancels, up to 500 characters; you see it on the
+ subscription.
+ """
+ return await self._api.send(
+ "POST",
+ "/public/session/billing/cancel",
+ SessionSubscription,
+ body=compact({"reason": reason}),
+ idempotent=True,
+ )
+
+ async def reactivate(self) -> SessionSubscription:
+ """Take back a cancellation scheduled for the period's end, before it."""
+ return await self._api.send(
+ "POST", "/public/session/billing/reactivate", SessionSubscription
+ )
+
+ async def set_addon_quantity(self, addon_slug: str, quantity: int) -> SessionAddon:
+ """Attach a public add-on to the instance, change its quantity, or remove it with ``0``.
+
+ With a live subscription, a lower quantity is scheduled for the next renewal:
+ ``pending_quantity`` shows it.
+ """
+ return await self._api.send(
+ "PUT",
+ api_path("public", "session", "addons", addon_slug),
+ SessionAddon,
+ body={"quantity": quantity},
+ )
+
+ async def create_payment_method_session(
+ self, *, return_url: str, currency: str | None = None
+ ) -> PaymentMethodSession:
+ """Open the payment provider's page where the customer saves a payment method.
+
+ Args:
+ return_url: Where the page sends the customer back, on an origin one of the
+ organization's publishable keys allows.
+ currency: Required when the customer has no live subscription.
+ """
+ return await self._api.send(
+ "POST",
+ "/public/session/billing/payment-method-session",
+ PaymentMethodSession,
+ body={"returnUrl": return_url, **compact({"currency": currency})},
+ )
+
+ async def complete_payment_method_session(
+ self, session_id: str
+ ) -> CompletedPaymentMethodSession:
+ """Check a setup session with the provider, and charge the customer's invoices to it.
+
+ Safe to repeat.
+ """
+ return await self._api.send(
+ "POST",
+ api_path(
+ "public", "session", "billing", "payment-method-session", session_id, "complete"
+ ),
+ CompletedPaymentMethodSession,
+ idempotent=True,
+ )
+
+ async def create_portal_session(self, *, return_url: str) -> PortalSession:
+ """Open the payment provider's portal for the customer.
+
+ Args:
+ return_url: Where the portal sends the customer back, on an origin one of the
+ organization's publishable keys allows.
+ """
+ return await self._api.send(
+ "POST",
+ "/public/session/billing/portal-session",
+ PortalSession,
+ body={"returnUrl": return_url},
+ )
+
+ async def validate_voucher(
+ self, code: str, *, license_price_id: str | None = None
+ ) -> SessionVoucherValidity:
+ """Say whether a code would apply to the customer, with what it gives. Nothing is redeemed.
+
+ Args:
+ code: The code as the customer typed it.
+ license_price_id: The price about to be checked out.
+ """
+ body = {"code": code, **compact({"licensePriceId": license_price_id})}
+ return await self._api.send(
+ "POST", "/public/session/vouchers/validate", SessionVoucherValidity, body=body
+ )
+
+ async def redeem_voucher(self, code: str, *, dry_run: bool | None = None) -> SessionRedemption:
+ """Redeem a code on the instance, and return the redemption -- never the code.
+
+ Args:
+ code: The code as the customer typed it.
+ dry_run: Answer the redemption it would make, and write nothing.
+ """
+ body = {"code": code, **compact({"dryRun": dry_run})}
+ return await self._api.send(
+ "POST", "/public/session/vouchers/redeem", SessionRedemption, body=body
+ )
diff --git a/src/kaitencloud/_async/resources/billing/sessions.py b/src/kaitencloud/_async/resources/billing/sessions.py
new file mode 100644
index 0000000..c437834
--- /dev/null
+++ b/src/kaitencloud/_async/resources/billing/sessions.py
@@ -0,0 +1,184 @@
+# Copyright 2026 KAITEN INC
+# SPDX-License-Identifier: Apache-2.0
+
+from __future__ import annotations
+
+import builtins
+from collections.abc import Sequence
+
+from ...._utils import api_path, check_limit, compact
+from ....types import (
+ CheckoutSessionAdmin,
+ CheckoutSessionStatus,
+ CreatedCustomerSession,
+ PublishableKey,
+ PublishableKeyCreated,
+)
+from .._base import AsyncResource
+
+__all__ = [
+ "AsyncBillingCheckoutSessions",
+ "AsyncBillingCustomerSessions",
+ "AsyncBillingPublishableKeys",
+]
+
+
+class AsyncBillingPublishableKeys(AsyncResource):
+ """Publishable keys (``pk_...``): what a web page reads the public catalog with.
+
+ A publishable key is not a secret: it ships in pages, and a browser may send it only from
+ one of its allowed origins. With ``anonymous_checkout``, it also starts checkouts for
+ visitors your backend does not know yet.
+ """
+
+ async def list(self, *, include_revoked: bool = False) -> builtins.list[PublishableKey]:
+ """Return the organization's keys, newest first: their last characters, never the keys."""
+ return await self._api.list_all(
+ "/publishable-keys", PublishableKey, params={"includeRevoked": include_revoked or None}
+ )
+
+ async def create(
+ self,
+ *,
+ label: str,
+ allowed_origins: Sequence[str],
+ anonymous_checkout: bool | None = None,
+ turnstile_site_key: str | None = None,
+ turnstile_secret: str | None = None,
+ ) -> PublishableKeyCreated:
+ """Issue a publishable key, and return it with the key, which exists in this response only.
+
+ Args:
+ label: What the key is for.
+ allowed_origins: The browser origins allowed to send it, as
+ ``scheme://host[:port]``: https, or http on localhost. Empty, no browser may;
+ a request without an ``Origin`` header -- server-side rendering -- still may.
+ anonymous_checkout: Let the key start anonymous checkouts. It needs a Turnstile site
+ key and secret.
+ turnstile_site_key: The Turnstile site key the page renders its widget with.
+ turnstile_secret: The Turnstile secret the server verifies tokens with, kept in
+ Vault and never returned.
+ """
+ body = {
+ "label": label,
+ "allowedOrigins": list(allowed_origins),
+ **compact(
+ {
+ "anonymousCheckout": anonymous_checkout,
+ "turnstileSiteKey": turnstile_site_key,
+ "turnstileSecret": turnstile_secret,
+ }
+ ),
+ }
+ return await self._api.send("POST", "/publishable-keys", PublishableKeyCreated, body=body)
+
+ async def update(
+ self,
+ key_id: str,
+ *,
+ label: str | None = None,
+ allowed_origins: Sequence[str] | None = None,
+ anonymous_checkout: bool | None = None,
+ turnstile_site_key: str | None = None,
+ turnstile_secret: str | None = None,
+ ) -> PublishableKey:
+ """Change a live key; what is omitted keeps its value, and ``""`` clears a Turnstile value.
+
+ Raises:
+ ConflictError: The key is revoked (``UpdatePublishableKey.Revoked``).
+ """
+ body = compact(
+ {
+ "label": label,
+ "allowedOrigins": list(allowed_origins) if allowed_origins is not None else None,
+ "anonymousCheckout": anonymous_checkout,
+ "turnstileSiteKey": turnstile_site_key,
+ "turnstileSecret": turnstile_secret,
+ }
+ )
+ return await self._api.send(
+ "PATCH", api_path("publishable-keys", key_id), PublishableKey, body=body
+ )
+
+ async def revoke(self, key_id: str) -> PublishableKey:
+ """Revoke a key, for good, with its anonymous checkout sessions. Idempotent."""
+ return await self._api.send(
+ "POST", api_path("publishable-keys", key_id, "revoke"), PublishableKey, idempotent=True
+ )
+
+
+class AsyncBillingCustomerSessions(AsyncResource):
+ """Customer sessions (``kst_...``): what lets a customer's browser act on its own billing.
+
+ Mint one from your backend, where the customer is signed in, and hand its token to the
+ page, which calls the routes of :attr:`AsyncBilling.session` with it.
+ """
+
+ async def create(
+ self,
+ *,
+ customer_slug: str,
+ instance_slug: str | None = None,
+ ttl_seconds: int | None = None,
+ ) -> CreatedCustomerSession:
+ """Mint a session for a customer, and return it with its token, shown this once.
+
+ What the session writes is attributed to the credential that minted it.
+
+ Args:
+ customer_slug: The customer the session acts for.
+ instance_slug: Bind it to one of the customer's instances, which checkout, cancel
+ and the other subscription routes need.
+ ttl_seconds: How long it lives, 300 to 3600 seconds. 30 minutes when omitted.
+ """
+ body = {
+ "customerSlug": customer_slug,
+ **compact({"instanceSlug": instance_slug, "ttlSeconds": ttl_seconds}),
+ }
+ return await self._api.send("POST", "/customer-sessions", CreatedCustomerSession, body=body)
+
+ async def revoke(self, session_id: str) -> None:
+ """Stop a session authenticating, from its next request: on sign-out, say. Idempotent."""
+ await self._api.send_empty(
+ "POST", api_path("customer-sessions", session_id, "revoke"), idempotent=True
+ )
+
+
+class AsyncBillingCheckoutSessions(AsyncResource):
+ """Anonymous checkouts, as the organization reads them: never their token or voucher code.
+
+ It is how a buyer is linked to your own account when the ``checkout_session`` events were
+ missed.
+ """
+
+ async def list(
+ self,
+ *,
+ status: Sequence[CheckoutSessionStatus] | None = None,
+ email: str | None = None,
+ client_reference_id: str | None = None,
+ limit: int | None = None,
+ ) -> builtins.list[CheckoutSessionAdmin]:
+ """Return the organization's anonymous checkouts, newest first.
+
+ Args:
+ status: Only sessions in these statuses. A session ``OPEN`` past its expiry is
+ ``ABANDONED``.
+ email: Only the visitor with this e-mail, in any letter case.
+ client_reference_id: Only the sessions started with this reference of yours.
+ limit: The most sessions to return. Every page is walked when omitted.
+ """
+ return await self._api.list_all(
+ "/checkout-sessions",
+ CheckoutSessionAdmin,
+ params={
+ "status": list(status) if status is not None else None,
+ "email": email,
+ "clientReferenceId": client_reference_id,
+ },
+ limit=check_limit(limit),
+ )
+
+ async def get(self, session_id: str) -> CheckoutSessionAdmin:
+ """Return an anonymous checkout. One closed 90 days ago is gone."""
+ return await self._api.get(api_path("checkout-sessions", session_id), CheckoutSessionAdmin)
diff --git a/src/kaitencloud/_async/resources/billing/subscriptions.py b/src/kaitencloud/_async/resources/billing/subscriptions.py
new file mode 100644
index 0000000..01f58d0
--- /dev/null
+++ b/src/kaitencloud/_async/resources/billing/subscriptions.py
@@ -0,0 +1,346 @@
+# Copyright 2026 KAITEN INC
+# SPDX-License-Identifier: Apache-2.0
+
+from __future__ import annotations
+
+import builtins
+from collections.abc import Mapping, Sequence
+from datetime import datetime
+from typing import Any
+
+from ...._types import NOT_GIVEN, NotGiven
+from ...._utils import addon_quantities, api_path, check_limit, compact, optional_datetime
+from ....types import (
+ BillingProviderKind,
+ CanceledSubscription,
+ CancellationMode,
+ CollectionMethod,
+ HandoffStatus,
+ InstanceAddon,
+ InstanceBilling,
+ InvoiceKind,
+ InvoicePreview,
+ InvoiceStatus,
+ InvoiceSummary,
+ Redemption,
+ RedemptionStatus,
+ StartedSubscription,
+)
+from .._base import AsyncResource
+from .invoices import invoice_query
+
+__all__ = [
+ "AsyncBillingInstanceAddons",
+ "AsyncBillingInstanceVouchers",
+ "AsyncBillingSubscriptions",
+]
+
+
+class AsyncBillingSubscriptions(AsyncResource):
+ """Instances' subscriptions: what bills an instance, period after period.
+
+ A subscription is pinned to a ``FLAT_FEE`` price of the instance's license version. Each
+ period closes into a ``RENEWAL`` invoice; a base price billed in advance issues the first
+ period's ``ACTIVATION`` invoice at once. While it is live, the instance's customer and
+ license cannot change.
+ """
+
+ async def get(self, instance_slug: str) -> InstanceBilling:
+ """Return the instance's subscription, live or ``CANCELED``.
+
+ Raises:
+ NotFoundError: The instance was never subscribed.
+ """
+ return await self._api.get(api_path("instances", instance_slug, "billing"), InstanceBilling)
+
+ async def subscribe(
+ self,
+ instance_slug: str,
+ *,
+ base_price_id: str,
+ add_ons: Mapping[str, int] | None = None,
+ voucher_code: str | None = None,
+ trial_days: int | None = None,
+ start_at: datetime | str | None = None,
+ provider_kind: BillingProviderKind | None = None,
+ collection_method: CollectionMethod | None = None,
+ days_until_due: int | None = None,
+ ) -> StartedSubscription:
+ """Start billing an instance, and return its subscription with its first invoice.
+
+ A ``CANCELED`` subscription is subscribed again.
+
+ Args:
+ instance_slug: The instance.
+ base_price_id: An ``ACTIVE`` ``FLAT_FEE`` price of the instance's license version,
+ which must be ``PUBLISHED``. The subscription stays pinned to it.
+ add_ons: Add-ons to attach with it, as ``{addon_slug: quantity}``; the
+ ``ACTIVATION`` invoice bills them.
+ voucher_code: A voucher to redeem with the subscription.
+ trial_days: Days of trial, 1 to 365: nothing is billed, and no usage counts, until
+ it ends.
+ start_at: What periods are counted from: now when omitted, at most one billing
+ period ago otherwise.
+ provider_kind: Who issues and collects the invoices: ``NOOP`` (the default), the
+ organization itself through the handoff queue, or a connected provider.
+ collection_method: ``SEND_INVOICE`` or ``CHARGE_AUTOMATICALLY``. The
+ organization's default, read at each invoice, when omitted.
+ days_until_due: Payment terms in days, 0 to 365. The organization's default when
+ omitted.
+ """
+ body = {
+ "basePriceId": base_price_id,
+ **compact(
+ {
+ "addOns": addon_quantities(add_ons),
+ "voucherCode": voucher_code,
+ "trialDays": trial_days,
+ "startAt": optional_datetime(start_at),
+ "providerKind": provider_kind,
+ "collectionMethod": collection_method,
+ "daysUntilDue": days_until_due,
+ }
+ ),
+ }
+ return await self._api.send(
+ "POST", api_path("instances", instance_slug, "billing"), StartedSubscription, body=body
+ )
+
+ async def update(
+ self,
+ instance_slug: str,
+ *,
+ provider_kind: BillingProviderKind | None = None,
+ collection_method: CollectionMethod | NotGiven | None = NOT_GIVEN,
+ days_until_due: int | NotGiven | None = NOT_GIVEN,
+ ) -> InstanceBilling:
+ """Change a subscription's provider and terms from its next invoice on.
+
+ What is not passed keeps its value; ``None`` restores the organization's default.
+ Invoices already composed keep their own terms and provider.
+
+ Args:
+ instance_slug: The instance.
+ provider_kind: Who issues and collects the invoices from the next one on.
+ collection_method: ``SEND_INVOICE`` or ``CHARGE_AUTOMATICALLY``.
+ days_until_due: Payment terms in days, 0 to 365.
+ """
+ body: dict[str, Any] = compact({"providerKind": provider_kind})
+ if not isinstance(collection_method, NotGiven):
+ body["collectionMethod"] = collection_method
+ if not isinstance(days_until_due, NotGiven):
+ body["daysUntilDue"] = days_until_due
+ return await self._api.send(
+ "PATCH", api_path("instances", instance_slug, "billing"), InstanceBilling, body=body
+ )
+
+ async def cancel(
+ self,
+ instance_slug: str,
+ *,
+ mode: CancellationMode | None = None,
+ reason: str | None = None,
+ ) -> CanceledSubscription:
+ """Cancel a subscription.
+
+ A trial ends at once, with no invoice. Otherwise ``AT_PERIOD_END`` (the default) lets the
+ period paid for run out, then issues the ``FINAL`` invoice; ``IMMEDIATE`` issues it now.
+ Cancellation changes billing only: entitlements and license dates stay yours to change.
+
+ Args:
+ instance_slug: The instance.
+ mode: ``AT_PERIOD_END`` or ``IMMEDIATE``.
+ reason: Why, up to 500 characters.
+ """
+ return await self._api.send(
+ "POST",
+ api_path("instances", instance_slug, "billing", "cancel"),
+ CanceledSubscription,
+ body=compact({"mode": mode, "reason": reason}),
+ )
+
+ async def reactivate(self, instance_slug: str) -> InstanceBilling:
+ """Take back a cancellation scheduled for the period's end, before it."""
+ return await self._api.send(
+ "POST", api_path("instances", instance_slug, "billing", "reactivate"), InstanceBilling
+ )
+
+ async def schedule_plan_change(
+ self, instance_slug: str, license_price_id: str
+ ) -> InstanceBilling:
+ """Schedule a move to another ``FLAT_FEE`` price for the next boundary.
+
+ That boundary's ``RENEWAL`` bills the old plan's arrears and the new plan's advance, and
+ moves the instance to the new version, without proration. Scheduling another target
+ replaces the change.
+
+ Args:
+ instance_slug: The instance.
+ license_price_id: An ``ACTIVE`` ``FLAT_FEE`` price of a ``PUBLISHED`` license
+ version, in the subscription's currency.
+ """
+ return await self._api.send(
+ "PUT",
+ api_path("instances", instance_slug, "billing", "scheduled-change"),
+ InstanceBilling,
+ body={"licensePriceId": license_price_id},
+ )
+
+ async def cancel_plan_change(self, instance_slug: str) -> InstanceBilling:
+ """Drop the plan change scheduled for the next boundary."""
+ return await self._api.send(
+ "DELETE",
+ api_path("instances", instance_slug, "billing", "scheduled-change"),
+ InstanceBilling,
+ )
+
+ async def get_upcoming_invoice(self, instance_slug: str) -> InvoicePreview:
+ """Return the invoice the next boundary will issue, from the usage so far, writing nothing.
+
+ ``would_hold`` lists the meters whose usage journal fails a check and would hold it.
+ """
+ return await self._api.get(
+ api_path("instances", instance_slug, "billing", "upcoming-invoice"), InvoicePreview
+ )
+
+ async def list_invoices(
+ self,
+ instance_slug: str,
+ *,
+ customer_slug: str | None = None,
+ status: Sequence[InvoiceStatus] | None = None,
+ kind: InvoiceKind | None = None,
+ provider_kind: BillingProviderKind | None = None,
+ overdue: bool | None = None,
+ held: bool | None = None,
+ handoff_status: HandoffStatus | None = None,
+ issued_from: datetime | str | None = None,
+ issued_to: datetime | str | None = None,
+ boundary_from: datetime | str | None = None,
+ boundary_to: datetime | str | None = None,
+ updated_since: datetime | str | None = None,
+ limit: int | None = None,
+ ) -> builtins.list[InvoiceSummary]:
+ """Return the invoices of an instance's subscription, across every time it was subscribed.
+
+ The filters are those of :meth:`AsyncBillingInvoices.list`.
+ """
+ return await self._api.list_all(
+ api_path("instances", instance_slug, "invoices"),
+ InvoiceSummary,
+ params=invoice_query(
+ customer_slug=customer_slug,
+ status=status,
+ kind=kind,
+ provider_kind=provider_kind,
+ overdue=overdue,
+ held=held,
+ handoff_status=handoff_status,
+ issued_from=issued_from,
+ issued_to=issued_to,
+ boundary_from=boundary_from,
+ boundary_to=boundary_to,
+ updated_since=updated_since,
+ ),
+ limit=check_limit(limit),
+ )
+
+
+class AsyncBillingInstanceAddons(AsyncResource):
+ """The add-ons an instance holds, by the unit.
+
+ Their entitlements apply at once; a subscription bills the quantity held at each boundary,
+ without proration. An instance holds one version per add-on family.
+ """
+
+ async def list(
+ self, instance_slug: str, *, include_removed: bool = False
+ ) -> builtins.list[InstanceAddon]:
+ """Return the add-ons the instance holds, in attachment order, with the prices billed.
+
+ Args:
+ instance_slug: The instance.
+ include_removed: Also return the add-ons removed from it.
+ """
+ return await self._api.get_list(
+ api_path("instances", instance_slug, "addons"),
+ InstanceAddon,
+ params={"includeRemoved": include_removed or None},
+ )
+
+ async def attach(
+ self, instance_slug: str, addon_slug: str, *, quantity: int | None = None
+ ) -> InstanceAddon:
+ """Give the instance an add-on version, and return the attachment.
+
+ Moving to another version of the same add-on is a :meth:`detach`, then an attach.
+ """
+ body = {"addonSlug": addon_slug, **compact({"quantity": quantity})}
+ return await self._api.send(
+ "POST", api_path("instances", instance_slug, "addons"), InstanceAddon, body=body
+ )
+
+ async def set_quantity(
+ self, instance_slug: str, addon_slug: str, quantity: int
+ ) -> InstanceAddon:
+ """Change how many units of an add-on the instance holds, at once, a decrease included.
+
+ It cancels a decrease the customer scheduled, even to the quantity held.
+ """
+ return await self._api.send(
+ "PATCH",
+ api_path("instances", instance_slug, "addons", addon_slug),
+ InstanceAddon,
+ body={"quantity": quantity},
+ idempotent=True,
+ )
+
+ async def detach(self, instance_slug: str, addon_slug: str) -> None:
+ """Remove an add-on from the instance, at once. Its entitlements drop; nothing is refunded."""
+ await self._api.send_empty(
+ "DELETE", api_path("instances", instance_slug, "addons", addon_slug)
+ )
+
+
+class AsyncBillingInstanceVouchers(AsyncResource):
+ """The vouchers an instance redeemed."""
+
+ async def list(
+ self, instance_slug: str, *, status: RedemptionStatus | None = None
+ ) -> builtins.list[Redemption]:
+ """Return the instance's redemptions, newest first."""
+ return await self._api.get_list(
+ api_path("instances", instance_slug, "vouchers"), Redemption, params={"status": status}
+ )
+
+ async def redeem(self, instance_slug: str, code: str) -> Redemption:
+ """Redeem a voucher code for the instance, and return the redemption.
+
+ A boost applies from the next read or report, for its duration; a ``PRICE`` voucher
+ discounts the subscription's next invoices.
+ """
+ return await self._api.send(
+ "POST",
+ api_path("instances", instance_slug, "vouchers", "redeem"),
+ Redemption,
+ body={"code": code},
+ )
+
+ async def revoke(
+ self, instance_slug: str, instance_voucher_id: str, *, reason: str
+ ) -> Redemption:
+ """Stop a redemption applying: a boost ends at once, a discount applies to no further invoice.
+
+ Invoices already issued are untouched.
+
+ Args:
+ instance_slug: The instance.
+ instance_voucher_id: The redemption's id.
+ reason: Why, 1 to 500 characters.
+ """
+ return await self._api.send(
+ "POST",
+ api_path("instances", instance_slug, "vouchers", instance_voucher_id, "revoke"),
+ Redemption,
+ body={"reason": reason},
+ )
diff --git a/src/kaitencloud/_async/resources/billing/vouchers.py b/src/kaitencloud/_async/resources/billing/vouchers.py
new file mode 100644
index 0000000..b5171b3
--- /dev/null
+++ b/src/kaitencloud/_async/resources/billing/vouchers.py
@@ -0,0 +1,321 @@
+# Copyright 2026 KAITEN INC
+# SPDX-License-Identifier: Apache-2.0
+
+from __future__ import annotations
+
+import builtins
+from collections.abc import Mapping, Sequence
+from datetime import datetime
+from typing import Any, Literal
+
+from ...._utils import api_path, check_limit, compact, optional_datetime
+from ....types import (
+ DiscountAppliesTo,
+ DiscountType,
+ Redemption,
+ RedemptionRulesParam,
+ Validity,
+ Voucher,
+ VoucherDuration,
+ VoucherGrantParam,
+ VoucherStatus,
+ VoucherType,
+)
+from .._base import AsyncResource
+
+__all__ = ["AsyncBillingVouchers"]
+
+
+class AsyncBillingVouchers(AsyncResource):
+ """The organization's vouchers: codes that discount a price or boost entitlements.
+
+ A ``PRICE`` voucher discounts a subscription's invoices; an ``ENTITLEMENT_BOOST`` changes
+ entitlements while it applies; a ``FLAG_GRANT`` serves flag variants; a ``COMPOSITE`` does
+ several. A voucher is created ``DRAFT`` and becomes redeemable once published.
+ """
+
+ async def list(
+ self,
+ *,
+ status: VoucherStatus | None = None,
+ voucher_type: Literal["PRICE", "ENTITLEMENT_BOOST"] | None = None,
+ restricted_customer_slug: str | None = None,
+ ) -> builtins.list[Voucher]:
+ """Return the organization's vouchers, newest first, with their codes, walking all pages."""
+ return await self._api.list_all(
+ "/vouchers",
+ Voucher,
+ params={
+ "status": status,
+ "voucherType": voucher_type,
+ "restrictedCustomerSlug": restricted_customer_slug,
+ },
+ )
+
+ async def get(self, voucher_id: str) -> Voucher:
+ """Return a voucher, with its code."""
+ return await self._api.get(api_path("vouchers", voucher_id), Voucher)
+
+ async def create(
+ self,
+ *,
+ name: str,
+ voucher_type: VoucherType,
+ duration: VoucherDuration,
+ code: str | None = None,
+ description: str | None = None,
+ duration_in_periods: int | None = None,
+ starts_at: datetime | str | None = None,
+ expires_at: datetime | str | None = None,
+ max_redemptions: int | None = None,
+ restricted_customer_slug: str | None = None,
+ price_discount_type: DiscountType | None = None,
+ price_discount_value: str | None = None,
+ currency: str | None = None,
+ price_applies_to: DiscountAppliesTo | None = None,
+ applicable_license_ids: Sequence[str] | None = None,
+ applicable_license_price_ids: Sequence[str] | None = None,
+ applicable_addon_ids: Sequence[str] | None = None,
+ applicable_addon_price_ids: Sequence[str] | None = None,
+ grants: Sequence[VoucherGrantParam] | None = None,
+ flag_grants: Mapping[str, str] | None = None,
+ redemption_rules: RedemptionRulesParam | None = None,
+ ) -> Voucher:
+ """Create a ``DRAFT`` voucher, and return it with its code.
+
+ Args:
+ name: The voucher's name.
+ voucher_type: ``PRICE``, ``ENTITLEMENT_BOOST``, ``FLAG_GRANT`` or ``COMPOSITE``.
+ duration: How long a discount lasts: ``ONE_TIME``, ``REPEATING`` or ``FOREVER``.
+ code: 8 to 64 of ``[A-Za-z0-9_-]``, matched without case or separators. Generated
+ when omitted.
+ description: A description.
+ duration_in_periods: How many periods a ``REPEATING`` discount lasts.
+ starts_at: When it can be redeemed from.
+ expires_at: When it can no longer be redeemed.
+ max_redemptions: How many times it can be redeemed.
+ restricted_customer_slug: The only customer that may redeem it.
+ price_discount_type: ``PERCENTAGE`` or ``FIXED_AMOUNT``.
+ price_discount_value: A percentage in (0, 100], or an amount in minor units, as a
+ string.
+ currency: Required with ``FIXED_AMOUNT``, refused otherwise.
+ price_applies_to: What the discount applies to: ``LICENSE_BASE``, ``ADDONS``,
+ ``BOTH``, or ``SELECTED_PRICES``.
+ applicable_license_ids: The license versions it applies to.
+ applicable_license_price_ids: With ``SELECTED_PRICES``, the license prices.
+ applicable_addon_ids: The add-on versions it applies to.
+ applicable_addon_price_ids: With ``SELECTED_PRICES``, the add-on prices.
+ grants: What an ``ENTITLEMENT_BOOST`` grants, one entitlement each.
+ flag_grants: What a ``FLAG_GRANT`` serves, as ``{flag_slug: variant}``.
+ redemption_rules: Who may redeem it, beyond its dates and count.
+ """
+ body = _voucher(
+ name=name,
+ voucher_type=voucher_type,
+ duration=duration,
+ code=code,
+ description=description,
+ duration_in_periods=duration_in_periods,
+ starts_at=starts_at,
+ expires_at=expires_at,
+ max_redemptions=max_redemptions,
+ restricted_customer_slug=restricted_customer_slug,
+ price_discount_type=price_discount_type,
+ price_discount_value=price_discount_value,
+ currency=currency,
+ price_applies_to=price_applies_to,
+ applicable_license_ids=applicable_license_ids,
+ applicable_license_price_ids=applicable_license_price_ids,
+ applicable_addon_ids=applicable_addon_ids,
+ applicable_addon_price_ids=applicable_addon_price_ids,
+ grants=grants,
+ flag_grants=flag_grants,
+ redemption_rules=redemption_rules,
+ )
+ return await self._api.send("POST", "/vouchers", Voucher, body=body)
+
+ async def update(
+ self,
+ voucher_id: str,
+ *,
+ name: str,
+ voucher_type: VoucherType,
+ duration: VoucherDuration,
+ code: str | None = None,
+ description: str | None = None,
+ duration_in_periods: int | None = None,
+ starts_at: datetime | str | None = None,
+ expires_at: datetime | str | None = None,
+ max_redemptions: int | None = None,
+ restricted_customer_slug: str | None = None,
+ price_discount_type: DiscountType | None = None,
+ price_discount_value: str | None = None,
+ currency: str | None = None,
+ price_applies_to: DiscountAppliesTo | None = None,
+ applicable_license_ids: Sequence[str] | None = None,
+ applicable_license_price_ids: Sequence[str] | None = None,
+ applicable_addon_ids: Sequence[str] | None = None,
+ applicable_addon_price_ids: Sequence[str] | None = None,
+ grants: Sequence[VoucherGrantParam] | None = None,
+ flag_grants: Mapping[str, str] | None = None,
+ redemption_rules: RedemptionRulesParam | None = None,
+ ) -> Voucher:
+ """Replace a voucher, and return it. The arguments are :meth:`create`'s.
+
+ A ``DRAFT`` is replaced whole, but its type. An ``ACTIVE`` voucher takes a new name,
+ description, expiry and redemption count only; its other members must stay as they are
+ (``UpdateVoucher.NotEditable``).
+ """
+ body = _voucher(
+ name=name,
+ voucher_type=voucher_type,
+ duration=duration,
+ code=code,
+ description=description,
+ duration_in_periods=duration_in_periods,
+ starts_at=starts_at,
+ expires_at=expires_at,
+ max_redemptions=max_redemptions,
+ restricted_customer_slug=restricted_customer_slug,
+ price_discount_type=price_discount_type,
+ price_discount_value=price_discount_value,
+ currency=currency,
+ price_applies_to=price_applies_to,
+ applicable_license_ids=applicable_license_ids,
+ applicable_license_price_ids=applicable_license_price_ids,
+ applicable_addon_ids=applicable_addon_ids,
+ applicable_addon_price_ids=applicable_addon_price_ids,
+ grants=grants,
+ flag_grants=flag_grants,
+ redemption_rules=redemption_rules,
+ )
+ return await self._api.send("PUT", api_path("vouchers", voucher_id), Voucher, body=body)
+
+ async def delete(self, voucher_id: str) -> None:
+ """Delete a voucher no instance ever redeemed, whatever its status, freeing its code.
+
+ Raises:
+ ConflictError: It was redeemed at least once, or a checkout waiting for its payment
+ applies it (``DeleteVoucher.Redeemed``): :meth:`archive` it instead.
+ """
+ await self._api.send_empty("DELETE", api_path("vouchers", voucher_id))
+
+ async def publish(self, voucher_id: str) -> Voucher:
+ """Make a ``DRAFT`` voucher redeemable."""
+ return await self._api.send("POST", api_path("vouchers", voucher_id, "publish"), Voucher)
+
+ async def archive(self, voucher_id: str) -> Voucher:
+ """Stop a voucher being redeemed. Redemptions already made keep applying until revoked."""
+ return await self._api.send("POST", api_path("vouchers", voucher_id, "archive"), Voucher)
+
+ async def list_redemptions(
+ self, voucher_id: str, *, limit: int | None = None
+ ) -> builtins.list[Redemption]:
+ """Return every redemption of a voucher, newest first.
+
+ Args:
+ voucher_id: The voucher.
+ limit: The most redemptions to return. Every page is walked when omitted.
+ """
+ return await self._api.list_all(
+ api_path("vouchers", voucher_id, "redemptions"), Redemption, limit=check_limit(limit)
+ )
+
+ async def lookup(self, code: str) -> Voucher:
+ """Return the voucher a code names, matched without case or separators.
+
+ The code travels in the request body, never in a URL.
+ """
+ return await self._api.send(
+ "POST", "/vouchers/lookup", Voucher, body={"code": code}, idempotent=True
+ )
+
+ async def validate(
+ self,
+ code: str,
+ *,
+ instance_slug: str | None = None,
+ license_price_id: str | None = None,
+ ) -> Validity:
+ """Run the redemption checks of a code without redeeming it.
+
+ ``valid`` says whether it would apply; otherwise ``reason`` is the first check that
+ failed, and ``rule`` the eligibility rule.
+
+ Args:
+ code: The code.
+ instance_slug: Also run the checks that need the redeeming instance.
+ license_price_id: The ``FLAT_FEE`` price a subscription would start on, read instead
+ of the instance's live subscription.
+ """
+ body = {
+ "code": code,
+ **compact({"instanceSlug": instance_slug, "licensePriceId": license_price_id}),
+ }
+ return await self._api.send(
+ "POST", "/vouchers/validate", Validity, body=body, idempotent=True
+ )
+
+
+def _voucher(
+ *,
+ name: str,
+ voucher_type: VoucherType,
+ duration: VoucherDuration,
+ code: str | None,
+ description: str | None,
+ duration_in_periods: int | None,
+ starts_at: datetime | str | None,
+ expires_at: datetime | str | None,
+ max_redemptions: int | None,
+ restricted_customer_slug: str | None,
+ price_discount_type: DiscountType | None,
+ price_discount_value: str | None,
+ currency: str | None,
+ price_applies_to: DiscountAppliesTo | None,
+ applicable_license_ids: Sequence[str] | None,
+ applicable_license_price_ids: Sequence[str] | None,
+ applicable_addon_ids: Sequence[str] | None,
+ applicable_addon_price_ids: Sequence[str] | None,
+ grants: Sequence[VoucherGrantParam] | None,
+ flag_grants: Mapping[str, str] | None,
+ redemption_rules: RedemptionRulesParam | None,
+) -> dict[str, Any]:
+ return {
+ "name": name,
+ "voucherType": voucher_type,
+ "duration": duration,
+ **compact(
+ {
+ "code": code,
+ "description": description,
+ "durationInPeriods": duration_in_periods,
+ "startsAt": optional_datetime(starts_at),
+ "expiresAt": optional_datetime(expires_at),
+ "maxRedemptions": max_redemptions,
+ "restrictedCustomerSlug": restricted_customer_slug,
+ "priceDiscountType": price_discount_type,
+ "priceDiscountValue": price_discount_value,
+ "currency": currency,
+ "priceAppliesTo": price_applies_to,
+ "applicableLicenseIds": _listed(applicable_license_ids),
+ "applicableLicensePriceIds": _listed(applicable_license_price_ids),
+ "applicableAddonIds": _listed(applicable_addon_ids),
+ "applicableAddonPriceIds": _listed(applicable_addon_price_ids),
+ "grants": _listed(grants),
+ "flagGrants": (
+ [
+ {"flagSlug": flag, "variant": variant}
+ for flag, variant in flag_grants.items()
+ ]
+ if flag_grants is not None
+ else None
+ ),
+ "redemptionRules": dict(redemption_rules) if redemption_rules is not None else None,
+ }
+ ),
+ }
+
+
+def _listed(values: Sequence[Any] | None) -> list[Any] | None:
+ return list(values) if values is not None else None
diff --git a/src/kaitencloud/_async/resources/customers.py b/src/kaitencloud/_async/resources/customers.py
index cacdfd0..3439a22 100644
--- a/src/kaitencloud/_async/resources/customers.py
+++ b/src/kaitencloud/_async/resources/customers.py
@@ -33,6 +33,7 @@ async def create(
external_customer_id: str | None = None,
domain: str | None = None,
integrations: Mapping[str, IntegrationParam] | None = None,
+ billing_email: str | None = None,
) -> Customer:
"""Create a customer.
@@ -44,6 +45,8 @@ async def create(
domain: Its domain name, e.g. ``"acme.com"``.
integrations: Links to records in third-party systems, keyed by adapter name.
Only settable here: afterwards, use :meth:`update_integration`.
+ billing_email: Where invoices sent to the customer are delivered, at most 254
+ characters. Personal data: never included in event payloads.
Raises:
ConflictError: The slug is taken, or the organization reached its customer limit.
@@ -55,6 +58,7 @@ async def create(
"externalCustomerId": external_customer_id,
"domain": domain,
"integrations": integrations_body(integrations),
+ "billingEmail": billing_email,
}
)
return await self._api.send("POST", "/customers", Customer, body=body)
@@ -66,13 +70,22 @@ async def update(
name: str,
external_customer_id: str | None = None,
domain: str | None = None,
+ billing_email: str | None = None,
) -> None:
"""Replace a customer's details.
A full replacement: the external customer id and the domain are cleared when left out.
- The slug cannot be changed, and integrations have their own methods.
+ The billing e-mail is the exception: left out, it keeps its value, and ``""`` removes
+ it. The slug cannot be changed, and integrations have their own methods.
"""
- body = compact({"name": name, "externalCustomerId": external_customer_id, "domain": domain})
+ body = compact(
+ {
+ "name": name,
+ "externalCustomerId": external_customer_id,
+ "domain": domain,
+ "billingEmail": billing_email,
+ }
+ )
await self._api.send_empty("PUT", api_path("customers", customer_slug), body=body)
async def delete(self, customer_slug: str) -> None:
diff --git a/src/kaitencloud/_async/resources/instances.py b/src/kaitencloud/_async/resources/instances.py
index d7b6906..c0f6a61 100644
--- a/src/kaitencloud/_async/resources/instances.py
+++ b/src/kaitencloud/_async/resources/instances.py
@@ -6,11 +6,15 @@
import builtins
from collections.abc import Mapping
from datetime import datetime
-from typing import Any
+from typing import Any, Literal
-from ..._exceptions import ThresholdExceededError
+from ..._constants import EXPORT_MEDIA_TYPES
+from ..._exceptions import ThresholdExceededError, TransactionIdReusedError
+from ..._models import UsageReportResult
+from ..._types import EntitlementValueInput
from ..._utils import (
api_path,
+ check_limit,
compact,
format_datetime,
integration_body,
@@ -19,12 +23,16 @@
)
from ...types import (
AuditTrail,
+ EntitlementException,
EntitlementUsage,
+ ExceptionKind,
Instance,
InstanceIntegration,
InstanceStatus,
IntegrationParam,
+ ReportedEntitlementUsage,
UsageBehavior,
+ UsageReport,
)
from ._base import AsyncResource
@@ -237,26 +245,272 @@ async def report_usage(
ThresholdExceededError: The report would take usage past the maximum the license
allows -- the granted value plus its overage allowance. Nothing was recorded.
- Never retried automatically: without an idempotency key on the wire, a report retried
- after a lost response would be counted twice.
+ Never retried automatically: a report retried after a lost response would be counted
+ twice. :meth:`report_usage_detailed` sends one under an idempotency key, which makes
+ sending it again safe.
"""
- if isinstance(value, bool) or not isinstance(value, (int, float)):
- raise TypeError(f"usage must be a number, got {type(value).__name__}")
- body = compact(
- {
- "value": {"type": "number", "value": value},
- "behavior": behavior,
- "metadata": dict(metadata) if metadata is not None else None,
- }
- )
return await self._api.send(
"POST",
- api_path("instances", instance_slug, "entitlements", entitlement_slug, "usage"),
+ _usage_path(instance_slug, entitlement_slug),
EntitlementUsage,
- body=body,
+ body=_usage_report(value, behavior, metadata),
errors={409: ThresholdExceededError},
)
+ async def report_usage_detailed(
+ self,
+ instance_slug: str,
+ entitlement_slug: str,
+ value: float,
+ *,
+ behavior: UsageBehavior = "append",
+ metadata: Mapping[str, Any] | None = None,
+ transaction_id: str | None = None,
+ ) -> UsageReportResult:
+ """Report usage, under an idempotency key, and return what the API said about it.
+
+ Kaiten applies a report at most once per key, instance and entitlement within its
+ idempotency window -- 35 days by default. A report sent again under the same key, with
+ the same behavior and value, answers the original report and counts nothing: it comes
+ back with ``replayed`` set.
+
+ Args:
+ instance_slug: The instance that consumed.
+ entitlement_slug: The entitlement consumed.
+ value: The amount, as :meth:`report_usage` takes it.
+ behavior: ``"append"`` (the default) or ``"set"``.
+ metadata: Free-form metadata, stored with the report in the usage history when its
+ compact JSON is at most 4 KiB; above, it is dropped and ``metadata_dropped`` set.
+ It must hold no personal data.
+ transaction_id: The id of the business event the report counts -- 1 to 128 of
+ letters, digits, ``.``, ``_``, ``:`` and ``-``, e.g. ``"llm-call-9f2c:tokens"``.
+ Sent as the ``Idempotency-Key`` header.
+
+ Raises:
+ ThresholdExceededError: The report would take usage past the maximum the license
+ allows. Nothing was recorded.
+ TransactionIdReusedError: The key was spent on a report with another behavior or
+ value. Nothing was recorded.
+
+ Never retried automatically, key or not: a Kaiten older than keys ignores the header and
+ would count a resent report twice. Resend it yourself, under the same key.
+ """
+ try:
+ response = await self._api.request(
+ "POST",
+ _usage_path(instance_slug, entitlement_slug),
+ body=_usage_report(value, behavior, metadata),
+ headers=compact({"Idempotency-Key": transaction_id}),
+ errors={409: ThresholdExceededError},
+ )
+ except ThresholdExceededError as error:
+ # The usage endpoint answers 409 for two reasons; only the code tells them apart.
+ if error.code == "ReportEntitlementUsageMetric.TransactionIdReused":
+ raise TransactionIdReusedError(
+ str(error), response=error.response, problem=error.problem
+ ) from None
+ raise
+ return UsageReportResult(
+ usage=self._api.validate(
+ ReportedEntitlementUsage, self._api.decode(response), response
+ ),
+ replayed=response.headers.get("Idempotent-Replayed") == "true",
+ metadata_dropped=bool(response.headers.get("Kaiten-Metadata-Dropped")),
+ )
+
+ async def list_usage_reports(
+ self,
+ instance_slug: str,
+ entitlement_slug: str,
+ *,
+ from_: datetime | str | None = None,
+ to: datetime | str | None = None,
+ transaction_id: str | None = None,
+ limit: int | None = None,
+ ) -> builtins.list[UsageReport]:
+ """Return the usage history of one entitlement for an instance, oldest report first.
+
+ Every report the API accepted in the range, with the counter before and after it and
+ the limit in force. Decimals are strings, exact to the digit. Reports are kept for the
+ organization's usage history retention.
+
+ Args:
+ instance_slug: The instance.
+ entitlement_slug: The entitlement.
+ from_: Start of the range, inclusive. Defaults to 30 days before ``to``; a moment
+ before the usage history starts is refused (``ListUsageReports.OutsideRetention``).
+ to: End of the range, exclusive. Defaults to now.
+ transaction_id: Only the report sent with this key.
+ limit: The most reports to return. Every page is walked when omitted.
+ """
+ return await self._api.list_reports(
+ api_path(
+ "instances", instance_slug, "entitlements", entitlement_slug, "usage", "reports"
+ ),
+ UsageReport,
+ params={
+ "from": optional_datetime(from_),
+ "to": optional_datetime(to),
+ "transactionId": transaction_id,
+ },
+ limit=check_limit(limit),
+ )
+
+ async def export_usage_reports(
+ self,
+ instance_slug: str,
+ entitlement_slug: str,
+ *,
+ from_: datetime | str | None = None,
+ to: datetime | str | None = None,
+ format: Literal["csv", "json"] = "csv",
+ ) -> bytes:
+ """Return the usage history of one entitlement for an instance, exported.
+
+ At most 366 days per call, decimals written exactly as the journal stores them.
+
+ Args:
+ instance_slug: The instance.
+ entitlement_slug: The entitlement.
+ from_: Start of the range, inclusive (see :meth:`list_usage_reports`).
+ to: End of the range, exclusive. Defaults to now.
+ format: ``"csv"`` (the default), RFC 4180 with a header row, or ``"json"`` for
+ NDJSON, one report per line in the shape :meth:`list_usage_reports` returns.
+ """
+ response = await self._api.request(
+ "GET",
+ api_path(
+ "instances",
+ instance_slug,
+ "entitlements",
+ entitlement_slug,
+ "usage",
+ "reports",
+ "export",
+ ),
+ params={
+ "from": optional_datetime(from_),
+ "to": optional_datetime(to),
+ "format": format,
+ },
+ headers={"Accept": EXPORT_MEDIA_TYPES[format]},
+ )
+ return response.content
+
+ async def export_organization_usage_reports(
+ self,
+ *,
+ from_: datetime | str | None = None,
+ to: datetime | str | None = None,
+ format: Literal["csv", "json"] = "csv",
+ instance_slug: str | None = None,
+ instance_id: str | None = None,
+ entitlement_slug: str | None = None,
+ entitlement_id: str | None = None,
+ ) -> bytes:
+ """Return the whole organization's usage history, exported, ordered by acceptance.
+
+ Every instance and entitlement, at most 31 days per call: keep it before deleting the
+ organization. ``instance_id`` and ``entitlement_id`` reach deleted ones, whose reports
+ are kept.
+
+ Args:
+ from_: Start of the range, inclusive (see :meth:`list_usage_reports`).
+ to: End of the range, exclusive. Defaults to now.
+ format: ``"csv"`` (the default) or ``"json"`` for NDJSON.
+ instance_slug: Only this instance.
+ instance_id: Only this instance, by id.
+ entitlement_slug: Only this entitlement.
+ entitlement_id: Only this entitlement, by id.
+ """
+ response = await self._api.request(
+ "GET",
+ "/usage/reports/export",
+ params={
+ "from": optional_datetime(from_),
+ "to": optional_datetime(to),
+ "format": format,
+ "instanceSlug": instance_slug,
+ "instanceId": instance_id,
+ "entitlementSlug": entitlement_slug,
+ "entitlementId": entitlement_id,
+ },
+ headers={"Accept": EXPORT_MEDIA_TYPES[format]},
+ )
+ return response.content
+
+ async def list_entitlement_exceptions(
+ self, instance_slug: str, *, include_inactive: bool = False
+ ) -> builtins.list[EntitlementException]:
+ """Return the instance's entitlement exceptions, latest start first, with their status.
+
+ Args:
+ instance_slug: The instance.
+ include_inactive: Also return the revoked and ended ones. Only the active and the
+ scheduled ones otherwise.
+ """
+ return await self._api.get_list(
+ api_path("instances", instance_slug, "entitlement-exceptions"),
+ EntitlementException,
+ params={"filter": "all" if include_inactive else None},
+ )
+
+ async def create_entitlement_exception(
+ self,
+ instance_slug: str,
+ entitlement_slug: str,
+ *,
+ kind: ExceptionKind,
+ reason: str,
+ value: EntitlementValueInput | None = None,
+ starts_at: datetime | str | None = None,
+ ends_at: datetime | str | None = None,
+ ) -> EntitlementException:
+ """Override one entitlement of an instance, for a while, and return the exception.
+
+ An exception is your explicit, dated decision on one instance: the last layer of its
+ effective entitlements, after the license, the add-ons and the boosts. It wins over
+ them, and may lower the value. An instance has one unrevoked exception per entitlement
+ at any moment. It works whether or not billing is enabled.
+
+ Args:
+ instance_slug: The instance.
+ entitlement_slug: The entitlement it overrides, granted or not.
+ kind: ``SET`` works on every type; ``ADD`` and ``UNLIMITED`` on numbers only.
+ reason: Why, 1 to 500 characters, shown to you only.
+ value: For ``SET``, a number of 0 or more, a ``bool`` or a mapping, by the
+ entitlement's type; for ``ADD``, a number above 0; nothing for ``UNLIMITED``.
+ starts_at: When it starts: now when omitted; a future moment schedules it.
+ ends_at: When it ends. It never does when omitted.
+ """
+ body = {
+ "entitlementSlug": entitlement_slug,
+ "kind": kind,
+ "reason": reason,
+ **compact(
+ {
+ "value": dict(value) if isinstance(value, Mapping) else value,
+ "startsAt": optional_datetime(starts_at),
+ "endsAt": optional_datetime(ends_at),
+ }
+ ),
+ }
+ return await self._api.send(
+ "POST",
+ api_path("instances", instance_slug, "entitlement-exceptions"),
+ EntitlementException,
+ body=body,
+ )
+
+ async def revoke_entitlement_exception(self, instance_slug: str, exception_id: str) -> None:
+ """Revoke an exception, which is kept: a scheduled one never starts, an active one ends.
+
+ Revoking one already revoked or ended changes nothing.
+ """
+ await self._api.send_empty(
+ "DELETE", api_path("instances", instance_slug, "entitlement-exceptions", exception_id)
+ )
+
async def get_integration(
self, instance_slug: str, integration_name: str
) -> InstanceIntegration:
@@ -347,3 +601,21 @@ def _instance(
}
),
}
+
+
+def _usage_path(instance_slug: str, entitlement_slug: str) -> str:
+ return api_path("instances", instance_slug, "entitlements", entitlement_slug, "usage")
+
+
+def _usage_report(
+ value: float, behavior: UsageBehavior, metadata: Mapping[str, Any] | None
+) -> dict[str, Any]:
+ if isinstance(value, bool) or not isinstance(value, (int, float)):
+ raise TypeError(f"usage must be a number, got {type(value).__name__}")
+ return compact(
+ {
+ "value": {"type": "number", "value": value},
+ "behavior": behavior,
+ "metadata": dict(metadata) if metadata is not None else None,
+ }
+ )
diff --git a/src/kaitencloud/_async/resources/license_families.py b/src/kaitencloud/_async/resources/license_families.py
index c71485f..35ad794 100644
--- a/src/kaitencloud/_async/resources/license_families.py
+++ b/src/kaitencloud/_async/resources/license_families.py
@@ -6,8 +6,8 @@
import builtins
from typing import Any
-from ..._utils import api_path
-from ...types import LicenseFamilyView
+from ..._utils import api_path, compact
+from ...types import LicenseFamilyView, TrialEndWithoutPaymentMethod
from ._base import AsyncResource
__all__ = ["AsyncLicenseFamilies"]
@@ -59,3 +59,38 @@ async def get(
return await self._api.get(
api_path("license-families", family_slug), LicenseFamilyView, params=params or None
)
+
+ async def update(
+ self,
+ family_slug: str,
+ *,
+ is_public: bool | None = None,
+ fallback_license_family_slug: str | None = None,
+ trial_end_without_payment_method: TrialEndWithoutPaymentMethod | None = None,
+ trial_reminder_days: int | None = None,
+ ) -> LicenseFamilyView:
+ """Change a family's own settings, and return the family; what is omitted keeps its value.
+
+ Args:
+ family_slug: The family's slug.
+ is_public: List the family's default ``PUBLISHED`` version, with its ``ACTIVE``
+ prices, in the public catalog. Families are private until made public.
+ fallback_license_family_slug: Another family of the organization, every version of
+ it priced ``FREE``, whose default version an instance moves to when its
+ subscription ends. ``""`` removes the fallback.
+ trial_end_without_payment_method: What a trial charged automatically becomes at its
+ end when the customer has no active payment method: ``CANCEL`` or ``CONVERT``.
+ trial_reminder_days: How many days before a trial's end ``trial_will_end`` fires,
+ 1 to 30, for the trials started after the change.
+ """
+ body = compact(
+ {
+ "isPublic": is_public,
+ "fallbackLicenseFamilySlug": fallback_license_family_slug,
+ "trialEndWithoutPaymentMethod": trial_end_without_payment_method,
+ "trialReminderDays": trial_reminder_days,
+ }
+ )
+ return await self._api.send(
+ "PATCH", api_path("license-families", family_slug), LicenseFamilyView, body=body
+ )
diff --git a/src/kaitencloud/_async/resources/licenses.py b/src/kaitencloud/_async/resources/licenses.py
index 2e49181..4ec6fa5 100644
--- a/src/kaitencloud/_async/resources/licenses.py
+++ b/src/kaitencloud/_async/resources/licenses.py
@@ -8,7 +8,7 @@
from ..._types import EntitlementValueInput
from ..._utils import api_path, compact, entitlement_value
-from ...types import License, LicenseEntitlement, LicenseType
+from ...types import License, LicenseEntitlement, LicenseType, PricingType
from ._base import AsyncResource
__all__ = ["AsyncLicenses"]
@@ -45,6 +45,10 @@ async def create(
family_slug: str | None = None,
family_id: str | None = None,
lifecycle_state: Literal["DRAFT", "PUBLISHED"] | None = None,
+ pricing_type: PricingType | None = None,
+ trial_period_days: int | None = None,
+ requires_payment_method: bool | None = None,
+ self_serve_cta_url: str | None = None,
) -> License:
"""Create a license: a new license family, or the next version of an existing one.
@@ -71,6 +75,15 @@ async def create(
lifecycle_state: ``DRAFT`` to create the version off sale, to be put on sale with
:meth:`publish`. ``PUBLISHED`` when omitted. A version is never created
``ARCHIVED``: it is archived with :meth:`archive` once it has been on sale.
+ pricing_type: How the version is sold: ``FREE`` or ``PAID`` can be bought self-serve,
+ ``CUSTOM`` sends a buyer to ``self_serve_cta_url`` or a conversation. ``CUSTOM``
+ when omitted.
+ trial_period_days: The trial a subscription to the version starts with by default,
+ 1 to 365 days.
+ requires_payment_method: Whether self-serve signup captures a payment method before
+ activation.
+ self_serve_cta_url: Where a buyer is sent when the version cannot be bought
+ self-serve: an http(s) URL.
Raises:
NotFoundError: ``family_slug`` or ``family_id`` names no family
@@ -93,6 +106,10 @@ async def create(
"familySlug": family_slug,
"familyId": family_id,
"lifecycleState": lifecycle_state,
+ "pricingType": pricing_type,
+ "trialPeriodDays": trial_period_days,
+ "requiresPaymentMethod": requires_payment_method,
+ "selfServeCtaUrl": self_serve_cta_url,
}
),
}
@@ -107,6 +124,10 @@ async def update(
description: str = "",
is_default: bool = False,
version_name: str | None = None,
+ pricing_type: PricingType | None = None,
+ trial_period_days: int | None = None,
+ requires_payment_method: bool | None = None,
+ self_serve_cta_url: str | None = None,
) -> None:
"""Replace a license version's details.
@@ -115,6 +136,11 @@ async def update(
:meth:`publish`, :meth:`archive` and :meth:`unarchive`. ``is_default=True`` takes the
flag from the family's previous default, and ``False`` unsets it on this version.
+ The commercial terms -- ``pricing_type``, ``trial_period_days``,
+ ``requires_payment_method`` and ``self_serve_cta_url`` -- keep their value when left
+ out, as :meth:`create` describes them. ``trial_period_days=0`` removes the trial, and
+ ``self_serve_cta_url=""`` the URL.
+
Raises:
ConflictError: ``is_default=True`` on a version that is not ``PUBLISHED``
(``UpdateLicense.DefaultMustBePublished``): publish or unarchive it first.
@@ -124,7 +150,15 @@ async def update(
"description": description,
"type": type,
"isDefault": is_default,
- **compact({"versionName": version_name}),
+ **compact(
+ {
+ "versionName": version_name,
+ "pricingType": pricing_type,
+ "trialPeriodDays": trial_period_days,
+ "requiresPaymentMethod": requires_payment_method,
+ "selfServeCtaUrl": self_serve_cta_url,
+ }
+ ),
}
await self._api.send_empty("PUT", api_path("licenses", license_slug), body=body)
diff --git a/src/kaitencloud/_async/resources/metadata_fields.py b/src/kaitencloud/_async/resources/metadata_fields.py
index bcf2a2b..1fb8f07 100644
--- a/src/kaitencloud/_async/resources/metadata_fields.py
+++ b/src/kaitencloud/_async/resources/metadata_fields.py
@@ -21,10 +21,17 @@ class AsyncMetadataFields(AsyncResource):
against the active fields declared for its resource type.
"""
- async def list(self, resource_type: MetadataResourceType) -> builtins.list[MetadataField]:
- """Return the metadata fields declared for ``resource_type``, walking all pages."""
+ async def list(
+ self, resource_type: MetadataResourceType, *, include_archived: bool = False
+ ) -> builtins.list[MetadataField]:
+ """Return the metadata fields declared for ``resource_type``, walking all pages.
+
+ Archived fields are left out unless ``include_archived`` asks for them.
+ """
return await self._api.list_all(
- "/metadata-fields", MetadataField, params={"resourceType": resource_type}
+ "/metadata-fields",
+ MetadataField,
+ params={"resourceType": resource_type, "includeArchived": include_archived or None},
)
async def create(
diff --git a/src/kaitencloud/_async/resources/platform/__init__.py b/src/kaitencloud/_async/resources/platform/__init__.py
index 5b0ec25..a145a9e 100644
--- a/src/kaitencloud/_async/resources/platform/__init__.py
+++ b/src/kaitencloud/_async/resources/platform/__init__.py
@@ -1,12 +1,14 @@
# Copyright 2026 KAITEN INC
# SPDX-License-Identifier: Apache-2.0
+from .billing import AsyncPlatformBilling
from .connectors import AsyncPlatformConnectors
from .organizations import AsyncPlatformOrganizations
from .tokens import AsyncPlatformTokens
from .users import AsyncPlatformUsers
__all__ = [
+ "AsyncPlatformBilling",
"AsyncPlatformConnectors",
"AsyncPlatformOrganizations",
"AsyncPlatformTokens",
diff --git a/src/kaitencloud/_async/resources/platform/billing.py b/src/kaitencloud/_async/resources/platform/billing.py
new file mode 100644
index 0000000..8647f12
--- /dev/null
+++ b/src/kaitencloud/_async/resources/platform/billing.py
@@ -0,0 +1,39 @@
+# Copyright 2026 KAITEN INC
+# SPDX-License-Identifier: Apache-2.0
+
+from __future__ import annotations
+
+from ...._utils import api_path, compact, validate_uuid
+from ....types import ClosePeriodsReport
+from .._base import AsyncResource
+
+__all__ = ["AsyncPlatformBilling"]
+
+
+class AsyncPlatformBilling(AsyncResource):
+ """Billing across organizations."""
+
+ async def close_periods(
+ self, organization_id: str, *, instance_slug: str | None = None
+ ) -> ClosePeriodsReport:
+ """Close an organization's subscriptions whose period has ended, now.
+
+ The same as :meth:`AsyncBilling.close_periods` on the Core API, for any organization,
+ and recorded under ``system:kaiten``. It needs ``write:billing``.
+
+ Args:
+ organization_id: The organization.
+ instance_slug: Close only this instance's subscription.
+ """
+ return await self._api.send(
+ "POST",
+ api_path(
+ "platform",
+ "organizations",
+ validate_uuid(organization_id, "organization_id"),
+ "billing",
+ "close-periods",
+ ),
+ ClosePeriodsReport,
+ body=compact({"instanceSlug": instance_slug}),
+ )
diff --git a/src/kaitencloud/_constants.py b/src/kaitencloud/_constants.py
index 7acd6a3..d3cd1ee 100644
--- a/src/kaitencloud/_constants.py
+++ b/src/kaitencloud/_constants.py
@@ -22,6 +22,12 @@
Walking at this size is the fewest round trips a complete list can take.
"""
+MAX_REPORT_PAGE_SIZE: Final = 500
+"""The largest page the usage report histories accept, which page by ``afterSeq``."""
+
+EXPORT_MEDIA_TYPES: Final = {"csv": "text/csv", "json": "application/x-ndjson"}
+"""What an export answers in each format it takes: CSV, or NDJSON for ``json``."""
+
INITIAL_RETRY_DELAY: Final = 0.25
MAX_RETRY_DELAY: Final = 8.0
MAX_RETRY_AFTER: Final = 60.0
diff --git a/src/kaitencloud/_exceptions.py b/src/kaitencloud/_exceptions.py
index 23c680e..bcbce46 100644
--- a/src/kaitencloud/_exceptions.py
+++ b/src/kaitencloud/_exceptions.py
@@ -30,6 +30,7 @@
"RateLimitError",
"ServiceUnavailableError",
"ThresholdExceededError",
+ "TransactionIdReusedError",
"UnprocessableEntityError",
"WebhookPayloadError",
"WebhookVerificationError",
@@ -44,8 +45,10 @@ class CredentialError(KaitenError, ValueError):
"""A credential is missing, or belongs to a class the target API refuses.
Raised before anything is sent: a platform credential (``ksm_...``) handed to the Core
- client, an organization token handed to the platform client, or a webhook signing secret
- (``whsec_...``) handed to either.
+ client, an organization token handed to the platform client, a webhook signing secret
+ (``whsec_...``) handed to either, or a route under ``/public`` called without the
+ credential it takes -- a publishable key (``pk_...``), a customer session (``kst_...``) or a
+ checkout session's token (``ksc_...``).
"""
@@ -162,6 +165,15 @@ class ThresholdExceededError(ConflictError):
"""
+class TransactionIdReusedError(ConflictError):
+ """A usage report reused a ``transaction_id`` already spent on a different report.
+
+ The same key with the same behavior and value is a replay, applied once; with another
+ behavior or value, it is this error. Nothing was recorded, and sending it again will not
+ change the answer: give the report a key of its own.
+ """
+
+
class UnprocessableEntityError(APIStatusError):
"""The API answered 422: the request is well-formed but its content is refused."""
diff --git a/src/kaitencloud/_models.py b/src/kaitencloud/_models.py
index 1f1a869..e5654a0 100644
--- a/src/kaitencloud/_models.py
+++ b/src/kaitencloud/_models.py
@@ -1,15 +1,19 @@
# Copyright 2026 KAITEN INC
# SPDX-License-Identifier: Apache-2.0
-"""The base class of every model the SDK returns."""
+"""The base class of every model the SDK returns, and the results it composes from them."""
from __future__ import annotations
-from typing import Any
+from dataclasses import dataclass
+from typing import TYPE_CHECKING, Any
from pydantic import BaseModel, ConfigDict
-__all__ = ["KaitenModel"]
+if TYPE_CHECKING:
+ from .types import ReportedEntitlementUsage
+
+__all__ = ["KaitenModel", "UsageReportResult"]
class KaitenModel(BaseModel):
@@ -37,3 +41,20 @@ def to_dict(self) -> dict[str, Any]:
def to_json(self, *, indent: int | None = None) -> str:
"""Return the model as a JSON document, with wire key names."""
return self.model_dump_json(by_alias=True, exclude_unset=True, indent=indent)
+
+
+@dataclass(frozen=True)
+class UsageReportResult:
+ """What a usage report answered: the usage it results in, and what the API said about it."""
+
+ usage: ReportedEntitlementUsage
+ """The entitlement's usage after the report. On a replay, the earlier report's answer: it
+ may describe a usage window that has closed since, so do not read it as the current gauge.
+ """
+
+ replayed: bool
+ """The report repeated one already accepted under its ``transaction_id``, and nothing was
+ counted again."""
+
+ metadata_dropped: bool
+ """The report's metadata was above 4 KiB and was not stored. The report itself counted."""
diff --git a/src/kaitencloud/_sync/_http.py b/src/kaitencloud/_sync/_http.py
index 7c44ffc..77b77d2 100644
--- a/src/kaitencloud/_sync/_http.py
+++ b/src/kaitencloud/_sync/_http.py
@@ -14,7 +14,7 @@
import httpx
from pydantic import BaseModel, ValidationError
-from .._constants import DEFAULT_TIMEOUT, MAX_PAGE_SIZE
+from .._constants import DEFAULT_TIMEOUT, MAX_PAGE_SIZE, MAX_REPORT_PAGE_SIZE
from .._exceptions import (
APIConnectionError,
APIResponseValidationError,
@@ -24,7 +24,15 @@
make_status_error,
)
from .._types import NOT_GIVEN, Credential, NotGiven, TimeoutTypes
-from .._utils import USER_AGENT, Surface, check_token, resolve_token, retry_delay, sleep
+from .._utils import (
+ PUBLISHABLE_KEY_PREFIX,
+ USER_AGENT,
+ Surface,
+ check_token,
+ resolve_token,
+ retry_delay,
+ sleep,
+)
__all__ = ["APIClient"]
@@ -98,6 +106,7 @@ def request(
*,
params: Mapping[str, Any] | None = None,
body: Any = None,
+ headers: Mapping[str, str] | None = None,
idempotent: bool | None = None,
errors: Mapping[int, type[APIStatusError]] | None = None,
) -> httpx.Response:
@@ -115,7 +124,11 @@ def request(
while True:
request = self._build(
- method, url, params=_query(params), body=body, headers=self._headers()
+ method,
+ url,
+ params=_query(params),
+ body=body,
+ headers={**self._headers(path), **(headers or {})},
)
try:
response = self.http_client.send(request)
@@ -252,6 +265,43 @@ def list_all(
rows.append(row)
return rows
+ def list_reports(
+ self,
+ path: str,
+ model: type[ModelT],
+ *,
+ params: Mapping[str, Any] | None = None,
+ limit: int | None = None,
+ ) -> list[ModelT]:
+ """Every row of a usage report history, which pages by ``afterSeq`` rather than a cursor.
+
+ ``limit`` is a ceiling, as for :meth:`paginate`. A ``nextAfterSeq`` that does not move
+ past the page it follows raises :class:`PaginationError`.
+ """
+ rows: list[ModelT] = []
+ after: int | None = None
+ while True:
+ size = MAX_REPORT_PAGE_SIZE
+ if limit is not None:
+ size = min(size, limit - len(rows))
+ response = self.request(
+ "GET", path, params={**(params or {}), "afterSeq": after, "limit": size}
+ )
+ page = self.decode(response)
+ if not isinstance(page, dict) or not isinstance(page.get("items"), list):
+ raise APIResponseValidationError(
+ f"GET {path}: expected a page of items", response=response
+ )
+ rows += [self.validate(model, item, response) for item in page["items"]]
+ following = page.get("nextAfterSeq")
+ if following is None or (limit is not None and len(rows) >= limit):
+ return rows[:limit]
+ if not isinstance(following, int) or (after is not None and following <= after):
+ raise PaginationError(
+ f"GET {path}: the API reported more rows but no afterSeq past {after}"
+ )
+ after = following
+
@staticmethod
def decode(response: httpx.Response) -> Any:
if not response.content:
@@ -293,14 +343,15 @@ def _build(
method, url, params=params, json=body, headers=headers, timeout=self.timeout
)
- def _headers(self) -> dict[str, str]:
- token = check_token(resolve_token(self.credential), self.surface)
- return {
- "Accept": "application/json",
- "User-Agent": USER_AGENT,
- **self.default_headers,
- "Authorization": f"Bearer {token}",
- }
+ def _headers(self, path: str) -> dict[str, str]:
+ token = check_token(resolve_token(self.credential), self.surface, path)
+ headers = {"Accept": "application/json", "User-Agent": USER_AGENT, **self.default_headers}
+ if token.startswith(PUBLISHABLE_KEY_PREFIX):
+ # Not a bearer credential: the API refuses a publishable key sent as Authorization.
+ headers["X-Kaiten-Publishable-Key"] = token
+ else:
+ headers["Authorization"] = f"Bearer {token}"
+ return headers
def _back_off(
self, request: httpx.Request, attempt: int, reason: str, retry_after: str | None = None
diff --git a/src/kaitencloud/_sync/client.py b/src/kaitencloud/_sync/client.py
index 7699942..5a5c9ab 100644
--- a/src/kaitencloud/_sync/client.py
+++ b/src/kaitencloud/_sync/client.py
@@ -28,6 +28,7 @@
from ..types import PlatformCredential
from ._http import APIClient
from .resources import (
+ Billing,
Components,
Connectors,
Customers,
@@ -44,6 +45,7 @@
ServiceAccounts,
)
from .resources.platform import (
+ PlatformBilling,
PlatformConnectors,
PlatformOrganizations,
PlatformTokens,
@@ -115,7 +117,7 @@ class KaitenClient(_BaseClient):
The Core API is what an organization's own credential talks to: an organization token
(``ksh_...``) or a JWT from your identity provider. It covers customers, instances,
- licenses, entitlements and usage, feature flags, releases and connectors::
+ licenses, entitlements and usage, billing, feature flags, releases and connectors::
with KaitenClient(
base_url="https://kaiten.example.com", token="ksh_..."
@@ -138,6 +140,9 @@ class KaitenClient(_BaseClient):
TLS or transports. The Kaiten client never closes a client it did not create.
"""
+ billing: Billing
+ """Billing: prices, subscriptions, invoices, add-ons, vouchers and the public catalog."""
+
components: Components
"""Components: the versioned building blocks releases are made of."""
@@ -212,6 +217,7 @@ def __init__(
def _bind(self) -> None:
api = self._api
+ self.billing = Billing(api)
self.components = Components(api)
self.connectors = Connectors(api)
self.customers = Customers(api)
@@ -253,6 +259,9 @@ class KaitenPlatformClient(_BaseClient):
http_client: An :class:`httpx.Client` to send requests with.
"""
+ billing: PlatformBilling
+ """Billing across organizations: closing their due periods."""
+
connectors: PlatformConnectors
"""The deployment-wide connector registry."""
@@ -308,6 +317,7 @@ def me(self) -> PlatformCredential:
def _bind(self) -> None:
api = self._api
+ self.billing = PlatformBilling(api)
self.connectors = PlatformConnectors(api)
self.organizations = PlatformOrganizations(api)
self.tokens = PlatformTokens(api)
diff --git a/src/kaitencloud/_sync/resources/__init__.py b/src/kaitencloud/_sync/resources/__init__.py
index ddad12b..38f1f7a 100644
--- a/src/kaitencloud/_sync/resources/__init__.py
+++ b/src/kaitencloud/_sync/resources/__init__.py
@@ -3,6 +3,7 @@
# Generated by scripts/unasync.py from src/kaitencloud/_async/resources/__init__.py. Do not edit by hand:
# change the async source, then run `task generate`.
+from .billing import Billing
from .components import Components
from .connectors import Connectors
from .customers import Customers
@@ -19,6 +20,7 @@
from .service_accounts import ServiceAccounts
__all__ = [
+ "Billing",
"Components",
"Connectors",
"Customers",
diff --git a/src/kaitencloud/_sync/resources/billing/__init__.py b/src/kaitencloud/_sync/resources/billing/__init__.py
new file mode 100644
index 0000000..02ce218
--- /dev/null
+++ b/src/kaitencloud/_sync/resources/billing/__init__.py
@@ -0,0 +1,218 @@
+# Copyright 2026 KAITEN INC
+# SPDX-License-Identifier: Apache-2.0
+
+# Generated by scripts/unasync.py from src/kaitencloud/_async/resources/billing/__init__.py. Do not edit by hand:
+# change the async source, then run `task generate`.
+from __future__ import annotations
+
+from ...._utils import compact
+from ....types import (
+ BillingCapabilities,
+ BillingHealth,
+ BillingSettings,
+ ClosePeriodsReport,
+ CollectionMethod,
+ SyncReport,
+)
+from ..._http import APIClient
+from .._base import Resource
+from .addons import BillingAddons
+from .customers import BillingCustomers
+from .invoices import BillingHandoff, BillingInvoices
+from .license_prices import BillingLicensePrices
+from .public import BillingPublic, BillingSession
+from .sessions import (
+ BillingCheckoutSessions,
+ BillingCustomerSessions,
+ BillingPublishableKeys,
+)
+from .subscriptions import (
+ BillingInstanceAddons,
+ BillingInstanceVouchers,
+ BillingSubscriptions,
+)
+from .vouchers import BillingVouchers
+
+__all__ = [
+ "Billing",
+ "BillingAddons",
+ "BillingCheckoutSessions",
+ "BillingCustomerSessions",
+ "BillingCustomers",
+ "BillingHandoff",
+ "BillingInstanceAddons",
+ "BillingInstanceVouchers",
+ "BillingInvoices",
+ "BillingLicensePrices",
+ "BillingPublic",
+ "BillingPublishableKeys",
+ "BillingSession",
+ "BillingSubscriptions",
+ "BillingVouchers",
+]
+
+
+class Billing(Resource):
+ """Billing: the organization's settings and payment providers, and a namespace per resource.
+
+ Kaiten composes every invoice from the prices of a license version, its add-ons and the
+ usage it metered. Each subscription names who issues and collects them: ``NOOP``, the
+ organization itself through :attr:`handoff`, or a payment provider. Most operations here
+ require billing to be enabled for the organization; :meth:`get_capabilities` says whether
+ it is.
+ """
+
+ __slots__ = (
+ "addons",
+ "checkout_sessions",
+ "customer_sessions",
+ "customers",
+ "handoff",
+ "instance_addons",
+ "instance_vouchers",
+ "invoices",
+ "license_prices",
+ "public",
+ "publishable_keys",
+ "session",
+ "subscriptions",
+ "vouchers",
+ )
+
+ addons: BillingAddons
+ """The add-on catalog: families, versions, grants, prices and the license families they fit."""
+
+ checkout_sessions: BillingCheckoutSessions
+ """Anonymous checkouts, as the organization reads them."""
+
+ customer_sessions: BillingCustomerSessions
+ """Customer sessions (``kst_...``), for a customer's browser to act on its own billing."""
+
+ customers: BillingCustomers
+ """A customer's billing: its payment method, and the payment provider's portal."""
+
+ handoff: BillingHandoff
+ """The accounting handoff: invoices claimed in leased batches and acknowledged one by one."""
+
+ instance_addons: BillingInstanceAddons
+ """The add-ons an instance holds."""
+
+ instance_vouchers: BillingInstanceVouchers
+ """The vouchers an instance redeemed."""
+
+ invoices: BillingInvoices
+ """The organization's invoices."""
+
+ license_prices: BillingLicensePrices
+ """The prices of license versions, and the invoice a subscription to one would get."""
+
+ public: BillingPublic
+ """What a publishable key (``pk_...``) reaches. Build the client with that key."""
+
+ publishable_keys: BillingPublishableKeys
+ """Publishable keys (``pk_...``), which a web page reads the public catalog with."""
+
+ session: BillingSession
+ """What a customer session (``kst_...``) reaches. Build the client with that session."""
+
+ subscriptions: BillingSubscriptions
+ """Instances' subscriptions."""
+
+ vouchers: BillingVouchers
+ """The organization's vouchers."""
+
+ def __init__(self, api: APIClient) -> None:
+ super().__init__(api)
+ self.addons = BillingAddons(api)
+ self.checkout_sessions = BillingCheckoutSessions(api)
+ self.customer_sessions = BillingCustomerSessions(api)
+ self.customers = BillingCustomers(api)
+ self.handoff = BillingHandoff(api)
+ self.instance_addons = BillingInstanceAddons(api)
+ self.instance_vouchers = BillingInstanceVouchers(api)
+ self.invoices = BillingInvoices(api)
+ self.license_prices = BillingLicensePrices(api)
+ self.public = BillingPublic(api)
+ self.publishable_keys = BillingPublishableKeys(api)
+ self.session = BillingSession(api)
+ self.subscriptions = BillingSubscriptions(api)
+ self.vouchers = BillingVouchers(api)
+
+ def get_settings(self) -> BillingSettings:
+ """Return the organization's billing defaults.
+
+ They are the collection method and payment terms a subscription gets when it names none
+ of its own. An organization that never wrote them has ``SEND_INVOICE`` and 30 days.
+ """
+ return self._api.get("/billing/settings", BillingSettings)
+
+ def update_settings(
+ self,
+ *,
+ default_collection_method: CollectionMethod,
+ default_days_until_due: int,
+ handoff_stripe_invoices: bool,
+ ) -> BillingSettings:
+ """Replace the organization's billing defaults, and return them.
+
+ A subscription that names its own terms keeps them; the others take these at their
+ next invoice.
+
+ Args:
+ default_collection_method: ``SEND_INVOICE`` sends an invoice for payment;
+ ``CHARGE_AUTOMATICALLY`` charges the customer's payment method, on a provider
+ that can.
+ default_days_until_due: Days an invoice is due after it is issued, 0 to 365.
+ handoff_stripe_invoices: Whether invoices a payment provider issues also enter the
+ handoff queue, for an accounting system that wants every invoice.
+ """
+ body = {
+ "defaultCollectionMethod": default_collection_method,
+ "defaultDaysUntilDue": default_days_until_due,
+ "handoffStripeInvoices": handoff_stripe_invoices,
+ }
+ return self._api.send("PUT", "/billing/settings", BillingSettings, body=body)
+
+ def get_capabilities(self) -> BillingCapabilities:
+ """Return whether billing is enabled -- and if not, why -- and what it can do here.
+
+ Who can collect the organization's invoices, and which parts of billing this release
+ ships, so a client shows only what works. It answers even when billing is disabled.
+ """
+ return self._api.get("/billing/capabilities", BillingCapabilities)
+
+ def get_health(self) -> BillingHealth:
+ """Return what the organization's billing needs attention for, computed when read.
+
+ Periods waiting for their close, held invoices, invoices a payment provider keeps
+ refusing, reconciliation mismatches, each provider's sync, overdue invoices and the
+ handoff backlog.
+ """
+ return self._api.get("/billing/health", BillingHealth)
+
+ def sync(self) -> SyncReport:
+ """Run one sync pass now with each payment provider the organization connected.
+
+ What was paid, voided or finalized in a provider is mirrored, as the periodic pass does.
+
+ Raises:
+ ConflictError: No provider is connected (``SyncProvider.NotConnected``).
+ """
+ return self._api.send("POST", "/billing/sync", SyncReport)
+
+ def close_periods(self, *, instance_slug: str | None = None) -> ClosePeriodsReport:
+ """Close the subscriptions whose period has ended, now rather than at the next pass.
+
+ Each one's ``RENEWAL`` invoice is composed and issued -- or held, when its usage journal
+ fails a check -- and its period advances. Only what is due closes, at most a batch per
+ call: ``has_more`` says when more is left.
+
+ Args:
+ instance_slug: Close only this instance's subscription.
+ """
+ return self._api.send(
+ "POST",
+ "/billing/close-periods",
+ ClosePeriodsReport,
+ body=compact({"instanceSlug": instance_slug}),
+ )
diff --git a/src/kaitencloud/_sync/resources/billing/addons.py b/src/kaitencloud/_sync/resources/billing/addons.py
new file mode 100644
index 0000000..37b5958
--- /dev/null
+++ b/src/kaitencloud/_sync/resources/billing/addons.py
@@ -0,0 +1,347 @@
+# Copyright 2026 KAITEN INC
+# SPDX-License-Identifier: Apache-2.0
+
+# Generated by scripts/unasync.py from src/kaitencloud/_async/resources/billing/addons.py. Do not edit by hand:
+# change the async source, then run `task generate`.
+from __future__ import annotations
+
+import builtins
+from typing import Any, Literal
+
+from ...._types import EntitlementValueInput
+from ...._utils import api_path, compact, entitlement_value, price_body
+from ....types import (
+ Addon,
+ AddonEntitlement,
+ AddonFamily,
+ AddonLifecycleState,
+ BillingModel,
+ BillingPeriod,
+ BillingTiming,
+ CompatibleLicenseFamilies,
+ IncreaseBilling,
+ OverrideBehavior,
+ Price,
+ PriceStatus,
+ PricingType,
+)
+from .._base import Resource
+
+__all__ = ["BillingAddons"]
+
+
+class BillingAddons(Resource):
+ """The add-on catalog: what an instance can hold on top of its license, by the unit.
+
+ Add-ons are versioned like licenses: an add-on *family* is the product, each version has
+ its grants and its prices, and moves through ``DRAFT``, ``PUBLISHED`` and ``ARCHIVED``. A
+ version fits the license families :meth:`set_compatibility` names, and none until then.
+ """
+
+ def list(
+ self,
+ *,
+ lifecycle_state: AddonLifecycleState | None = None,
+ family_slug: str | None = None,
+ ) -> builtins.list[Addon]:
+ """Return the organization's add-on versions, newest first, walking all pages.
+
+ Args:
+ lifecycle_state: Only versions in this state.
+ family_slug: Only versions of this family.
+ """
+ return self._api.list_all(
+ "/addons",
+ Addon,
+ params={"lifecycleState": lifecycle_state, "familySlug": family_slug},
+ )
+
+ def get(self, addon_slug: str) -> Addon:
+ """Return the add-on version identified by ``addon_slug``."""
+ return self._api.get(api_path("addons", addon_slug), Addon)
+
+ def create(
+ self,
+ *,
+ name: str,
+ pricing_type: PricingType,
+ description: str = "",
+ slug: str | None = None,
+ family_slug: str | None = None,
+ family_id: str | None = None,
+ version_name: str | None = None,
+ lifecycle_state: Literal["DRAFT", "PUBLISHED"] | None = None,
+ is_default: bool | None = None,
+ max_quantity: int | None = None,
+ increase_billing: IncreaseBilling | None = None,
+ ) -> Addon:
+ """Create an add-on version: the first of a new family, or the next of an existing one.
+
+ Args:
+ name: The add-on's name, e.g. ``"Extra seats"``.
+ pricing_type: ``FREE``, ``PAID`` or ``CUSTOM``.
+ description: A description.
+ slug: The version's slug. Defaults to ``-v`` for a new version of a
+ family, and to one generated from the name for a new family.
+ family_slug: Make this a new version of this family.
+ family_id: The same family, by identifier.
+ version_name: A label for the version. Defaults to ``"Version - "``.
+ lifecycle_state: ``DRAFT``, or ``PUBLISHED`` when omitted.
+ is_default: Make this version its family's default.
+ max_quantity: The largest quantity an instance can hold.
+ increase_billing: Read only when this opens a new family: how a mid-period increase
+ of its add-ons billed in advance is billed, ``TRUE_UP`` (the default) or
+ ``NEXT_BOUNDARY``.
+ """
+ body = {
+ "name": name,
+ "description": description,
+ "pricingType": pricing_type,
+ **compact(
+ {
+ "slug": slug,
+ "familySlug": family_slug,
+ "familyId": family_id,
+ "versionName": version_name,
+ "lifecycleState": lifecycle_state,
+ "isDefault": is_default,
+ "maxQuantity": max_quantity,
+ "increaseBilling": increase_billing,
+ }
+ ),
+ }
+ return self._api.send("POST", "/addons", Addon, body=body)
+
+ def update(
+ self,
+ addon_slug: str,
+ *,
+ name: str,
+ description: str = "",
+ is_default: bool = False,
+ version_name: str | None = None,
+ max_quantity: int | None = None,
+ ) -> Addon:
+ """Replace an add-on version's name, description, version name, default flag and maximum.
+
+ A full replacement, as for a license version: its family, pricing type and state are
+ not part of it.
+ """
+ body = {
+ "name": name,
+ "description": description,
+ "isDefault": is_default,
+ **compact({"versionName": version_name, "maxQuantity": max_quantity}),
+ }
+ return self._api.send("PUT", api_path("addons", addon_slug), Addon, body=body)
+
+ def delete(self, addon_slug: str) -> None:
+ """Delete an add-on version that was never attached and grants nothing.
+
+ Its family goes with its last version.
+
+ Raises:
+ ConflictError: The version is in use (``DeleteAddon.InUseConflict``).
+ """
+ self._api.send_empty("DELETE", api_path("addons", addon_slug))
+
+ def publish(self, addon_slug: str) -> Addon:
+ """Put a ``DRAFT`` version on sale, and return it ``PUBLISHED``."""
+ return self._api.send("POST", api_path("addons", addon_slug, "publish"), Addon)
+
+ def archive(self, addon_slug: str) -> Addon:
+ """Withdraw a ``PUBLISHED`` version from sale: instances holding it keep it."""
+ return self._api.send("POST", api_path("addons", addon_slug, "archive"), Addon)
+
+ def unarchive(self, addon_slug: str) -> Addon:
+ """Put an ``ARCHIVED`` version back on sale, and return it ``PUBLISHED``."""
+ return self._api.send("POST", api_path("addons", addon_slug, "unarchive"), Addon)
+
+ def list_compatibility(self, addon_slug: str) -> CompatibleLicenseFamilies:
+ """Return the license families an instance must be on to attach this version.
+
+ None means it cannot be attached at all.
+ """
+ return self._api.get(
+ api_path("addons", addon_slug, "compatible-license-families"),
+ CompatibleLicenseFamilies,
+ )
+
+ def set_compatibility(self, addon_slug: str, family_slug: str) -> None:
+ """Let instances on any version of the license family attach this add-on version."""
+ self._api.send_empty(
+ "PUT", api_path("addons", addon_slug, "compatible-license-families", family_slug)
+ )
+
+ def remove_compatibility(self, addon_slug: str, family_slug: str) -> None:
+ """Stop the license family attaching this version; instances holding it keep it."""
+ self._api.send_empty(
+ "DELETE", api_path("addons", addon_slug, "compatible-license-families", family_slug)
+ )
+
+ def list_entitlements(self, addon_slug: str) -> builtins.list[AddonEntitlement]:
+ """Return what the add-on version grants per unit of quantity."""
+ return self._api.get_list(api_path("addons", addon_slug, "entitlements"), AddonEntitlement)
+
+ def get_entitlement(self, addon_slug: str, entitlement_slug: str) -> AddonEntitlement:
+ """Return one grant of the add-on version."""
+ return self._api.get(
+ api_path("addons", addon_slug, "entitlements", entitlement_slug), AddonEntitlement
+ )
+
+ def assign_entitlement(
+ self,
+ addon_slug: str,
+ entitlement_slug: str,
+ value: EntitlementValueInput,
+ *,
+ override_behavior: OverrideBehavior | None = None,
+ overage_percent: int | None = None,
+ ) -> AddonEntitlement:
+ """Grant an entitlement per unit of the add-on version, and return the grant.
+
+ Args:
+ addon_slug: The add-on version that grants.
+ entitlement_slug: The entitlement granted.
+ value: What one unit grants, as for a license grant: a ``bool``, a number, or a
+ mapping for a CONFIG entitlement. A number counts once per unit held.
+ override_behavior: How a number grant combines with the license's: ``ADD``,
+ ``OVERRIDE`` or ``MAX``.
+ overage_percent: The overage the add-on allows: ``-1`` with an unlimited value,
+ ``0`` or more otherwise. Omitted, it inherits the license grant's.
+
+ Raises:
+ ConflictError: An instance with a live subscription holds the version
+ (``AssignAddonEntitlement.BillingActive``).
+ """
+ body = {
+ "entitlementSlug": entitlement_slug,
+ **_grant(value, override_behavior, overage_percent),
+ }
+ return self._api.send(
+ "POST", api_path("addons", addon_slug, "entitlements"), AddonEntitlement, body=body
+ )
+
+ def update_entitlement(
+ self,
+ addon_slug: str,
+ entitlement_slug: str,
+ value: EntitlementValueInput,
+ *,
+ override_behavior: OverrideBehavior | None = None,
+ overage_percent: int | None = None,
+ ) -> AddonEntitlement:
+ """Replace a grant's value, override behavior and overage: see :meth:`assign_entitlement`."""
+ return self._api.send(
+ "PUT",
+ api_path("addons", addon_slug, "entitlements", entitlement_slug),
+ AddonEntitlement,
+ body=_grant(value, override_behavior, overage_percent),
+ )
+
+ def unassign_entitlement(self, addon_slug: str, entitlement_slug: str) -> None:
+ """Remove a grant from the add-on version.
+
+ Raises:
+ ConflictError: An instance with a live subscription holds the version, or an
+ ``ACTIVE`` price of the version meters the entitlement.
+ """
+ self._api.send_empty(
+ "DELETE", api_path("addons", addon_slug, "entitlements", entitlement_slug)
+ )
+
+ def list_prices(
+ self, addon_slug: str, *, status: PriceStatus | None = None
+ ) -> builtins.list[Price]:
+ """Return the prices of the add-on version, in display order; both statuses when omitted."""
+ return self._api.get_list(
+ api_path("addons", addon_slug, "prices"), Price, params={"status": status}
+ )
+
+ def create_price(
+ self,
+ addon_slug: str,
+ *,
+ billing_model: BillingModel,
+ currency: str,
+ unit_amount_decimal: str,
+ billing_period: BillingPeriod | None = None,
+ billing_timing: BillingTiming | None = None,
+ metered_entitlement_slug: str | None = None,
+ is_default: bool | None = None,
+ display_label: str | None = None,
+ display_order: int | None = None,
+ ) -> Price:
+ """Add a price to the add-on version, under the rules of a license price.
+
+ A price never changes once created: create another and deprecate this one. See
+ :meth:`BillingLicensePrices.create` for the arguments.
+ """
+ body = price_body(
+ billing_model=billing_model,
+ currency=currency,
+ unit_amount_decimal=unit_amount_decimal,
+ billing_period=billing_period,
+ billing_timing=billing_timing,
+ metered_entitlement_slug=metered_entitlement_slug,
+ is_default=is_default,
+ display_label=display_label,
+ display_order=display_order,
+ )
+ return self._api.send("POST", api_path("addons", addon_slug, "prices"), Price, body=body)
+
+ def deprecate_price(self, addon_slug: str, price_id: str) -> Price:
+ """Stop offering a price of the add-on version.
+
+ Raises:
+ ConflictError: It is a default price (``DeprecateAddonPrice.IsDefault``), which bills
+ every instance holding the version: retire it through a new version.
+ """
+ return self._api.send(
+ "POST", api_path("addons", addon_slug, "prices", price_id, "deprecate"), Price
+ )
+
+ def list_families(self) -> builtins.list[AddonFamily]:
+ """Return every add-on family with its versions, newest family first."""
+ return self._api.get_list("/addon-families", AddonFamily)
+
+ def get_family(self, family_slug: str) -> AddonFamily:
+ """Return an add-on family with its versions and its current version."""
+ return self._api.get(api_path("addon-families", family_slug), AddonFamily)
+
+ def update_family(
+ self,
+ family_slug: str,
+ *,
+ is_public: bool | None = None,
+ increase_billing: IncreaseBilling | None = None,
+ ) -> AddonFamily:
+ """Change an add-on family's own settings; what is omitted keeps its value.
+
+ Args:
+ family_slug: The family.
+ is_public: List the family's default ``PUBLISHED`` version, with its ``ACTIVE``
+ prices, in the public catalog. Families are private until made public.
+ increase_billing: How a mid-period increase of its add-ons billed in advance is
+ billed, from the next composition: ``TRUE_UP`` or ``NEXT_BOUNDARY``.
+ """
+ body = compact({"isPublic": is_public, "increaseBilling": increase_billing})
+ return self._api.send(
+ "PATCH", api_path("addon-families", family_slug), AddonFamily, body=body
+ )
+
+
+def _grant(
+ value: EntitlementValueInput,
+ override_behavior: OverrideBehavior | None,
+ overage_percent: int | None,
+) -> dict[str, Any]:
+ return {
+ "value": entitlement_value(value),
+ **compact(
+ {
+ "overrideBehavior": override_behavior,
+ "limitCapExceededOveragePercent": overage_percent,
+ }
+ ),
+ }
diff --git a/src/kaitencloud/_sync/resources/billing/customers.py b/src/kaitencloud/_sync/resources/billing/customers.py
new file mode 100644
index 0000000..958e4d9
--- /dev/null
+++ b/src/kaitencloud/_sync/resources/billing/customers.py
@@ -0,0 +1,104 @@
+# Copyright 2026 KAITEN INC
+# SPDX-License-Identifier: Apache-2.0
+
+# Generated by scripts/unasync.py from src/kaitencloud/_async/resources/billing/customers.py. Do not edit by hand:
+# change the async source, then run `task generate`.
+from __future__ import annotations
+
+from ...._utils import api_path, compact
+from ....types import (
+ CompletedPaymentMethodSession,
+ CustomerBilling,
+ PaymentMethodSession,
+ PortalSession,
+)
+from .._base import Resource
+
+__all__ = ["BillingCustomers"]
+
+
+class BillingCustomers(Resource):
+ """A customer's billing: its payment method, and its payment provider's portal.
+
+ The pages it opens are the provider's own: Kaiten never sees card data.
+ """
+
+ def get(self, customer_slug: str) -> CustomerBilling:
+ """Return the customer's billing e-mail, and its side in each payment provider.
+
+ Its id there and the labels of its default payment method -- never card data.
+ """
+ return self._api.get(api_path("customers", customer_slug, "billing"), CustomerBilling)
+
+ def detach_payment_method(self, customer_slug: str) -> None:
+ """Remove the customer's payment method from its payment provider.
+
+ Raises:
+ ConflictError: A live subscription of the customer is charged automatically
+ (``.InUseByAutomaticCollection``), or there is no payment method.
+ """
+ self._api.send_empty(
+ "DELETE", api_path("customers", customer_slug, "billing", "payment-method")
+ )
+
+ def create_payment_method_session(
+ self, customer_slug: str, *, return_url: str, currency: str | None = None
+ ) -> PaymentMethodSession:
+ """Open the payment provider's page where the customer saves a payment method.
+
+ Send the customer to its ``url``. When it comes back to ``return_url``, with
+ ``kaiten_setup_session`` in the query, call :meth:`complete_payment_method_session`.
+
+ Args:
+ customer_slug: The customer.
+ return_url: Where the customer comes back to: https, or http on localhost.
+ currency: The currency the payment method is set up in; required when the customer
+ has no live subscription, whose currency is used otherwise.
+ """
+ return self._api.send(
+ "POST",
+ api_path("customers", customer_slug, "billing", "payment-method-session"),
+ PaymentMethodSession,
+ body={"returnUrl": return_url, **compact({"currency": currency})},
+ )
+
+ def complete_payment_method_session(
+ self, customer_slug: str, session_id: str
+ ) -> CompletedPaymentMethodSession:
+ """Check a setup session with the provider, and charge the customer's invoices to it.
+
+ The redirect is never trusted: the provider is asked. Idempotent.
+
+ Raises:
+ NotFoundError: The session is unknown, or another customer's.
+ ConflictError: The customer did not complete it (``.SessionNotComplete``).
+ """
+ return self._api.send(
+ "POST",
+ api_path(
+ "customers",
+ customer_slug,
+ "billing",
+ "payment-method-session",
+ session_id,
+ "complete",
+ ),
+ CompletedPaymentMethodSession,
+ idempotent=True,
+ )
+
+ def create_portal_session(self, customer_slug: str, *, return_url: str) -> PortalSession:
+ """Open the payment provider's portal, where the customer manages its payment methods.
+
+ What it changes there reaches Kaiten through the provider sync.
+
+ Args:
+ customer_slug: The customer.
+ return_url: Where the portal sends the customer back: https, or http on localhost.
+ """
+ return self._api.send(
+ "POST",
+ api_path("customers", customer_slug, "billing", "portal-session"),
+ PortalSession,
+ body={"returnUrl": return_url},
+ )
diff --git a/src/kaitencloud/_sync/resources/billing/invoices.py b/src/kaitencloud/_sync/resources/billing/invoices.py
new file mode 100644
index 0000000..dea6a88
--- /dev/null
+++ b/src/kaitencloud/_sync/resources/billing/invoices.py
@@ -0,0 +1,379 @@
+# Copyright 2026 KAITEN INC
+# SPDX-License-Identifier: Apache-2.0
+
+# Generated by scripts/unasync.py from src/kaitencloud/_async/resources/billing/invoices.py. Do not edit by hand:
+# change the async source, then run `task generate`.
+from __future__ import annotations
+
+import builtins
+from collections.abc import Sequence
+from datetime import datetime
+from typing import Any, Literal
+
+from ...._constants import EXPORT_MEDIA_TYPES
+from ...._utils import api_path, check_limit, compact, optional_datetime
+from ....types import (
+ BillingProviderKind,
+ HandoffClaim,
+ HandoffStatus,
+ Invoice,
+ InvoiceKind,
+ InvoiceStatus,
+ InvoiceSummary,
+ QueuedInvoice,
+ UsageReport,
+)
+from .._base import Resource
+
+__all__ = ["BillingHandoff", "BillingInvoices"]
+
+
+class BillingInvoices(Resource):
+ """The organization's invoices, addressed by id: an invoice outlives its instance and customer.
+
+ Amounts are integer minor units, as strings. An invoice is a ``DRAFT`` until it is issued:
+ ``MANUAL`` when the organization collects it, ``PUSHED`` to a payment provider otherwise.
+ """
+
+ def list(
+ self,
+ *,
+ instance_slug: str | None = None,
+ customer_slug: str | None = None,
+ status: Sequence[InvoiceStatus] | None = None,
+ kind: InvoiceKind | None = None,
+ provider_kind: BillingProviderKind | None = None,
+ overdue: bool | None = None,
+ held: bool | None = None,
+ handoff_status: HandoffStatus | None = None,
+ issued_from: datetime | str | None = None,
+ issued_to: datetime | str | None = None,
+ boundary_from: datetime | str | None = None,
+ boundary_to: datetime | str | None = None,
+ updated_since: datetime | str | None = None,
+ limit: int | None = None,
+ ) -> builtins.list[InvoiceSummary]:
+ """Return the organization's invoices, newest first.
+
+ With ``updated_since``, the invoices changed since then, oldest change first: store the
+ last ``updated_at`` you read and pass it back for an incremental sync.
+
+ Args:
+ instance_slug: Only this instance's invoices.
+ customer_slug: Only this customer's invoices.
+ status: Only invoices in these statuses.
+ kind: ``ACTIVATION``, ``RENEWAL`` or ``FINAL``.
+ provider_kind: Only invoices issued by ``NOOP`` or by a payment provider.
+ overdue: Only issued invoices still unpaid past their due date.
+ held: Only drafts held for their usage journal.
+ handoff_status: ``NOT_REQUIRED``, ``PENDING`` or ``ACKNOWLEDGED``.
+ issued_from: Issued at or after this moment.
+ issued_to: Issued before this moment.
+ boundary_from: Billing a boundary at or after this moment.
+ boundary_to: Billing a boundary before this moment.
+ updated_since: Only invoices changed at or after this moment.
+ limit: The most invoices to return. Every page is walked when omitted.
+ """
+ return self._api.list_all(
+ "/invoices",
+ InvoiceSummary,
+ params={
+ "instanceSlug": instance_slug,
+ **invoice_query(
+ customer_slug=customer_slug,
+ status=status,
+ kind=kind,
+ provider_kind=provider_kind,
+ overdue=overdue,
+ held=held,
+ handoff_status=handoff_status,
+ issued_from=issued_from,
+ issued_to=issued_to,
+ boundary_from=boundary_from,
+ boundary_to=boundary_to,
+ updated_since=updated_since,
+ ),
+ },
+ limit=check_limit(limit),
+ )
+
+ def get(self, invoice_id: str) -> Invoice:
+ """Return an invoice with its lines.
+
+ It carries its hold and its handoff, and its customer and instance as they were when it
+ was composed.
+ """
+ return self._api.get(api_path("invoices", invoice_id), Invoice)
+
+ def export(
+ self,
+ *,
+ format: Literal["csv", "json"] = "csv",
+ granularity: Literal["line", "invoice"] | None = None,
+ instance_slug: str | None = None,
+ customer_slug: str | None = None,
+ status: Sequence[InvoiceStatus] | None = None,
+ kind: InvoiceKind | None = None,
+ provider_kind: BillingProviderKind | None = None,
+ overdue: bool | None = None,
+ held: bool | None = None,
+ handoff_status: HandoffStatus | None = None,
+ issued_from: datetime | str | None = None,
+ issued_to: datetime | str | None = None,
+ boundary_from: datetime | str | None = None,
+ boundary_to: datetime | str | None = None,
+ updated_since: datetime | str | None = None,
+ ) -> bytes:
+ """Return the invoices the filters of :meth:`list` select, exported.
+
+ Amounts are integer minor units with the currency's exponent beside them. The export
+ carries billing e-mails: treat it as the accounting read it is.
+
+ Args:
+ format: ``"csv"`` (the default), RFC 4180 with a header row, or ``"json"`` for
+ NDJSON, one invoice with its lines per line.
+ granularity: For CSV, ``"line"`` (the default) -- one row per invoice line -- or
+ ``"invoice"``, one row per invoice.
+ instance_slug: Only this instance's invoices. The other filters are
+ :meth:`list`'s.
+ """
+ response = self._api.request(
+ "GET",
+ "/invoices/export",
+ params={
+ "format": format,
+ "granularity": granularity,
+ "instanceSlug": instance_slug,
+ **invoice_query(
+ customer_slug=customer_slug,
+ status=status,
+ kind=kind,
+ provider_kind=provider_kind,
+ overdue=overdue,
+ held=held,
+ handoff_status=handoff_status,
+ issued_from=issued_from,
+ issued_to=issued_to,
+ boundary_from=boundary_from,
+ boundary_to=boundary_to,
+ updated_since=updated_since,
+ ),
+ },
+ headers={"Accept": EXPORT_MEDIA_TYPES[format]},
+ )
+ return response.content
+
+ def void(self, invoice_id: str, *, reason: str) -> Invoice:
+ """Void an invoice not yet settled, which frees its boundary for a recompose.
+
+ A payment provider's invoice is voided there first, and here only once it confirms.
+ Voiding a ``VOID`` invoice again answers it unchanged.
+
+ Args:
+ invoice_id: The invoice.
+ reason: Why, 1 to 500 characters.
+ """
+ return self._api.send(
+ "POST",
+ api_path("invoices", invoice_id, "void"),
+ Invoice,
+ body={"reason": reason},
+ idempotent=True,
+ )
+
+ def recompose(self, invoice_id: str) -> Invoice:
+ """Compose an invoice again from the usage journal as it is now, keeping its base lines.
+
+ A held draft is rewritten in place; a ``VOID`` invoice gets a replacement for the same
+ boundary, which is returned. Any other invoice must be voided first.
+ """
+ return self._api.send("POST", api_path("invoices", invoice_id, "recompose"), Invoice)
+
+ def mark_paid(
+ self,
+ invoice_id: str,
+ *,
+ paid_at: datetime | str | None = None,
+ external_reference: str | None = None,
+ note: str | None = None,
+ ) -> Invoice:
+ """Record that a ``MANUAL`` invoice was paid.
+
+ An invoice still waiting in the handoff queue is acknowledged by the same write. An
+ invoice a payment provider collects is settled there instead.
+
+ Args:
+ invoice_id: The invoice.
+ paid_at: When it was paid, now or earlier. Now when omitted.
+ external_reference: Its number in the accounting system, 1 to 255 characters.
+ note: A note, such as the transfer's reference, kept only in the paid event.
+ """
+ body = compact(
+ {
+ "paidAt": optional_datetime(paid_at),
+ "externalReference": external_reference,
+ "note": note,
+ }
+ )
+ return self._api.send(
+ "POST", api_path("invoices", invoice_id, "mark-paid"), Invoice, body=body
+ )
+
+ def write_off(self, invoice_id: str, *, reason: str) -> Invoice:
+ """Give up collecting a ``MANUAL`` invoice: it becomes ``UNCOLLECTIBLE``.
+
+ Args:
+ invoice_id: The invoice.
+ reason: Why, 1 to 500 characters.
+ """
+ return self._api.send(
+ "POST",
+ api_path("invoices", invoice_id, "write-off"),
+ Invoice,
+ body={"reason": reason},
+ idempotent=True,
+ )
+
+ def release_hold(self, invoice_id: str, *, reason: str) -> Invoice:
+ """Accept a held invoice as composed, and issue it.
+
+ Args:
+ invoice_id: The invoice.
+ reason: Why, 1 to 500 characters. The release keeps it, with who released it.
+
+ Raises:
+ ConflictError: The hold is ``PROVIDER_ITEM_LIMIT``, which the provider would refuse
+ (``ReleaseInvoiceHold.ProviderItemLimit``).
+ """
+ return self._api.send(
+ "POST",
+ api_path("invoices", invoice_id, "release-hold"),
+ Invoice,
+ body={"reason": reason},
+ )
+
+ def retry_push(self, invoice_id: str) -> Invoice:
+ """Put a ``DRAFT`` or ``PUSH_FAILED`` invoice of a payment provider back in the push queue."""
+ return self._api.send("POST", api_path("invoices", invoice_id, "retry-push"), Invoice)
+
+ def sync(self, invoice_id: str) -> Invoice:
+ """Read an invoice from its payment provider now, and apply what the provider says."""
+ return self._api.send("POST", api_path("invoices", invoice_id, "sync"), Invoice)
+
+ def list_line_reports(
+ self, invoice_id: str, line_id: str, *, limit: int | None = None
+ ) -> builtins.list[UsageReport]:
+ """Return the usage reports a ``USAGE`` or ``OVERAGE`` line was measured from.
+
+ Summed by reset window and floored at 0, they give the line's measured quantity. It
+ answers after the instance was deleted.
+
+ Args:
+ invoice_id: The invoice.
+ line_id: The line, as the invoice's lines carry it.
+ limit: The most reports to return. Every page is walked when omitted.
+ """
+ return self._api.list_reports(
+ api_path("invoices", invoice_id, "lines", line_id, "reports"),
+ UsageReport,
+ limit=check_limit(limit),
+ )
+
+
+class BillingHandoff(Resource):
+ """The accounting handoff: the invoices waiting for the organization's accounting system.
+
+ Claim a batch under a lease, book each invoice, then acknowledge it. An invoice not
+ acknowledged before its lease expires is claimed again: the queue delivers at least once,
+ so deduplicate on the invoice id.
+ """
+
+ def list(
+ self,
+ *,
+ status: Literal["PENDING", "ACKNOWLEDGED"] | None = None,
+ limit: int | None = None,
+ ) -> builtins.list[QueuedInvoice]:
+ """Return the queue, oldest issue first, without leasing anything.
+
+ Args:
+ status: ``PENDING`` (the default) or ``ACKNOWLEDGED``.
+ limit: The most invoices to return. Every page is walked when omitted.
+ """
+ return self._api.list_all(
+ "/billing/handoff",
+ QueuedInvoice,
+ params={"status": status},
+ limit=check_limit(limit),
+ )
+
+ def claim(self, *, limit: int | None = None, lease_seconds: int | None = None) -> HandoffClaim:
+ """Lease the oldest invoices waiting, under one lease, and return them.
+
+ Two consumers claiming at once get different invoices. A claim is not idempotent: a lost
+ response leaves its invoices leased until ``leased_until``, so it is never retried.
+
+ Args:
+ limit: How many invoices to lease, 1 to 100. 25 when omitted.
+ lease_seconds: How long the lease lasts, 60 to 3600 seconds. 900 when omitted.
+ """
+ body = compact({"limit": limit, "leaseSeconds": lease_seconds})
+ return self._api.send("POST", "/billing/handoff/claim", HandoffClaim, body=body)
+
+ def ack(
+ self,
+ invoice_id: str,
+ *,
+ lease_id: str | None = None,
+ external_reference: str | None = None,
+ ) -> Invoice:
+ """Record that the accounting system booked an invoice of the queue. Safe to repeat.
+
+ Args:
+ invoice_id: The invoice.
+ lease_id: The claim's lease. When given, an invoice another claim took since is
+ refused (``AckHandoff.LeaseMismatch``).
+ external_reference: The invoice's number in the accounting system. Another
+ reference than the one it was acknowledged under is refused
+ (``AckHandoff.ReferenceMismatch``): take it as already handed off.
+ """
+ body = compact({"leaseId": lease_id, "externalReference": external_reference})
+ return self._api.send(
+ "POST",
+ api_path("billing", "handoff", invoice_id, "ack"),
+ Invoice,
+ body=body,
+ idempotent=True,
+ )
+
+
+def invoice_query(
+ *,
+ customer_slug: str | None,
+ status: Sequence[InvoiceStatus] | None,
+ kind: InvoiceKind | None,
+ provider_kind: BillingProviderKind | None,
+ overdue: bool | None,
+ held: bool | None,
+ handoff_status: HandoffStatus | None,
+ issued_from: datetime | str | None,
+ issued_to: datetime | str | None,
+ boundary_from: datetime | str | None,
+ boundary_to: datetime | str | None,
+ updated_since: datetime | str | None,
+) -> dict[str, Any]:
+ """The filters every invoice listing takes, as query parameters."""
+ return {
+ "customerSlug": customer_slug,
+ "status": list(status) if status is not None else None,
+ "kind": kind,
+ "providerKind": provider_kind,
+ "overdue": overdue,
+ "held": held,
+ "handoffStatus": handoff_status,
+ "issuedFrom": optional_datetime(issued_from),
+ "issuedTo": optional_datetime(issued_to),
+ "boundaryFrom": optional_datetime(boundary_from),
+ "boundaryTo": optional_datetime(boundary_to),
+ "updatedSince": optional_datetime(updated_since),
+ }
diff --git a/src/kaitencloud/_sync/resources/billing/license_prices.py b/src/kaitencloud/_sync/resources/billing/license_prices.py
new file mode 100644
index 0000000..4780630
--- /dev/null
+++ b/src/kaitencloud/_sync/resources/billing/license_prices.py
@@ -0,0 +1,194 @@
+# Copyright 2026 KAITEN INC
+# SPDX-License-Identifier: Apache-2.0
+
+# Generated by scripts/unasync.py from src/kaitencloud/_async/resources/billing/license_prices.py. Do not edit by hand:
+# change the async source, then run `task generate`.
+from __future__ import annotations
+
+import builtins
+from collections.abc import Mapping
+
+from ...._utils import addon_quantities, api_path, compact, price_body
+from ....types import (
+ BillingModel,
+ BillingPeriod,
+ BillingTiming,
+ InvoicePreview,
+ Price,
+ PriceStatus,
+)
+from .._base import Resource
+
+__all__ = ["BillingLicensePrices"]
+
+
+class BillingLicensePrices(Resource):
+ """The prices of license versions.
+
+ A ``FLAT_FEE`` price bills a subscription each period; a ``USAGE_BASED`` price bills the
+ usage of an entitlement, and an ``OVERAGE`` price the usage above the license's limit, per
+ sale unit. Amounts are decimal strings in minor units: ``"4900"`` is 49.00 in a currency
+ with two decimals. The prices of a published version never change.
+ """
+
+ def list(
+ self,
+ license_slug: str,
+ *,
+ status: PriceStatus | None = None,
+ billing_model: BillingModel | None = None,
+ ) -> builtins.list[Price]:
+ """Return the prices of the license version, in display order; both statuses when omitted."""
+ return self._api.get_list(
+ api_path("licenses", license_slug, "prices"),
+ Price,
+ params={"status": status, "billingModel": billing_model},
+ )
+
+ def get(self, license_slug: str, price_id: str) -> Price:
+ """Return one price of the license version."""
+ return self._api.get(api_path("licenses", license_slug, "prices", price_id), Price)
+
+ def create(
+ self,
+ license_slug: str,
+ *,
+ billing_model: BillingModel,
+ currency: str,
+ unit_amount_decimal: str,
+ billing_period: BillingPeriod | None = None,
+ billing_timing: BillingTiming | None = None,
+ metered_entitlement_slug: str | None = None,
+ is_default: bool | None = None,
+ display_label: str | None = None,
+ display_order: int | None = None,
+ ) -> Price:
+ """Add a price to the license version, and return it.
+
+ Args:
+ license_slug: The license version.
+ billing_model: ``FLAT_FEE``, ``USAGE_BASED`` or ``OVERAGE``.
+ currency: An ISO 4217 code, upper case. One currency per version.
+ unit_amount_decimal: The amount in minor units, as a decimal string: per period for
+ ``FLAT_FEE``, per sale unit for a metered price.
+ billing_period: ``MONTHLY``, ``QUARTERLY``, ``SEMI_ANNUAL`` or ``ANNUAL``. Required
+ on a ``FLAT_FEE`` price, refused on a metered one.
+ billing_timing: ``ADVANCE`` (the default for ``FLAT_FEE``) or ``ARREARS``, which a
+ metered price must be.
+ metered_entitlement_slug: The entitlement a metered price measures: granted by the
+ version, with a reset period, aggregated by ``SUM`` or ``COUNT``.
+ is_default: Make this ``FLAT_FEE`` price the default of its billing period.
+ display_label: The invoice line's label.
+ display_order: Its position among the version's prices.
+
+ Raises:
+ ConflictError: Another price is already the default of the period
+ (``CreateLicensePrice.DefaultConflict``).
+ """
+ body = price_body(
+ billing_model=billing_model,
+ currency=currency,
+ unit_amount_decimal=unit_amount_decimal,
+ billing_period=billing_period,
+ billing_timing=billing_timing,
+ metered_entitlement_slug=metered_entitlement_slug,
+ is_default=is_default,
+ display_label=display_label,
+ display_order=display_order,
+ )
+ return self._api.send(
+ "POST", api_path("licenses", license_slug, "prices"), Price, body=body
+ )
+
+ def update(
+ self,
+ license_slug: str,
+ price_id: str,
+ *,
+ unit_amount_decimal: str | None = None,
+ billing_period: BillingPeriod | None = None,
+ billing_timing: BillingTiming | None = None,
+ metered_entitlement_slug: str | None = None,
+ is_default: bool | None = None,
+ display_label: str | None = None,
+ display_order: int | None = None,
+ ) -> Price:
+ """Edit a price of a ``DRAFT`` version; what is omitted keeps its value.
+
+ Its currency and billing model never change, and the prices of a published version are
+ immutable (``UpdateLicensePrice.VersionNotDraft``).
+ """
+ body = compact(
+ {
+ "unitAmountDecimal": unit_amount_decimal,
+ "billingPeriod": billing_period,
+ "billingTiming": billing_timing,
+ "meteredEntitlementSlug": metered_entitlement_slug,
+ "isDefault": is_default,
+ "displayLabel": display_label,
+ "displayOrder": display_order,
+ }
+ )
+ return self._api.send(
+ "PATCH", api_path("licenses", license_slug, "prices", price_id), Price, body=body
+ )
+
+ def deprecate(self, license_slug: str, price_id: str) -> Price:
+ """Retire a price from the catalog, for good.
+
+ It keeps billing the subscriptions pinned to it, is no longer offered, and stops being a
+ default.
+ """
+ return self._api.send(
+ "POST", api_path("licenses", license_slug, "prices", price_id, "deprecate"), Price
+ )
+
+ def preview_invoice(
+ self,
+ license_slug: str,
+ *,
+ base_price_id: str | None = None,
+ add_ons: Mapping[str, int] | None = None,
+ sample_usage: Mapping[str, str] | None = None,
+ instance_slug: str | None = None,
+ voucher_code: str | None = None,
+ ) -> InvoicePreview:
+ """Compose the ``RENEWAL`` invoice a subscription to the version would get now.
+
+ Nothing is written, and it works on a ``DRAFT`` version, to check its prices before
+ publishing it.
+
+ Args:
+ license_slug: The license version.
+ base_price_id: The ``FLAT_FEE`` price billed as the base. Defaults to the version's
+ default one.
+ add_ons: Add-on versions priced in, as ``{addon_slug: quantity}``.
+ sample_usage: Usage of the metered entitlements over the period that ends, as
+ ``{entitlement_slug: quantity}`` in decimal strings. An entitlement left out
+ used nothing.
+ instance_slug: Rate this instance's own usage instead of a sample.
+ voucher_code: Apply a ``PRICE`` voucher's discount, consuming nothing.
+ """
+ body = compact(
+ {
+ "basePriceId": base_price_id,
+ "addOns": addon_quantities(add_ons),
+ "sampleUsage": (
+ [
+ {"entitlementSlug": slug, "quantity": quantity}
+ for slug, quantity in sample_usage.items()
+ ]
+ if sample_usage is not None
+ else None
+ ),
+ "instanceSlug": instance_slug,
+ "voucherCode": voucher_code,
+ }
+ )
+ return self._api.send(
+ "POST",
+ api_path("licenses", license_slug, "invoice-preview"),
+ InvoicePreview,
+ body=body,
+ idempotent=True,
+ )
diff --git a/src/kaitencloud/_sync/resources/billing/public.py b/src/kaitencloud/_sync/resources/billing/public.py
new file mode 100644
index 0000000..ee1fdb0
--- /dev/null
+++ b/src/kaitencloud/_sync/resources/billing/public.py
@@ -0,0 +1,324 @@
+# Copyright 2026 KAITEN INC
+# SPDX-License-Identifier: Apache-2.0
+
+# Generated by scripts/unasync.py from src/kaitencloud/_async/resources/billing/public.py. Do not edit by hand:
+# change the async source, then run `task generate`.
+from __future__ import annotations
+
+import builtins
+from collections.abc import Mapping
+
+from ...._utils import addon_quantities, api_path, check_limit, compact
+from ....types import (
+ BillingPeriod,
+ CheckoutConfirmation,
+ CheckoutSession,
+ CheckoutSessionStart,
+ CompletedPaymentMethodSession,
+ PaymentMethodSession,
+ PortalSession,
+ PublicCatalog,
+ SessionAddon,
+ SessionCheckout,
+ SessionInvoice,
+ SessionPortal,
+ SessionRedemption,
+ SessionSubscription,
+ SessionVoucherValidity,
+)
+from .._base import Resource
+
+__all__ = ["BillingPublic", "BillingSession"]
+
+
+class BillingPublic(Resource):
+ """What a publishable key (``pk_...``) reaches: the public catalog and anonymous checkouts.
+
+ Build the client with the key as its token -- the SDK sends it as the API requires, in
+ ``X-Kaiten-Publishable-Key`` and never as a bearer token::
+
+ public = KaitenClient(base_url="https://kaiten.example.com", token="pk_...")
+ catalog = public.billing.public.get_catalog()
+
+ An anonymous checkout's own routes take the token it was started with (``ksc_...``)
+ instead: build a client with that token to read or confirm it.
+ """
+
+ def get_catalog(
+ self, *, family_slug: str | None = None, include_addons: bool = True
+ ) -> PublicCatalog:
+ """Return what a pricing page shows: the public plans, their prices and entitlements.
+
+ For each license family the organization made public, its default version with its
+ ``ACTIVE`` prices and user-facing entitlements, and the same for public add-on
+ families. Amounts are in minor units: never sum metered prices into a headline.
+
+ Args:
+ family_slug: Only this license family.
+ include_addons: Also list the public add-ons. True by default.
+ """
+ return self._api.get(
+ "/public/catalog",
+ PublicCatalog,
+ params={"familySlug": family_slug, "include": None if include_addons else ["none"]},
+ )
+
+ def create_checkout_session(
+ self,
+ *,
+ turnstile_token: str,
+ price_id: str | None = None,
+ license_slug: str | None = None,
+ billing_period: BillingPeriod | None = None,
+ add_ons: Mapping[str, int] | None = None,
+ email: str | None = None,
+ return_url: str | None = None,
+ voucher_code: str | None = None,
+ trial: bool | None = None,
+ client_reference_id: str | None = None,
+ dry_run: bool | None = None,
+ ) -> CheckoutSessionStart:
+ """Start a checkout for a visitor your backend does not know yet.
+
+ It writes one checkout session and nothing else: the customer, the instance and the
+ subscription are created when it completes. The session's token (``ksc_...``) is in
+ this response only. The key must allow anonymous checkouts.
+
+ Args:
+ turnstile_token: The token the Turnstile widget gave the page. Single-use.
+ price_id: A ``FLAT_FEE`` price of a public plan. Not with ``license_slug``.
+ license_slug: A public plan's version, with ``billing_period``: its default
+ ``ACTIVE`` ``FLAT_FEE`` price of that period.
+ billing_period: ``MONTHLY``, ``QUARTERLY``, ``SEMI_ANNUAL`` or ``ANNUAL``.
+ add_ons: Public add-ons, as ``{addon_slug: quantity}``.
+ email: The buyer's e-mail, which becomes the new customer's billing e-mail. Required
+ unless ``dry_run``.
+ return_url: Where the payment-method page sends the visitor back, on an origin the
+ key allows. Required unless ``dry_run``.
+ voucher_code: A code to apply to the selection.
+ trial: ``False`` declines the license's trial.
+ client_reference_id: Your own reference, echoed on the ``checkout_session`` events.
+ dry_run: Compute what would be due today, and write nothing.
+ """
+ body = {
+ "turnstileToken": turnstile_token,
+ **compact(
+ {
+ "priceId": price_id,
+ "licenseSlug": license_slug,
+ "billingPeriod": billing_period,
+ "addOns": addon_quantities(add_ons),
+ "email": email,
+ "returnUrl": return_url,
+ "voucherCode": voucher_code,
+ "trial": trial,
+ "clientReferenceId": client_reference_id,
+ "dryRun": dry_run,
+ }
+ ),
+ }
+ return self._api.send("POST", "/public/checkout-sessions", CheckoutSessionStart, body=body)
+
+ def get_checkout_session(self, session_id: str) -> CheckoutSession:
+ """Return the checkout session the client's ``ksc_`` token was minted for.
+
+ Its status, what it bought and what is due, and once completed, the customer and
+ instance it created. The token reads it for 24 hours after it closed.
+ """
+ return self._api.get(api_path("public", "checkout-sessions", session_id), CheckoutSession)
+
+ def confirm_checkout_session(self, session_id: str) -> CheckoutConfirmation:
+ """Complete the checkout session when nothing has to be captured, or say what is missing.
+
+ A free plan, a trial that needs no payment method, or nothing due completes it at once:
+ ``completed``. Otherwise ``requires_payment_method`` comes with the page where the
+ visitor saves one, and the next confirmation completes it.
+ """
+ return self._api.send(
+ "POST",
+ api_path("public", "checkout-sessions", session_id, "confirm"),
+ CheckoutConfirmation,
+ )
+
+
+class BillingSession(Resource):
+ """What a customer session (``kst_...``) reaches: one customer's own billing.
+
+ These are the routes a customer's page calls with the session your backend minted with
+ :meth:`BillingCustomerSessions.create`. Build the client with the session's token to
+ call them from Python::
+
+ customer = KaitenClient(base_url="https://kaiten.example.com", token=session.token)
+ portal = customer.billing.session.get_portal()
+
+ The subscription routes need a session bound to an instance.
+ """
+
+ def get_portal(self) -> SessionPortal:
+ """Return everything the customer's portal shows, in one read.
+
+ The customer, and for a session bound to an instance its subscription, effective quotas
+ with where they come from, add-ons, vouchers and next invoice; the payment method; and
+ what the portal may offer.
+ """
+ return self._api.get("/public/session/portal", SessionPortal)
+
+ def list_invoices(self, *, limit: int | None = None) -> builtins.list[SessionInvoice]:
+ """Return the customer's issued invoices, newest first, with how to pay each.
+
+ Args:
+ limit: The most invoices to return. Every page is walked when omitted.
+ """
+ return self._api.list_all(
+ "/public/session/invoices", SessionInvoice, limit=check_limit(limit)
+ )
+
+ def checkout(
+ self,
+ *,
+ license_price_id: str,
+ add_ons: Mapping[str, int] | None = None,
+ voucher_code: str | None = None,
+ billing_email: str | None = None,
+ return_url: str | None = None,
+ setup_session_id: str | None = None,
+ trial: bool | None = None,
+ dry_run: bool | None = None,
+ ) -> SessionCheckout:
+ """Subscribe the session's instance to a price of the public catalog, in one call.
+
+ ``status`` says what happened: ``preview`` for a dry run, ``requires_payment_method``
+ with the page where the customer saves one -- call again with its ``setup_session_id``
+ afterwards -- or ``subscribed``.
+
+ Args:
+ license_price_id: An ``ACTIVE`` flat-fee price of a public plan.
+ add_ons: Public add-ons that fit the plan, as ``{addon_slug: quantity}``.
+ voucher_code: A code to redeem with the checkout.
+ billing_email: Where invoices go; required when the customer has none.
+ return_url: Where the payment-method page sends the customer back.
+ setup_session_id: The setup session a ``requires_payment_method`` answer gave.
+ trial: ``False`` skips the plan's trial.
+ dry_run: Answer what the checkout would bill today, and change nothing.
+ """
+ body = {
+ "licensePriceId": license_price_id,
+ **compact(
+ {
+ "addOns": addon_quantities(add_ons),
+ "voucherCode": voucher_code,
+ "billingEmail": billing_email,
+ "returnUrl": return_url,
+ "setupSessionId": setup_session_id,
+ "trial": trial,
+ "dryRun": dry_run,
+ }
+ ),
+ }
+ return self._api.send("POST", "/public/session/checkout", SessionCheckout, body=body)
+
+ def cancel(self, *, reason: str | None = None) -> SessionSubscription:
+ """Cancel the instance's subscription at the end of the period paid for.
+
+ It keeps billing and granting until then. A trial ends at once. Repeating it changes
+ nothing.
+
+ Args:
+ reason: Why the customer cancels, up to 500 characters; you see it on the
+ subscription.
+ """
+ return self._api.send(
+ "POST",
+ "/public/session/billing/cancel",
+ SessionSubscription,
+ body=compact({"reason": reason}),
+ idempotent=True,
+ )
+
+ def reactivate(self) -> SessionSubscription:
+ """Take back a cancellation scheduled for the period's end, before it."""
+ return self._api.send("POST", "/public/session/billing/reactivate", SessionSubscription)
+
+ def set_addon_quantity(self, addon_slug: str, quantity: int) -> SessionAddon:
+ """Attach a public add-on to the instance, change its quantity, or remove it with ``0``.
+
+ With a live subscription, a lower quantity is scheduled for the next renewal:
+ ``pending_quantity`` shows it.
+ """
+ return self._api.send(
+ "PUT",
+ api_path("public", "session", "addons", addon_slug),
+ SessionAddon,
+ body={"quantity": quantity},
+ )
+
+ def create_payment_method_session(
+ self, *, return_url: str, currency: str | None = None
+ ) -> PaymentMethodSession:
+ """Open the payment provider's page where the customer saves a payment method.
+
+ Args:
+ return_url: Where the page sends the customer back, on an origin one of the
+ organization's publishable keys allows.
+ currency: Required when the customer has no live subscription.
+ """
+ return self._api.send(
+ "POST",
+ "/public/session/billing/payment-method-session",
+ PaymentMethodSession,
+ body={"returnUrl": return_url, **compact({"currency": currency})},
+ )
+
+ def complete_payment_method_session(self, session_id: str) -> CompletedPaymentMethodSession:
+ """Check a setup session with the provider, and charge the customer's invoices to it.
+
+ Safe to repeat.
+ """
+ return self._api.send(
+ "POST",
+ api_path(
+ "public", "session", "billing", "payment-method-session", session_id, "complete"
+ ),
+ CompletedPaymentMethodSession,
+ idempotent=True,
+ )
+
+ def create_portal_session(self, *, return_url: str) -> PortalSession:
+ """Open the payment provider's portal for the customer.
+
+ Args:
+ return_url: Where the portal sends the customer back, on an origin one of the
+ organization's publishable keys allows.
+ """
+ return self._api.send(
+ "POST",
+ "/public/session/billing/portal-session",
+ PortalSession,
+ body={"returnUrl": return_url},
+ )
+
+ def validate_voucher(
+ self, code: str, *, license_price_id: str | None = None
+ ) -> SessionVoucherValidity:
+ """Say whether a code would apply to the customer, with what it gives. Nothing is redeemed.
+
+ Args:
+ code: The code as the customer typed it.
+ license_price_id: The price about to be checked out.
+ """
+ body = {"code": code, **compact({"licensePriceId": license_price_id})}
+ return self._api.send(
+ "POST", "/public/session/vouchers/validate", SessionVoucherValidity, body=body
+ )
+
+ def redeem_voucher(self, code: str, *, dry_run: bool | None = None) -> SessionRedemption:
+ """Redeem a code on the instance, and return the redemption -- never the code.
+
+ Args:
+ code: The code as the customer typed it.
+ dry_run: Answer the redemption it would make, and write nothing.
+ """
+ body = {"code": code, **compact({"dryRun": dry_run})}
+ return self._api.send(
+ "POST", "/public/session/vouchers/redeem", SessionRedemption, body=body
+ )
diff --git a/src/kaitencloud/_sync/resources/billing/sessions.py b/src/kaitencloud/_sync/resources/billing/sessions.py
new file mode 100644
index 0000000..e085136
--- /dev/null
+++ b/src/kaitencloud/_sync/resources/billing/sessions.py
@@ -0,0 +1,186 @@
+# Copyright 2026 KAITEN INC
+# SPDX-License-Identifier: Apache-2.0
+
+# Generated by scripts/unasync.py from src/kaitencloud/_async/resources/billing/sessions.py. Do not edit by hand:
+# change the async source, then run `task generate`.
+from __future__ import annotations
+
+import builtins
+from collections.abc import Sequence
+
+from ...._utils import api_path, check_limit, compact
+from ....types import (
+ CheckoutSessionAdmin,
+ CheckoutSessionStatus,
+ CreatedCustomerSession,
+ PublishableKey,
+ PublishableKeyCreated,
+)
+from .._base import Resource
+
+__all__ = [
+ "BillingCheckoutSessions",
+ "BillingCustomerSessions",
+ "BillingPublishableKeys",
+]
+
+
+class BillingPublishableKeys(Resource):
+ """Publishable keys (``pk_...``): what a web page reads the public catalog with.
+
+ A publishable key is not a secret: it ships in pages, and a browser may send it only from
+ one of its allowed origins. With ``anonymous_checkout``, it also starts checkouts for
+ visitors your backend does not know yet.
+ """
+
+ def list(self, *, include_revoked: bool = False) -> builtins.list[PublishableKey]:
+ """Return the organization's keys, newest first: their last characters, never the keys."""
+ return self._api.list_all(
+ "/publishable-keys", PublishableKey, params={"includeRevoked": include_revoked or None}
+ )
+
+ def create(
+ self,
+ *,
+ label: str,
+ allowed_origins: Sequence[str],
+ anonymous_checkout: bool | None = None,
+ turnstile_site_key: str | None = None,
+ turnstile_secret: str | None = None,
+ ) -> PublishableKeyCreated:
+ """Issue a publishable key, and return it with the key, which exists in this response only.
+
+ Args:
+ label: What the key is for.
+ allowed_origins: The browser origins allowed to send it, as
+ ``scheme://host[:port]``: https, or http on localhost. Empty, no browser may;
+ a request without an ``Origin`` header -- server-side rendering -- still may.
+ anonymous_checkout: Let the key start anonymous checkouts. It needs a Turnstile site
+ key and secret.
+ turnstile_site_key: The Turnstile site key the page renders its widget with.
+ turnstile_secret: The Turnstile secret the server verifies tokens with, kept in
+ Vault and never returned.
+ """
+ body = {
+ "label": label,
+ "allowedOrigins": list(allowed_origins),
+ **compact(
+ {
+ "anonymousCheckout": anonymous_checkout,
+ "turnstileSiteKey": turnstile_site_key,
+ "turnstileSecret": turnstile_secret,
+ }
+ ),
+ }
+ return self._api.send("POST", "/publishable-keys", PublishableKeyCreated, body=body)
+
+ def update(
+ self,
+ key_id: str,
+ *,
+ label: str | None = None,
+ allowed_origins: Sequence[str] | None = None,
+ anonymous_checkout: bool | None = None,
+ turnstile_site_key: str | None = None,
+ turnstile_secret: str | None = None,
+ ) -> PublishableKey:
+ """Change a live key; what is omitted keeps its value, and ``""`` clears a Turnstile value.
+
+ Raises:
+ ConflictError: The key is revoked (``UpdatePublishableKey.Revoked``).
+ """
+ body = compact(
+ {
+ "label": label,
+ "allowedOrigins": list(allowed_origins) if allowed_origins is not None else None,
+ "anonymousCheckout": anonymous_checkout,
+ "turnstileSiteKey": turnstile_site_key,
+ "turnstileSecret": turnstile_secret,
+ }
+ )
+ return self._api.send(
+ "PATCH", api_path("publishable-keys", key_id), PublishableKey, body=body
+ )
+
+ def revoke(self, key_id: str) -> PublishableKey:
+ """Revoke a key, for good, with its anonymous checkout sessions. Idempotent."""
+ return self._api.send(
+ "POST", api_path("publishable-keys", key_id, "revoke"), PublishableKey, idempotent=True
+ )
+
+
+class BillingCustomerSessions(Resource):
+ """Customer sessions (``kst_...``): what lets a customer's browser act on its own billing.
+
+ Mint one from your backend, where the customer is signed in, and hand its token to the
+ page, which calls the routes of :attr:`Billing.session` with it.
+ """
+
+ def create(
+ self,
+ *,
+ customer_slug: str,
+ instance_slug: str | None = None,
+ ttl_seconds: int | None = None,
+ ) -> CreatedCustomerSession:
+ """Mint a session for a customer, and return it with its token, shown this once.
+
+ What the session writes is attributed to the credential that minted it.
+
+ Args:
+ customer_slug: The customer the session acts for.
+ instance_slug: Bind it to one of the customer's instances, which checkout, cancel
+ and the other subscription routes need.
+ ttl_seconds: How long it lives, 300 to 3600 seconds. 30 minutes when omitted.
+ """
+ body = {
+ "customerSlug": customer_slug,
+ **compact({"instanceSlug": instance_slug, "ttlSeconds": ttl_seconds}),
+ }
+ return self._api.send("POST", "/customer-sessions", CreatedCustomerSession, body=body)
+
+ def revoke(self, session_id: str) -> None:
+ """Stop a session authenticating, from its next request: on sign-out, say. Idempotent."""
+ self._api.send_empty(
+ "POST", api_path("customer-sessions", session_id, "revoke"), idempotent=True
+ )
+
+
+class BillingCheckoutSessions(Resource):
+ """Anonymous checkouts, as the organization reads them: never their token or voucher code.
+
+ It is how a buyer is linked to your own account when the ``checkout_session`` events were
+ missed.
+ """
+
+ def list(
+ self,
+ *,
+ status: Sequence[CheckoutSessionStatus] | None = None,
+ email: str | None = None,
+ client_reference_id: str | None = None,
+ limit: int | None = None,
+ ) -> builtins.list[CheckoutSessionAdmin]:
+ """Return the organization's anonymous checkouts, newest first.
+
+ Args:
+ status: Only sessions in these statuses. A session ``OPEN`` past its expiry is
+ ``ABANDONED``.
+ email: Only the visitor with this e-mail, in any letter case.
+ client_reference_id: Only the sessions started with this reference of yours.
+ limit: The most sessions to return. Every page is walked when omitted.
+ """
+ return self._api.list_all(
+ "/checkout-sessions",
+ CheckoutSessionAdmin,
+ params={
+ "status": list(status) if status is not None else None,
+ "email": email,
+ "clientReferenceId": client_reference_id,
+ },
+ limit=check_limit(limit),
+ )
+
+ def get(self, session_id: str) -> CheckoutSessionAdmin:
+ """Return an anonymous checkout. One closed 90 days ago is gone."""
+ return self._api.get(api_path("checkout-sessions", session_id), CheckoutSessionAdmin)
diff --git a/src/kaitencloud/_sync/resources/billing/subscriptions.py b/src/kaitencloud/_sync/resources/billing/subscriptions.py
new file mode 100644
index 0000000..dcb29f7
--- /dev/null
+++ b/src/kaitencloud/_sync/resources/billing/subscriptions.py
@@ -0,0 +1,340 @@
+# Copyright 2026 KAITEN INC
+# SPDX-License-Identifier: Apache-2.0
+
+# Generated by scripts/unasync.py from src/kaitencloud/_async/resources/billing/subscriptions.py. Do not edit by hand:
+# change the async source, then run `task generate`.
+from __future__ import annotations
+
+import builtins
+from collections.abc import Mapping, Sequence
+from datetime import datetime
+from typing import Any
+
+from ...._types import NOT_GIVEN, NotGiven
+from ...._utils import addon_quantities, api_path, check_limit, compact, optional_datetime
+from ....types import (
+ BillingProviderKind,
+ CanceledSubscription,
+ CancellationMode,
+ CollectionMethod,
+ HandoffStatus,
+ InstanceAddon,
+ InstanceBilling,
+ InvoiceKind,
+ InvoicePreview,
+ InvoiceStatus,
+ InvoiceSummary,
+ Redemption,
+ RedemptionStatus,
+ StartedSubscription,
+)
+from .._base import Resource
+from .invoices import invoice_query
+
+__all__ = [
+ "BillingInstanceAddons",
+ "BillingInstanceVouchers",
+ "BillingSubscriptions",
+]
+
+
+class BillingSubscriptions(Resource):
+ """Instances' subscriptions: what bills an instance, period after period.
+
+ A subscription is pinned to a ``FLAT_FEE`` price of the instance's license version. Each
+ period closes into a ``RENEWAL`` invoice; a base price billed in advance issues the first
+ period's ``ACTIVATION`` invoice at once. While it is live, the instance's customer and
+ license cannot change.
+ """
+
+ def get(self, instance_slug: str) -> InstanceBilling:
+ """Return the instance's subscription, live or ``CANCELED``.
+
+ Raises:
+ NotFoundError: The instance was never subscribed.
+ """
+ return self._api.get(api_path("instances", instance_slug, "billing"), InstanceBilling)
+
+ def subscribe(
+ self,
+ instance_slug: str,
+ *,
+ base_price_id: str,
+ add_ons: Mapping[str, int] | None = None,
+ voucher_code: str | None = None,
+ trial_days: int | None = None,
+ start_at: datetime | str | None = None,
+ provider_kind: BillingProviderKind | None = None,
+ collection_method: CollectionMethod | None = None,
+ days_until_due: int | None = None,
+ ) -> StartedSubscription:
+ """Start billing an instance, and return its subscription with its first invoice.
+
+ A ``CANCELED`` subscription is subscribed again.
+
+ Args:
+ instance_slug: The instance.
+ base_price_id: An ``ACTIVE`` ``FLAT_FEE`` price of the instance's license version,
+ which must be ``PUBLISHED``. The subscription stays pinned to it.
+ add_ons: Add-ons to attach with it, as ``{addon_slug: quantity}``; the
+ ``ACTIVATION`` invoice bills them.
+ voucher_code: A voucher to redeem with the subscription.
+ trial_days: Days of trial, 1 to 365: nothing is billed, and no usage counts, until
+ it ends.
+ start_at: What periods are counted from: now when omitted, at most one billing
+ period ago otherwise.
+ provider_kind: Who issues and collects the invoices: ``NOOP`` (the default), the
+ organization itself through the handoff queue, or a connected provider.
+ collection_method: ``SEND_INVOICE`` or ``CHARGE_AUTOMATICALLY``. The
+ organization's default, read at each invoice, when omitted.
+ days_until_due: Payment terms in days, 0 to 365. The organization's default when
+ omitted.
+ """
+ body = {
+ "basePriceId": base_price_id,
+ **compact(
+ {
+ "addOns": addon_quantities(add_ons),
+ "voucherCode": voucher_code,
+ "trialDays": trial_days,
+ "startAt": optional_datetime(start_at),
+ "providerKind": provider_kind,
+ "collectionMethod": collection_method,
+ "daysUntilDue": days_until_due,
+ }
+ ),
+ }
+ return self._api.send(
+ "POST", api_path("instances", instance_slug, "billing"), StartedSubscription, body=body
+ )
+
+ def update(
+ self,
+ instance_slug: str,
+ *,
+ provider_kind: BillingProviderKind | None = None,
+ collection_method: CollectionMethod | NotGiven | None = NOT_GIVEN,
+ days_until_due: int | NotGiven | None = NOT_GIVEN,
+ ) -> InstanceBilling:
+ """Change a subscription's provider and terms from its next invoice on.
+
+ What is not passed keeps its value; ``None`` restores the organization's default.
+ Invoices already composed keep their own terms and provider.
+
+ Args:
+ instance_slug: The instance.
+ provider_kind: Who issues and collects the invoices from the next one on.
+ collection_method: ``SEND_INVOICE`` or ``CHARGE_AUTOMATICALLY``.
+ days_until_due: Payment terms in days, 0 to 365.
+ """
+ body: dict[str, Any] = compact({"providerKind": provider_kind})
+ if not isinstance(collection_method, NotGiven):
+ body["collectionMethod"] = collection_method
+ if not isinstance(days_until_due, NotGiven):
+ body["daysUntilDue"] = days_until_due
+ return self._api.send(
+ "PATCH", api_path("instances", instance_slug, "billing"), InstanceBilling, body=body
+ )
+
+ def cancel(
+ self,
+ instance_slug: str,
+ *,
+ mode: CancellationMode | None = None,
+ reason: str | None = None,
+ ) -> CanceledSubscription:
+ """Cancel a subscription.
+
+ A trial ends at once, with no invoice. Otherwise ``AT_PERIOD_END`` (the default) lets the
+ period paid for run out, then issues the ``FINAL`` invoice; ``IMMEDIATE`` issues it now.
+ Cancellation changes billing only: entitlements and license dates stay yours to change.
+
+ Args:
+ instance_slug: The instance.
+ mode: ``AT_PERIOD_END`` or ``IMMEDIATE``.
+ reason: Why, up to 500 characters.
+ """
+ return self._api.send(
+ "POST",
+ api_path("instances", instance_slug, "billing", "cancel"),
+ CanceledSubscription,
+ body=compact({"mode": mode, "reason": reason}),
+ )
+
+ def reactivate(self, instance_slug: str) -> InstanceBilling:
+ """Take back a cancellation scheduled for the period's end, before it."""
+ return self._api.send(
+ "POST", api_path("instances", instance_slug, "billing", "reactivate"), InstanceBilling
+ )
+
+ def schedule_plan_change(self, instance_slug: str, license_price_id: str) -> InstanceBilling:
+ """Schedule a move to another ``FLAT_FEE`` price for the next boundary.
+
+ That boundary's ``RENEWAL`` bills the old plan's arrears and the new plan's advance, and
+ moves the instance to the new version, without proration. Scheduling another target
+ replaces the change.
+
+ Args:
+ instance_slug: The instance.
+ license_price_id: An ``ACTIVE`` ``FLAT_FEE`` price of a ``PUBLISHED`` license
+ version, in the subscription's currency.
+ """
+ return self._api.send(
+ "PUT",
+ api_path("instances", instance_slug, "billing", "scheduled-change"),
+ InstanceBilling,
+ body={"licensePriceId": license_price_id},
+ )
+
+ def cancel_plan_change(self, instance_slug: str) -> InstanceBilling:
+ """Drop the plan change scheduled for the next boundary."""
+ return self._api.send(
+ "DELETE",
+ api_path("instances", instance_slug, "billing", "scheduled-change"),
+ InstanceBilling,
+ )
+
+ def get_upcoming_invoice(self, instance_slug: str) -> InvoicePreview:
+ """Return the invoice the next boundary will issue, from the usage so far, writing nothing.
+
+ ``would_hold`` lists the meters whose usage journal fails a check and would hold it.
+ """
+ return self._api.get(
+ api_path("instances", instance_slug, "billing", "upcoming-invoice"), InvoicePreview
+ )
+
+ def list_invoices(
+ self,
+ instance_slug: str,
+ *,
+ customer_slug: str | None = None,
+ status: Sequence[InvoiceStatus] | None = None,
+ kind: InvoiceKind | None = None,
+ provider_kind: BillingProviderKind | None = None,
+ overdue: bool | None = None,
+ held: bool | None = None,
+ handoff_status: HandoffStatus | None = None,
+ issued_from: datetime | str | None = None,
+ issued_to: datetime | str | None = None,
+ boundary_from: datetime | str | None = None,
+ boundary_to: datetime | str | None = None,
+ updated_since: datetime | str | None = None,
+ limit: int | None = None,
+ ) -> builtins.list[InvoiceSummary]:
+ """Return the invoices of an instance's subscription, across every time it was subscribed.
+
+ The filters are those of :meth:`BillingInvoices.list`.
+ """
+ return self._api.list_all(
+ api_path("instances", instance_slug, "invoices"),
+ InvoiceSummary,
+ params=invoice_query(
+ customer_slug=customer_slug,
+ status=status,
+ kind=kind,
+ provider_kind=provider_kind,
+ overdue=overdue,
+ held=held,
+ handoff_status=handoff_status,
+ issued_from=issued_from,
+ issued_to=issued_to,
+ boundary_from=boundary_from,
+ boundary_to=boundary_to,
+ updated_since=updated_since,
+ ),
+ limit=check_limit(limit),
+ )
+
+
+class BillingInstanceAddons(Resource):
+ """The add-ons an instance holds, by the unit.
+
+ Their entitlements apply at once; a subscription bills the quantity held at each boundary,
+ without proration. An instance holds one version per add-on family.
+ """
+
+ def list(
+ self, instance_slug: str, *, include_removed: bool = False
+ ) -> builtins.list[InstanceAddon]:
+ """Return the add-ons the instance holds, in attachment order, with the prices billed.
+
+ Args:
+ instance_slug: The instance.
+ include_removed: Also return the add-ons removed from it.
+ """
+ return self._api.get_list(
+ api_path("instances", instance_slug, "addons"),
+ InstanceAddon,
+ params={"includeRemoved": include_removed or None},
+ )
+
+ def attach(
+ self, instance_slug: str, addon_slug: str, *, quantity: int | None = None
+ ) -> InstanceAddon:
+ """Give the instance an add-on version, and return the attachment.
+
+ Moving to another version of the same add-on is a :meth:`detach`, then an attach.
+ """
+ body = {"addonSlug": addon_slug, **compact({"quantity": quantity})}
+ return self._api.send(
+ "POST", api_path("instances", instance_slug, "addons"), InstanceAddon, body=body
+ )
+
+ def set_quantity(self, instance_slug: str, addon_slug: str, quantity: int) -> InstanceAddon:
+ """Change how many units of an add-on the instance holds, at once, a decrease included.
+
+ It cancels a decrease the customer scheduled, even to the quantity held.
+ """
+ return self._api.send(
+ "PATCH",
+ api_path("instances", instance_slug, "addons", addon_slug),
+ InstanceAddon,
+ body={"quantity": quantity},
+ idempotent=True,
+ )
+
+ def detach(self, instance_slug: str, addon_slug: str) -> None:
+ """Remove an add-on from the instance, at once. Its entitlements drop; nothing is refunded."""
+ self._api.send_empty("DELETE", api_path("instances", instance_slug, "addons", addon_slug))
+
+
+class BillingInstanceVouchers(Resource):
+ """The vouchers an instance redeemed."""
+
+ def list(
+ self, instance_slug: str, *, status: RedemptionStatus | None = None
+ ) -> builtins.list[Redemption]:
+ """Return the instance's redemptions, newest first."""
+ return self._api.get_list(
+ api_path("instances", instance_slug, "vouchers"), Redemption, params={"status": status}
+ )
+
+ def redeem(self, instance_slug: str, code: str) -> Redemption:
+ """Redeem a voucher code for the instance, and return the redemption.
+
+ A boost applies from the next read or report, for its duration; a ``PRICE`` voucher
+ discounts the subscription's next invoices.
+ """
+ return self._api.send(
+ "POST",
+ api_path("instances", instance_slug, "vouchers", "redeem"),
+ Redemption,
+ body={"code": code},
+ )
+
+ def revoke(self, instance_slug: str, instance_voucher_id: str, *, reason: str) -> Redemption:
+ """Stop a redemption applying: a boost ends at once, a discount applies to no further invoice.
+
+ Invoices already issued are untouched.
+
+ Args:
+ instance_slug: The instance.
+ instance_voucher_id: The redemption's id.
+ reason: Why, 1 to 500 characters.
+ """
+ return self._api.send(
+ "POST",
+ api_path("instances", instance_slug, "vouchers", instance_voucher_id, "revoke"),
+ Redemption,
+ body={"reason": reason},
+ )
diff --git a/src/kaitencloud/_sync/resources/billing/vouchers.py b/src/kaitencloud/_sync/resources/billing/vouchers.py
new file mode 100644
index 0000000..b41e6e4
--- /dev/null
+++ b/src/kaitencloud/_sync/resources/billing/vouchers.py
@@ -0,0 +1,321 @@
+# Copyright 2026 KAITEN INC
+# SPDX-License-Identifier: Apache-2.0
+
+# Generated by scripts/unasync.py from src/kaitencloud/_async/resources/billing/vouchers.py. Do not edit by hand:
+# change the async source, then run `task generate`.
+from __future__ import annotations
+
+import builtins
+from collections.abc import Mapping, Sequence
+from datetime import datetime
+from typing import Any, Literal
+
+from ...._utils import api_path, check_limit, compact, optional_datetime
+from ....types import (
+ DiscountAppliesTo,
+ DiscountType,
+ Redemption,
+ RedemptionRulesParam,
+ Validity,
+ Voucher,
+ VoucherDuration,
+ VoucherGrantParam,
+ VoucherStatus,
+ VoucherType,
+)
+from .._base import Resource
+
+__all__ = ["BillingVouchers"]
+
+
+class BillingVouchers(Resource):
+ """The organization's vouchers: codes that discount a price or boost entitlements.
+
+ A ``PRICE`` voucher discounts a subscription's invoices; an ``ENTITLEMENT_BOOST`` changes
+ entitlements while it applies; a ``FLAG_GRANT`` serves flag variants; a ``COMPOSITE`` does
+ several. A voucher is created ``DRAFT`` and becomes redeemable once published.
+ """
+
+ def list(
+ self,
+ *,
+ status: VoucherStatus | None = None,
+ voucher_type: Literal["PRICE", "ENTITLEMENT_BOOST"] | None = None,
+ restricted_customer_slug: str | None = None,
+ ) -> builtins.list[Voucher]:
+ """Return the organization's vouchers, newest first, with their codes, walking all pages."""
+ return self._api.list_all(
+ "/vouchers",
+ Voucher,
+ params={
+ "status": status,
+ "voucherType": voucher_type,
+ "restrictedCustomerSlug": restricted_customer_slug,
+ },
+ )
+
+ def get(self, voucher_id: str) -> Voucher:
+ """Return a voucher, with its code."""
+ return self._api.get(api_path("vouchers", voucher_id), Voucher)
+
+ def create(
+ self,
+ *,
+ name: str,
+ voucher_type: VoucherType,
+ duration: VoucherDuration,
+ code: str | None = None,
+ description: str | None = None,
+ duration_in_periods: int | None = None,
+ starts_at: datetime | str | None = None,
+ expires_at: datetime | str | None = None,
+ max_redemptions: int | None = None,
+ restricted_customer_slug: str | None = None,
+ price_discount_type: DiscountType | None = None,
+ price_discount_value: str | None = None,
+ currency: str | None = None,
+ price_applies_to: DiscountAppliesTo | None = None,
+ applicable_license_ids: Sequence[str] | None = None,
+ applicable_license_price_ids: Sequence[str] | None = None,
+ applicable_addon_ids: Sequence[str] | None = None,
+ applicable_addon_price_ids: Sequence[str] | None = None,
+ grants: Sequence[VoucherGrantParam] | None = None,
+ flag_grants: Mapping[str, str] | None = None,
+ redemption_rules: RedemptionRulesParam | None = None,
+ ) -> Voucher:
+ """Create a ``DRAFT`` voucher, and return it with its code.
+
+ Args:
+ name: The voucher's name.
+ voucher_type: ``PRICE``, ``ENTITLEMENT_BOOST``, ``FLAG_GRANT`` or ``COMPOSITE``.
+ duration: How long a discount lasts: ``ONE_TIME``, ``REPEATING`` or ``FOREVER``.
+ code: 8 to 64 of ``[A-Za-z0-9_-]``, matched without case or separators. Generated
+ when omitted.
+ description: A description.
+ duration_in_periods: How many periods a ``REPEATING`` discount lasts.
+ starts_at: When it can be redeemed from.
+ expires_at: When it can no longer be redeemed.
+ max_redemptions: How many times it can be redeemed.
+ restricted_customer_slug: The only customer that may redeem it.
+ price_discount_type: ``PERCENTAGE`` or ``FIXED_AMOUNT``.
+ price_discount_value: A percentage in (0, 100], or an amount in minor units, as a
+ string.
+ currency: Required with ``FIXED_AMOUNT``, refused otherwise.
+ price_applies_to: What the discount applies to: ``LICENSE_BASE``, ``ADDONS``,
+ ``BOTH``, or ``SELECTED_PRICES``.
+ applicable_license_ids: The license versions it applies to.
+ applicable_license_price_ids: With ``SELECTED_PRICES``, the license prices.
+ applicable_addon_ids: The add-on versions it applies to.
+ applicable_addon_price_ids: With ``SELECTED_PRICES``, the add-on prices.
+ grants: What an ``ENTITLEMENT_BOOST`` grants, one entitlement each.
+ flag_grants: What a ``FLAG_GRANT`` serves, as ``{flag_slug: variant}``.
+ redemption_rules: Who may redeem it, beyond its dates and count.
+ """
+ body = _voucher(
+ name=name,
+ voucher_type=voucher_type,
+ duration=duration,
+ code=code,
+ description=description,
+ duration_in_periods=duration_in_periods,
+ starts_at=starts_at,
+ expires_at=expires_at,
+ max_redemptions=max_redemptions,
+ restricted_customer_slug=restricted_customer_slug,
+ price_discount_type=price_discount_type,
+ price_discount_value=price_discount_value,
+ currency=currency,
+ price_applies_to=price_applies_to,
+ applicable_license_ids=applicable_license_ids,
+ applicable_license_price_ids=applicable_license_price_ids,
+ applicable_addon_ids=applicable_addon_ids,
+ applicable_addon_price_ids=applicable_addon_price_ids,
+ grants=grants,
+ flag_grants=flag_grants,
+ redemption_rules=redemption_rules,
+ )
+ return self._api.send("POST", "/vouchers", Voucher, body=body)
+
+ def update(
+ self,
+ voucher_id: str,
+ *,
+ name: str,
+ voucher_type: VoucherType,
+ duration: VoucherDuration,
+ code: str | None = None,
+ description: str | None = None,
+ duration_in_periods: int | None = None,
+ starts_at: datetime | str | None = None,
+ expires_at: datetime | str | None = None,
+ max_redemptions: int | None = None,
+ restricted_customer_slug: str | None = None,
+ price_discount_type: DiscountType | None = None,
+ price_discount_value: str | None = None,
+ currency: str | None = None,
+ price_applies_to: DiscountAppliesTo | None = None,
+ applicable_license_ids: Sequence[str] | None = None,
+ applicable_license_price_ids: Sequence[str] | None = None,
+ applicable_addon_ids: Sequence[str] | None = None,
+ applicable_addon_price_ids: Sequence[str] | None = None,
+ grants: Sequence[VoucherGrantParam] | None = None,
+ flag_grants: Mapping[str, str] | None = None,
+ redemption_rules: RedemptionRulesParam | None = None,
+ ) -> Voucher:
+ """Replace a voucher, and return it. The arguments are :meth:`create`'s.
+
+ A ``DRAFT`` is replaced whole, but its type. An ``ACTIVE`` voucher takes a new name,
+ description, expiry and redemption count only; its other members must stay as they are
+ (``UpdateVoucher.NotEditable``).
+ """
+ body = _voucher(
+ name=name,
+ voucher_type=voucher_type,
+ duration=duration,
+ code=code,
+ description=description,
+ duration_in_periods=duration_in_periods,
+ starts_at=starts_at,
+ expires_at=expires_at,
+ max_redemptions=max_redemptions,
+ restricted_customer_slug=restricted_customer_slug,
+ price_discount_type=price_discount_type,
+ price_discount_value=price_discount_value,
+ currency=currency,
+ price_applies_to=price_applies_to,
+ applicable_license_ids=applicable_license_ids,
+ applicable_license_price_ids=applicable_license_price_ids,
+ applicable_addon_ids=applicable_addon_ids,
+ applicable_addon_price_ids=applicable_addon_price_ids,
+ grants=grants,
+ flag_grants=flag_grants,
+ redemption_rules=redemption_rules,
+ )
+ return self._api.send("PUT", api_path("vouchers", voucher_id), Voucher, body=body)
+
+ def delete(self, voucher_id: str) -> None:
+ """Delete a voucher no instance ever redeemed, whatever its status, freeing its code.
+
+ Raises:
+ ConflictError: It was redeemed at least once, or a checkout waiting for its payment
+ applies it (``DeleteVoucher.Redeemed``): :meth:`archive` it instead.
+ """
+ self._api.send_empty("DELETE", api_path("vouchers", voucher_id))
+
+ def publish(self, voucher_id: str) -> Voucher:
+ """Make a ``DRAFT`` voucher redeemable."""
+ return self._api.send("POST", api_path("vouchers", voucher_id, "publish"), Voucher)
+
+ def archive(self, voucher_id: str) -> Voucher:
+ """Stop a voucher being redeemed. Redemptions already made keep applying until revoked."""
+ return self._api.send("POST", api_path("vouchers", voucher_id, "archive"), Voucher)
+
+ def list_redemptions(
+ self, voucher_id: str, *, limit: int | None = None
+ ) -> builtins.list[Redemption]:
+ """Return every redemption of a voucher, newest first.
+
+ Args:
+ voucher_id: The voucher.
+ limit: The most redemptions to return. Every page is walked when omitted.
+ """
+ return self._api.list_all(
+ api_path("vouchers", voucher_id, "redemptions"), Redemption, limit=check_limit(limit)
+ )
+
+ def lookup(self, code: str) -> Voucher:
+ """Return the voucher a code names, matched without case or separators.
+
+ The code travels in the request body, never in a URL.
+ """
+ return self._api.send(
+ "POST", "/vouchers/lookup", Voucher, body={"code": code}, idempotent=True
+ )
+
+ def validate(
+ self,
+ code: str,
+ *,
+ instance_slug: str | None = None,
+ license_price_id: str | None = None,
+ ) -> Validity:
+ """Run the redemption checks of a code without redeeming it.
+
+ ``valid`` says whether it would apply; otherwise ``reason`` is the first check that
+ failed, and ``rule`` the eligibility rule.
+
+ Args:
+ code: The code.
+ instance_slug: Also run the checks that need the redeeming instance.
+ license_price_id: The ``FLAT_FEE`` price a subscription would start on, read instead
+ of the instance's live subscription.
+ """
+ body = {
+ "code": code,
+ **compact({"instanceSlug": instance_slug, "licensePriceId": license_price_id}),
+ }
+ return self._api.send("POST", "/vouchers/validate", Validity, body=body, idempotent=True)
+
+
+def _voucher(
+ *,
+ name: str,
+ voucher_type: VoucherType,
+ duration: VoucherDuration,
+ code: str | None,
+ description: str | None,
+ duration_in_periods: int | None,
+ starts_at: datetime | str | None,
+ expires_at: datetime | str | None,
+ max_redemptions: int | None,
+ restricted_customer_slug: str | None,
+ price_discount_type: DiscountType | None,
+ price_discount_value: str | None,
+ currency: str | None,
+ price_applies_to: DiscountAppliesTo | None,
+ applicable_license_ids: Sequence[str] | None,
+ applicable_license_price_ids: Sequence[str] | None,
+ applicable_addon_ids: Sequence[str] | None,
+ applicable_addon_price_ids: Sequence[str] | None,
+ grants: Sequence[VoucherGrantParam] | None,
+ flag_grants: Mapping[str, str] | None,
+ redemption_rules: RedemptionRulesParam | None,
+) -> dict[str, Any]:
+ return {
+ "name": name,
+ "voucherType": voucher_type,
+ "duration": duration,
+ **compact(
+ {
+ "code": code,
+ "description": description,
+ "durationInPeriods": duration_in_periods,
+ "startsAt": optional_datetime(starts_at),
+ "expiresAt": optional_datetime(expires_at),
+ "maxRedemptions": max_redemptions,
+ "restrictedCustomerSlug": restricted_customer_slug,
+ "priceDiscountType": price_discount_type,
+ "priceDiscountValue": price_discount_value,
+ "currency": currency,
+ "priceAppliesTo": price_applies_to,
+ "applicableLicenseIds": _listed(applicable_license_ids),
+ "applicableLicensePriceIds": _listed(applicable_license_price_ids),
+ "applicableAddonIds": _listed(applicable_addon_ids),
+ "applicableAddonPriceIds": _listed(applicable_addon_price_ids),
+ "grants": _listed(grants),
+ "flagGrants": (
+ [
+ {"flagSlug": flag, "variant": variant}
+ for flag, variant in flag_grants.items()
+ ]
+ if flag_grants is not None
+ else None
+ ),
+ "redemptionRules": dict(redemption_rules) if redemption_rules is not None else None,
+ }
+ ),
+ }
+
+
+def _listed(values: Sequence[Any] | None) -> list[Any] | None:
+ return list(values) if values is not None else None
diff --git a/src/kaitencloud/_sync/resources/customers.py b/src/kaitencloud/_sync/resources/customers.py
index b17eac3..aa39eea 100644
--- a/src/kaitencloud/_sync/resources/customers.py
+++ b/src/kaitencloud/_sync/resources/customers.py
@@ -35,6 +35,7 @@ def create(
external_customer_id: str | None = None,
domain: str | None = None,
integrations: Mapping[str, IntegrationParam] | None = None,
+ billing_email: str | None = None,
) -> Customer:
"""Create a customer.
@@ -46,6 +47,8 @@ def create(
domain: Its domain name, e.g. ``"acme.com"``.
integrations: Links to records in third-party systems, keyed by adapter name.
Only settable here: afterwards, use :meth:`update_integration`.
+ billing_email: Where invoices sent to the customer are delivered, at most 254
+ characters. Personal data: never included in event payloads.
Raises:
ConflictError: The slug is taken, or the organization reached its customer limit.
@@ -57,6 +60,7 @@ def create(
"externalCustomerId": external_customer_id,
"domain": domain,
"integrations": integrations_body(integrations),
+ "billingEmail": billing_email,
}
)
return self._api.send("POST", "/customers", Customer, body=body)
@@ -68,13 +72,22 @@ def update(
name: str,
external_customer_id: str | None = None,
domain: str | None = None,
+ billing_email: str | None = None,
) -> None:
"""Replace a customer's details.
A full replacement: the external customer id and the domain are cleared when left out.
- The slug cannot be changed, and integrations have their own methods.
+ The billing e-mail is the exception: left out, it keeps its value, and ``""`` removes
+ it. The slug cannot be changed, and integrations have their own methods.
"""
- body = compact({"name": name, "externalCustomerId": external_customer_id, "domain": domain})
+ body = compact(
+ {
+ "name": name,
+ "externalCustomerId": external_customer_id,
+ "domain": domain,
+ "billingEmail": billing_email,
+ }
+ )
self._api.send_empty("PUT", api_path("customers", customer_slug), body=body)
def delete(self, customer_slug: str) -> None:
diff --git a/src/kaitencloud/_sync/resources/instances.py b/src/kaitencloud/_sync/resources/instances.py
index ecae47d..d7a838f 100644
--- a/src/kaitencloud/_sync/resources/instances.py
+++ b/src/kaitencloud/_sync/resources/instances.py
@@ -8,11 +8,15 @@
import builtins
from collections.abc import Mapping
from datetime import datetime
-from typing import Any
+from typing import Any, Literal
-from ..._exceptions import ThresholdExceededError
+from ..._constants import EXPORT_MEDIA_TYPES
+from ..._exceptions import ThresholdExceededError, TransactionIdReusedError
+from ..._models import UsageReportResult
+from ..._types import EntitlementValueInput
from ..._utils import (
api_path,
+ check_limit,
compact,
format_datetime,
integration_body,
@@ -21,12 +25,16 @@
)
from ...types import (
AuditTrail,
+ EntitlementException,
EntitlementUsage,
+ ExceptionKind,
Instance,
InstanceIntegration,
InstanceStatus,
IntegrationParam,
+ ReportedEntitlementUsage,
UsageBehavior,
+ UsageReport,
)
from ._base import Resource
@@ -239,26 +247,272 @@ def report_usage(
ThresholdExceededError: The report would take usage past the maximum the license
allows -- the granted value plus its overage allowance. Nothing was recorded.
- Never retried automatically: without an idempotency key on the wire, a report retried
- after a lost response would be counted twice.
+ Never retried automatically: a report retried after a lost response would be counted
+ twice. :meth:`report_usage_detailed` sends one under an idempotency key, which makes
+ sending it again safe.
"""
- if isinstance(value, bool) or not isinstance(value, (int, float)):
- raise TypeError(f"usage must be a number, got {type(value).__name__}")
- body = compact(
- {
- "value": {"type": "number", "value": value},
- "behavior": behavior,
- "metadata": dict(metadata) if metadata is not None else None,
- }
- )
return self._api.send(
"POST",
- api_path("instances", instance_slug, "entitlements", entitlement_slug, "usage"),
+ _usage_path(instance_slug, entitlement_slug),
EntitlementUsage,
- body=body,
+ body=_usage_report(value, behavior, metadata),
errors={409: ThresholdExceededError},
)
+ def report_usage_detailed(
+ self,
+ instance_slug: str,
+ entitlement_slug: str,
+ value: float,
+ *,
+ behavior: UsageBehavior = "append",
+ metadata: Mapping[str, Any] | None = None,
+ transaction_id: str | None = None,
+ ) -> UsageReportResult:
+ """Report usage, under an idempotency key, and return what the API said about it.
+
+ Kaiten applies a report at most once per key, instance and entitlement within its
+ idempotency window -- 35 days by default. A report sent again under the same key, with
+ the same behavior and value, answers the original report and counts nothing: it comes
+ back with ``replayed`` set.
+
+ Args:
+ instance_slug: The instance that consumed.
+ entitlement_slug: The entitlement consumed.
+ value: The amount, as :meth:`report_usage` takes it.
+ behavior: ``"append"`` (the default) or ``"set"``.
+ metadata: Free-form metadata, stored with the report in the usage history when its
+ compact JSON is at most 4 KiB; above, it is dropped and ``metadata_dropped`` set.
+ It must hold no personal data.
+ transaction_id: The id of the business event the report counts -- 1 to 128 of
+ letters, digits, ``.``, ``_``, ``:`` and ``-``, e.g. ``"llm-call-9f2c:tokens"``.
+ Sent as the ``Idempotency-Key`` header.
+
+ Raises:
+ ThresholdExceededError: The report would take usage past the maximum the license
+ allows. Nothing was recorded.
+ TransactionIdReusedError: The key was spent on a report with another behavior or
+ value. Nothing was recorded.
+
+ Never retried automatically, key or not: a Kaiten older than keys ignores the header and
+ would count a resent report twice. Resend it yourself, under the same key.
+ """
+ try:
+ response = self._api.request(
+ "POST",
+ _usage_path(instance_slug, entitlement_slug),
+ body=_usage_report(value, behavior, metadata),
+ headers=compact({"Idempotency-Key": transaction_id}),
+ errors={409: ThresholdExceededError},
+ )
+ except ThresholdExceededError as error:
+ # The usage endpoint answers 409 for two reasons; only the code tells them apart.
+ if error.code == "ReportEntitlementUsageMetric.TransactionIdReused":
+ raise TransactionIdReusedError(
+ str(error), response=error.response, problem=error.problem
+ ) from None
+ raise
+ return UsageReportResult(
+ usage=self._api.validate(
+ ReportedEntitlementUsage, self._api.decode(response), response
+ ),
+ replayed=response.headers.get("Idempotent-Replayed") == "true",
+ metadata_dropped=bool(response.headers.get("Kaiten-Metadata-Dropped")),
+ )
+
+ def list_usage_reports(
+ self,
+ instance_slug: str,
+ entitlement_slug: str,
+ *,
+ from_: datetime | str | None = None,
+ to: datetime | str | None = None,
+ transaction_id: str | None = None,
+ limit: int | None = None,
+ ) -> builtins.list[UsageReport]:
+ """Return the usage history of one entitlement for an instance, oldest report first.
+
+ Every report the API accepted in the range, with the counter before and after it and
+ the limit in force. Decimals are strings, exact to the digit. Reports are kept for the
+ organization's usage history retention.
+
+ Args:
+ instance_slug: The instance.
+ entitlement_slug: The entitlement.
+ from_: Start of the range, inclusive. Defaults to 30 days before ``to``; a moment
+ before the usage history starts is refused (``ListUsageReports.OutsideRetention``).
+ to: End of the range, exclusive. Defaults to now.
+ transaction_id: Only the report sent with this key.
+ limit: The most reports to return. Every page is walked when omitted.
+ """
+ return self._api.list_reports(
+ api_path(
+ "instances", instance_slug, "entitlements", entitlement_slug, "usage", "reports"
+ ),
+ UsageReport,
+ params={
+ "from": optional_datetime(from_),
+ "to": optional_datetime(to),
+ "transactionId": transaction_id,
+ },
+ limit=check_limit(limit),
+ )
+
+ def export_usage_reports(
+ self,
+ instance_slug: str,
+ entitlement_slug: str,
+ *,
+ from_: datetime | str | None = None,
+ to: datetime | str | None = None,
+ format: Literal["csv", "json"] = "csv",
+ ) -> bytes:
+ """Return the usage history of one entitlement for an instance, exported.
+
+ At most 366 days per call, decimals written exactly as the journal stores them.
+
+ Args:
+ instance_slug: The instance.
+ entitlement_slug: The entitlement.
+ from_: Start of the range, inclusive (see :meth:`list_usage_reports`).
+ to: End of the range, exclusive. Defaults to now.
+ format: ``"csv"`` (the default), RFC 4180 with a header row, or ``"json"`` for
+ NDJSON, one report per line in the shape :meth:`list_usage_reports` returns.
+ """
+ response = self._api.request(
+ "GET",
+ api_path(
+ "instances",
+ instance_slug,
+ "entitlements",
+ entitlement_slug,
+ "usage",
+ "reports",
+ "export",
+ ),
+ params={
+ "from": optional_datetime(from_),
+ "to": optional_datetime(to),
+ "format": format,
+ },
+ headers={"Accept": EXPORT_MEDIA_TYPES[format]},
+ )
+ return response.content
+
+ def export_organization_usage_reports(
+ self,
+ *,
+ from_: datetime | str | None = None,
+ to: datetime | str | None = None,
+ format: Literal["csv", "json"] = "csv",
+ instance_slug: str | None = None,
+ instance_id: str | None = None,
+ entitlement_slug: str | None = None,
+ entitlement_id: str | None = None,
+ ) -> bytes:
+ """Return the whole organization's usage history, exported, ordered by acceptance.
+
+ Every instance and entitlement, at most 31 days per call: keep it before deleting the
+ organization. ``instance_id`` and ``entitlement_id`` reach deleted ones, whose reports
+ are kept.
+
+ Args:
+ from_: Start of the range, inclusive (see :meth:`list_usage_reports`).
+ to: End of the range, exclusive. Defaults to now.
+ format: ``"csv"`` (the default) or ``"json"`` for NDJSON.
+ instance_slug: Only this instance.
+ instance_id: Only this instance, by id.
+ entitlement_slug: Only this entitlement.
+ entitlement_id: Only this entitlement, by id.
+ """
+ response = self._api.request(
+ "GET",
+ "/usage/reports/export",
+ params={
+ "from": optional_datetime(from_),
+ "to": optional_datetime(to),
+ "format": format,
+ "instanceSlug": instance_slug,
+ "instanceId": instance_id,
+ "entitlementSlug": entitlement_slug,
+ "entitlementId": entitlement_id,
+ },
+ headers={"Accept": EXPORT_MEDIA_TYPES[format]},
+ )
+ return response.content
+
+ def list_entitlement_exceptions(
+ self, instance_slug: str, *, include_inactive: bool = False
+ ) -> builtins.list[EntitlementException]:
+ """Return the instance's entitlement exceptions, latest start first, with their status.
+
+ Args:
+ instance_slug: The instance.
+ include_inactive: Also return the revoked and ended ones. Only the active and the
+ scheduled ones otherwise.
+ """
+ return self._api.get_list(
+ api_path("instances", instance_slug, "entitlement-exceptions"),
+ EntitlementException,
+ params={"filter": "all" if include_inactive else None},
+ )
+
+ def create_entitlement_exception(
+ self,
+ instance_slug: str,
+ entitlement_slug: str,
+ *,
+ kind: ExceptionKind,
+ reason: str,
+ value: EntitlementValueInput | None = None,
+ starts_at: datetime | str | None = None,
+ ends_at: datetime | str | None = None,
+ ) -> EntitlementException:
+ """Override one entitlement of an instance, for a while, and return the exception.
+
+ An exception is your explicit, dated decision on one instance: the last layer of its
+ effective entitlements, after the license, the add-ons and the boosts. It wins over
+ them, and may lower the value. An instance has one unrevoked exception per entitlement
+ at any moment. It works whether or not billing is enabled.
+
+ Args:
+ instance_slug: The instance.
+ entitlement_slug: The entitlement it overrides, granted or not.
+ kind: ``SET`` works on every type; ``ADD`` and ``UNLIMITED`` on numbers only.
+ reason: Why, 1 to 500 characters, shown to you only.
+ value: For ``SET``, a number of 0 or more, a ``bool`` or a mapping, by the
+ entitlement's type; for ``ADD``, a number above 0; nothing for ``UNLIMITED``.
+ starts_at: When it starts: now when omitted; a future moment schedules it.
+ ends_at: When it ends. It never does when omitted.
+ """
+ body = {
+ "entitlementSlug": entitlement_slug,
+ "kind": kind,
+ "reason": reason,
+ **compact(
+ {
+ "value": dict(value) if isinstance(value, Mapping) else value,
+ "startsAt": optional_datetime(starts_at),
+ "endsAt": optional_datetime(ends_at),
+ }
+ ),
+ }
+ return self._api.send(
+ "POST",
+ api_path("instances", instance_slug, "entitlement-exceptions"),
+ EntitlementException,
+ body=body,
+ )
+
+ def revoke_entitlement_exception(self, instance_slug: str, exception_id: str) -> None:
+ """Revoke an exception, which is kept: a scheduled one never starts, an active one ends.
+
+ Revoking one already revoked or ended changes nothing.
+ """
+ self._api.send_empty(
+ "DELETE", api_path("instances", instance_slug, "entitlement-exceptions", exception_id)
+ )
+
def get_integration(self, instance_slug: str, integration_name: str) -> InstanceIntegration:
"""Return the instance's link to the ``integration_name`` adapter."""
return self._api.get(
@@ -347,3 +601,21 @@ def _instance(
}
),
}
+
+
+def _usage_path(instance_slug: str, entitlement_slug: str) -> str:
+ return api_path("instances", instance_slug, "entitlements", entitlement_slug, "usage")
+
+
+def _usage_report(
+ value: float, behavior: UsageBehavior, metadata: Mapping[str, Any] | None
+) -> dict[str, Any]:
+ if isinstance(value, bool) or not isinstance(value, (int, float)):
+ raise TypeError(f"usage must be a number, got {type(value).__name__}")
+ return compact(
+ {
+ "value": {"type": "number", "value": value},
+ "behavior": behavior,
+ "metadata": dict(metadata) if metadata is not None else None,
+ }
+ )
diff --git a/src/kaitencloud/_sync/resources/license_families.py b/src/kaitencloud/_sync/resources/license_families.py
index cc2525e..16d6636 100644
--- a/src/kaitencloud/_sync/resources/license_families.py
+++ b/src/kaitencloud/_sync/resources/license_families.py
@@ -8,8 +8,8 @@
import builtins
from typing import Any
-from ..._utils import api_path
-from ...types import LicenseFamilyView
+from ..._utils import api_path, compact
+from ...types import LicenseFamilyView, TrialEndWithoutPaymentMethod
from ._base import Resource
__all__ = ["LicenseFamilies"]
@@ -61,3 +61,38 @@ def get(
return self._api.get(
api_path("license-families", family_slug), LicenseFamilyView, params=params or None
)
+
+ def update(
+ self,
+ family_slug: str,
+ *,
+ is_public: bool | None = None,
+ fallback_license_family_slug: str | None = None,
+ trial_end_without_payment_method: TrialEndWithoutPaymentMethod | None = None,
+ trial_reminder_days: int | None = None,
+ ) -> LicenseFamilyView:
+ """Change a family's own settings, and return the family; what is omitted keeps its value.
+
+ Args:
+ family_slug: The family's slug.
+ is_public: List the family's default ``PUBLISHED`` version, with its ``ACTIVE``
+ prices, in the public catalog. Families are private until made public.
+ fallback_license_family_slug: Another family of the organization, every version of
+ it priced ``FREE``, whose default version an instance moves to when its
+ subscription ends. ``""`` removes the fallback.
+ trial_end_without_payment_method: What a trial charged automatically becomes at its
+ end when the customer has no active payment method: ``CANCEL`` or ``CONVERT``.
+ trial_reminder_days: How many days before a trial's end ``trial_will_end`` fires,
+ 1 to 30, for the trials started after the change.
+ """
+ body = compact(
+ {
+ "isPublic": is_public,
+ "fallbackLicenseFamilySlug": fallback_license_family_slug,
+ "trialEndWithoutPaymentMethod": trial_end_without_payment_method,
+ "trialReminderDays": trial_reminder_days,
+ }
+ )
+ return self._api.send(
+ "PATCH", api_path("license-families", family_slug), LicenseFamilyView, body=body
+ )
diff --git a/src/kaitencloud/_sync/resources/licenses.py b/src/kaitencloud/_sync/resources/licenses.py
index ecbc355..84e1fb8 100644
--- a/src/kaitencloud/_sync/resources/licenses.py
+++ b/src/kaitencloud/_sync/resources/licenses.py
@@ -10,7 +10,7 @@
from ..._types import EntitlementValueInput
from ..._utils import api_path, compact, entitlement_value
-from ...types import License, LicenseEntitlement, LicenseType
+from ...types import License, LicenseEntitlement, LicenseType, PricingType
from ._base import Resource
__all__ = ["Licenses"]
@@ -47,6 +47,10 @@ def create(
family_slug: str | None = None,
family_id: str | None = None,
lifecycle_state: Literal["DRAFT", "PUBLISHED"] | None = None,
+ pricing_type: PricingType | None = None,
+ trial_period_days: int | None = None,
+ requires_payment_method: bool | None = None,
+ self_serve_cta_url: str | None = None,
) -> License:
"""Create a license: a new license family, or the next version of an existing one.
@@ -73,6 +77,15 @@ def create(
lifecycle_state: ``DRAFT`` to create the version off sale, to be put on sale with
:meth:`publish`. ``PUBLISHED`` when omitted. A version is never created
``ARCHIVED``: it is archived with :meth:`archive` once it has been on sale.
+ pricing_type: How the version is sold: ``FREE`` or ``PAID`` can be bought self-serve,
+ ``CUSTOM`` sends a buyer to ``self_serve_cta_url`` or a conversation. ``CUSTOM``
+ when omitted.
+ trial_period_days: The trial a subscription to the version starts with by default,
+ 1 to 365 days.
+ requires_payment_method: Whether self-serve signup captures a payment method before
+ activation.
+ self_serve_cta_url: Where a buyer is sent when the version cannot be bought
+ self-serve: an http(s) URL.
Raises:
NotFoundError: ``family_slug`` or ``family_id`` names no family
@@ -95,6 +108,10 @@ def create(
"familySlug": family_slug,
"familyId": family_id,
"lifecycleState": lifecycle_state,
+ "pricingType": pricing_type,
+ "trialPeriodDays": trial_period_days,
+ "requiresPaymentMethod": requires_payment_method,
+ "selfServeCtaUrl": self_serve_cta_url,
}
),
}
@@ -109,6 +126,10 @@ def update(
description: str = "",
is_default: bool = False,
version_name: str | None = None,
+ pricing_type: PricingType | None = None,
+ trial_period_days: int | None = None,
+ requires_payment_method: bool | None = None,
+ self_serve_cta_url: str | None = None,
) -> None:
"""Replace a license version's details.
@@ -117,6 +138,11 @@ def update(
:meth:`publish`, :meth:`archive` and :meth:`unarchive`. ``is_default=True`` takes the
flag from the family's previous default, and ``False`` unsets it on this version.
+ The commercial terms -- ``pricing_type``, ``trial_period_days``,
+ ``requires_payment_method`` and ``self_serve_cta_url`` -- keep their value when left
+ out, as :meth:`create` describes them. ``trial_period_days=0`` removes the trial, and
+ ``self_serve_cta_url=""`` the URL.
+
Raises:
ConflictError: ``is_default=True`` on a version that is not ``PUBLISHED``
(``UpdateLicense.DefaultMustBePublished``): publish or unarchive it first.
@@ -126,7 +152,15 @@ def update(
"description": description,
"type": type,
"isDefault": is_default,
- **compact({"versionName": version_name}),
+ **compact(
+ {
+ "versionName": version_name,
+ "pricingType": pricing_type,
+ "trialPeriodDays": trial_period_days,
+ "requiresPaymentMethod": requires_payment_method,
+ "selfServeCtaUrl": self_serve_cta_url,
+ }
+ ),
}
self._api.send_empty("PUT", api_path("licenses", license_slug), body=body)
diff --git a/src/kaitencloud/_sync/resources/metadata_fields.py b/src/kaitencloud/_sync/resources/metadata_fields.py
index 7cdc36b..6ba6cb9 100644
--- a/src/kaitencloud/_sync/resources/metadata_fields.py
+++ b/src/kaitencloud/_sync/resources/metadata_fields.py
@@ -23,10 +23,17 @@ class MetadataFields(Resource):
against the active fields declared for its resource type.
"""
- def list(self, resource_type: MetadataResourceType) -> builtins.list[MetadataField]:
- """Return the metadata fields declared for ``resource_type``, walking all pages."""
+ def list(
+ self, resource_type: MetadataResourceType, *, include_archived: bool = False
+ ) -> builtins.list[MetadataField]:
+ """Return the metadata fields declared for ``resource_type``, walking all pages.
+
+ Archived fields are left out unless ``include_archived`` asks for them.
+ """
return self._api.list_all(
- "/metadata-fields", MetadataField, params={"resourceType": resource_type}
+ "/metadata-fields",
+ MetadataField,
+ params={"resourceType": resource_type, "includeArchived": include_archived or None},
)
def create(
diff --git a/src/kaitencloud/_sync/resources/platform/__init__.py b/src/kaitencloud/_sync/resources/platform/__init__.py
index 2d43ae9..a225aea 100644
--- a/src/kaitencloud/_sync/resources/platform/__init__.py
+++ b/src/kaitencloud/_sync/resources/platform/__init__.py
@@ -3,12 +3,14 @@
# Generated by scripts/unasync.py from src/kaitencloud/_async/resources/platform/__init__.py. Do not edit by hand:
# change the async source, then run `task generate`.
+from .billing import PlatformBilling
from .connectors import PlatformConnectors
from .organizations import PlatformOrganizations
from .tokens import PlatformTokens
from .users import PlatformUsers
__all__ = [
+ "PlatformBilling",
"PlatformConnectors",
"PlatformOrganizations",
"PlatformTokens",
diff --git a/src/kaitencloud/_sync/resources/platform/billing.py b/src/kaitencloud/_sync/resources/platform/billing.py
new file mode 100644
index 0000000..9436890
--- /dev/null
+++ b/src/kaitencloud/_sync/resources/platform/billing.py
@@ -0,0 +1,41 @@
+# Copyright 2026 KAITEN INC
+# SPDX-License-Identifier: Apache-2.0
+
+# Generated by scripts/unasync.py from src/kaitencloud/_async/resources/platform/billing.py. Do not edit by hand:
+# change the async source, then run `task generate`.
+from __future__ import annotations
+
+from ...._utils import api_path, compact, validate_uuid
+from ....types import ClosePeriodsReport
+from .._base import Resource
+
+__all__ = ["PlatformBilling"]
+
+
+class PlatformBilling(Resource):
+ """Billing across organizations."""
+
+ def close_periods(
+ self, organization_id: str, *, instance_slug: str | None = None
+ ) -> ClosePeriodsReport:
+ """Close an organization's subscriptions whose period has ended, now.
+
+ The same as :meth:`Billing.close_periods` on the Core API, for any organization,
+ and recorded under ``system:kaiten``. It needs ``write:billing``.
+
+ Args:
+ organization_id: The organization.
+ instance_slug: Close only this instance's subscription.
+ """
+ return self._api.send(
+ "POST",
+ api_path(
+ "platform",
+ "organizations",
+ validate_uuid(organization_id, "organization_id"),
+ "billing",
+ "close-periods",
+ ),
+ ClosePeriodsReport,
+ body=compact({"instanceSlug": instance_slug}),
+ )
diff --git a/src/kaitencloud/_utils.py b/src/kaitencloud/_utils.py
index f568008..34e87b2 100644
--- a/src/kaitencloud/_utils.py
+++ b/src/kaitencloud/_utils.py
@@ -146,6 +146,50 @@ def integrations_body(
return {name: dict(integration) for name, integration in integrations.items()}
+def price_body(
+ *,
+ billing_model: str,
+ currency: str,
+ unit_amount_decimal: str,
+ billing_period: str | None,
+ billing_timing: str | None,
+ metered_entitlement_slug: str | None,
+ is_default: bool | None,
+ display_label: str | None,
+ display_order: int | None,
+) -> dict[str, Any]:
+ """The body of a new price: a license version's or an add-on version's."""
+ return {
+ "billingModel": billing_model,
+ "currency": currency,
+ "unitAmountDecimal": unit_amount_decimal,
+ **compact(
+ {
+ "billingPeriod": billing_period,
+ "billingTiming": billing_timing,
+ "meteredEntitlementSlug": metered_entitlement_slug,
+ "isDefault": is_default,
+ "displayLabel": display_label,
+ "displayOrder": display_order,
+ }
+ ),
+ }
+
+
+def addon_quantities(add_ons: Mapping[str, int] | None) -> list[dict[str, Any]] | None:
+ """``{addon_slug: quantity}``, as the list of add-ons a billing request carries."""
+ if add_ons is None:
+ return None
+ return [{"addonSlug": slug, "quantity": quantity} for slug, quantity in add_ons.items()]
+
+
+def check_limit(limit: int | None) -> int | None:
+ """``limit``, refused below 1: a ceiling of zero rows is a mistake, not a request."""
+ if limit is not None and limit < 1:
+ raise ValueError(f"limit must be at least 1, got {limit}")
+ return limit
+
+
def validate_uuid(value: str, name: str) -> str:
try:
return str(uuid.UUID(str(value)))
@@ -153,12 +197,29 @@ def validate_uuid(value: str, name: str) -> str:
raise ValueError(f"{name} must be a UUID, got {value!r}") from None
-def check_token(token: object, surface: Surface) -> str:
+PUBLISHABLE_KEY_PREFIX = "pk_"
+
+# The routes under /public take a credential of their own, chosen by path as the API chooses
+# it: a customer session on /public/session, an anonymous checkout's own session on one
+# checkout session, and a publishable key on the rest.
+_PUBLIC_CREDENTIALS = (
+ (
+ "/public/session/",
+ "kst_",
+ "a customer session (kst_...), from billing.customer_sessions.create()",
+ ),
+ ("/public/checkout-sessions/", "ksc_", "the checkout session's own token (ksc_...)"),
+ ("/public/", PUBLISHABLE_KEY_PREFIX, "a publishable key (pk_...)"),
+)
+
+
+def check_token(token: object, surface: Surface, path: str = "") -> str:
"""Return ``token`` when the API ``surface`` accepts its credential class; raise otherwise.
Each Kaiten listener refuses the other's credential with a deliberately uninformative
error. Catching the mix-up here, before a request leaves, is the only place a useful
- message can come from.
+ message can come from. A route under ``/public`` takes the one credential its path calls
+ for, and nothing else.
"""
if not isinstance(token, str) or not token.strip():
raise CredentialError("The token is empty.")
@@ -178,6 +239,14 @@ def check_token(token: object, surface: Surface) -> str:
"The Platform API only accepts platform credentials (ksm_...). Organization "
"tokens (ksh_...) and identity-provider JWTs belong to KaitenClient."
)
+ for prefix, required, credential in _PUBLIC_CREDENTIALS:
+ if path.startswith(prefix):
+ if not token.startswith(required):
+ raise CredentialError(
+ f"{path} takes {credential}, and refuses any other credential. Build a "
+ "KaitenClient with that token to call it."
+ )
+ break
return token
diff --git a/src/kaitencloud/_version.py b/src/kaitencloud/_version.py
index bfc8daa..d37edb2 100644
--- a/src/kaitencloud/_version.py
+++ b/src/kaitencloud/_version.py
@@ -1,4 +1,4 @@
# Copyright 2026 KAITEN INC
# SPDX-License-Identifier: Apache-2.0
-__version__ = "1.0.0"
+__version__ = "1.1.0"
diff --git a/src/kaitencloud/types/__init__.py b/src/kaitencloud/types/__init__.py
index 86d1c02..589163c 100644
--- a/src/kaitencloud/types/__init__.py
+++ b/src/kaitencloud/types/__init__.py
@@ -11,37 +11,123 @@
"""
from ._core import (
+ ActorKind,
+ Addon,
+ AddonDelivered,
+ AddonEntitlement,
+ AddonFamily,
+ AddonGrantUpdateDelivered,
+ AddonLifecycleState,
+ AddonPriceUpdateDelivered,
+ AddonUpdateDelivered,
AggregationMethod,
+ AuditActor,
AuditTrail,
BasicTargeting,
BasicVariant,
+ BillingCapabilities,
+ BillingDisabledReason,
+ BillingFeatures,
+ BillingHealth,
+ BillingModel,
+ BillingPeriod,
+ BillingProvider,
+ BillingProviderKind,
+ BillingSettings,
+ BillingStartedDelivered,
+ BillingTiming,
BooleanEntitlementValue,
+ CanceledSubscription,
+ CancellationChangeDelivered,
+ CancellationDelivered,
+ CancellationMode,
+ CancellationReason,
+ CheckoutConfirmation,
+ CheckoutConfirmationStatus,
+ CheckoutPayment,
+ CheckoutSession,
+ CheckoutSessionAdmin,
+ CheckoutSessionEventStatus,
+ CheckoutSessionStart,
+ CheckoutSessionStatus,
+ CheckoutStartStatus,
+ CloseBacklog,
+ ClosedInvoice,
+ ClosePeriodsReport,
+ CollectionMethod,
+ CompatibleLicenseFamilies,
+ CompletedPaymentMethodSession,
Component,
+ ComponentDelivered,
ConfigEntitlementValue,
+ ConnectionDelivered,
Connector,
ConnectorActivation,
ConnectorSettings,
ConnectorSettingsSchema,
ConnectorState,
+ CreatedCustomerSession,
Customer,
+ CustomerBilling,
CustomerCreationRejected,
+ CustomerEventDelivered,
+ CustomerInProvider,
CustomerIntegration,
CustomerIntegrationResource,
DefaultVariant,
+ DeletedAddonDelivered,
Deployment,
DeploymentZone,
+ DeploymentZoneDelivered,
+ DiscountAllocation,
+ DiscountAppliesTo,
+ DiscountDifference,
+ DiscountType,
+ DueDateDifference,
+ DueToday,
Entitlement,
+ EntitlementDelivered,
+ EntitlementException,
EntitlementGroup,
+ EntitlementGroupDelivered,
EntitlementGroupSummary,
EntitlementGroupUsage,
+ EntitlementSource,
EntitlementType,
EntitlementUsage,
+ EntitlementUsageDelivered,
EntitlementValue,
ErrorDetail,
+ EventHeaders,
+ EventVia,
+ ExceptionEventDelivered,
+ ExceptionKind,
+ ExceptionStatus,
+ ExhaustedEventDelivered,
+ ExpiredEventDelivered,
+ ExtraDiscount,
FeatureFlag,
+ FeatureFlagDelivered,
FeatureFlagEvaluated,
FeatureFlagType,
+ FlagGrant,
+ Grant,
+ GrantModifier,
+ HandoffAcknowledgementDelivered,
+ HandoffBacklog,
+ HandoffClaim,
+ HandoffStatus,
+ HeldInvoiceDelivered,
+ HeldInvoices,
+ HeldPair,
+ HoldDetail,
+ HoldRecord,
+ IncreaseBilling,
Instance,
+ InstanceAddon,
+ InstanceAddonUpdateDelivered,
+ InstanceBilling,
+ InstanceDelivered,
InstanceEntitlementCapExceeded,
InstanceEntitlementUsagePeriodRolledOver,
InstanceEntitlementUsageReportAccepted,
@@ -52,32 +138,156 @@
InstanceLifecycleStageChanged,
InstanceStatus,
InstanceStatusChanged,
+ InstanceUpdateDelivered,
+ Invoice,
+ InvoiceHandoff,
+ InvoiceHold,
+ InvoiceHoldReason,
+ InvoiceKind,
+ InvoiceLine,
+ InvoiceLineDiscount,
+ InvoiceLineLedger,
+ InvoiceLineMetering,
+ InvoiceLineOverage,
+ InvoiceLineProvider,
+ InvoiceLineTrueUp,
+ InvoiceLineType,
+ InvoicePreview,
+ InvoiceStatus,
+ InvoiceSummary,
+ IssuedInvoiceDelivered,
License,
+ LicenseDelivered,
LicenseEntitlement,
+ LicenseEntitlementDelivered,
LicenseEntitlementType,
LicenseFamilyView,
+ LicenseFamilyViewDelivered,
LicenseLifecycleState,
+ LicensePriceEventDelivered,
+ LicensePriceUpdatedEventDelivered,
LicenseType,
+ LineDifference,
+ LineReportPage,
ManifestEnvelope,
ManifestFlag,
MetadataField,
+ MetadataFieldDelivered,
MetadataFieldImpact,
MetadataFieldImpactSample,
MetadataFieldReordered,
MetadataResourceType,
+ MinimumAmount,
+ MismatchedInvoiceDelivered,
NumberEntitlementValue,
+ Outcome,
+ OverageLimit,
+ OverrideBehavior,
+ PaidInvoiceDelivered,
+ Payment,
+ PaymentFailedInvoiceDelivered,
+ PaymentMethodAttachedDelivered,
+ PaymentMethodDetachedDelivered,
+ PaymentMethodExpiringDelivered,
+ PaymentMethodLabels,
+ PaymentMethodSession,
+ PaymentMethodStatus,
+ PaymentSource,
+ PaymentStatus,
+ PeriodRolloverReason,
PlainToken,
+ PlanChangeDelivered,
+ PlanChangeScheduleDelivered,
+ PortalCapabilities,
+ PortalCustomer,
+ PortalEntitlement,
+ PortalInstance,
+ PortalInstanceSummary,
+ PortalPaymentMethod,
+ PortalSession,
+ PortalTrial,
+ PortalTrialCharge,
+ PortalTrialPayment,
+ PortalUpcomingInvoice,
+ Price,
+ PriceMeter,
+ PriceStatus,
+ PricingType,
Problem,
+ Provenance,
+ ProvenanceAddon,
+ ProvenanceBoost,
+ ProvenanceException,
+ ProvenanceLicense,
+ ProvenanceNumber,
+ ProviderCapabilities,
+ ProviderChangeDelivered,
+ ProviderInvoiceStatus,
+ ProviderLineLimit,
+ ProviderRecord,
+ ProviderSync,
+ ProviderUnavailableReason,
+ PublicAddOn,
+ PublicAddOnEntitlement,
+ PublicBranding,
+ PublicCatalog,
+ PublicCatalogCapabilities,
+ PublicMeter,
+ PublicPlan,
+ PublicPlanEntitlement,
+ PublicPrice,
+ PublicSurface,
+ PublishableKey,
+ PublishableKeyCreated,
+ PublishableKeyEventDelivered,
+ PushedInvoiceDelivered,
+ PushFailedInvoiceDelivered,
+ PushFailures,
+ QueuedInvoice,
+ Reconciliation,
+ ReconciliationStatus,
+ Redemption,
+ RedemptionDelivered,
+ RedemptionRules,
+ RedemptionStatus,
Release,
+ ReleaseDelivered,
+ ReleasedInvoiceDelivered,
+ ReportedEntitlementUsage,
ResetAnchor,
ResetPeriod,
+ RetentionSource,
RolloutDateTargeting,
RolloutDateVariant,
RolloutPercentageTargeting,
RolloutPercentageVariant,
RolloutStep,
+ ScheduledChange,
Scope,
ServiceAccount,
+ SessionAddon,
+ SessionAddonSelection,
+ SessionBoost,
+ SessionCheckout,
+ SessionCheckoutStatus,
+ SessionDiscount,
+ SessionEventDelivered,
+ SessionInvoice,
+ SessionInvoiceStatus,
+ SessionLine,
+ SessionPortal,
+ SessionRedemption,
+ SessionScheduledChange,
+ SessionSubscription,
+ SessionVoucher,
+ SessionVoucherValidity,
+ StartedSubscription,
+ StatusChangeDelivered,
+ SubscriptionStatus,
+ SubscriptionStatusChangeReason,
+ SyncFailureDelivered,
+ SyncReport,
+ SyncStatus,
SystemTokenIssuance,
Targeting,
TargetingContext,
@@ -88,16 +298,38 @@
TargetingRuleVerdict,
Targetings,
Token,
+ TotalsDifference,
+ TrialCharge,
+ TrialCustomer,
+ TrialEndWithoutPaymentMethod,
+ TrialInstance,
+ TrialPaymentMethod,
+ TrialWillEndDelivered,
+ UncollectibleInvoiceDelivered,
UsageBehavior,
+ UsageLedger,
+ UsageReport,
+ UsageReportPage,
UsageReportStatus,
User,
+ Validity,
Variant,
+ VoidedInvoiceDelivered,
+ Voucher,
+ VoucherDelivered,
+ VoucherDuration,
+ VoucherInvalidReason,
+ VoucherRule,
+ VoucherStatus,
+ VoucherType,
)
from ._params import (
BasicTargetingParam,
BasicVariantParam,
DefaultVariantParam,
IntegrationParam,
+ MinimumAmountParam,
+ RedemptionRulesParam,
RolloutDateTargetingParam,
RolloutDateVariantParam,
RolloutPercentageTargetingParam,
@@ -105,6 +337,7 @@
RolloutStepParam,
TargetingParam,
VariantParam,
+ VoucherGrantParam,
)
from ._platform import (
Organization,
@@ -112,40 +345,126 @@
)
__all__ = [
+ "ActorKind",
+ "Addon",
+ "AddonDelivered",
+ "AddonEntitlement",
+ "AddonFamily",
+ "AddonGrantUpdateDelivered",
+ "AddonLifecycleState",
+ "AddonPriceUpdateDelivered",
+ "AddonUpdateDelivered",
"AggregationMethod",
+ "AuditActor",
"AuditTrail",
"BasicTargeting",
"BasicTargetingParam",
"BasicVariant",
"BasicVariantParam",
+ "BillingCapabilities",
+ "BillingDisabledReason",
+ "BillingFeatures",
+ "BillingHealth",
+ "BillingModel",
+ "BillingPeriod",
+ "BillingProvider",
+ "BillingProviderKind",
+ "BillingSettings",
+ "BillingStartedDelivered",
+ "BillingTiming",
"BooleanEntitlementValue",
+ "CanceledSubscription",
+ "CancellationChangeDelivered",
+ "CancellationDelivered",
+ "CancellationMode",
+ "CancellationReason",
+ "CheckoutConfirmation",
+ "CheckoutConfirmationStatus",
+ "CheckoutPayment",
+ "CheckoutSession",
+ "CheckoutSessionAdmin",
+ "CheckoutSessionEventStatus",
+ "CheckoutSessionStart",
+ "CheckoutSessionStatus",
+ "CheckoutStartStatus",
+ "CloseBacklog",
+ "ClosePeriodsReport",
+ "ClosedInvoice",
+ "CollectionMethod",
+ "CompatibleLicenseFamilies",
+ "CompletedPaymentMethodSession",
"Component",
+ "ComponentDelivered",
"ConfigEntitlementValue",
+ "ConnectionDelivered",
"Connector",
"ConnectorActivation",
"ConnectorSettings",
"ConnectorSettingsSchema",
"ConnectorState",
+ "CreatedCustomerSession",
"Customer",
+ "CustomerBilling",
"CustomerCreationRejected",
+ "CustomerEventDelivered",
+ "CustomerInProvider",
"CustomerIntegration",
"CustomerIntegrationResource",
"DefaultVariant",
"DefaultVariantParam",
+ "DeletedAddonDelivered",
"Deployment",
"DeploymentZone",
+ "DeploymentZoneDelivered",
+ "DiscountAllocation",
+ "DiscountAppliesTo",
+ "DiscountDifference",
+ "DiscountType",
+ "DueDateDifference",
+ "DueToday",
"Entitlement",
+ "EntitlementDelivered",
+ "EntitlementException",
"EntitlementGroup",
+ "EntitlementGroupDelivered",
"EntitlementGroupSummary",
"EntitlementGroupUsage",
+ "EntitlementSource",
"EntitlementType",
"EntitlementUsage",
+ "EntitlementUsageDelivered",
"EntitlementValue",
"ErrorDetail",
+ "EventHeaders",
+ "EventVia",
+ "ExceptionEventDelivered",
+ "ExceptionKind",
+ "ExceptionStatus",
+ "ExhaustedEventDelivered",
+ "ExpiredEventDelivered",
+ "ExtraDiscount",
"FeatureFlag",
+ "FeatureFlagDelivered",
"FeatureFlagEvaluated",
"FeatureFlagType",
+ "FlagGrant",
+ "Grant",
+ "GrantModifier",
+ "HandoffAcknowledgementDelivered",
+ "HandoffBacklog",
+ "HandoffClaim",
+ "HandoffStatus",
+ "HeldInvoiceDelivered",
+ "HeldInvoices",
+ "HeldPair",
+ "HoldDetail",
+ "HoldRecord",
+ "IncreaseBilling",
"Instance",
+ "InstanceAddon",
+ "InstanceAddonUpdateDelivered",
+ "InstanceBilling",
+ "InstanceDelivered",
"InstanceEntitlementCapExceeded",
"InstanceEntitlementUsagePeriodRolledOver",
"InstanceEntitlementUsageReportAccepted",
@@ -156,28 +475,130 @@
"InstanceLifecycleStageChanged",
"InstanceStatus",
"InstanceStatusChanged",
+ "InstanceUpdateDelivered",
"IntegrationParam",
+ "Invoice",
+ "InvoiceHandoff",
+ "InvoiceHold",
+ "InvoiceHoldReason",
+ "InvoiceKind",
+ "InvoiceLine",
+ "InvoiceLineDiscount",
+ "InvoiceLineLedger",
+ "InvoiceLineMetering",
+ "InvoiceLineOverage",
+ "InvoiceLineProvider",
+ "InvoiceLineTrueUp",
+ "InvoiceLineType",
+ "InvoicePreview",
+ "InvoiceStatus",
+ "InvoiceSummary",
+ "IssuedInvoiceDelivered",
"License",
+ "LicenseDelivered",
"LicenseEntitlement",
+ "LicenseEntitlementDelivered",
"LicenseEntitlementType",
"LicenseFamilyView",
+ "LicenseFamilyViewDelivered",
"LicenseLifecycleState",
+ "LicensePriceEventDelivered",
+ "LicensePriceUpdatedEventDelivered",
"LicenseType",
+ "LineDifference",
+ "LineReportPage",
"ManifestEnvelope",
"ManifestFlag",
"MetadataField",
+ "MetadataFieldDelivered",
"MetadataFieldImpact",
"MetadataFieldImpactSample",
"MetadataFieldReordered",
"MetadataResourceType",
+ "MinimumAmount",
+ "MinimumAmountParam",
+ "MismatchedInvoiceDelivered",
"NumberEntitlementValue",
"Organization",
+ "Outcome",
+ "OverageLimit",
+ "OverrideBehavior",
+ "PaidInvoiceDelivered",
+ "Payment",
+ "PaymentFailedInvoiceDelivered",
+ "PaymentMethodAttachedDelivered",
+ "PaymentMethodDetachedDelivered",
+ "PaymentMethodExpiringDelivered",
+ "PaymentMethodLabels",
+ "PaymentMethodSession",
+ "PaymentMethodStatus",
+ "PaymentSource",
+ "PaymentStatus",
+ "PeriodRolloverReason",
"PlainToken",
+ "PlanChangeDelivered",
+ "PlanChangeScheduleDelivered",
"PlatformCredential",
+ "PortalCapabilities",
+ "PortalCustomer",
+ "PortalEntitlement",
+ "PortalInstance",
+ "PortalInstanceSummary",
+ "PortalPaymentMethod",
+ "PortalSession",
+ "PortalTrial",
+ "PortalTrialCharge",
+ "PortalTrialPayment",
+ "PortalUpcomingInvoice",
+ "Price",
+ "PriceMeter",
+ "PriceStatus",
+ "PricingType",
"Problem",
+ "Provenance",
+ "ProvenanceAddon",
+ "ProvenanceBoost",
+ "ProvenanceException",
+ "ProvenanceLicense",
+ "ProvenanceNumber",
+ "ProviderCapabilities",
+ "ProviderChangeDelivered",
+ "ProviderInvoiceStatus",
+ "ProviderLineLimit",
+ "ProviderRecord",
+ "ProviderSync",
+ "ProviderUnavailableReason",
+ "PublicAddOn",
+ "PublicAddOnEntitlement",
+ "PublicBranding",
+ "PublicCatalog",
+ "PublicCatalogCapabilities",
+ "PublicMeter",
+ "PublicPlan",
+ "PublicPlanEntitlement",
+ "PublicPrice",
+ "PublicSurface",
+ "PublishableKey",
+ "PublishableKeyCreated",
+ "PublishableKeyEventDelivered",
+ "PushFailedInvoiceDelivered",
+ "PushFailures",
+ "PushedInvoiceDelivered",
+ "QueuedInvoice",
+ "Reconciliation",
+ "ReconciliationStatus",
+ "Redemption",
+ "RedemptionDelivered",
+ "RedemptionRules",
+ "RedemptionRulesParam",
+ "RedemptionStatus",
"Release",
+ "ReleaseDelivered",
+ "ReleasedInvoiceDelivered",
+ "ReportedEntitlementUsage",
"ResetAnchor",
"ResetPeriod",
+ "RetentionSource",
"RolloutDateTargeting",
"RolloutDateTargetingParam",
"RolloutDateVariant",
@@ -188,8 +609,32 @@
"RolloutPercentageVariantParam",
"RolloutStep",
"RolloutStepParam",
+ "ScheduledChange",
"Scope",
"ServiceAccount",
+ "SessionAddon",
+ "SessionAddonSelection",
+ "SessionBoost",
+ "SessionCheckout",
+ "SessionCheckoutStatus",
+ "SessionDiscount",
+ "SessionEventDelivered",
+ "SessionInvoice",
+ "SessionInvoiceStatus",
+ "SessionLine",
+ "SessionPortal",
+ "SessionRedemption",
+ "SessionScheduledChange",
+ "SessionSubscription",
+ "SessionVoucher",
+ "SessionVoucherValidity",
+ "StartedSubscription",
+ "StatusChangeDelivered",
+ "SubscriptionStatus",
+ "SubscriptionStatusChangeReason",
+ "SyncFailureDelivered",
+ "SyncReport",
+ "SyncStatus",
"SystemTokenIssuance",
"Targeting",
"TargetingContext",
@@ -201,9 +646,30 @@
"TargetingRuleVerdict",
"Targetings",
"Token",
+ "TotalsDifference",
+ "TrialCharge",
+ "TrialCustomer",
+ "TrialEndWithoutPaymentMethod",
+ "TrialInstance",
+ "TrialPaymentMethod",
+ "TrialWillEndDelivered",
+ "UncollectibleInvoiceDelivered",
"UsageBehavior",
+ "UsageLedger",
+ "UsageReport",
+ "UsageReportPage",
"UsageReportStatus",
"User",
+ "Validity",
"Variant",
"VariantParam",
+ "VoidedInvoiceDelivered",
+ "Voucher",
+ "VoucherDelivered",
+ "VoucherDuration",
+ "VoucherGrantParam",
+ "VoucherInvalidReason",
+ "VoucherRule",
+ "VoucherStatus",
+ "VoucherType",
]
diff --git a/src/kaitencloud/types/_core.py b/src/kaitencloud/types/_core.py
index 833b070..ac66228 100644
--- a/src/kaitencloud/types/_core.py
+++ b/src/kaitencloud/types/_core.py
@@ -14,37 +14,123 @@
from .._models import KaitenModel
__all__ = [
+ "ActorKind",
+ "Addon",
+ "AddonDelivered",
+ "AddonEntitlement",
+ "AddonFamily",
+ "AddonGrantUpdateDelivered",
+ "AddonLifecycleState",
+ "AddonPriceUpdateDelivered",
+ "AddonUpdateDelivered",
"AggregationMethod",
+ "AuditActor",
"AuditTrail",
"BasicTargeting",
"BasicVariant",
+ "BillingCapabilities",
+ "BillingDisabledReason",
+ "BillingFeatures",
+ "BillingHealth",
+ "BillingModel",
+ "BillingPeriod",
+ "BillingProvider",
+ "BillingProviderKind",
+ "BillingSettings",
+ "BillingStartedDelivered",
+ "BillingTiming",
"BooleanEntitlementValue",
+ "CanceledSubscription",
+ "CancellationChangeDelivered",
+ "CancellationDelivered",
+ "CancellationMode",
+ "CancellationReason",
+ "CheckoutConfirmation",
+ "CheckoutConfirmationStatus",
+ "CheckoutPayment",
+ "CheckoutSession",
+ "CheckoutSessionAdmin",
+ "CheckoutSessionEventStatus",
+ "CheckoutSessionStart",
+ "CheckoutSessionStatus",
+ "CheckoutStartStatus",
+ "CloseBacklog",
+ "ClosePeriodsReport",
+ "ClosedInvoice",
+ "CollectionMethod",
+ "CompatibleLicenseFamilies",
+ "CompletedPaymentMethodSession",
"Component",
+ "ComponentDelivered",
"ConfigEntitlementValue",
+ "ConnectionDelivered",
"Connector",
"ConnectorActivation",
"ConnectorSettings",
"ConnectorSettingsSchema",
"ConnectorState",
+ "CreatedCustomerSession",
"Customer",
+ "CustomerBilling",
"CustomerCreationRejected",
+ "CustomerEventDelivered",
+ "CustomerInProvider",
"CustomerIntegration",
"CustomerIntegrationResource",
"DefaultVariant",
+ "DeletedAddonDelivered",
"Deployment",
"DeploymentZone",
+ "DeploymentZoneDelivered",
+ "DiscountAllocation",
+ "DiscountAppliesTo",
+ "DiscountDifference",
+ "DiscountType",
+ "DueDateDifference",
+ "DueToday",
"Entitlement",
+ "EntitlementDelivered",
+ "EntitlementException",
"EntitlementGroup",
+ "EntitlementGroupDelivered",
"EntitlementGroupSummary",
"EntitlementGroupUsage",
+ "EntitlementSource",
"EntitlementType",
"EntitlementUsage",
+ "EntitlementUsageDelivered",
"EntitlementValue",
"ErrorDetail",
+ "EventHeaders",
+ "EventVia",
+ "ExceptionEventDelivered",
+ "ExceptionKind",
+ "ExceptionStatus",
+ "ExhaustedEventDelivered",
+ "ExpiredEventDelivered",
+ "ExtraDiscount",
"FeatureFlag",
+ "FeatureFlagDelivered",
"FeatureFlagEvaluated",
"FeatureFlagType",
+ "FlagGrant",
+ "Grant",
+ "GrantModifier",
+ "HandoffAcknowledgementDelivered",
+ "HandoffBacklog",
+ "HandoffClaim",
+ "HandoffStatus",
+ "HeldInvoiceDelivered",
+ "HeldInvoices",
+ "HeldPair",
+ "HoldDetail",
+ "HoldRecord",
+ "IncreaseBilling",
"Instance",
+ "InstanceAddon",
+ "InstanceAddonUpdateDelivered",
+ "InstanceBilling",
+ "InstanceDelivered",
"InstanceEntitlementCapExceeded",
"InstanceEntitlementUsagePeriodRolledOver",
"InstanceEntitlementUsageReportAccepted",
@@ -55,32 +141,156 @@
"InstanceLifecycleStageChanged",
"InstanceStatus",
"InstanceStatusChanged",
+ "InstanceUpdateDelivered",
+ "Invoice",
+ "InvoiceHandoff",
+ "InvoiceHold",
+ "InvoiceHoldReason",
+ "InvoiceKind",
+ "InvoiceLine",
+ "InvoiceLineDiscount",
+ "InvoiceLineLedger",
+ "InvoiceLineMetering",
+ "InvoiceLineOverage",
+ "InvoiceLineProvider",
+ "InvoiceLineTrueUp",
+ "InvoiceLineType",
+ "InvoicePreview",
+ "InvoiceStatus",
+ "InvoiceSummary",
+ "IssuedInvoiceDelivered",
"License",
+ "LicenseDelivered",
"LicenseEntitlement",
+ "LicenseEntitlementDelivered",
"LicenseEntitlementType",
"LicenseFamilyView",
+ "LicenseFamilyViewDelivered",
"LicenseLifecycleState",
+ "LicensePriceEventDelivered",
+ "LicensePriceUpdatedEventDelivered",
"LicenseType",
+ "LineDifference",
+ "LineReportPage",
"ManifestEnvelope",
"ManifestFlag",
"MetadataField",
+ "MetadataFieldDelivered",
"MetadataFieldImpact",
"MetadataFieldImpactSample",
"MetadataFieldReordered",
"MetadataResourceType",
+ "MinimumAmount",
+ "MismatchedInvoiceDelivered",
"NumberEntitlementValue",
+ "Outcome",
+ "OverageLimit",
+ "OverrideBehavior",
+ "PaidInvoiceDelivered",
+ "Payment",
+ "PaymentFailedInvoiceDelivered",
+ "PaymentMethodAttachedDelivered",
+ "PaymentMethodDetachedDelivered",
+ "PaymentMethodExpiringDelivered",
+ "PaymentMethodLabels",
+ "PaymentMethodSession",
+ "PaymentMethodStatus",
+ "PaymentSource",
+ "PaymentStatus",
+ "PeriodRolloverReason",
"PlainToken",
+ "PlanChangeDelivered",
+ "PlanChangeScheduleDelivered",
+ "PortalCapabilities",
+ "PortalCustomer",
+ "PortalEntitlement",
+ "PortalInstance",
+ "PortalInstanceSummary",
+ "PortalPaymentMethod",
+ "PortalSession",
+ "PortalTrial",
+ "PortalTrialCharge",
+ "PortalTrialPayment",
+ "PortalUpcomingInvoice",
+ "Price",
+ "PriceMeter",
+ "PriceStatus",
+ "PricingType",
"Problem",
+ "Provenance",
+ "ProvenanceAddon",
+ "ProvenanceBoost",
+ "ProvenanceException",
+ "ProvenanceLicense",
+ "ProvenanceNumber",
+ "ProviderCapabilities",
+ "ProviderChangeDelivered",
+ "ProviderInvoiceStatus",
+ "ProviderLineLimit",
+ "ProviderRecord",
+ "ProviderSync",
+ "ProviderUnavailableReason",
+ "PublicAddOn",
+ "PublicAddOnEntitlement",
+ "PublicBranding",
+ "PublicCatalog",
+ "PublicCatalogCapabilities",
+ "PublicMeter",
+ "PublicPlan",
+ "PublicPlanEntitlement",
+ "PublicPrice",
+ "PublicSurface",
+ "PublishableKey",
+ "PublishableKeyCreated",
+ "PublishableKeyEventDelivered",
+ "PushFailedInvoiceDelivered",
+ "PushFailures",
+ "PushedInvoiceDelivered",
+ "QueuedInvoice",
+ "Reconciliation",
+ "ReconciliationStatus",
+ "Redemption",
+ "RedemptionDelivered",
+ "RedemptionRules",
+ "RedemptionStatus",
"Release",
+ "ReleaseDelivered",
+ "ReleasedInvoiceDelivered",
+ "ReportedEntitlementUsage",
"ResetAnchor",
"ResetPeriod",
+ "RetentionSource",
"RolloutDateTargeting",
"RolloutDateVariant",
"RolloutPercentageTargeting",
"RolloutPercentageVariant",
"RolloutStep",
+ "ScheduledChange",
"Scope",
"ServiceAccount",
+ "SessionAddon",
+ "SessionAddonSelection",
+ "SessionBoost",
+ "SessionCheckout",
+ "SessionCheckoutStatus",
+ "SessionDiscount",
+ "SessionEventDelivered",
+ "SessionInvoice",
+ "SessionInvoiceStatus",
+ "SessionLine",
+ "SessionPortal",
+ "SessionRedemption",
+ "SessionScheduledChange",
+ "SessionSubscription",
+ "SessionVoucher",
+ "SessionVoucherValidity",
+ "StartedSubscription",
+ "StatusChangeDelivered",
+ "SubscriptionStatus",
+ "SubscriptionStatusChangeReason",
+ "SyncFailureDelivered",
+ "SyncReport",
+ "SyncStatus",
"SystemTokenIssuance",
"Targeting",
"TargetingContext",
@@ -91,25 +301,148 @@
"TargetingRuleVerdict",
"Targetings",
"Token",
+ "TotalsDifference",
+ "TrialCharge",
+ "TrialCustomer",
+ "TrialEndWithoutPaymentMethod",
+ "TrialInstance",
+ "TrialPaymentMethod",
+ "TrialWillEndDelivered",
+ "UncollectibleInvoiceDelivered",
"UsageBehavior",
+ "UsageLedger",
+ "UsageReport",
+ "UsageReportPage",
"UsageReportStatus",
"User",
+ "Validity",
"Variant",
+ "VoidedInvoiceDelivered",
+ "Voucher",
+ "VoucherDelivered",
+ "VoucherDuration",
+ "VoucherInvalidReason",
+ "VoucherRule",
+ "VoucherStatus",
+ "VoucherType",
]
+ActorKind = Literal["USER", "SERVICE", "SYSTEM"]
+"""Who acted: a person, a service account, or Kaiten itself."""
+
+AddonLifecycleState = Literal["DRAFT", "PUBLISHED", "ARCHIVED"]
+"""Whether an add-on version may be sold: ``DRAFT`` before it goes on sale, ``PUBLISHED`` while
+it is on sale, ``ARCHIVED`` once it has been withdrawn.
+"""
+
AggregationMethod = Literal["COUNT", "SUM", "AVERAGE", "MIN", "MAX", "LATEST"]
"""How each reported usage value folds into an entitlement's stored total."""
+BillingDisabledReason = Literal["DEPLOYMENT_DISABLED", "NOT_ENTITLED"]
+"""Why billing is off: the deployment disabled it, or the plan does not include it."""
+
+BillingModel = Literal["FLAT_FEE", "USAGE_BASED", "OVERAGE"]
+"""How a price bills: ``FLAT_FEE`` per period, ``USAGE_BASED`` per sale unit of usage,
+``OVERAGE`` per sale unit above the license's limit.
+"""
+
+BillingPeriod = Literal["MONTHLY", "QUARTERLY", "SEMI_ANNUAL", "ANNUAL"]
+"""How often a subscription bills its flat fees."""
+
+BillingProviderKind = Literal["NOOP", "STRIPE"]
+"""Who issues and collects an invoice: ``NOOP``, the organization itself through the handoff
+queue, or ``STRIPE``.
+"""
+
+BillingTiming = Literal["ADVANCE", "ARREARS"]
+"""When a price bills: in ``ADVANCE`` of its period, or in ``ARREARS`` after it."""
+
+CancellationMode = Literal["AT_PERIOD_END", "IMMEDIATE"]
+"""When a cancellation takes effect: at the end of the period paid for, or now."""
+
+CancellationReason = Literal["TRIAL_ENDED_WITHOUT_PAYMENT_METHOD", "INCOMPLETE_EXPIRED"]
+"""Why Kaiten canceled a subscription on its own."""
+
+CheckoutConfirmationStatus = Literal["requires_payment_method", "awaiting_payment", "completed"]
+"""Where an anonymous checkout stands after a confirmation."""
+
+CheckoutSessionEventStatus = Literal["OPEN", "COMPLETED", "ABANDONED", "EXPIRED"]
+"""The status of an anonymous checkout session, as its events report it."""
+
+CheckoutSessionStatus = Literal["OPEN", "AWAITING_PAYMENT", "COMPLETED", "ABANDONED", "EXPIRED"]
+"""The status of an anonymous checkout session."""
+
+CheckoutStartStatus = Literal["preview", "open"]
+"""What starting an anonymous checkout did: ``preview`` for a dry run, ``open`` otherwise."""
+
+CollectionMethod = Literal["SEND_INVOICE", "CHARGE_AUTOMATICALLY"]
+"""How an invoice is collected: ``SEND_INVOICE`` sends it for payment, ``CHARGE_AUTOMATICALLY``
+charges the customer's payment method.
+"""
+
+DiscountAppliesTo = Literal["LICENSE_BASE", "ADDONS", "BOTH", "SELECTED_PRICES"]
+"""What a price voucher discounts."""
+
+DiscountType = Literal["PERCENTAGE", "FIXED_AMOUNT"]
+"""How a discount is expressed: a percentage, or a fixed amount in minor units."""
+
+EntitlementSource = Literal["license", "addon", "exception"]
+"""Where an instance's entitlement comes from: its ``license``, an ``addon``, or an
+``exception`` alone.
+"""
+
EntitlementType = Literal["BOOLEAN", "NUMBER", "CONFIG", "NUMBER_AI_CREDIT"]
"""The value type of an entitlement in the catalog."""
+EventVia = Literal["customer_session", "checkout_session"]
+"""What wrote the change an event reports, when it was not an organization credential: a
+customer session, or an anonymous checkout.
+"""
+
+ExceptionKind = Literal["SET", "ADD", "UNLIMITED"]
+"""How an entitlement exception overrides a value: ``SET`` it, ``ADD`` to it, or lift it
+(``UNLIMITED``).
+"""
+
+ExceptionStatus = Literal["SCHEDULED", "ACTIVE", "EXPIRED", "REVOKED"]
+"""Where an entitlement exception stands."""
+
FeatureFlagType = Literal["boolean", "string", "number", "object"]
"""The value type of a feature flag's variants."""
+GrantModifier = Literal["SET", "ADD", "MULTIPLY", "UNLIMITED"]
+"""How a voucher grant changes an entitlement: ``SET``, ``ADD``, ``MULTIPLY`` or ``UNLIMITED``."""
+
+HandoffStatus = Literal["NOT_REQUIRED", "PENDING", "ACKNOWLEDGED"]
+"""Where an invoice stands in the accounting handoff."""
+
+IncreaseBilling = Literal["TRUE_UP", "NEXT_BOUNDARY"]
+"""How a mid-period increase of an add-on billed in advance is billed: a ``TRUE_UP`` line, or
+from the ``NEXT_BOUNDARY``.
+"""
+
InstanceStatus = Literal["HEALTHY", "DEGRADED", "INCIDENT", "MAINTENANCE"]
"""The operational status of an instance."""
+InvoiceHoldReason = Literal[
+ "LEDGER_SEQUENCE_GAP", "LEDGER_CHAIN_BREAK", "LEDGER_COUNTER_MISMATCH", "PROVIDER_ITEM_LIMIT"
+]
+"""Why an invoice is held as a draft."""
+
+InvoiceKind = Literal["ACTIVATION", "RENEWAL", "FINAL"]
+"""What an invoice bills: a subscription's ``ACTIVATION``, a ``RENEWAL``, or its ``FINAL``
+period.
+"""
+
+InvoiceLineType = Literal["BASE", "ADDON", "USAGE", "OVERAGE", "DISCOUNT"]
+"""What an invoice line bills."""
+
+InvoiceStatus = Literal[
+ "DRAFT", "PUSHED", "PUSH_FAILED", "MANUAL", "PAID", "PAYMENT_FAILED", "UNCOLLECTIBLE", "VOID"
+]
+"""Where an invoice stands, from ``DRAFT`` to ``PAID``, ``UNCOLLECTIBLE`` or ``VOID``."""
+
LicenseEntitlementType = Literal["BOOLEAN", "NUMBER", "CONFIG"]
"""The value type of an entitlement granted by a license."""
@@ -124,14 +457,55 @@
MetadataResourceType = Literal["DEPLOYMENT_ZONE", "INSTANCE"]
"""The resource a metadata field applies to."""
-ResetAnchor = Literal["CALENDAR", "LICENSE_START"]
+OverrideBehavior = Literal["ADD", "OVERRIDE", "MAX"]
+"""How an add-on's number grant combines with the license's: ``ADD``, ``OVERRIDE`` or ``MAX``."""
+
+PaymentMethodStatus = Literal["ACTIVE", "EXPIRED", "FAILED"]
+"""Whether a saved payment method can be charged."""
+
+PaymentSource = Literal["MARK_PAID", "PROVIDER", "ZERO_TOTAL"]
+"""What recorded an invoice as paid."""
+
+PaymentStatus = Literal["paid", "requires_action", "failed", "processing", "not_required"]
+"""How a checkout's first payment went."""
+
+PeriodRolloverReason = Literal["PERIOD_ENDED", "ANCHOR_CHANGED"]
+"""Why a usage window closed: its period ended, or its anchor changed."""
+
+PriceStatus = Literal["ACTIVE", "DEPRECATED"]
+"""Whether a price is ``ACTIVE``, or ``DEPRECATED``: still billing the subscriptions on it, and
+no new one.
+"""
+
+PricingType = Literal["FREE", "PAID", "CUSTOM"]
+"""How a version is sold: ``FREE`` or ``PAID`` self-serve, or ``CUSTOM``, through a conversation."""
+
+ProviderInvoiceStatus = Literal["draft", "open", "paid", "uncollectible", "void"]
+"""An invoice's status at its payment provider."""
+
+ProviderUnavailableReason = Literal["NOT_ENTITLED", "VAULT_NOT_CONFIGURED"]
+"""Why a payment provider cannot be connected."""
+
+ReconciliationStatus = Literal["MATCHED", "MISMATCH"]
+"""Whether an invoice matches its copy at the payment provider."""
+
+RedemptionStatus = Literal["ACTIVE", "EXPIRED", "REVOKED"]
+"""Where a voucher redemption stands."""
+
+ResetAnchor = Literal["CALENDAR", "LICENSE_START", "BILLING_PERIOD"]
"""What a periodic usage window is aligned on."""
ResetPeriod = Literal["HOUR", "DAY", "WEEK", "MONTH", "YEAR"]
"""How often a periodic entitlement's usage resets."""
+RetentionSource = Literal["KBK", "ENV", "UNKNOWN"]
+"""Where the usage history's retention period comes from."""
+
Scope = Literal[
+ "read:addons",
+ "read:billing",
"read:components",
+ "read:customer_sessions",
"read:customers",
"read:deployment_zones",
"read:entitlements",
@@ -141,10 +515,18 @@
"read:metadata_fields",
"read:notifications",
"read:organizations",
+ "read:profile",
+ "read:publishable_keys",
"read:releases",
"read:tokens",
+ "read:users",
+ "read:voucher_redemptions",
+ "read:vouchers",
"read:webhooks",
+ "write:addons",
+ "write:billing",
"write:components",
+ "write:customer_sessions",
"write:customers",
"write:deployment_zones",
"write:entitlements",
@@ -154,27 +536,189 @@
"write:metadata_fields",
"write:notifications",
"write:organizations",
+ "write:profile",
+ "write:publishable_keys",
"write:releases",
"write:tokens",
+ "write:users",
+ "write:voucher_redemptions",
+ "write:vouchers",
"write:webhooks",
]
"""A permission an organization credential can carry, ``read:`` or
``write:``; ``write`` implies ``read`` on the same resource.
"""
+SessionCheckoutStatus = Literal["preview", "requires_payment_method", "subscribed"]
+"""What a customer session's checkout did."""
+
+SessionInvoiceStatus = Literal[
+ "MANUAL", "PUSHED", "PAID", "PAYMENT_FAILED", "UNCOLLECTIBLE", "VOID"
+]
+"""An issued invoice's status, as the customer's portal shows it."""
+
+SubscriptionStatus = Literal["INCOMPLETE", "TRIAL", "ACTIVE", "PAST_DUE", "CANCELED"]
+"""Where a subscription stands."""
+
+SubscriptionStatusChangeReason = Literal[
+ "TRIAL_ENDED", "INVOICE_OVERDUE", "PAYMENT_FAILED", "SETTLED"
+]
+"""Why a subscription's status changed."""
+
+SyncStatus = Literal["SUCCESS", "PARTIAL", "FAILED"]
+"""How a sync with a payment provider went."""
+
TargetingFactType = Literal["string", "number", "boolean", "object", "map", "dyn"]
"""The shape of a fact a targeting rule can read."""
+TrialEndWithoutPaymentMethod = Literal["CANCEL", "CONVERT"]
+"""What a trial charged automatically becomes at its end without a payment method: ``CANCEL``
+or ``CONVERT``.
+"""
+
UsageBehavior = Literal["append", "set"]
"""How a usage report applies: ``append`` folds the value in, ``set`` overwrites it."""
UsageReportStatus = Literal["ACCEPTED", "REJECTED"]
"""Whether a usage report was persisted."""
+VoucherDuration = Literal["ONE_TIME", "REPEATING", "FOREVER"]
+"""How long a price voucher's discount lasts."""
+
+VoucherInvalidReason = Literal[
+ "NOT_FOUND",
+ "NOT_ACTIVE",
+ "NOT_YET_VALID",
+ "EXPIRED",
+ "EXHAUSTED",
+ "ALREADY_REDEEMED",
+ "NOT_ELIGIBLE",
+ "CURRENCY_MISMATCH",
+ "DISCOUNT_LIMIT_EXCEEDED",
+]
+"""Why a voucher code would not apply."""
+
+VoucherRule = Literal[
+ "RESTRICTED_CUSTOMER",
+ "LICENSE_NOT_APPLICABLE",
+ "ADDON_NOT_APPLICABLE",
+ "FIRST_TIME_ONLY",
+ "ANNUAL_ONLY",
+ "MINIMUM_SUBSCRIPTION_AMOUNT",
+ "NOTHING_TO_BOOST",
+]
+"""The redemption rule a voucher code failed."""
+
+VoucherStatus = Literal["DRAFT", "ACTIVE", "EXPIRED", "EXHAUSTED", "ARCHIVED"]
+"""Where a voucher stands."""
+
+VoucherType = Literal["PRICE", "ENTITLEMENT_BOOST", "FLAG_GRANT", "COMPOSITE"]
+"""What a voucher gives: a price discount, an entitlement boost, flag grants, or several."""
+
+
+class Addon(KaitenModel):
+ created_at: datetime = Field(alias="createdAt")
+
+ description: str
+
+ family_slug: str = Field(alias="familySlug")
+ """The family the version belongs to"""
+
+ id: str
+
+ increase_billing: IncreaseBilling | str = Field(alias="increaseBilling")
+ """The family's: how a mid-period increase or attachment of an ADVANCE add-on is billed.
+ TRUE_UP adds a true-up line for the elapsed period at the next renewal; NEXT_BOUNDARY
+ bills from the next boundary only
+ """
+
+ is_default: bool = Field(alias="isDefault")
+ """The version the family resolves to. Only a PUBLISHED version."""
+
+ lifecycle_state: AddonLifecycleState | str = Field(alias="lifecycleState")
+ """A DRAFT can be attached to try it; an ARCHIVED version stays on the instances holding it
+ but cannot be attached again.
+ """
+
+ max_quantity: int | None = Field(alias="maxQuantity")
+ """The largest quantity an instance can hold; unbounded when null"""
+
+ name: str
+
+ pricing_type: PricingType | str = Field(alias="pricingType")
+
+ slug: str
+ """The version's slug, unique in the organization"""
+
+ updated_at: datetime = Field(alias="updatedAt")
+
+ version: int
+ """The version number in its family, assigned on create"""
+
+ version_name: str = Field(alias="versionName")
+
+
+class AddonEntitlement(KaitenModel):
+ entitlement_slug: str = Field(alias="entitlementSlug")
+
+ entitlement_type: EntitlementType | str = Field(alias="entitlementType")
+
+ id: str
+
+ limit_cap_exceeded_overage_percent: int | None = Field(alias="limitCapExceededOveragePercent")
+ """The overage the add-on allows; null inherits the licence grant's"""
+
+ override_behavior: OverrideBehavior | str = Field(alias="overrideBehavior")
+ """How a number grant combines with the licence's: ADD sums value × quantity, OVERRIDE
+ replaces it with the latest attached add-on's, MAX keeps the larger. A boolean always
+ ORs, a config always overrides.
+ """
+
+ value: dict[str, Any]
+ """{type, value}, shaped like a licence grant. A number counts once per unit of quantity."""
+
+
+class AddonFamily(KaitenModel):
+ current_version: Addon | None = Field(alias="currentVersion")
+ """The default version, else the highest PUBLISHED one; null when none is published"""
+
+ id: str
+
+ increase_billing: IncreaseBilling | str = Field(alias="increaseBilling")
+ """How a mid-period increase or attachment of the family's ADVANCE add-ons is billed (V31-70)"""
+
+ is_public: bool = Field(alias="isPublic")
+ """Whether the family is listed in the public catalogue"""
+
+ last_version: int = Field(alias="lastVersion")
+ """The highest version number the family has assigned"""
+
+ slug: str
+
+ versions: list[Addon] | None
+ """Every version, newest first"""
+
+
+class AuditActor(KaitenModel):
+ id: str
+
+ kind: ActorKind | str
+ """USER: a person. SERVICE: a service account. SYSTEM: Kaiten itself -- jobs, and the
+ anonymous checkouts no one of the organization makes
+ """
+
+ name: str
+ """The user's name; "Kaiten (system)" for the work Kaiten does itself"""
+
class AuditTrail(KaitenModel):
"""One entry of an instance's audit trail."""
+ actor: AuditActor | None
+ """Who the event is recorded under; null for an entry recorded before actors were (E-27),
+ or whose user is gone
+ """
+
event_name: str = Field(alias="eventName")
"""Event name (e.g. ENTITLEMENT_VALUE_GET)"""
@@ -218,703 +762,650 @@ class BasicVariant(KaitenModel):
value: str
-class BooleanEntitlementValue(KaitenModel):
- """A boolean entitlement value: whether a feature is included."""
+class BillingCapabilities(KaitenModel):
+ disabled_reason: BillingDisabledReason | str | None = Field(alias="disabledReason")
+ """Why they do not: billing is off on this deployment, or the organization's plan does not
+ include it
+ """
- type: Literal["boolean"]
- """Value type discriminator"""
+ enabled: bool
+ """Whether the billing routes answer for this organization"""
- value: bool
- """Boolean value"""
+ features: BillingFeatures
+ """Which parts of billing this release ships"""
+ providers: list[BillingProvider]
+ """Who can collect invoices"""
-class Component(KaitenModel):
- """A versioned building block of your product, that releases are made of."""
+ public_surface: PublicSurface = Field(alias="publicSurface")
- created_at: datetime = Field(alias="createdAt")
- """Timestamp when the component was created"""
+ usage_history_retention_months: int | None = Field(alias="usageHistoryRetentionMonths")
+ """How long usage reports are kept, in months; null when they are kept forever or it cannot
+ be told
+ """
- created_by: User = Field(alias="createdBy")
- """User who created this component"""
+ usage_history_retention_source: RetentionSource | str = Field(
+ alias="usageHistoryRetentionSource"
+ )
+ """Where the retention comes from: KBK, the organization's licence or an add-on; ENV, the
+ deployment's KAITEN_USAGE_LEDGER_RETENTION_MONTHS; UNKNOWN, the licensing authority
+ could not tell. A null usageHistoryRetentionMonths reads as kept forever with KBK or
+ ENV, and as unknown with UNKNOWN (V31-11).
+ """
- description: str | None = None
- """Description of the component"""
+ usage_idempotency_window_days: int = Field(alias="usageIdempotencyWindowDays")
+ """How long a usage report's transactionId is remembered, in days"""
- id: str
- """Unique identifier for the component"""
- name: str
- """Name of the component"""
+class BillingFeatures(KaitenModel):
+ addons: bool
- previous_component_id: str | None = Field(default=None, alias="previousComponentId")
- """Reference to the previous version of this component"""
+ charge_automatically: bool = Field(alias="chargeAutomatically")
- slug: str | None = None
- """Optional URL-friendly identifier, unique per organization. Auto-generated if not provided."""
+ lifecycle: bool
+ """Cancellation, reactivation, plan changes and overdue tracking"""
- version: str
- """Version of the component"""
+ public_surface: bool = Field(alias="publicSurface")
+ stripe: bool
-class ConfigEntitlementValue(KaitenModel):
- """A configuration entitlement value: a free-form object."""
+ trials: bool
- type: Literal["object"]
- """Value type discriminator"""
+ vouchers: bool
- value: dict[str, Any]
- """Object configuration value"""
+class BillingHealth(KaitenModel):
+ close_backlog: CloseBacklog = Field(alias="closeBacklog")
+ """Subscriptions whose period ended more than KAITEN_BILLING_CLOSE_GRACE ago and has not
+ closed yet
+ """
-class Connector(KaitenModel):
- """A connector registered with this Kaiten deployment."""
+ handoff: HandoffBacklog
- created_at: datetime
- """Timestamp when connector was first registered"""
+ held_invoices: HeldInvoices = Field(alias="heldInvoices")
- entitlement_slug: str | None = None
- """Slug of the BOOLEAN entitlement a license must grant for an organization to activate
- this connector. Absent means the connector is ungated.
- """
+ overdue_invoices: int = Field(alias="overdueInvoices")
- name: str
- """Stable connector identifier"""
+ past_due_subscriptions: int = Field(alias="pastDueSubscriptions")
- settings_schema: dict[str, Any]
- """JSON schema used to validate connector settings payloads"""
+ payment_methods_expiring30d: int = Field(alias="paymentMethodsExpiring30d")
+ """ACTIVE payment methods whose card expires in the next 30 days"""
- updated_at: datetime
- """Timestamp when connector registration was last updated"""
+ provider_sync: list[ProviderSync] = Field(alias="providerSync")
- version: str
- """Registered connector version"""
+ push_failures: PushFailures = Field(alias="pushFailures")
+ """Invoices whose push failed at least as many times as the alert threshold"""
+ reconciliation_mismatches30d: int = Field(alias="reconciliationMismatches30d")
+ """Invoices whose provider's amounts differed from Kaiten's, in the last 30 days"""
-class ConnectorActivation(KaitenModel):
- """An organization's activation of a connector."""
+ usage_ledger: UsageLedger = Field(alias="usageLedger")
- activated_at: datetime = Field(alias="activatedAt")
- """When this organization first activated the connector"""
- connector_name: str = Field(alias="connectorName")
- """Stable connector identifier"""
+class BillingProvider(KaitenModel):
+ available: bool
+ """Whether the organization may connect it on this deployment"""
+ capabilities: ProviderCapabilities
-class ConnectorSettings(KaitenModel):
- """An organization's settings for a connector."""
+ connected: bool
+ """Whether it is set up for the organization; NOOP always is"""
- connector_name: str
- """Stable connector identifier"""
+ kind: BillingProviderKind | str
- settings: dict[str, Any]
- """Connector settings payload"""
+ livemode: bool | None
+ """Whether the connection reaches the provider's live account rather than a test one; null
+ when not connected, and for NOOP
+ """
+ unavailable_reason: ProviderUnavailableReason | str | None = Field(alias="unavailableReason")
+ """Why it is not available; null when it is, and when the licensing authority cannot answer"""
-class ConnectorSettingsSchema(KaitenModel):
- """The JSON schema a connector's settings are validated against."""
- connector_name: str
- """Stable connector identifier"""
+class BillingSettings(KaitenModel):
+ default_collection_method: CollectionMethod | str = Field(alias="defaultCollectionMethod")
+ """How an invoice is collected when its subscription does not say: SEND_INVOICE sends it
+ for payment; CHARGE_AUTOMATICALLY charges the customer's payment method, on a provider
+ that can. A NOOP subscription sends its invoices whatever the default.
+ """
- json_schema: dict[str, Any] = Field(alias="schema")
- """JSON schema describing supported connector settings payload"""
+ default_days_until_due: int = Field(alias="defaultDaysUntilDue")
+ """Days an invoice is due after it is issued, when its subscription does not say; 0 to 365."""
- version: str
- """Registered connector version"""
+ handoff_stripe_invoices: bool = Field(alias="handoffStripeInvoices")
+ """Whether invoices a payment provider issues also enter the handoff queue, for an
+ accounting system that wants every invoice. Invoices without a provider always do.
+ """
-class ConnectorState(KaitenModel):
- """Whether a connector is available, licensed and activated for this organization."""
+class BooleanEntitlementValue(KaitenModel):
+ """A boolean entitlement value: whether a feature is included."""
- activated: bool
- """Whether this organization has turned the connector on"""
+ type: Literal["boolean"]
+ """Value type discriminator"""
- activated_at: datetime | None = Field(default=None, alias="activatedAt")
- """When this organization activated the connector. Absent when it has not."""
+ value: bool
+ """Boolean value"""
- available: bool
- """Whether this deployment has the connector registered"""
- connector_name: str = Field(alias="connectorName")
- """Stable connector identifier"""
+class CanceledSubscription(KaitenModel):
+ anchor_at: datetime = Field(alias="anchorAt")
+ """The instant its periods are counted from"""
- entitled: bool
- """Whether this organization's licence includes the connector. True for an ungated connector."""
+ base_price: Price = Field(alias="basePrice")
+ """The FLAT_FEE price the subscription is pinned to, deprecated or not"""
- version: str | None = None
- """Registered connector version. Absent when the connector is not available."""
+ billing_period: BillingPeriod | str = Field(alias="billingPeriod")
+ cancel_at_period_end: bool = Field(alias="cancelAtPeriodEnd")
-class Customer(KaitenModel):
- """A customer of your organization: the end client instances are deployed for."""
+ cancel_requested_at: datetime | None = Field(alias="cancelRequestedAt")
- created_at: datetime = Field(alias="createdAt")
- """Timestamp when the customer was created"""
+ canceled_at: datetime | None = Field(alias="canceledAt")
- created_by: User = Field(alias="createdBy")
- """User who created this customer"""
+ cancellation_reason: str | None = Field(alias="cancellationReason")
- domain: str | None = None
- """Optional customer domain name"""
+ collection_method: CollectionMethod | str = Field(alias="collectionMethod")
+ """Effective: the subscription's own, else the organization's default"""
- external_customer_id: str | None = Field(default=None, alias="externalCustomerId")
- """Optional identifier for the customer in an external system"""
+ collection_method_override: str | None = Field(alias="collectionMethodOverride")
+ """The subscription's own collection method; null when it takes the organization's"""
- id: str
- """Unique identifier for the customer"""
+ created_at: datetime = Field(alias="createdAt")
- integrations: dict[str, CustomerIntegration] | None = None
- """Integrations grouped by adapter name"""
+ currency: str
- name: str
- """Name of the customer"""
+ current_period_end: datetime = Field(alias="currentPeriodEnd")
+ """The next boundary: the period closes, and its invoice is composed, after it"""
- slug: str | None = None
- """Optional URL-friendly identifier, unique per organization. Auto-generated if not provided."""
+ current_period_start: datetime = Field(alias="currentPeriodStart")
- updated_at: datetime = Field(alias="updatedAt")
- """Timestamp when the customer was last updated"""
+ customer_name: str = Field(alias="customerName")
- updated_by: User = Field(alias="updatedBy")
- """User who last updated this customer"""
+ customer_slug: str = Field(alias="customerSlug")
+ days_until_due: int = Field(alias="daysUntilDue")
+ """Effective: the subscription's own, else the organization's default"""
-class CustomerCreationRejected(KaitenModel):
- """A customer creation refused because the organization reached its limit."""
+ days_until_due_override: int | None = Field(alias="daysUntilDueOverride")
+ """The subscription's own payment terms; null when it takes the organization's"""
- name: str
+ final_invoice: InvoiceSummary | None = Field(default=None, alias="finalInvoice")
+ """The FINAL invoice of an immediate cancellation"""
- reason: str
+ id: str
- slug: str
+ incomplete_expires_at: datetime | None = Field(alias="incompleteExpiresAt")
+ """When a checkout waiting for its payment ends unpaid: INCOMPLETE, or a free subscription
+ that carries one
+ """
+ instance_name: str = Field(alias="instanceName")
-class CustomerIntegration(KaitenModel):
- """A customer's link to a record in a third-party system."""
+ instance_slug: str = Field(alias="instanceSlug")
+ """As it was at the subscription's last write"""
- external_id: str
- """External identifier in the third-party adapter"""
+ past_due_since: datetime | None = Field(alias="pastDueSince")
- last_error: str | None = None
- """Last synchronization error message, if any"""
+ pending_license_slug: str | None = Field(alias="pendingLicenseSlug")
+ """The licence version a checkout waiting for its payment moves the instance to once paid"""
- metadata: dict[str, Any] | None = None
- """Adapter-specific integration metadata"""
+ provider_kind: BillingProviderKind | str = Field(alias="providerKind")
+ """Who collects its invoices. NOOP: the organization itself, through the handoff queue"""
- synced_at: datetime
- """Timestamp of the last synchronization with the adapter"""
+ reminder_at: datetime | None = Field(alias="reminderAt")
+ """When trial_will_end is (or was) emitted: trialEndsAt minus the family's
+ trialReminderDays as it was when the trial started; set with trialEndsAt
+ """
- web_url: str | None = None
- """Absolute http(s) link to the record in the third-party system"""
+ scheduled_change: ScheduledChange | None = Field(alias="scheduledChange")
+ """A plan change waiting for the next boundary"""
+ started_at: datetime = Field(alias="startedAt")
+ """When billing started: usage before it is never billed"""
-class CustomerIntegrationResource(KaitenModel):
- """A customer as seen through one of its integrations."""
-
- domain: str | None = None
- """Optional customer domain name"""
+ status: SubscriptionStatus | str
- error: str | None = None
- """Last synchronization error for this integration, if any"""
+ trial_ends_at: datetime | None = Field(alias="trialEndsAt")
+ """When the trial ends, or ended: usage before it is never billed"""
- integration_metadata: dict[str, Any] = Field(alias="integrationMetadata")
- """Adapter-specific customer integration metadata"""
+ updated_at: datetime = Field(alias="updatedAt")
- name: str
- """Customer name"""
- slug: str
- """Customer slug"""
+class CheckoutConfirmation(KaitenModel):
+ session: CheckoutSession
- web_url: str | None = Field(default=None, alias="webUrl")
- """Absolute http(s) link to the record in the third-party system"""
+ setup_expires_at: datetime | None = Field(default=None, alias="setupExpiresAt")
+ """requires_payment_method only"""
+ setup_url: str | None = Field(default=None, alias="setupUrl")
+ """requires_payment_method only: the hosted page that saves the payment method"""
-class Deployment(KaitenModel):
- """A release deployed to a deployment zone: an append-only history record."""
+ status: CheckoutConfirmationStatus | str
- created_at: datetime = Field(alias="createdAt")
- """Timestamp when the deployment was recorded"""
- created_by: User = Field(alias="createdBy")
- """User who recorded this deployment"""
+class CheckoutPayment(KaitenModel):
+ hosted_invoice_url: str | None = Field(alias="hostedInvoiceUrl")
- deployment_zone_id: str = Field(alias="deploymentZoneId")
- """Deployment zone the release was deployed to"""
+ status: PaymentStatus | str
- id: str
- """Unique identifier for this deployment event"""
- release_id: str = Field(alias="releaseId")
- """Release that was deployed"""
+class CheckoutSession(KaitenModel):
+ activation_invoice: SessionInvoice | None = Field(alias="activationInvoice")
+ """Once an ACTIVATION was issued"""
+ add_ons: list[SessionAddonSelection] = Field(alias="addOns")
-class DeploymentZone(KaitenModel):
- """An environment instances run in, and the release currently deployed to it."""
+ billing_period: str = Field(alias="billingPeriod")
- created_at: datetime = Field(alias="createdAt")
- """Timestamp when the deployment zone was created"""
+ client_reference_id: str | None = Field(alias="clientReferenceId")
- created_by: User = Field(alias="createdBy")
- """User who created this deployment zone"""
+ completed_at: datetime | None = Field(alias="completedAt")
- description: str
- """Description of the deployment zone"""
+ created_at: datetime = Field(alias="createdAt")
- id: str
- """Unique identifier for the deployment zone"""
+ customer_slug: str | None = Field(alias="customerSlug")
+ """Set by the completion"""
- metadata: dict[str, Any] | None = None
- """Typed metadata for the deployment zone, validated against the org-scoped MetadataField
- schema
- """
+ due_today: DueToday = Field(alias="dueToday")
+ """The voucher's discount shows as a line; the code itself is never echoed"""
- name: str
- """Name of the deployment zone"""
+ email: str
- release_id: str | None = Field(default=None, alias="releaseId")
- """Currently deployed release ID"""
+ expires_at: datetime = Field(alias="expiresAt")
- slug: str | None = None
- """Optional URL-friendly identifier, unique per organization. Auto-generated if not provided."""
+ instance_slug: str | None = Field(alias="instanceSlug")
+ """Set by the completion"""
- type: str
- """Environment class of the deployment zone. Free-form: any value is stored as sent, but
- production, staging and development are the ones the console labels, and feature-flag
- targeting rules match on this field as __kaiten.deploymentZone.type.
- """
+ license_slug: str = Field(alias="licenseSlug")
- updated_at: datetime = Field(alias="updatedAt")
- """Timestamp when the deployment zone was last updated"""
+ payment: CheckoutPayment | None
- updated_by: User = Field(alias="updatedBy")
- """User who last updated the deployment zone"""
+ price_id: str = Field(alias="priceId")
+ requires_payment_method: bool = Field(alias="requiresPaymentMethod")
-class Entitlement(KaitenModel):
- """A catalog entry: something a license can grant, and how its usage is measured."""
+ session_id: str = Field(alias="sessionId")
- aggregation_method: AggregationMethod | str | None = Field(
- default=None, alias="aggregationMethod"
- )
- """Method used to fold each reported usage value into the stored total (NUMBER only).
- Defaults to SUM.
- """
+ status: CheckoutSessionStatus | str
+ """A session still OPEN after expiresAt reads ABANDONED"""
- created_at: datetime = Field(alias="createdAt")
- """Timestamp when this entitlement was created"""
+ trial_ends_at: datetime | None = Field(alias="trialEndsAt")
- description: str | None
- """Description of the entitlement"""
+ trial_refused_reason: str | None = Field(alias="trialRefusedReason")
- display_order: int | None = Field(default=None, alias="displayOrder")
- """Sort order of this entitlement in customer-facing components (ascending). Defaults to 0."""
- entitlement_groups: list[EntitlementGroupSummary] | None = Field(
- default=None, alias="entitlementGroups"
- )
- """Groups associated with this entitlement"""
+class CheckoutSessionAdmin(KaitenModel):
+ add_ons: list[SessionAddonSelection] = Field(alias="addOns")
- group_slugs: list[str] | None = Field(default=None, alias="groupSlugs")
- """Groups associated with this entitlement, identified by slug"""
+ billing_period: str = Field(alias="billingPeriod")
- icon: str | None = None
- """Optional icon, stored as a provider-namespaced token (provider:name). Decoupled from any
- specific icon library.
- """
+ client_reference_id: str | None = Field(alias="clientReferenceId")
- id: str
- """Unique identifier for the entitlement"""
+ closed_at: datetime | None = Field(alias="closedAt")
+ """When it became COMPLETED, ABANDONED or EXPIRED; null while OPEN or AWAITING_PAYMENT"""
- name: str
- """Name of the entitlement"""
+ completed_at: datetime | None = Field(alias="completedAt")
- reset_anchor: ResetAnchor | str | None = Field(default=None, alias="resetAnchor")
- """Phase of the periodic usage window: CALENDAR (UTC calendar-aligned) or LICENSE_START
- (phased off the instance's license start date). Required exactly when resetPeriod is
- set; defaults to CALENDAR when omitted. Immutable once set.
- """
+ created_at: datetime = Field(alias="createdAt")
- reset_period: ResetPeriod | str | None = Field(default=None, alias="resetPeriod")
- """Cadence at which usage automatically resets for this NUMBER entitlement. NULL means a
- lifetime counter (default). Immutable once set -- a full-replace PUT must echo back the
- stored value.
- """
+ customer_slug: str | None = Field(alias="customerSlug")
+ """Set by the completion"""
- sale_unit_factor: float | None = Field(default=None, alias="saleUnitFactor")
- """Number of base units one sale unit represents (1 sale unit = N base units)."""
+ due_today: DueToday = Field(alias="dueToday")
- sale_unit_plural: str | None = Field(default=None, alias="saleUnitPlural")
- """Plural label of the unit this entitlement is sold in, when it differs from the base unit."""
+ email: str
+ """As the visitor gave it. Personal data"""
- sale_unit_singular: str | None = Field(default=None, alias="saleUnitSingular")
- """Singular label of the unit this entitlement is sold in, when it differs from the base
- unit. Requires the base unit labels and the full sale unit trio (saleUnitSingular,
- saleUnitPlural, saleUnitFactor).
- """
+ expires_at: datetime = Field(alias="expiresAt")
- slug: str | None = None
- """Optional URL-friendly identifier, unique per organization. Auto-generated if not provided."""
+ instance_slug: str | None = Field(alias="instanceSlug")
+ """Set by the completion"""
- type: EntitlementType | str | None = None
- """Type of the entitlement"""
+ license_slug: str = Field(alias="licenseSlug")
- unit_plural: str | None = Field(default=None, alias="unitPlural")
- """Plural label of the base unit this entitlement is measured in (NUMBER type only). Must
- be provided together with unitSingular.
- """
+ price_id: str = Field(alias="priceId")
- unit_singular: str | None = Field(default=None, alias="unitSingular")
- """Singular label of the base unit this entitlement is measured in (NUMBER type only). Must
- be provided together with unitPlural.
- """
+ publishable_key_id: str = Field(alias="publishableKeyId")
+ """The key whose page started it"""
- updated_at: datetime = Field(alias="updatedAt")
- """Timestamp when this entitlement was last updated"""
+ session_id: str = Field(alias="sessionId")
- user_facing: bool | None = Field(default=None, alias="userFacing")
- """Whether this entitlement is displayed in customer-facing components (e.g. plan and
- pricing pages). Defaults to false.
- """
+ status: CheckoutSessionStatus | str
+ """A session still OPEN after expiresAt reads ABANDONED"""
- warning_threshold_percent: int | None = Field(default=None, alias="warningThresholdPercent")
- """Percentage of the cap at which an early-warning event fires before the cap is reached. 0
- disables the warning (default).
- """
+ trial_ends_at: datetime | None = Field(alias="trialEndsAt")
+ trial_refused_reason: str | None = Field(alias="trialRefusedReason")
-class EntitlementGroup(KaitenModel):
- """A named group of entitlements."""
- description: str | None
- """Description of the entitlement group"""
+class CheckoutSessionStart(KaitenModel):
+ due_today: DueToday = Field(alias="dueToday")
- id: str
- """Unique identifier for the entitlement group"""
+ expires_at: datetime | None = Field(default=None, alias="expiresAt")
+ """open only: 24 h after the start"""
- name: str
- """Name of the entitlement group"""
+ requires_payment_method: bool = Field(alias="requiresPaymentMethod")
+ """Whether the confirm will send the visitor to a payment-method setup page"""
- slug: str | None = None
- """URL-friendly identifier, unique per organization. Auto-generated if not provided."""
+ session_id: str | None = Field(default=None, alias="sessionId")
+ """open only"""
+ status: CheckoutStartStatus | str
-class EntitlementGroupSummary(KaitenModel):
- """A reference to an entitlement group."""
+ token: str | None = None
+ """open only: the session's ksc_, returned once. Keep it in memory and sessionStorage,
+ never in a URL.
+ """
- id: str
- """Unique identifier for the entitlement group"""
+ trial_ends_at: datetime | None = Field(alias="trialEndsAt")
+ """The end of the trial the subscription would start with; null without one"""
- name: str
- """Name of the entitlement group"""
+ trial_refused_reason: str | None = Field(alias="trialRefusedReason")
+ """ALREADY_CONSUMED: a customer with this e-mail already had a trial of the family, so none
+ is given; null otherwise
+ """
- slug: str
- """URL-friendly identifier for the entitlement group"""
+class CloseBacklog(KaitenModel):
+ count: int
-class EntitlementGroupUsage(KaitenModel):
- """One entitlement's grant and usage within a group, for one instance."""
+ oldest_due_at: datetime | None = Field(alias="oldestDueAt")
- current_period_end: datetime | None = Field(default=None, alias="currentPeriodEnd")
- """End of the current usage window (exclusive). Null for a lifetime entitlement."""
- current_period_start: datetime | None = Field(default=None, alias="currentPeriodStart")
- """Start of the current usage window (inclusive). Null for a lifetime entitlement."""
+class ClosePeriodsReport(KaitenModel):
+ closed: int
+ """Periods closed into an invoice"""
- entitlement_id: str = Field(alias="entitlementId")
- """Entitlement ID"""
+ examined: int
+ """Subscriptions the batch looked at"""
- entitlement_name: str = Field(alias="entitlementName")
- """Entitlement name"""
+ has_more: bool = Field(alias="hasMore")
+ """Set when due subscriptions are left beyond the batch"""
- entitlement_slug: str = Field(alias="entitlementSlug")
- """Entitlement slug"""
+ held: int
+ """Of those, invoices held as DRAFTs for their usage journal"""
- entitlement_type: str = Field(alias="entitlementType")
- """Entitlement type"""
+ invoices: list[ClosedInvoice]
- license_value: EntitlementValue | None = Field(default=None, alias="licenseValue")
- """License limit value, discriminated by the 'type' field. Null when the license grants no
- value.
+ skipped: int
+ """Subscriptions left for later: held by another transaction, their meters' clock not at
+ the boundary, or failed (logged)
"""
- usage_value: EntitlementValue | None = Field(default=None, alias="usageValue")
- """Current usage value, discriminated by the 'type' field. Null when nothing has been
- reported yet.
- """
+class ClosedInvoice(KaitenModel):
+ boundary_at: datetime = Field(alias="boundaryAt")
-class EntitlementUsage(KaitenModel):
- """An instance's consumption of one entitlement, next to the grant it is measured against."""
+ held: bool
+ """Set when the invoice is a held DRAFT"""
- current_period_end: datetime | None = Field(default=None, alias="currentPeriodEnd")
- """End of the current usage window (exclusive). Null for a lifetime entitlement (no
- configured reset period).
- """
+ id: str
- current_period_start: datetime | None = Field(default=None, alias="currentPeriodStart")
- """Start of the current usage window (inclusive). Null for a lifetime entitlement (no
- configured reset period).
- """
+ instance_slug: str = Field(alias="instanceSlug")
- entitlement_id: str = Field(alias="entitlementId")
- """Unique identifier for the entitlement"""
+ kind: InvoiceKind | str
- entitlement_slug: str = Field(alias="entitlementSlug")
- """URL-friendly identifier of the entitlement"""
+ status: InvoiceStatus | str
- license_id: str = Field(alias="licenseId")
- """Unique identifier for the license to which this entitlement usage belongs"""
- license_slug: str = Field(alias="licenseSlug")
- """URL-friendly identifier of the license"""
+class CompatibleLicenseFamilies(KaitenModel):
+ family_slugs: list[str] = Field(alias="familySlugs")
- limit: EntitlementValue | None = None
- """License grant this usage is measured against, discriminated by the 'type' field. Null
- when the license grants no value; -1 means unlimited for a NUMBER entitlement.
- """
- value: EntitlementValue
- """Resolved entitlement value, discriminated by the 'type' field"""
+class CompletedPaymentMethodSession(KaitenModel):
+ payment_method: PaymentMethodLabels = Field(alias="paymentMethod")
-class ErrorDetail(KaitenModel):
- """One validation problem inside an API error."""
+class Component(KaitenModel):
+ """A versioned building block of your product, that releases are made of."""
- location: str | None = None
- """Where the error occurred, e.g. 'body.items[3].tags' or 'path.thing-id'"""
+ created_at: datetime = Field(alias="createdAt")
+ """Timestamp when the component was created"""
- message: str | None = None
- """Error message text"""
+ created_by: User = Field(alias="createdBy")
+ """User who created this component"""
- value: Any | None = None
- """The value at the given location"""
+ description: str | None = None
+ """Description of the component"""
+ id: str
+ """Unique identifier for the component"""
-class FeatureFlag(KaitenModel):
- """A feature flag: its variants, targeting rules and default variant."""
+ name: str
+ """Name of the component"""
- default_variant: DefaultVariant
+ previous_component_id: str | None = Field(default=None, alias="previousComponentId")
+ """Reference to the previous version of this component"""
- description: str | None
+ slug: str | None = None
+ """Optional URL-friendly identifier, unique per organization. Auto-generated if not provided."""
- enabled: bool
+ version: str
+ """Version of the component"""
- event_name: str
- id: str
+class ConfigEntitlementValue(KaitenModel):
+ """A configuration entitlement value: a free-form object."""
- metadata: dict[str, Any]
+ type: Literal["object"]
+ """Value type discriminator"""
- name: str
+ value: dict[str, Any]
+ """Object configuration value"""
- slug: str | None = None
- targetings: Targetings
+class ConnectionDelivered(KaitenModel):
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
- type: FeatureFlagType | str
+ connector_name: str = Field(alias="connectorName")
+ """The connector that configures it"""
- variants: list[Variant] | None
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
+ livemode: bool
+ """Whether the connection reaches the provider's live account rather than a test one"""
-class FeatureFlagEvaluated(KaitenModel):
- """A feature flag evaluation, as reported to webhooks."""
+ provider_kind: Literal["STRIPE"] = Field(alias="providerKind")
+ """The payment provider"""
- error_code: str | None = None
- """Set when the evaluation failed and the default was returned"""
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
+ """
- error_message: str | None = None
- """Human-readable detail accompanying error_code"""
- flag_id: str
- """Unique identifier of the evaluated flag"""
+class Connector(KaitenModel):
+ """A connector registered with this Kaiten deployment."""
- flag_slug: str
- """URL-friendly identifier of the evaluated flag"""
-
- matched_rule_name: str | None = None
- """Name of the targeting rule that matched, when the reason is TARGETING_MATCH"""
-
- reason: str
- """Why the value resolved as it did"""
+ created_at: datetime
+ """Timestamp when connector was first registered"""
- variant: str | None = None
- """Name of the resolved variant, when the flag has one"""
+ entitlement_slug: str | None = None
+ """Slug of the BOOLEAN entitlement a license must grant for an organization to activate
+ this connector. Absent means the connector is ungated.
+ """
+ name: str
+ """Stable connector identifier"""
-class Instance(KaitenModel):
- """A deployment of your product for one customer, under one license."""
+ settings_schema: dict[str, Any]
+ """JSON schema used to validate connector settings payloads"""
- created_at: datetime = Field(alias="createdAt")
- """Timestamp when the instance was created"""
+ updated_at: datetime
+ """Timestamp when connector registration was last updated"""
- created_by: User = Field(alias="createdBy")
- """User who created this instance"""
+ version: str
+ """Registered connector version"""
- customer_id: str = Field(alias="customerId")
- """ID of the customer associated with this instance"""
- customer_slug: str = Field(alias="customerSlug")
- """Slug of the customer associated with this instance"""
+class ConnectorActivation(KaitenModel):
+ """An organization's activation of a connector."""
- deployment_zone_id: str | None = Field(default=None, alias="deploymentZoneId")
- """ID of the deployment zone associated with this instance (optional)"""
+ activated_at: datetime = Field(alias="activatedAt")
+ """When this organization first activated the connector"""
- deployment_zone_slug: str | None = Field(default=None, alias="deploymentZoneSlug")
- """Slug of the deployment zone associated with this instance (optional)"""
+ connector_name: str = Field(alias="connectorName")
+ """Stable connector identifier"""
- description: str
- """Brief description of the instance"""
- end_license_date: datetime = Field(alias="endLicenseDate")
- """End date of the license for this instance"""
+class ConnectorSettings(KaitenModel):
+ """An organization's settings for a connector."""
- id: str
- """Unique identifier for the instance"""
+ configured: bool
+ """Whether settings were ever stored for this connector. A connector never configured reads
+ false, with empty settings, rather than 404.
+ """
- integrations: dict[str, InstanceIntegration] | None = None
- """Integrations grouped by adapter name"""
+ connector_name: str
+ """Stable connector identifier"""
- license_id: str = Field(alias="licenseId")
- """ID of the license version this instance is pinned to. An ARCHIVED version cannot be
- assigned, on create or by changing the license (CreateInstance.LicenseArchived,
- UpdateInstance.LicenseArchived); an instance already on a version that is archived later
- keeps it. A DRAFT version can be assigned, to try it before publishing.
- """
+ settings: dict[str, Any]
+ """Connector settings payload"""
- license_slug: str = Field(alias="licenseSlug")
- """Slug of the license associated with this instance"""
- lifecycle_stage: str | None = Field(default=None, alias="lifecycleStage")
- """Commercial lifecycle stage of the instance (free-form; suggested: TRIAL, ACTIVE,
- AT_RISK, CHURNED)
- """
+class ConnectorSettingsSchema(KaitenModel):
+ """The JSON schema a connector's settings are validated against."""
- metadata: dict[str, Any] | None = None
- """Metadata for the instance"""
+ connector_name: str
+ """Stable connector identifier"""
- name: str
- """Name of the instance"""
+ json_schema: dict[str, Any] = Field(alias="schema")
+ """JSON schema describing supported connector settings payload"""
- slug: str | None = None
- """Optional URL-friendly identifier, unique per organization. Auto-generated if not provided."""
+ version: str
+ """Registered connector version"""
- start_license_date: datetime = Field(alias="startLicenseDate")
- """Start date of the license for this instance"""
- status: InstanceStatus | str | None = None
- """Operational status of the instance"""
+class ConnectorState(KaitenModel):
+ """Whether a connector is available, licensed and activated for this organization."""
- updated_at: datetime = Field(alias="updatedAt")
- """Timestamp when the instance was last updated"""
+ activated: bool
+ """Whether this organization has turned the connector on"""
- updated_by: User = Field(alias="updatedBy")
- """User who last updated this instance"""
+ activated_at: datetime | None = Field(default=None, alias="activatedAt")
+ """When this organization activated the connector. Absent when it has not."""
+ available: bool
+ """Whether this installation has the connector registered"""
-class InstanceEntitlementCapExceeded(KaitenModel):
- """Usage crossed an entitlement's included value, within its allowance."""
+ connector_name: str = Field(alias="connectorName")
+ """Stable connector identifier"""
- entitlement_slug: str
- """URL-friendly identifier of the entitlement"""
+ entitled: bool
+ """Whether this organization's license includes the connector. True for an ungated connector."""
- overage: float
- """Amount by which usage exceeds the cap"""
+ version: str | None = None
+ """Registered connector version. Absent when the connector is not available."""
- threshold: float
- """The entitlement cap"""
- value: float
- """Current usage value, including overage"""
+class CreatedCustomerSession(KaitenModel):
+ customer_slug: str = Field(alias="customerSlug")
+ expires_at: datetime = Field(alias="expiresAt")
-class InstanceEntitlementUsagePeriodRolledOver(KaitenModel):
- """A periodic usage window closed and a new one opened."""
+ id: str
+ """Revoke it with POST /customer-sessions/{id}/revoke"""
- closed_period_end: datetime | None
+ instance_slug: str | None = Field(default=None, alias="instanceSlug")
- closed_period_start: datetime | None
+ token: str
+ """The kst_ token: hand it to the customer's browser, which sends it as Authorization:
+ Bearer on /api/public/session routes. Shown once; only its digest is stored.
+ """
- entitlement_id: str
- entitlement_slug: str
+class Customer(KaitenModel):
+ """A customer of your organization: the end client instances are deployed for."""
- event_count: int
+ billing_email: str | None = Field(default=None, alias="billingEmail")
+ """Where invoices sent to this customer are delivered, at most 254 characters
+ (CreateCustomer.InvalidBillingEmail). Absent when there is none. On update, omit it or
+ send null to keep the stored value, or send an empty string to remove it. Personal data:
+ never included in event payloads.
+ """
- instance_id: str
+ created_at: datetime = Field(alias="createdAt")
+ """Timestamp when the customer was created"""
- is_synthetic: bool
+ created_by: User = Field(alias="createdBy")
+ """User who created this customer"""
- new_period_start: datetime
+ domain: str | None = None
+ """Optional customer domain name"""
- value: float
+ external_customer_id: str | None = Field(default=None, alias="externalCustomerId")
+ """Optional identifier for the customer in an external system"""
+ id: str
+ """Unique identifier for the customer"""
-class InstanceEntitlementUsageReportAccepted(KaitenModel):
- """The outcome of a usage report."""
+ integrations: dict[str, CustomerIntegration] | None = None
+ """Integrations grouped by adapter name"""
- behavior: UsageBehavior | str
- """Report behavior: append folds the value into the stored total, set overwrites it"""
+ name: str
+ """Name of the customer"""
- entitlement_slug: str
- """URL-friendly identifier of the entitlement the usage was reported against"""
+ slug: str | None = None
+ """Optional URL-friendly identifier, unique per organization. Auto-generated if not provided."""
- event_count: int
- """Number of reports folded into the current usage window, including this one"""
+ updated_at: datetime = Field(alias="updatedAt")
+ """Timestamp when the customer was last updated"""
- status: UsageReportStatus | str
- """Whether the report was persisted"""
+ updated_by: User = Field(alias="updatedBy")
+ """User who last updated this customer"""
- value: float
- """Value carried by the report"""
+class CustomerBilling(KaitenModel):
+ billing_email: str | None = Field(alias="billingEmail")
+ """Where the customer's invoices are sent. Personal data"""
-class InstanceEntitlementUsageWarningThresholdReached(KaitenModel):
- """Usage crossed an entitlement's warning threshold."""
+ providers: list[CustomerInProvider]
+ """The customer in each payment provider; none for NOOP"""
- boundary: float
- """Usage value at which the warning fires (cap * warningThresholdPercent / 100)"""
- entitlement_slug: str
- """URL-friendly identifier of the entitlement"""
+class CustomerCreationRejected(KaitenModel):
+ """A customer creation refused because the organization reached its limit."""
- threshold: float
- """The entitlement cap"""
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
- value: float
- """Current usage value"""
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
+ name: str
-class InstanceEntitlementValueGet(KaitenModel):
- """An entitlement value read for an instance."""
+ reason: str
- entitlement_id: str
- """Unique identifier for the entitlement"""
+ slug: str
- entitlement_slug: str
- """URL-friendly identifier of the entitlement"""
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
+ """
- instance_id: str
- """Unique identifier for the instance the value was read for"""
- license_id: str
- """Unique identifier for the license carrying the entitlement"""
+class CustomerInProvider(KaitenModel):
+ external_customer_id: str = Field(alias="externalCustomerId")
+ """The customer's id in the provider"""
- organization_id: str
- """Unique identifier for the owning organization"""
+ payment_method: PaymentMethodLabels | None = Field(alias="paymentMethod")
+ """Its default payment method; null when there is none"""
- timestamp: datetime
- """When the value was read"""
+ provider_kind: Literal["STRIPE"] = Field(alias="providerKind")
- type: str
- """Discriminator of the resolved entitlement value"""
+ synced_at: datetime | None = Field(alias="syncedAt")
- value: EntitlementValue
- """Resolved entitlement value at read time"""
+ web_url: str | None = Field(alias="webUrl")
+ """The customer's page in the provider's dashboard"""
-class InstanceIntegration(KaitenModel):
- """An instance's link to a record in a third-party system."""
+class CustomerIntegration(KaitenModel):
+ """A customer's link to a record in a third-party system."""
external_id: str
"""External identifier in the third-party adapter"""
@@ -932,658 +1423,4798 @@ class InstanceIntegration(KaitenModel):
"""Absolute http(s) link to the record in the third-party system"""
-class InstanceIntegrationResource(KaitenModel):
- """An instance as seen through one of its integrations."""
-
- customer_external_id: str | None = Field(default=None, alias="customerExternalId")
- """External identifier of the related customer in the same adapter"""
-
- deployment_zone_id: str | None = Field(default=None, alias="deploymentZoneId")
- """Deployment zone ID"""
-
- description: str
- """Instance description"""
+class CustomerIntegrationResource(KaitenModel):
+ """A customer as seen through one of its integrations."""
- end_license_date: datetime = Field(alias="endLicenseDate")
- """License end date"""
+ domain: str | None = None
+ """Optional customer domain name"""
error: str | None = None
"""Last synchronization error for this integration, if any"""
integration_metadata: dict[str, Any] = Field(alias="integrationMetadata")
- """Adapter-specific instance integration metadata"""
-
- license_id: str = Field(alias="licenseId")
- """License ID"""
+ """Adapter-specific customer integration metadata"""
name: str
- """Instance name"""
+ """Customer name"""
slug: str
- """Instance slug"""
-
- start_license_date: datetime = Field(alias="startLicenseDate")
- """License start date"""
+ """Customer slug"""
web_url: str | None = Field(default=None, alias="webUrl")
"""Absolute http(s) link to the record in the third-party system"""
-class InstanceLifecycleStageChanged(KaitenModel):
- """An instance's commercial lifecycle stage changed."""
+class DeletedAddonDelivered(KaitenModel):
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
- changed_by: str
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
- instance_id: str
+ family_slug: str = Field(alias="familySlug")
- new_lifecycle_stage: str | None
+ id: str
- previous_lifecycle_stage: str | None
+ slug: str
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
+ """
-class InstanceStatusChanged(KaitenModel):
- """An instance's operational status changed."""
- changed_by: str
+class Deployment(KaitenModel):
+ """A release deployed to a deployment zone: an append-only history record."""
- instance_id: str
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
- new_status: InstanceStatus | str
+ created_at: datetime = Field(alias="createdAt")
+ """Timestamp when the deployment was recorded"""
- previous_status: InstanceStatus | str
+ created_by: User = Field(alias="createdBy")
+ """User who recorded this deployment"""
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
-class License(KaitenModel):
- """A version of a license family: one set of entitlement grants, with its lifecycle state."""
+ deployment_zone_id: str = Field(alias="deploymentZoneId")
+ """Deployment zone the release was deployed to"""
- created_at: datetime = Field(alias="createdAt")
- """Timestamp when this license was created"""
+ id: str
+ """Unique identifier for this deployment event"""
+
+ release_id: str = Field(alias="releaseId")
+ """Release that was deployed"""
+
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
+ """
+
+
+class DeploymentZone(KaitenModel):
+ """An environment instances run in, and the release currently deployed to it."""
+
+ created_at: datetime = Field(alias="createdAt")
+ """Timestamp when the deployment zone was created"""
+
+ created_by: User = Field(alias="createdBy")
+ """User who created this deployment zone"""
+
+ description: str
+ """Description of the deployment zone"""
+
+ id: str
+ """Unique identifier for the deployment zone"""
+
+ metadata: dict[str, Any] | None = None
+ """Typed metadata for the deployment zone, validated against the org-scoped MetadataField
+ schema
+ """
+
+ name: str
+ """Name of the deployment zone"""
+
+ release_id: str | None = Field(default=None, alias="releaseId")
+ """Currently deployed release ID"""
+
+ slug: str | None = None
+ """Optional URL-friendly identifier, unique per organization. Auto-generated if not provided."""
+
+ type: str
+ """Environment class of the deployment zone. Free-form: any value is stored as sent, but
+ production, staging and development are the ones the console labels, and feature-flag
+ targeting rules match on this field as __kaiten.deploymentZone.type.
+ """
+
+ updated_at: datetime = Field(alias="updatedAt")
+ """Timestamp when the deployment zone was last updated"""
+
+ updated_by: User = Field(alias="updatedBy")
+ """User who last updated the deployment zone"""
+
+
+class DiscountAllocation(KaitenModel):
+ amount: int
+ """In minor units, positive"""
+
+ target_seq: int = Field(alias="targetSeq")
+ """The target line"""
+
+
+class DiscountDifference(KaitenModel):
+ coupon_id: str = Field(alias="couponId")
+ """The provider's discount, empty when none was recorded"""
+
+ kaiten_amount: int = Field(alias="kaitenAmount")
+
+ line_id: str = Field(alias="lineId")
+ """The DISCOUNT line"""
+
+ provider_amount: int = Field(alias="providerAmount")
+ """0 when the provider did not apply it"""
+
+ seq: int
+
+ target_seq: int = Field(alias="targetSeq")
+
+
+class DueDateDifference(KaitenModel):
+ kaiten_due_at: datetime = Field(alias="kaitenDueAt")
+
+ provider_due_at: datetime = Field(alias="providerDueAt")
+
+
+class DueToday(KaitenModel):
+ currency: str
+
+ discount_total: int = Field(alias="discountTotal")
+ """Minor units"""
+
+ lines: list[SessionLine]
+
+ subtotal: int
+ """Minor units"""
+
+ total: int
+ """Minor units"""
+
+
+class Entitlement(KaitenModel):
+ """A catalog entry: something a license can grant, and how its usage is measured."""
+
+ aggregation_method: AggregationMethod | str | None = Field(
+ default=None, alias="aggregationMethod"
+ )
+ """Method used to fold each reported usage value into the stored total (NUMBER only).
+ Defaults to SUM.
+ """
+
+ created_at: datetime = Field(alias="createdAt")
+ """Timestamp when this entitlement was created"""
+
+ description: str | None
+ """Description of the entitlement"""
+
+ display_order: int | None = Field(default=None, alias="displayOrder")
+ """Sort order of this entitlement in customer-facing components (ascending). Defaults to 0."""
+
+ entitlement_groups: list[EntitlementGroupSummary] | None = Field(
+ default=None, alias="entitlementGroups"
+ )
+ """Groups associated with this entitlement"""
+
+ group_slugs: list[str] | None = Field(default=None, alias="groupSlugs")
+ """Groups associated with this entitlement, identified by slug"""
+
+ icon: str | None = None
+ """Optional icon, stored as a provider-namespaced token (provider:name). Decoupled from any
+ specific icon library.
+ """
+
+ id: str
+ """Unique identifier for the entitlement"""
+
+ name: str
+ """Name of the entitlement"""
+
+ reset_anchor: ResetAnchor | str | None = Field(default=None, alias="resetAnchor")
+ """Phase of the periodic usage window: CALENDAR (UTC calendar-aligned), LICENSE_START
+ (phased off the instance's license start date) or BILLING_PERIOD (phased off the
+ instance's live subscription's billing anchor, LICENSE_START without one; with
+ resetPeriod MONTH or YEAR only). Required exactly when resetPeriod is set; defaults to
+ CALENDAR when omitted. Immutable once set.
+ """
+
+ reset_period: ResetPeriod | str | None = Field(default=None, alias="resetPeriod")
+ """Cadence at which usage automatically resets for this NUMBER entitlement. NULL means a
+ lifetime counter (default). Immutable once set -- a full-replace PUT must echo back the
+ stored value.
+ """
+
+ sale_unit_factor: float | None = Field(default=None, alias="saleUnitFactor")
+ """Number of base units one sale unit represents (1 sale unit = N base units)."""
+
+ sale_unit_plural: str | None = Field(default=None, alias="saleUnitPlural")
+ """Plural label of the unit this entitlement is sold in, when it differs from the base unit."""
+
+ sale_unit_singular: str | None = Field(default=None, alias="saleUnitSingular")
+ """Singular label of the unit this entitlement is sold in, when it differs from the base
+ unit. Requires the base unit labels and the full sale unit trio (saleUnitSingular,
+ saleUnitPlural, saleUnitFactor).
+ """
+
+ slug: str | None = None
+ """Optional URL-friendly identifier, unique per organization. Auto-generated if not provided."""
+
+ type: EntitlementType | str | None = None
+ """Type of the entitlement"""
+
+ unit_plural: str | None = Field(default=None, alias="unitPlural")
+ """Plural label of the base unit this entitlement is measured in (NUMBER type only). Must
+ be provided together with unitSingular.
+ """
+
+ unit_singular: str | None = Field(default=None, alias="unitSingular")
+ """Singular label of the base unit this entitlement is measured in (NUMBER type only). Must
+ be provided together with unitPlural.
+ """
+
+ updated_at: datetime = Field(alias="updatedAt")
+ """Timestamp when this entitlement was last updated"""
+
+ user_facing: bool | None = Field(default=None, alias="userFacing")
+ """Whether this entitlement is displayed in customer-facing components (e.g. plan and
+ pricing pages). Defaults to false.
+ """
+
+ warning_threshold_percent: int | None = Field(default=None, alias="warningThresholdPercent")
+ """Percentage of the cap at which an early-warning event fires before the cap is reached. 0
+ disables the warning (default).
+ """
+
+
+class EntitlementException(KaitenModel):
+ created_at: datetime = Field(alias="createdAt")
+
+ ends_at: datetime | None = Field(alias="endsAt")
+ """Null: no end"""
+
+ entitlement_slug: str = Field(alias="entitlementSlug")
+
+ entitlement_type: EntitlementType | str = Field(alias="entitlementType")
+
+ id: str
+
+ instance_slug: str = Field(alias="instanceSlug")
+
+ kind: ExceptionKind | str
+
+ reason: str
+
+ revoked_at: datetime | None = Field(alias="revokedAt")
+
+ starts_at: datetime = Field(alias="startsAt")
+
+ status: ExceptionStatus | str
+ """At the read: SCHEDULED before startsAt, ACTIVE in its window, EXPIRED after endsAt,
+ REVOKED once revoked
+ """
+
+ value: float | bool | dict[str, Any] | None
+ """A number for NUMBER and NUMBER_AI_CREDIT, a boolean for BOOLEAN, the CONFIG document as
+ an object; null for UNLIMITED
+ """
+
+
+class EntitlementGroup(KaitenModel):
+ """A named group of entitlements."""
+
+ description: str | None
+ """Description of the entitlement group"""
+
+ id: str
+ """Unique identifier for the entitlement group"""
+
+ name: str
+ """Name of the entitlement group"""
+
+ slug: str | None = None
+ """URL-friendly identifier, unique per organization. Auto-generated if not provided."""
+
+
+class EntitlementGroupSummary(KaitenModel):
+ """A reference to an entitlement group."""
+
+ id: str
+ """Unique identifier for the entitlement group"""
+
+ name: str
+ """Name of the entitlement group"""
+
+ slug: str
+ """URL-friendly identifier for the entitlement group"""
+
+
+class EntitlementGroupUsage(KaitenModel):
+ """One entitlement's grant and usage within a group, for one instance."""
+
+ current_period_end: datetime | None = Field(default=None, alias="currentPeriodEnd")
+ """End of the current usage window (exclusive). Null for a lifetime entitlement."""
+
+ current_period_start: datetime | None = Field(default=None, alias="currentPeriodStart")
+ """Start of the current usage window (inclusive). Null for a lifetime entitlement."""
+
+ entitlement_id: str = Field(alias="entitlementId")
+ """Entitlement ID"""
+
+ entitlement_name: str = Field(alias="entitlementName")
+ """Entitlement name"""
+
+ entitlement_slug: str = Field(alias="entitlementSlug")
+ """Entitlement slug"""
+
+ entitlement_type: str = Field(alias="entitlementType")
+ """Entitlement type"""
+
+ license_value: EntitlementValue | None = Field(default=None, alias="licenseValue")
+ """License limit value, discriminated by the 'type' field. Null when the license grants no
+ value.
+ """
+
+ usage_value: EntitlementValue | None = Field(default=None, alias="usageValue")
+ """Current usage value, discriminated by the 'type' field. Null when nothing has been
+ reported yet.
+ """
+
+
+class EntitlementUsage(KaitenModel):
+ """An instance's consumption of one entitlement, next to the grant it is measured against."""
+
+ current_period_end: datetime | None = Field(default=None, alias="currentPeriodEnd")
+ """End of the current usage window (exclusive). Null for a lifetime entitlement (no
+ configured reset period).
+ """
+
+ current_period_start: datetime | None = Field(default=None, alias="currentPeriodStart")
+ """Start of the current usage window (inclusive). Null for a lifetime entitlement (no
+ configured reset period).
+ """
+
+ entitlement_id: str = Field(alias="entitlementId")
+ """Unique identifier for the entitlement"""
+
+ entitlement_slug: str = Field(alias="entitlementSlug")
+ """URL-friendly identifier of the entitlement"""
+
+ license_id: str = Field(alias="licenseId")
+ """Unique identifier for the license to which this entitlement usage belongs"""
+
+ license_slug: str = Field(alias="licenseSlug")
+ """URL-friendly identifier of the license"""
+
+ limit: EntitlementValue | None = None
+ """The effective value this usage is measured against (the licence's grant, with the
+ instance's add-ons and boosts applied; source and provenance say which), discriminated
+ by the 'type' field. Absent when nothing grants the entitlement a value; -1 means
+ unlimited for a NUMBER entitlement.
+ """
+
+ limit_cap_exceeded_overage_percent: int | None = Field(
+ default=None, alias="limitCapExceededOveragePercent"
+ )
+ """How far above limit usage is still accepted, in percent: -1 unlimited, 0 a hard limit.
+ Absent for BOOLEAN and CONFIG.
+ """
+
+ provenance: Provenance | None = None
+ """What the licence, the add-ons and the boosts each contribute"""
+
+ source: EntitlementSource | str
+ """license: the licence grants it (add-ons, boosts and an exception may change it); addon:
+ an add-on attached does; exception: only the instance's exception does
+ """
+
+ value: EntitlementValue
+ """Resolved entitlement value, discriminated by the 'type' field"""
+
+
+class ErrorDetail(KaitenModel):
+ """One validation problem inside an API error."""
+
+ location: str | None = None
+ """Where the error occurred, e.g. 'body.items[3].tags' or 'path.thing-id'"""
+
+ message: str | None = None
+ """Error message text"""
+
+ value: Any | None = None
+ """The value at the given location"""
+
+
+class EventHeaders(KaitenModel):
+ baggage: str | None = None
+ """W3C baggage of that request, when it carried any"""
+
+ instance_id: str | None = None
+ """The instance the event concerns, set on the events of one instance. The usage events'
+ payloads do not name it.
+ """
+
+ traceparent: str | None = None
+ """W3C trace context of the request that produced the event"""
+
+ tracestate: str | None = None
+ """W3C trace state of that request, when it carried one"""
+
+
+class ExceptionEventDelivered(KaitenModel):
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
+
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
+
+ ends_at: datetime | None = Field(alias="endsAt")
+ """Null: no end"""
+
+ entitlement_slug: str = Field(alias="entitlementSlug")
+
+ id: str
+
+ instance_slug: str = Field(alias="instanceSlug")
+
+ kind: ExceptionKind | str
+
+ reason: str
+
+ revoked_at: datetime | None = Field(alias="revokedAt")
+
+ starts_at: datetime = Field(alias="startsAt")
+
+ value: float | bool | dict[str, Any] | None
+ """A number for NUMBER and NUMBER_AI_CREDIT, a boolean for BOOLEAN, the CONFIG document as
+ an object; null for UNLIMITED
+ """
+
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
+ """
+
+
+class ExhaustedEventDelivered(KaitenModel):
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
+
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
+
+ id: str
+
+ max_redemptions: int | None = Field(default=None, alias="maxRedemptions")
+
+ name: str
+
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
+ """
+
+
+class ExpiredEventDelivered(KaitenModel):
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
+
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
+
+ expires_at: datetime = Field(alias="expiresAt")
+
+ id: str
+
+ name: str
+
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
+ """
+
+
+class ExtraDiscount(KaitenModel):
+ amount: int
+
+ discount_id: str = Field(alias="discountId")
+
+ external_line_id: str = Field(alias="externalLineId")
+
+
+class FeatureFlag(KaitenModel):
+ """A feature flag: its variants, targeting rules and default variant."""
+
+ default_variant: DefaultVariant
+
+ description: str | None
+
+ enabled: bool
+
+ event_name: str
+
+ id: str
+
+ metadata: dict[str, Any]
+
+ name: str
+
+ slug: str | None = None
+
+ targetings: Targetings
+
+ type: FeatureFlagType | str
+
+ variants: list[Variant] | None
+
+
+class FeatureFlagEvaluated(KaitenModel):
+ """A feature flag evaluation, as reported to webhooks."""
+
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
+
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
+
+ error_code: str | None = None
+ """Set when the evaluation failed and the default was returned"""
+
+ error_message: str | None = None
+ """Human-readable detail accompanying error_code"""
+
+ flag_id: str
+ """Unique identifier of the evaluated flag"""
+
+ flag_slug: str
+ """URL-friendly identifier of the evaluated flag"""
+
+ matched_rule_name: str | None = None
+ """Name of the targeting rule that matched, when the reason is TARGETING_MATCH"""
+
+ reason: str
+ """Why the value resolved as it did"""
+
+ variant: str | None = None
+ """Name of the resolved variant, when the flag has one"""
+
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
+ """
+
+
+class FlagGrant(KaitenModel):
+ flag_slug: str = Field(alias="flagSlug")
+
+ variant: str
+ """The name of one of the flag's variants"""
+
+
+class Grant(KaitenModel):
+ entitlement_slug: str = Field(alias="entitlementSlug")
+
+ modifier_type: GrantModifier | str = Field(alias="modifierType")
+ """SET replaces the value, ADD adds to it, MULTIPLY multiplies it, UNLIMITED lifts the
+ limit. A BOOLEAN or CONFIG entitlement takes SET only
+ """
+
+ modifier_value: str | None = Field(default=None, alias="modifierValue")
+ """A NUMBER grant's decimal: >= 0 for SET, > 0 for ADD and MULTIPLY, null for UNLIMITED;
+ null on a BOOLEAN or CONFIG grant. Optional on write.
+ """
+
+ value: bool | dict[str, Any] | None = None
+ """What a BOOLEAN or CONFIG SET grant sets: true or false, or the CONFIG document as an
+ object; null on a NUMBER grant. Optional on write.
+ """
+
+
+class HandoffAcknowledgementDelivered(KaitenModel):
+ acknowledged_by: str = Field(alias="acknowledgedBy")
+
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
+
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
+
+ external_reference: str | None = Field(default=None, alias="externalReference")
+ """The invoice's number in the organization's accounting system"""
+
+ invoice_id: str = Field(alias="invoiceId")
+
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
+ """
+
+
+class HandoffBacklog(KaitenModel):
+ oldest_pending_issued_at: datetime | None = Field(alias="oldestPendingIssuedAt")
+
+ pending: int
+
+ reclaimed_last24h: int = Field(alias="reclaimedLast24h")
+ """Invoices claimed again after a lease expired, in the last 24 hours: a consumer that
+ claims and does not acknowledge
+ """
+
+
+class HandoffClaim(KaitenModel):
+ invoices: list[Invoice]
+ """The leased invoices, oldest issue first, with their billing e-mail: what the accounting
+ system needs to book them
+ """
+
+ lease_id: str = Field(alias="leaseId")
+ """Pass it back when acknowledging an invoice of this claim"""
+
+ leased_until: datetime = Field(alias="leasedUntil")
+ """When the lease expires and the invoices are claimable again"""
+
+
+class HeldInvoices(KaitenModel):
+ by_reason: dict[str, int] = Field(alias="byReason")
+
+ count: int
+
+
+class HeldPair(KaitenModel):
+ counter_report_seq: int | None = Field(alias="counterReportSeq")
+
+ detail: str
+
+ entitlement_id: str = Field(alias="entitlementId")
+
+ expected: str | None
+
+ first_seq: int | None = Field(alias="firstSeq")
+
+ found: str | None
+
+ instance_id: str = Field(alias="instanceId")
+
+ invariant: str
+
+ last_seq: int | None = Field(alias="lastSeq")
+
+ report_seq: int | None = Field(alias="reportSeq")
+ """The report the failure is at, when one is"""
+
+
+class HoldDetail(KaitenModel):
+ item_limit: ProviderLineLimit | None = Field(default=None, alias="itemLimit")
+ """PROVIDER_ITEM_LIMIT only: the provider's line limit and how many items the invoice needs"""
+
+ pairs: list[HeldPair]
+
+
+class HoldRecord(KaitenModel):
+ held_at: datetime | None = Field(alias="heldAt")
+
+ release_reason: str | None = Field(alias="releaseReason")
+
+ released_at: datetime | None = Field(alias="releasedAt")
+
+ released_by: str | None = Field(alias="releasedBy")
+ """The user who released it; null when it was released by a later check"""
+
+
+class Instance(KaitenModel):
+ """A deployment of your product for one customer, under one license."""
+
+ created_at: datetime = Field(alias="createdAt")
+ """Timestamp when the instance was created"""
+
+ created_by: User = Field(alias="createdBy")
+ """User who created this instance"""
+
+ customer_id: str = Field(alias="customerId")
+ """ID of the customer associated with this instance"""
+
+ customer_slug: str = Field(alias="customerSlug")
+ """Slug of the customer associated with this instance"""
+
+ deployment_zone_id: str | None = Field(default=None, alias="deploymentZoneId")
+ """ID of the deployment zone associated with this instance (optional)"""
+
+ deployment_zone_slug: str | None = Field(default=None, alias="deploymentZoneSlug")
+ """Slug of the deployment zone associated with this instance (optional)"""
+
+ description: str
+ """Brief description of the instance"""
+
+ end_license_date: datetime = Field(alias="endLicenseDate")
+ """End date of the license for this instance"""
+
+ id: str
+ """Unique identifier for the instance"""
+
+ integrations: dict[str, InstanceIntegration] | None = None
+ """Integrations grouped by adapter name"""
+
+ license_id: str = Field(alias="licenseId")
+ """ID of the license version this instance is pinned to. An ARCHIVED version cannot be
+ assigned, on create or by changing the license (CreateInstance.LicenseArchived,
+ UpdateInstance.LicenseArchived); an instance already on a version that is archived later
+ keeps it. A DRAFT version can be assigned, to try it before publishing.
+ """
+
+ license_slug: str = Field(alias="licenseSlug")
+ """Slug of the license associated with this instance"""
+
+ lifecycle_stage: str | None = Field(default=None, alias="lifecycleStage")
+ """Commercial lifecycle stage of the instance (free-form; suggested: TRIAL, ACTIVE,
+ AT_RISK, CHURNED)
+ """
+
+ metadata: dict[str, Any] | None = None
+ """Metadata for the instance"""
+
+ name: str
+ """Name of the instance"""
+
+ slug: str | None = None
+ """Optional URL-friendly identifier, unique per organization. Auto-generated if not provided."""
+
+ start_license_date: datetime = Field(alias="startLicenseDate")
+ """Start date of the license for this instance"""
+
+ status: InstanceStatus | str | None = None
+ """Operational status of the instance"""
+
+ updated_at: datetime = Field(alias="updatedAt")
+ """Timestamp when the instance was last updated"""
+
+ updated_by: User = Field(alias="updatedBy")
+ """User who last updated this instance"""
+
+
+class InstanceAddon(KaitenModel):
+ addon_id: str = Field(alias="addonId")
+
+ addon_slug: str = Field(alias="addonSlug")
+
+ attached_at: datetime = Field(alias="attachedAt")
+
+ family_slug: str = Field(alias="familySlug")
+
+ id: str
+
+ max_quantity: int | None = Field(alias="maxQuantity")
+
+ name: str
+ """The add-on version's name"""
+
+ pending_quantity: int | None = Field(alias="pendingQuantity")
+ """A decrease scheduled for the next boundary, 0 for a removal; the current quantity holds
+ until then. Null when nothing is scheduled (V31-67)
+ """
+
+ prices: list[Price]
+ """What the instance's subscription bills for it: the default FLAT_FEE price of the
+ subscription's period and the ACTIVE metered prices. Empty when the instance is not
+ billed.
+ """
+
+ quantity: int
+
+ removed_at: datetime | None = Field(alias="removedAt")
+
+
+class InstanceBilling(KaitenModel):
+ anchor_at: datetime = Field(alias="anchorAt")
+ """The instant its periods are counted from"""
+
+ base_price: Price = Field(alias="basePrice")
+ """The FLAT_FEE price the subscription is pinned to, deprecated or not"""
+
+ billing_period: BillingPeriod | str = Field(alias="billingPeriod")
+
+ cancel_at_period_end: bool = Field(alias="cancelAtPeriodEnd")
+
+ cancel_requested_at: datetime | None = Field(alias="cancelRequestedAt")
+
+ canceled_at: datetime | None = Field(alias="canceledAt")
+
+ cancellation_reason: str | None = Field(alias="cancellationReason")
+
+ collection_method: CollectionMethod | str = Field(alias="collectionMethod")
+ """Effective: the subscription's own, else the organization's default"""
+
+ collection_method_override: str | None = Field(alias="collectionMethodOverride")
+ """The subscription's own collection method; null when it takes the organization's"""
+
+ created_at: datetime = Field(alias="createdAt")
+
+ currency: str
+
+ current_period_end: datetime = Field(alias="currentPeriodEnd")
+ """The next boundary: the period closes, and its invoice is composed, after it"""
+
+ current_period_start: datetime = Field(alias="currentPeriodStart")
+
+ customer_name: str = Field(alias="customerName")
+
+ customer_slug: str = Field(alias="customerSlug")
+
+ days_until_due: int = Field(alias="daysUntilDue")
+ """Effective: the subscription's own, else the organization's default"""
+
+ days_until_due_override: int | None = Field(alias="daysUntilDueOverride")
+ """The subscription's own payment terms; null when it takes the organization's"""
+
+ id: str
+
+ incomplete_expires_at: datetime | None = Field(alias="incompleteExpiresAt")
+ """When a checkout waiting for its payment ends unpaid: INCOMPLETE, or a free subscription
+ that carries one
+ """
+
+ instance_name: str = Field(alias="instanceName")
+
+ instance_slug: str = Field(alias="instanceSlug")
+ """As it was at the subscription's last write"""
+
+ past_due_since: datetime | None = Field(alias="pastDueSince")
+
+ pending_license_slug: str | None = Field(alias="pendingLicenseSlug")
+ """The licence version a checkout waiting for its payment moves the instance to once paid"""
+
+ provider_kind: BillingProviderKind | str = Field(alias="providerKind")
+ """Who collects its invoices. NOOP: the organization itself, through the handoff queue"""
+
+ reminder_at: datetime | None = Field(alias="reminderAt")
+ """When trial_will_end is (or was) emitted: trialEndsAt minus the family's
+ trialReminderDays as it was when the trial started; set with trialEndsAt
+ """
+
+ scheduled_change: ScheduledChange | None = Field(alias="scheduledChange")
+ """A plan change waiting for the next boundary"""
+
+ started_at: datetime = Field(alias="startedAt")
+ """When billing started: usage before it is never billed"""
+
+ status: SubscriptionStatus | str
+
+ trial_ends_at: datetime | None = Field(alias="trialEndsAt")
+ """When the trial ends, or ended: usage before it is never billed"""
+
+ updated_at: datetime = Field(alias="updatedAt")
+
+
+class InstanceEntitlementCapExceeded(KaitenModel):
+ """Usage crossed an entitlement's included value, within its allowance."""
+
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
+
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
+
+ entitlement_slug: str
+ """URL-friendly identifier of the entitlement"""
+
+ overage: float
+ """Amount by which usage exceeds the cap"""
+
+ threshold: float
+ """The entitlement cap"""
+
+ value: float
+ """Current usage value, including overage"""
+
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
+ """
+
+
+class InstanceEntitlementUsagePeriodRolledOver(KaitenModel):
+ """A periodic usage window closed and a new one opened."""
+
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
+
+ closed_period_end: datetime | None
+
+ closed_period_start: datetime | None
+
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
+
+ entitlement_id: str
+
+ entitlement_slug: str
+
+ event_count: int
+
+ instance_id: str
+
+ is_synthetic: bool
+
+ new_period_start: datetime
+
+ reason: PeriodRolloverReason | str
+
+ value: float
+
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
+ """
+
+
+class InstanceEntitlementUsageReportAccepted(KaitenModel):
+ """The outcome of a usage report."""
+
+ behavior: UsageBehavior | str
+ """Report behavior: append folds the value into the stored total, set overwrites it"""
+
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
+
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
+
+ entitlement_slug: str
+ """URL-friendly identifier of the entitlement the usage was reported against"""
+
+ event_count: int
+ """Number of reports folded into the current usage window, including this one"""
+
+ status: UsageReportStatus | str
+ """Whether the report was persisted"""
+
+ value: float
+ """Value carried by the report"""
+
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
+ """
+
+
+class InstanceEntitlementUsageWarningThresholdReached(KaitenModel):
+ """Usage crossed an entitlement's warning threshold."""
+
+ boundary: float
+ """Usage value at which the warning fires (cap * warningThresholdPercent / 100)"""
+
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
+
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
+
+ entitlement_slug: str
+ """URL-friendly identifier of the entitlement"""
+
+ threshold: float
+ """The entitlement cap"""
+
+ value: float
+ """Current usage value"""
+
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
+ """
+
+
+class InstanceEntitlementValueGet(KaitenModel):
+ """An entitlement value read for an instance."""
+
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
+
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
+
+ entitlement_id: str
+ """Unique identifier for the entitlement"""
+
+ entitlement_slug: str
+ """URL-friendly identifier of the entitlement"""
+
+ instance_id: str
+ """Unique identifier for the instance the value was read for"""
+
+ license_id: str
+ """Unique identifier for the license carrying the entitlement"""
+
+ organization_id: str
+ """Unique identifier for the owning organization"""
+
+ timestamp: datetime
+ """When the value was read"""
+
+ type: str
+ """Discriminator of the resolved entitlement value"""
+
+ value: EntitlementValue
+ """Resolved entitlement value at read time"""
+
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
+ """
+
+
+class InstanceIntegration(KaitenModel):
+ """An instance's link to a record in a third-party system."""
+
+ external_id: str
+ """External identifier in the third-party adapter"""
+
+ last_error: str | None = None
+ """Last synchronization error message, if any"""
+
+ metadata: dict[str, Any] | None = None
+ """Adapter-specific integration metadata"""
+
+ synced_at: datetime
+ """Timestamp of the last synchronization with the adapter"""
+
+ web_url: str | None = None
+ """Absolute http(s) link to the record in the third-party system"""
+
+
+class InstanceIntegrationResource(KaitenModel):
+ """An instance as seen through one of its integrations."""
+
+ customer_external_id: str | None = Field(default=None, alias="customerExternalId")
+ """External identifier of the related customer in the same adapter"""
+
+ deployment_zone_id: str | None = Field(default=None, alias="deploymentZoneId")
+ """Deployment zone ID"""
+
+ description: str
+ """Instance description"""
+
+ end_license_date: datetime = Field(alias="endLicenseDate")
+ """License end date"""
+
+ error: str | None = None
+ """Last synchronization error for this integration, if any"""
+
+ integration_metadata: dict[str, Any] = Field(alias="integrationMetadata")
+ """Adapter-specific instance integration metadata"""
+
+ license_id: str = Field(alias="licenseId")
+ """License ID"""
+
+ name: str
+ """Instance name"""
+
+ slug: str
+ """Instance slug"""
+
+ start_license_date: datetime = Field(alias="startLicenseDate")
+ """License start date"""
+
+ web_url: str | None = Field(default=None, alias="webUrl")
+ """Absolute http(s) link to the record in the third-party system"""
+
+
+class InstanceLifecycleStageChanged(KaitenModel):
+ """An instance's commercial lifecycle stage changed."""
+
+ changed_by: str
+
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
+
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
+
+ instance_id: str
+
+ new_lifecycle_stage: str | None
+
+ previous_lifecycle_stage: str | None
+
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
+ """
+
+
+class InstanceStatusChanged(KaitenModel):
+ """An instance's operational status changed."""
+
+ changed_by: str
+
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
+
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
+
+ instance_id: str
+
+ new_status: InstanceStatus | str
+
+ previous_status: InstanceStatus | str
+
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
+ """
+
+
+class Invoice(KaitenModel):
+ billing_email: str | None = Field(alias="billingEmail")
+ """The address the invoice is for, as it was when composed. Personal data: it appears in no
+ event
+ """
+
+ boundary_at: datetime = Field(alias="boundaryAt")
+ """The boundary the invoice bills. With the kind, it identifies the invoice within its
+ subscription
+ """
+
+ collection_method: CollectionMethod | str = Field(alias="collectionMethod")
+
+ created_at: datetime = Field(alias="createdAt")
+
+ currency: str
+
+ customer_name: str = Field(alias="customerName")
+
+ customer_slug: str = Field(alias="customerSlug")
+ """As it was when the invoice was composed"""
+
+ days_until_due: int | None = Field(alias="daysUntilDue")
+
+ discount_total: int = Field(alias="discountTotal")
+ """Sum of the discounts, in minor units"""
+
+ due_at: datetime | None = Field(alias="dueAt")
+ """issuedAt + daysUntilDue"""
+
+ handoff: InvoiceHandoff
+
+ handoff_status: HandoffStatus | str = Field(alias="handoffStatus")
+ """PENDING: waiting in the handoff queue for the organization's accounting system"""
+
+ hold: HoldRecord | None
+ """When the invoice was held and released"""
+
+ hold_detail: HoldDetail | None = Field(alias="holdDetail")
+ """Every meter whose journal failed a check"""
+
+ hold_reason: InvoiceHoldReason | str | None = Field(alias="holdReason")
+ """Why a DRAFT is held: the usage journal it was measured from failed a consistency check
+ (LEDGER_*), or its provider cannot take that many lines (PROVIDER_ITEM_LIMIT)
+ """
+
+ id: str
+ """The invoice's identifier: the key an accounting system deduplicates on"""
+
+ instance_name: str = Field(alias="instanceName")
+
+ instance_slug: str = Field(alias="instanceSlug")
+
+ issued_at: datetime | None = Field(alias="issuedAt")
+
+ kind: InvoiceKind | str
+ """ACTIVATION bills a subscription's first period, RENEWAL the period that ended and the
+ one that starts, FINAL what is left when a subscription ends
+ """
+
+ license_id: str = Field(alias="licenseId")
+
+ license_slug: str = Field(alias="licenseSlug")
+
+ lines: list[InvoiceLine]
+
+ overdue: bool
+ """Whether the invoice is overdue when read, the grace period of automatic collection
+ included: the predicate of ?overdue=true. Computed by the invoice list and the invoice
+ read; false in the answers of writes and in event payloads.
+ """
+
+ paid_at: datetime | None = Field(alias="paidAt")
+
+ provider: ProviderRecord | None
+ """The invoice in its payment provider; null for NOOP"""
+
+ provider_kind: BillingProviderKind | str = Field(alias="providerKind")
+ """Who collects it. NOOP: the organization itself, through the handoff queue"""
+
+ replaced_by_invoice_id: str | None = Field(alias="replacedByInvoiceId")
+ """The invoice recomposed from this VOID one"""
+
+ replaces_invoice_id: str | None = Field(alias="replacesInvoiceId")
+ """The VOID invoice this one was recomposed from"""
+
+ service_from: datetime = Field(alias="serviceFrom")
+ """Earliest start of its lines' service periods"""
+
+ service_to: datetime = Field(alias="serviceTo")
+ """Latest end of its lines' service periods"""
+
+ status: InvoiceStatus | str
+ """MANUAL: issued, for the organization to collect. PAID, UNCOLLECTIBLE and VOID are final.
+ DRAFT: held, waiting for a release or a recompose
+ """
+
+ subtotal: int
+ """Sum of the lines, in minor units"""
+
+ total: int
+ """subtotal − discountTotal, in minor units"""
+
+ uncollectible_at: datetime | None = Field(alias="uncollectibleAt")
+
+ updated_at: datetime = Field(alias="updatedAt")
+
+ void_reason: str | None = Field(alias="voidReason")
+
+ voided_at: datetime | None = Field(alias="voidedAt")
+
+
+class InvoiceHandoff(KaitenModel):
+ acknowledged_at: datetime | None = Field(alias="acknowledgedAt")
+
+ claim_count: int = Field(alias="claimCount")
+
+ external_reference: str | None = Field(alias="externalReference")
+ """The invoice's number in the organization's accounting system"""
+
+ lease_id: str | None = Field(alias="leaseId")
+ """The claim currently holding it"""
+
+ leased_until: datetime | None = Field(alias="leasedUntil")
+
+ status: HandoffStatus | str
+
+
+class InvoiceHold(KaitenModel):
+ entitlement_id: str = Field(alias="entitlementId")
+
+ invariant: str
+ """The check that failed"""
+
+
+class InvoiceLine(KaitenModel):
+ addon_id: str | None = Field(alias="addonId")
+ """The add-on version an ADDON line bills"""
+
+ addon_price_id: str | None = Field(alias="addonPriceId")
+ """The add-on price an ADDON line bills"""
+
+ amount: int
+ """round_half_up(quantity × unitAmountDecimal), in minor units; negative on a DISCOUNT line"""
+
+ billing_model: BillingModel | str | None = Field(alias="billingModel")
+ """The price's billing model; absent on a DISCOUNT line"""
+
+ billing_timing: BillingTiming | str | None = Field(alias="billingTiming")
+ """ADVANCE lines bill the period that starts at the boundary, ARREARS lines the one that
+ ends there; absent on a DISCOUNT line
+ """
+
+ capped: bool | None = None
+ """Set on a preview line whose sample exceeded what the licence accepts: reports above that
+ are rejected, so the excess is not billed
+ """
+
+ description: str
+ """The arithmetic of the line"""
+
+ discount: InvoiceLineDiscount | None
+ """How a DISCOUNT line was computed"""
+
+ entitlement_id: str | None = Field(alias="entitlementId")
+ """The metered entitlement, on USAGE and OVERAGE lines"""
+
+ entitlement_slug: str | None = Field(alias="entitlementSlug")
+ """Its slug, as it was when the line was composed"""
+
+ id: str | None = None
+ """The line's identifier, stable for the invoice's life; absent on a preview"""
+
+ instance_addon_id: str | None = Field(alias="instanceAddonId")
+ """The instance's attachment an ADDON line bills"""
+
+ instance_voucher_id: str | None = Field(alias="instanceVoucherId")
+ """The redemption a DISCOUNT line applies"""
+
+ label: str
+ """The price's display label, else a derived one"""
+
+ license_price_id: str | None = Field(alias="licensePriceId")
+ """The licence price the line bills; null on an ADDON line"""
+
+ metering: InvoiceLineMetering | None
+ """How a USAGE or OVERAGE line's quantity was measured"""
+
+ overage: InvoiceLineOverage | None
+ """The arithmetic of an OVERAGE line"""
+
+ provider: InvoiceLineProvider | None
+ """The line in the payment provider, once pushed"""
+
+ quantity: str
+ """In sale units, a decimal string: 1 on a BASE line, the quantity held on an ADDON line"""
+
+ seq: int
+ """Position on the invoice, from 1"""
+
+ service_from: datetime = Field(alias="serviceFrom")
+ """Start of the period the line bills (inclusive)"""
+
+ service_to: datetime = Field(alias="serviceTo")
+ """End of the period the line bills (exclusive)"""
+
+ true_up: InvoiceLineTrueUp | None = Field(alias="trueUp")
+ """On the true-up line of an ADVANCE add-on of a TRUE_UP family: the highest quantity held
+ in the elapsed period and the quantity billed in advance for it (V31-70)
+ """
+
+ type: InvoiceLineType | str
+ """BASE: the subscription's FLAT_FEE price. ADDON: an add-on's FLAT_FEE price times the
+ quantity held at the boundary. USAGE: a USAGE_BASED price's metered usage. OVERAGE: an
+ OVERAGE price's usage above the licence's limit. DISCOUNT: a PRICE voucher's discount,
+ negative.
+ """
+
+ unit_amount_decimal: str | None = Field(alias="unitAmountDecimal")
+ """The price's unit amount in minor units; absent on a DISCOUNT line"""
+
+ voucher_id: str | None = Field(alias="voucherId")
+ """The voucher a DISCOUNT line applies"""
+
+
+class InvoiceLineDiscount(KaitenModel):
+ allocations: list[DiscountAllocation]
+ """The part of the line each target bears, by ascending targetSeq: they sum to the line's
+ magnitude, and no target is discounted below 0. A payment provider applies each one to
+ its target line
+ """
+
+ application: int
+ """Which invoice of the redemption this is, from 1"""
+
+ applications_max: int | None = Field(alias="applicationsMax")
+ """How many invoices the redemption discounts; null for FOREVER"""
+
+ applies_to: DiscountAppliesTo | str = Field(alias="appliesTo")
+
+ base: str
+ """What its targets still amounted to before it, exact, in minor units"""
+
+ currency: str | None
+
+ discount_type: DiscountType | str = Field(alias="discountType")
+
+ discount_value: str = Field(alias="discountValue")
+ """The percentage, or the amount in minor units"""
+
+ target_seqs: list[int] | None = Field(alias="targetSeqs")
+ """The lines it discounts"""
+
+
+class InvoiceLineLedger(KaitenModel):
+ first_seq: int | None = Field(alias="firstSeq")
+ """First report of the period, null when it has none"""
+
+ instance_id: str | None = Field(alias="instanceId")
+ """The instance whose reports these are: with the line's entitlement, the pair the journal
+ is kept by. It outlives the instance
+ """
+
+ last_seq: int | None = Field(alias="lastSeq")
+ """Last report of the period, null when it has none"""
+
+ rows: int
+ """Reports in the period"""
+
+ sum_delta: str = Field(alias="sumDelta")
+ """Sum of the reports' movements, before any floor"""
+
+ sum_overage: str | None = Field(alias="sumOverage")
+ """Sum of their movements above the limit, before any floor"""
+
+
+class InvoiceLineMetering(KaitenModel):
+ ledger: InvoiceLineLedger | None
+ """The usage journal rows the line was measured from"""
+
+ measured_quantity: str = Field(alias="measuredQuantity")
+ """In measured units: the usage for a USAGE line, the overage for an OVERAGE line"""
+
+ negative_segments_floored: int = Field(alias="negativeSegmentsFloored")
+ """Windows whose net movement was negative and counted as 0"""
+
+ sale_unit_factor: str = Field(alias="saleUnitFactor")
+ """Measured units in one sale unit"""
+
+ windows: int
+ """Reset windows the period spans"""
+
+
+class InvoiceLineOverage(KaitenModel):
+ limits: list[OverageLimit]
+ """The limits applied, in the order they first applied"""
+
+ overage_measured: str = Field(alias="overageMeasured")
+ """Usage above the applied limit"""
+
+ usage_measured: str = Field(alias="usageMeasured")
+ """Usage in measured units"""
+
+
+class InvoiceLineProvider(KaitenModel):
+ amount: int | None = None
+ """The provider's amount, read back for reconciliation"""
+
+ coupon_ids: list[str] | None = Field(default=None, alias="couponIds")
+ """On a DISCOUNT line: the provider's discount (a Stripe coupon) of each allocation, in the
+ allocations' order
+ """
+
+ external_line_id: str | None = Field(default=None, alias="externalLineId")
+ """The provider's id of the line; absent on a DISCOUNT line, which the provider holds as
+ discounts on its targets
+ """
+
+
+class InvoiceLineTrueUp(KaitenModel):
+ billed_quantity: int = Field(alias="billedQuantity")
+ """The quantity its ADVANCE line billed at the previous boundary; 0 when it started after it"""
+
+ max_quantity: int = Field(alias="maxQuantity")
+ """The highest quantity the attachment held in the elapsed period"""
+
+
+class InvoicePreview(KaitenModel):
+ as_of: datetime = Field(alias="asOf")
+ """When the preview was composed"""
+
+ boundary_at: datetime = Field(alias="boundaryAt")
+ """The boundary the invoice bills"""
+
+ currency: str
+ """ISO 4217 code"""
+
+ discount_total: int = Field(alias="discountTotal")
+ """Sum of the discounts, in minor units"""
+
+ kind: InvoiceKind | str
+ """The boundary previewed"""
+
+ license_slug: str = Field(alias="licenseSlug")
+ """The licence version previewed"""
+
+ lines: list[InvoiceLine]
+
+ service_from: datetime | None = Field(default=None, alias="serviceFrom")
+ """Earliest start of the lines' service periods; absent without lines"""
+
+ service_to: datetime | None = Field(default=None, alias="serviceTo")
+ """Latest end of the lines' service periods; absent without lines"""
+
+ status: Literal["PREVIEW"]
+ """Always PREVIEW"""
+
+ subtotal: int
+ """Sum of the lines, in minor units"""
+
+ total: int
+ """subtotal − discountTotal, in minor units"""
+
+ would_hold: list[InvoiceHold] = Field(alias="wouldHold")
+ """Meters whose usage journal fails a consistency check, which would hold the invoice.
+ Always empty for a sample.
+ """
+
+
+class InvoiceSummary(KaitenModel):
+ boundary_at: datetime = Field(alias="boundaryAt")
+ """The boundary the invoice bills. With the kind, it identifies the invoice within its
+ subscription
+ """
+
+ collection_method: CollectionMethod | str = Field(alias="collectionMethod")
+
+ created_at: datetime = Field(alias="createdAt")
+
+ currency: str
+
+ customer_name: str = Field(alias="customerName")
+
+ customer_slug: str = Field(alias="customerSlug")
+ """As it was when the invoice was composed"""
+
+ days_until_due: int | None = Field(alias="daysUntilDue")
+
+ discount_total: int = Field(alias="discountTotal")
+ """Sum of the discounts, in minor units"""
+
+ due_at: datetime | None = Field(alias="dueAt")
+ """issuedAt + daysUntilDue"""
+
+ handoff_status: HandoffStatus | str = Field(alias="handoffStatus")
+ """PENDING: waiting in the handoff queue for the organization's accounting system"""
+
+ hold_reason: InvoiceHoldReason | str | None = Field(alias="holdReason")
+ """Why a DRAFT is held: the usage journal it was measured from failed a consistency check
+ (LEDGER_*), or its provider cannot take that many lines (PROVIDER_ITEM_LIMIT)
+ """
+
+ id: str
+ """The invoice's identifier: the key an accounting system deduplicates on"""
+
+ instance_name: str = Field(alias="instanceName")
+
+ instance_slug: str = Field(alias="instanceSlug")
+
+ issued_at: datetime | None = Field(alias="issuedAt")
+
+ kind: InvoiceKind | str
+ """ACTIVATION bills a subscription's first period, RENEWAL the period that ended and the
+ one that starts, FINAL what is left when a subscription ends
+ """
+
+ license_slug: str = Field(alias="licenseSlug")
+
+ overdue: bool
+ """Whether the invoice is overdue when read, the grace period of automatic collection
+ included: the predicate of ?overdue=true. Computed by the invoice list and the invoice
+ read; false in the answers of writes and in event payloads.
+ """
+
+ paid_at: datetime | None = Field(alias="paidAt")
+
+ provider_kind: BillingProviderKind | str = Field(alias="providerKind")
+ """Who collects it. NOOP: the organization itself, through the handoff queue"""
+
+ service_from: datetime = Field(alias="serviceFrom")
+ """Earliest start of its lines' service periods"""
+
+ service_to: datetime = Field(alias="serviceTo")
+ """Latest end of its lines' service periods"""
+
+ status: InvoiceStatus | str
+ """MANUAL: issued, for the organization to collect. PAID, UNCOLLECTIBLE and VOID are final.
+ DRAFT: held, waiting for a release or a recompose
+ """
+
+ subtotal: int
+ """Sum of the lines, in minor units"""
+
+ total: int
+ """subtotal − discountTotal, in minor units"""
+
+ updated_at: datetime = Field(alias="updatedAt")
+
+
+class License(KaitenModel):
+ """A version of a license family: one set of entitlement grants, with its lifecycle state."""
+
+ created_at: datetime = Field(alias="createdAt")
+ """Timestamp when this license was created"""
+
+ description: str
+ """Detailed description of the license capabilities and restrictions"""
+
+ family_id: str | None = Field(default=None, alias="familyId")
+ """Identifier of the license family this license is a version of. Stable across renames and
+ new versions: two licenses sharing it are the same product. Always present in responses.
+ On create, optional: names the family this license is a new version of, as familySlug
+ does by slug; omit both to create a new family. On update, optional and must be the
+ license's own family: a version cannot move between families
+ (UpdateLicense.FamilyNotReassignable).
+ """
+
+ family_slug: str | None = Field(default=None, alias="familySlug")
+ """Write-only on create: slug of the family this license is a new version of. familyId
+ names the same family by identifier; sent together they must agree. Omit both to create
+ a new family, of which this license becomes version 1. Never returned -- see familyId.
+ Refused on update: a version cannot move between families.
+ """
+
+ id: str
+ """Unique identifier for the license"""
+
+ is_default: bool = Field(alias="isDefault")
+ """Whether this version is the one its family puts forward: at most one version per family,
+ and only a PUBLISHED one (CreateLicense.DefaultMustBePublished,
+ UpdateLicense.DefaultMustBePublished). Resolution hands it out ahead of the highest-
+ numbered published version, so it keeps being served when a newer version is published.
+ Setting it takes the flag from the family's previous default, which gets a
+ LICENSE_UPDATED event of its own. On update, sending false unsets it; the default cannot
+ be archived until another version takes its place or the flag is unset
+ (ArchiveLicense.DefaultMustBePublished).
+ """
+
+ lifecycle_state: LicenseLifecycleState | str | None = Field(
+ default=None, alias="lifecycleState"
+ )
+ """Whether this version may be served. Always present in responses. Optional on create,
+ where it is DRAFT or PUBLISHED and PUBLISHED when omitted; ARCHIVED is refused
+ (CreateLicense.LifecycleStateNotSettable), since a version is archived by withdrawing it
+ from sale. After that it changes only through publish-license (DRAFT to PUBLISHED),
+ archive-license (PUBLISHED to ARCHIVED) and unarchive-license (ARCHIVED to PUBLISHED);
+ update accepts the stored state and refuses any other
+ (UpdateLicense.LifecycleStateNotSettable). Only a PUBLISHED version can be the family's
+ default. An ARCHIVED version cannot be assigned to an instance
+ (CreateInstance.LicenseArchived, UpdateInstance.LicenseArchived); instances already on
+ it keep it.
+ """
+
+ name: str
+ """Display name of the license"""
+
+ pricing_type: PricingType | str | None = Field(default=None, alias="pricingType")
+ """How this version is sold. FREE and PAID can be bought self-serve; CUSTOM sends a buyer
+ to selfServeCtaUrl or a conversation. Always present in responses. On create it defaults
+ to CUSTOM; on update, omit it or send null to keep the stored value.
+ """
+
+ requires_payment_method: bool | None = Field(default=None, alias="requiresPaymentMethod")
+ """Whether self-serve signup captures a payment method before activation. Always present in
+ responses; false when omitted on create. On update, omit it or send null to keep the
+ stored value.
+ """
+
+ self_serve_cta_url: str | None = Field(default=None, alias="selfServeCtaUrl")
+ """Where a buyer is sent when this version cannot be bought self-serve: an http(s) URL of
+ at most 2048 characters (CreateLicense.InvalidSelfServeCtaUrl). Null when there is none.
+ On update, omit it or send null to keep the stored value, or send an empty string to
+ remove it.
+ """
+
+ slug: str | None = None
+ """Optional URL-friendly identifier, unique per organization. When omitted, a new version
+ of an existing family takes {familySlug}-v{version}, and any other license gets one
+ generated from its name. A license that opens a new family gives the family its slug.
+ """
+
+ trial_period_days: int | None = Field(default=None, alias="trialPeriodDays")
+ """Trial length, in days, a subscription to this version starts with by default. Null when
+ there is none. From 1 to 365 (CreateLicense.InvalidTrialDays,
+ UpdateLicense.InvalidTrialDays). On update, omit it or send null to keep the stored
+ value, or send 0 to remove the trial.
+ """
+
+ type: LicenseType | str
+ """Type of the license determining its capabilities and restrictions"""
+
+ updated_at: datetime = Field(alias="updatedAt")
+ """Timestamp when this license was last updated"""
+
+ version: str
+ """Version of the license within its family, assigned by the server when the version is
+ created and never changed afterwards: the version's derived slug
+ ({familySlug}-v{version}, see slug) is built from it. A number is never reused within a
+ family, even after its version is deleted. Create refuses any value
+ (CreateLicense.VersionNotSettable); update accepts only the stored one, so a
+ representation can be written back as it was read (UpdateLicense.VersionNotSettable).
+ """
+
+ version_name: str | None = Field(default=None, alias="versionName")
+ """Optional human-readable name for the license version. Defaults to "Version - "."""
+
+
+class LicenseEntitlement(KaitenModel):
+ """A grant: the value a license gives one entitlement, and how it is enforced."""
+
+ created_at: datetime = Field(alias="createdAt")
+ """Timestamp when this entitlement was created"""
+
+ created_by: User = Field(alias="createdBy")
+ """User who created this entitlement"""
+
+ entitlement_groups: list[EntitlementGroupSummary] | None = Field(
+ default=None, alias="entitlementGroups"
+ )
+ """Groups associated with this entitlement"""
+
+ entitlement_name: str = Field(alias="entitlementName")
+ """Name of the entitlement feature or service"""
+
+ entitlement_slug: str | None = Field(default=None, alias="entitlementSlug")
+ """URL-friendly identifier for the entitlement"""
+
+ entitlement_type: LicenseEntitlementType | str | None = Field(
+ default=None, alias="entitlementType"
+ )
+ """Type of entitlement (BOOLEAN, NUMBER, or CONFIG)"""
+
+ license_id: str = Field(alias="licenseId")
+ """Unique identifier for the license this entitlement belongs to"""
+
+ license_slug: str = Field(alias="licenseSlug")
+ """URL-friendly identifier for the license this entitlement belongs to"""
+
+ limit_cap_exceeded_overage_percent: int | None = Field(
+ default=None, alias="limitCapExceededOveragePercent"
+ )
+ """Derives this grant's enforcement from its own numeric value: -1 when the value is
+ unlimited (no cap, no overage); otherwise 0 rejects usage above the value (hard limit)
+ and a positive percentage allows usage to exceed it by that much before being rejected
+ (soft limit). Null for non-numeric (BOOLEAN/CONFIG) grants.
+ """
+
+ updated_at: datetime = Field(alias="updatedAt")
+ """Timestamp when this entitlement was last updated"""
+
+ updated_by: User = Field(alias="updatedBy")
+ """User who last updated this entitlement"""
+
+ value: EntitlementValue
+ """Typed entitlement value. Supported shapes: {type:number,value:number},
+ {type:boolean,value:boolean}, {type:object,value:{...}}
+ """
+
+
+class LicenseFamilyView(KaitenModel):
+ """A license family -- the product -- resolved to one of its versions."""
+
+ created_at: datetime = Field(alias="createdAt")
+ """Timestamp when this family was created"""
+
+ current_version: License | None = Field(default=None, alias="currentVersion")
+ """The version this read is about. By default the version the family resolves to: its
+ default version if it has one, otherwise its highest-numbered PUBLISHED version. Under
+ ?version=N it is the version asked for instead, whatever its lifecycle state. Absent
+ when the family has no PUBLISHED version, which only a list or an include=versions read
+ returns.
+ """
+
+ fallback_license_family_slug: str | None = Field(alias="fallbackLicenseFamilySlug")
+ """The FREE family whose default PUBLISHED version an instance of this family moves to when
+ its subscription ends; null without one
+ """
+
+ id: str
+ """Unique identifier for the license family"""
+
+ is_public: bool = Field(alias="isPublic")
+ """Whether the family's default PUBLISHED version is listed in the public catalogue.
+ Private by default.
+ """
+
+ slug: str
+ """URL-friendly identifier of the family, unique per organization and stable across renames
+ and new versions. Deleting the family's last version deletes the family and frees the
+ slug.
+ """
+
+ trial_end_without_payment_method: TrialEndWithoutPaymentMethod | str = Field(
+ alias="trialEndWithoutPaymentMethod"
+ )
+ """What a trial charged automatically with a total above 0 becomes at its end when the
+ customer has no ACTIVE payment method: CANCEL (the default) or CONVERT
+ """
+
+ trial_reminder_days: int = Field(alias="trialReminderDays")
+ """How many days before a trial's end trial_will_end fires, for the trials started after it
+ was set. 3 by default.
+ """
+
+ updated_at: datetime = Field(alias="updatedAt")
+ """Timestamp when a version was last added to this family, or when the family was created.
+ Renames, lifecycle moves and default changes update the versions' own updatedAt, not
+ this one.
+ """
+
+ version_count: int = Field(alias="versionCount")
+ """Number of versions in this family, whatever their lifecycle state"""
+
+ versions: list[License] | None = None
+ """Every version of the family, oldest first. Only returned when explicitly requested with
+ include=versions, and then never null: a family always has at least one version.
+ """
+
+
+class LineDifference(KaitenModel):
+ external_line_id: str = Field(alias="externalLineId")
+
+ kaiten_amount: int = Field(alias="kaitenAmount")
+
+ line_id: str = Field(alias="lineId")
+
+ provider_amount: int = Field(alias="providerAmount")
+
+ seq: int
+
+
+class LineReportPage(KaitenModel):
+ items: list[UsageReport]
+ """The reports, in reportSeq order"""
+
+ next_after_seq: int | None = Field(default=None, alias="nextAfterSeq")
+ """Pass as afterSeq to read the next page. Absent on the last page."""
+
+
+class ManifestEnvelope(KaitenModel):
+ flags: list[ManifestFlag] | None
+
+
+class ManifestFlag(KaitenModel):
+ """One flag of the OpenFeature manifest."""
+
+ default_value: Any = Field(alias="defaultValue")
+
+ description: str | None = None
+
+ key: str
+
+ name: str | None = None
+
+ type: str
+
+
+class MetadataField(KaitenModel):
+ """A typed field an organization declares on its instances or deployment zones."""
+
+ archived_at: datetime | None = Field(default=None, alias="archivedAt")
+ """Timestamp when the field was archived (soft delete). Absent if active."""
+
+ created_at: datetime = Field(alias="createdAt")
+ """Timestamp when the field was created"""
+
+ created_by: User = Field(alias="createdBy")
+ """User who created this field"""
+
+ display_order: int | None = Field(default=None, alias="displayOrder")
+ """Sort order in the admin UI; ties are broken by createdAt ASC. Settable only through POST
+ /metadata-fields/reorder; never echoed back on update.
+ """
+
+ id: str
+ """Unique identifier"""
+
+ json_schema: dict[str, Any] = Field(alias="jsonSchema")
+ """JSON Schema 2020-12 document describing the value space. Example:
+ {"type":"string","enum":["a","b"]}
+ """
+
+ key: str
+ """Stable identifier, unique per (organization, resource type) on non-archived rows.
+ Immutable after creation; update must echo back the stored value.
+ """
+
+ label: str
+ """Human-readable label shown in the admin UI"""
+
+ resource_type: MetadataResourceType | str = Field(alias="resourceType")
+ """Resource type this field applies to. Immutable after creation; update must echo back the
+ stored value.
+ """
+
+ updated_at: datetime = Field(alias="updatedAt")
+ """Timestamp when the field was last updated"""
+
+ updated_by: User = Field(alias="updatedBy")
+ """User who last updated this field"""
+
+
+class MetadataFieldImpact(KaitenModel):
+ """What a candidate metadata field schema would invalidate."""
+
+ count: int
+ """Number of resources whose stored value for this field would no longer satisfy the
+ candidate schema
+ """
+
+ samples: list[MetadataFieldImpactSample] | None
+ """Up to 5 impacted resources, for display"""
+
+
+class MetadataFieldImpactSample(KaitenModel):
+ """A resource a metadata field schema change would invalidate."""
+
+ name: str
+ """Resource display name"""
+
+ slug: str
+ """Resource slug"""
+
+
+class MetadataFieldReordered(KaitenModel):
+ """Metadata fields were given a new display order."""
+
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
+
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
+
+ ids: list[str] | None
+ """Metadata field ids in their new display order"""
+
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
+ """
+
+
+class MinimumAmount(KaitenModel):
+ currency: str
+
+ unit_amount_decimal: str = Field(alias="unitAmountDecimal")
+ """In minor units, above 0"""
+
+
+class NumberEntitlementValue(KaitenModel):
+ """A numeric entitlement value: a quota, or an amount consumed."""
+
+ event_count: int | None = None
+ """Number of events accumulated (server-computed)"""
+
+ type: Literal["number"]
+ """Value type discriminator"""
+
+ value: float
+ """Numeric value"""
+
+
+class Outcome(KaitenModel):
+ applied: int
+ """Invoices read and applied"""
+
+ error: str | None = None
+
+ failed: int
+ """Invoices that could not be read or applied"""
+
+ provider_kind: BillingProviderKind | str = Field(alias="providerKind")
+
+ status: SyncStatus | str
+
+ synced_at: datetime = Field(alias="syncedAt")
+
+
+class OverageLimit(KaitenModel):
+ limit_value: str | None = Field(alias="limitValue")
+ """The limit, null when unlimited"""
+
+ overage_percent: int = Field(alias="overagePercent")
+ """Usage accepted above the limit, as a percent of it"""
+
+ rows: int
+ """Usage reports measured against it; 0 for a sample"""
+
+
+class Payment(KaitenModel):
+ hosted_invoice_url: str | None = Field(default=None, alias="hostedInvoiceUrl")
+
+ status: PaymentStatus | str
+ """paid. requires_action: the customer must confirm the payment on hostedInvoiceUrl.
+ failed: the charge was refused; save another payment method, or pay on hostedInvoiceUrl.
+ processing: the outcome is not known yet; poll GET /public/session/invoices.
+ not_required: nothing is due today.
+ """
+
+
+class PaymentMethodAttachedDelivered(KaitenModel):
+ attached_at: datetime = Field(alias="attachedAt")
+
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
+
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
+
+ customer_slug: str = Field(alias="customerSlug")
+
+ provider_kind: Literal["STRIPE"] = Field(alias="providerKind")
+
+ status: Literal["ACTIVE"]
+
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
+ """
+
+
+class PaymentMethodDetachedDelivered(KaitenModel):
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
+
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
+
+ customer_slug: str = Field(alias="customerSlug")
+
+ provider_kind: Literal["STRIPE"] = Field(alias="providerKind")
+
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
+ """
+
+
+class PaymentMethodExpiringDelivered(KaitenModel):
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
+
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
+
+ customer_slug: str = Field(alias="customerSlug")
+
+ expires_at: datetime = Field(alias="expiresAt")
+ """The last instant of the payment method's expiry month"""
+
+ provider_kind: Literal["STRIPE"] = Field(alias="providerKind")
+
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
+ """
+
+
+class PaymentMethodLabels(KaitenModel):
+ attached_at: datetime | None = Field(alias="attachedAt")
+
+ brand: str | None
+
+ exp_month: int | None = Field(alias="expMonth")
+
+ exp_year: int | None = Field(alias="expYear")
+
+ last4: str | None
+
+ status: PaymentMethodStatus | str
+ """ACTIVE: charged automatically. EXPIRED, FAILED: a charge said it is no longer usable;
+ save another one
+ """
+
+
+class PaymentMethodSession(KaitenModel):
+ expires_at: datetime = Field(alias="expiresAt")
+
+ session_id: str = Field(alias="sessionId")
+ """Complete it with POST …/payment-method-session/{sessionId}/complete once the customer is
+ back
+ """
+
+ url: str
+ """The provider's page; send the customer there"""
+
+
+class PlainToken(KaitenModel):
+ """A token together with its plaintext secret, which the API returns exactly once."""
+
+ created_at: datetime = Field(alias="createdAt")
+
+ created_by: User = Field(alias="createdBy")
+
+ expires_at: datetime | None = Field(default=None, alias="expiresAt")
+
+ id: str | None = None
+
+ name: str
+
+ revoked_at: datetime | None = Field(default=None, alias="revokedAt")
+
+ revoked_by: User | None = Field(default=None, alias="revokedBy")
+
+ scopes: list[str] | None
+
+ service_account_id: str | None = Field(default=None, alias="serviceAccountId")
+
+ slug: str | None = None
+ """Optional URL-friendly identifier, unique per service account. Auto-generated if not
+ provided.
+ """
+
+ token: str | None = None
+
+
+class PlanChangeDelivered(KaitenModel):
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
+
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
+
+ from_license_slug: str = Field(alias="fromLicenseSlug")
+
+ from_price_id: str = Field(alias="fromPriceId")
+
+ instance_slug: str = Field(alias="instanceSlug")
+
+ invoice_id: str = Field(alias="invoiceId")
+ """The RENEWAL that billed the old plan's arrears and the new plan's advance"""
+
+ to_license_slug: str = Field(alias="toLicenseSlug")
+
+ to_price_id: str = Field(alias="toPriceId")
+
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
+ """
+
+
+class PlanChangeScheduleDelivered(KaitenModel):
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
+
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
+
+ effective_at: datetime = Field(alias="effectiveAt")
+
+ from_price_id: str = Field(alias="fromPriceId")
+
+ instance_slug: str = Field(alias="instanceSlug")
+
+ to_price_id: str = Field(alias="toPriceId")
+
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
+ """
+
+
+class PortalCapabilities(KaitenModel):
+ checkout: bool
+ """POST /public/session/checkout: the instance has no live subscription and its plan can be
+ bought alone, or its live subscription is free (a FREE licence family, no add-on price
+ above 0) and the catalogue has a self-serve plan, which the checkout replaces it with
+ """
+
+ invoices: bool
+ """Billing is on: GET /public/session/invoices lists the customer's"""
+
+ payment_methods: bool = Field(alias="paymentMethods")
+ """The vendor's provider captures payment methods: the payment-method and portal sessions
+ work
+ """
+
+ unsubscribe: bool
+ """The instance's subscription is live and not already ending: POST
+ /public/session/billing/cancel
+ """
+
+
+class PortalCustomer(KaitenModel):
+ billing_email: str | None = Field(alias="billingEmail")
+ """Where its invoices are sent"""
+
+ name: str
+
+ slug: str
+
+
+class PortalEntitlement(KaitenModel):
+ current_period_end: datetime | None = Field(alias="currentPeriodEnd")
+ """The usage window's end; null for a lifetime quota"""
+
+ current_period_start: datetime | None = Field(alias="currentPeriodStart")
+ """The usage window's start; null for a lifetime quota"""
+
+ current_value: float | None = Field(alias="currentValue")
+ """A number's usage in its current window; null for BOOLEAN and CONFIG"""
+
+ limit_cap_exceeded_overage_percent: int | None = Field(alias="limitCapExceededOveragePercent")
+ """How far above the limit usage is still accepted, in percent: 0 a hard limit; null for
+ BOOLEAN and CONFIG
+ """
+
+ limit_value: EntitlementValue | None = Field(alias="limitValue")
+ """The effective value: a number's limit (-1 unlimited), a BOOLEAN's flag, a CONFIG's object"""
+
+ maximum_allowed_usage: float | None = Field(alias="maximumAllowedUsage")
+ """The most usage accepted, limit × (1 + percent ÷ 100); null when unlimited, and for
+ BOOLEAN and CONFIG
+ """
+
+ name: str
+
+ percentage_used: float | None = Field(alias="percentageUsed")
+ """currentValue ÷ limit × 100; null when unlimited or the limit is 0, and for BOOLEAN and
+ CONFIG
+ """
+
+ provenance: Provenance | None
+ """What the licence, the add-ons and the boosts each contribute"""
+
+ slug: str
+
+ source: EntitlementSource | str
+ """license: the licence grants it; addon: an add-on does; exception: only the vendor's
+ exception for this instance does
+ """
+
+ type: EntitlementType | str
+
+ unlimited: bool
+
+
+class PortalInstance(KaitenModel):
+ license_name: str = Field(alias="licenseName")
+
+ license_slug: str = Field(alias="licenseSlug")
+
+ name: str
+
+ slug: str
+
+
+class PortalInstanceSummary(KaitenModel):
+ billing_status: SubscriptionStatus | str | None = Field(alias="billingStatus")
+ """Its subscription's status; null when it was never subscribed"""
+
+ license_slug: str = Field(alias="licenseSlug")
+
+ name: str
+
+ slug: str
+
+
+class PortalPaymentMethod(KaitenModel):
+ brand: str | None
+
+ exp_month: int | None = Field(alias="expMonth")
+
+ exp_year: int | None = Field(alias="expYear")
+
+ last4: str | None
+
+ status: PaymentMethodStatus | str
+ """EXPIRED, FAILED: save another one"""
+
+
+class PortalSession(KaitenModel):
+ url: str
+
+
+class PortalTrial(KaitenModel):
+ charge: PortalTrialCharge | None
+ """What the conversion would charge, discounts included, before any tax the provider adds;
+ null when nothing would be
+ """
+
+ ends_at: datetime = Field(alias="endsAt")
+
+ payment_method: PortalTrialPayment | None = Field(alias="paymentMethod")
+ """The ACTIVE card the conversion would be charged to; null without one: ask for a card"""
+
+ reminder_at: datetime | None = Field(alias="reminderAt")
+ """When trial_will_end is, or was, due"""
+
+
+class PortalTrialCharge(KaitenModel):
+ amount: int
+
+ billing_period: BillingPeriod | str = Field(alias="billingPeriod")
+
+ currency: str
+
+
+class PortalTrialPayment(KaitenModel):
+ brand: str | None
+
+ last4: str | None
+
+
+class PortalUpcomingInvoice(KaitenModel):
+ boundary_at: datetime = Field(alias="boundaryAt")
+ """When it is issued"""
+
+ currency: str
+
+ discount_total: int = Field(alias="discountTotal")
+ """Minor units"""
+
+ subtotal: int
+ """Minor units"""
+
+ total: int
+ """Minor units"""
+
+
+class Price(KaitenModel):
+ billing_model: BillingModel | str = Field(alias="billingModel")
+ """FLAT_FEE: an amount per billing period. USAGE_BASED: an amount per sale unit of the
+ metered entitlement's usage. OVERAGE: an amount per sale unit of the usage above the
+ licence's limit.
+ """
+
+ billing_period: BillingPeriod | str | None = Field(alias="billingPeriod")
+ """The period a FLAT_FEE price is charged for. Null on metered prices, which bill on the
+ subscription's period.
+ """
+
+ billing_timing: BillingTiming | str = Field(alias="billingTiming")
+ """ADVANCE bills the period that starts; ARREARS the period that ended. Metered prices are
+ always ARREARS.
+ """
+
+ created_at: datetime = Field(alias="createdAt")
+
+ currency: str
+ """ISO 4217 code, upper case. One currency per licence version."""
+
+ deprecated_at: datetime | None = Field(alias="deprecatedAt")
+ """When the price was deprecated"""
+
+ display_label: str | None = Field(alias="displayLabel")
+ """The invoice line's label; a default is derived when it is null."""
+
+ display_order: int = Field(alias="displayOrder")
+ """Order among the version's prices"""
+
+ id: str
+ """Unique identifier of the price"""
+
+ is_default: bool = Field(alias="isDefault")
+ """The FLAT_FEE price picked for its billing period when none is named. At most one per
+ period; only an ACTIVE FLAT_FEE price.
+ """
+
+ metered: PriceMeter | None
+ """What a metered price measures. Null on FLAT_FEE prices."""
+
+ status: PriceStatus | str
+ """A DEPRECATED price keeps billing what is already pinned to it but is no longer offered."""
+
+ unit_amount: int | None = Field(alias="unitAmount")
+ """unitAmountDecimal as an integer number of minor units, when it is one; null otherwise."""
+
+ unit_amount_decimal: str = Field(alias="unitAmountDecimal")
+ """Amount in the currency's minor units (cents for EUR): per period for FLAT_FEE, per sale
+ unit for metered prices. Up to 12 decimal places.
+ """
+
+ updated_at: datetime = Field(alias="updatedAt")
+
+
+class PriceMeter(KaitenModel):
+ entitlement_slug: str = Field(alias="entitlementSlug")
+ """The metered entitlement"""
+
+ sale_unit_factor: str = Field(alias="saleUnitFactor")
+ """Measured units in one sale unit, captured when the price was created (1 when the
+ entitlement has no sale unit): 10000 bills per 10,000 tokens.
+ """
+
+ sale_unit_plural: str | None = Field(alias="saleUnitPlural")
+ """The sale unit's plural label; null when the entitlement has none"""
+
+ sale_unit_singular: str | None = Field(alias="saleUnitSingular")
+ """The sale unit's singular label; null when the entitlement has none"""
+
+
+class Problem(KaitenModel):
+ """An RFC 9457 problem document, which every API error carries."""
+
+ code: str | None = None
+ """Stable, machine-readable error code."""
+
+ detail: str | None = None
+ """A human-readable explanation specific to this occurrence of the problem."""
+
+ error_id: str | None = Field(default=None, alias="errorId")
+ """Correlation id for the server-side log entry describing the withheld cause."""
+
+ errors: list[ErrorDetail] | None = None
+ """Optional list of individual error details"""
+
+ instance: str | None = None
+ """A URI reference that identifies the specific occurrence of the problem."""
+
+ status: int | None = None
+ """HTTP status code"""
+
+ title: str | None = None
+ """A short, human-readable summary of the problem type. This value should not change
+ between occurrences of the error.
+ """
+
+ type: str | None = None
+ """A URI reference to human-readable documentation for the error."""
+
+
+class Provenance(KaitenModel):
+ addons: list[ProvenanceAddon]
+ """The active add-on grants, in attachment order"""
+
+ boosts: list[ProvenanceBoost]
+ """The boosts in their window, in redemption order"""
+
+ exception: ProvenanceException | None
+ """The instance's exception in force at the read, the last layer; null without one. Never
+ its reason or author
+ """
+
+ license: ProvenanceLicense | None
+ """The licence's grant; null when only add-ons grant the entitlement"""
+
+ number: ProvenanceNumber | None
+ """How a number was composed; null for BOOLEAN and CONFIG, and when no add-on, boost or
+ exception changes it
+ """
+
+
+class ProvenanceAddon(KaitenModel):
+ addon_entitlement_id: str = Field(alias="addonEntitlementId")
+
+ addon_id: str = Field(alias="addonId")
+
+ attached_at: datetime = Field(alias="attachedAt")
+
+ instance_addon_id: str = Field(alias="instanceAddonId")
+
+ limit_cap_exceeded_overage_percent: int | None = Field(alias="limitCapExceededOveragePercent")
+
+ override_behavior: OverrideBehavior | str = Field(alias="overrideBehavior")
+
+ quantity: int
+
+ value: EntitlementValue
+ """One unit's grant"""
+
+
+class ProvenanceBoost(KaitenModel):
+ effective_expires_at: datetime | None = Field(alias="effectiveExpiresAt")
+
+ effective_starts_at: datetime = Field(alias="effectiveStartsAt")
+
+ instance_voucher_id: str = Field(alias="instanceVoucherId")
+
+ modifier_type: GrantModifier | str = Field(alias="modifierType")
+
+ modifier_value: float | None = Field(alias="modifierValue")
+
+ redeemed_at: datetime = Field(alias="redeemedAt")
+
+ value: EntitlementValue | None
+ """The value a BOOLEAN or CONFIG SET boost sets, in the {type, value} shape; null for the
+ NUMBER modifiers
+ """
+
+ voucher_entitlement_grant_id: str = Field(alias="voucherEntitlementGrantId")
+
+ voucher_id: str = Field(alias="voucherId")
+
+
+class ProvenanceException(KaitenModel):
+ ends_at: datetime | None = Field(alias="endsAt")
+ """Null: no end"""
+
+ instance_entitlement_exception_id: str = Field(alias="instanceEntitlementExceptionId")
+
+ kind: ExceptionKind | str
+
+ starts_at: datetime = Field(alias="startsAt")
+
+ value: EntitlementValue | None
+ """The exception's value, in the {type, value} shape; null for UNLIMITED"""
+
+
+class ProvenanceLicense(KaitenModel):
+ license_entitlement_id: str = Field(alias="licenseEntitlementId")
+
+ limit_cap_exceeded_overage_percent: int | None = Field(alias="limitCapExceededOveragePercent")
+
+ value: EntitlementValue
+
+
+class ProvenanceNumber(KaitenModel):
+ after_addons: float = Field(alias="afterAddons")
+
+ after_boosts: float = Field(alias="afterBoosts")
+ """The value before the exception: -1 when unlimited"""
+
+ boost_add: float | None = Field(alias="boostAdd")
+
+ boost_multiply: float | None = Field(alias="boostMultiply")
+
+ boost_set: float | None = Field(alias="boostSet")
+
+ effective: float
+ """-1 when unlimited"""
+
+ license: float | None
+ """The licence's value; null when only add-ons or an exception grant it"""
+
+ unlimited: bool
+ """The licence, an add-on or a boost is unlimited; only an exception's SET makes it finite
+ again
+ """
+
+
+class ProviderCapabilities(KaitenModel):
+ automatic_collection: bool = Field(alias="automaticCollection")
+
+ billing_portal: bool = Field(alias="billingPortal")
+
+ payment_method_capture: bool = Field(alias="paymentMethodCapture")
+
+
+class ProviderChangeDelivered(KaitenModel):
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
+
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
+
+ effective_from: Literal["next_composition"] = Field(alias="effectiveFrom")
+ """Invoices already composed keep their provider"""
+
+ from_: BillingProviderKind | str = Field(alias="from")
+
+ instance_slug: str = Field(alias="instanceSlug")
+
+ to: BillingProviderKind | str
+
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
+ """
+
+
+class ProviderLineLimit(KaitenModel):
+ items: int
+ """The provider items the invoice needs"""
+
+ limit: int
+ """The most the provider takes on one invoice"""
+
+ provider_kind: Literal["STRIPE"] = Field(alias="providerKind")
+
+
+class ProviderRecord(KaitenModel):
+ external_customer_id: str | None = Field(alias="externalCustomerId")
+
+ external_invoice_id: str | None = Field(alias="externalInvoiceId")
+
+ hosted_invoice_url: str | None = Field(alias="hostedInvoiceUrl")
+
+ invoice_number: str | None = Field(alias="invoiceNumber")
+ """The provider's invoice number"""
+
+ invoice_pdf_url: str | None = Field(alias="invoicePdfUrl")
+
+ last_payment_error: str | None = Field(alias="lastPaymentError")
+ """Why the last automatic charge failed, as the provider coded it: authentication_required
+ (the customer must confirm the payment on the hosted invoice page), card_declined,
+ expired_card, no_payment_method...
+ """
+
+ last_push_error: str | None = Field(alias="lastPushError")
+ """The provider's code and message of the last failed push step"""
+
+ next_push_at: datetime | None = Field(alias="nextPushAt")
+ """When the push queue tries it next; null when it waits for a human (finalization in the
+ provider) or is pushed
+ """
+
+ push_attempts: int = Field(alias="pushAttempts")
+
+ pushed_at: datetime | None = Field(alias="pushedAt")
+
+ reconciled_at: datetime | None = Field(alias="reconciledAt")
+
+ reconciliation_detail: Reconciliation | None = Field(alias="reconciliationDetail")
+ """What differs, on a MISMATCH"""
+
+ reconciliation_status: ReconciliationStatus | str | None = Field(alias="reconciliationStatus")
+
+ status: ProviderInvoiceStatus | str | None
+ """The provider's status, mirrored verbatim"""
+
+ synced_at: datetime | None = Field(alias="syncedAt")
+
+ total_excluding_tax: int | None = Field(alias="totalExcludingTax")
+ """The provider's total excluding tax, read back"""
+
+
+class ProviderSync(KaitenModel):
+ consecutive_failures: int = Field(alias="consecutiveFailures")
+
+ lag_seconds: int | None = Field(alias="lagSeconds")
+ """Seconds since the last pass; null before the first"""
+
+ last_full_sweep_at: datetime | None = Field(alias="lastFullSweepAt")
+
+ last_successful_sync_at: datetime | None = Field(alias="lastSuccessfulSyncAt")
+ """The end of the last pass that ended SUCCESS; null before the first (V31-11)"""
+
+ last_sync_error: str | None = Field(alias="lastSyncError")
+
+ last_sync_status: SyncStatus | str | None = Field(alias="lastSyncStatus")
+
+ last_synced_at: datetime | None = Field(alias="lastSyncedAt")
+ """The end of the last pass, whatever its status; null before the first"""
+
+ provider_kind: BillingProviderKind | str = Field(alias="providerKind")
+
+
+class PublicAddOn(KaitenModel):
+ addon_id: str = Field(alias="addonId")
+
+ addon_slug: str = Field(alias="addonSlug")
+
+ compatible_license_families: list[str] = Field(alias="compatibleLicenseFamilies")
+ """Public licence families this add-on fits"""
+
+ description: str
+
+ entitlements: list[PublicAddOnEntitlement]
+ """User-facing entitlements one unit grants"""
+
+ family_slug: str = Field(alias="familySlug")
+
+ max_quantity: int | None = Field(alias="maxQuantity")
+ """Most units one instance may attach; null when unbounded"""
+
+ name: str
+
+ prices: list[PublicPrice]
+
+ pricing_type: PricingType | str = Field(alias="pricingType")
+
+
+class PublicAddOnEntitlement(KaitenModel):
+ limit_cap_exceeded_overage_percent: int | None = Field(alias="limitCapExceededOveragePercent")
+
+ name: str
+
+ override_behavior: OverrideBehavior | str = Field(alias="overrideBehavior")
+ """How the grant combines with the plan's: added to it, replacing it, or the larger of the
+ two
+ """
+
+ slug: str
+
+ type: EntitlementType | str
+
+ value: Any
+
+
+class PublicBranding(KaitenModel):
+ powered_by_removable: bool = Field(alias="poweredByRemovable")
+ """Whether the vendor's plan lets its storefront and portal hide "Powered by Kaiten" (the
+ remove-powered-by entitlement). False while Kaiten's licensing authority cannot answer;
+ always true self-hosted
+ """
+
+
+class PublicCatalog(KaitenModel):
+ add_ons: list[PublicAddOn] = Field(alias="addOns")
+ """The default version of each public add-on family; empty when include omits addons"""
+
+ branding: PublicBranding
+
+ capabilities: PublicCatalogCapabilities
+
+ plans: list[PublicPlan]
+ """The default version of each public licence family, by family slug"""
+
+
+class PublicCatalogCapabilities(KaitenModel):
+ anonymous_checkout: bool = Field(alias="anonymousCheckout")
+ """Whether this publishable key starts anonymous checkouts (POST /public/checkout-
+ sessions): on, with a Turnstile site key and secret
+ """
+
+ checkout: bool
+ """Whether a plan of the catalogue can be bought alone, through a customer session's
+ checkout (POST /public/session/checkout)
+ """
+
+ payment_methods: bool = Field(alias="paymentMethods")
+ """Whether the organization's billing provider captures payment methods"""
+
+ turnstile_site_key: str | None = Field(alias="turnstileSiteKey")
+ """The Turnstile site key the page renders its widget with; null unless anonymousCheckout"""
+
+
+class PublicMeter(KaitenModel):
+ entitlement_slug: str = Field(alias="entitlementSlug")
+
+ sale_unit_factor: str = Field(alias="saleUnitFactor")
+ """Measured units in one sale unit"""
+
+ sale_unit_plural: str | None = Field(alias="saleUnitPlural")
+ """The sale unit's plural label; null when the entitlement has none"""
+
+ sale_unit_singular: str | None = Field(alias="saleUnitSingular")
+ """The sale unit's singular label; null when the entitlement has none"""
+
+
+class PublicPlan(KaitenModel):
+ compatible_addon_families: list[str] = Field(alias="compatibleAddonFamilies")
+ """Public add-on families whose listed version fits this plan"""
+
+ currency: str | None
+ """ISO 4217 code of the plan's prices; null when it has none"""
+
+ description: str
+
+ entitlements: list[PublicPlanEntitlement]
+ """User-facing entitlements the plan grants, in display order"""
+
+ family_slug: str = Field(alias="familySlug")
+
+ license_id: str = Field(alias="licenseId")
+
+ license_slug: str = Field(alias="licenseSlug")
+ """The version's own slug"""
+
+ lifecycle_state: Literal["PUBLISHED"] = Field(alias="lifecycleState")
+ """Always PUBLISHED: only the default version is listed, and a default version is published"""
+
+ name: str
+
+ prices: list[PublicPrice]
+ """ACTIVE prices, in display order"""
+
+ pricing_type: PricingType | str = Field(alias="pricingType")
+ """CUSTOM: priced on request; render selfServeCtaUrl"""
+
+ requires_payment_method: bool = Field(alias="requiresPaymentMethod")
+ """A card is captured before subscribing, even when nothing is due"""
+
+ self_serve: bool = Field(alias="selfServe")
+ """Whether a customer can subscribe to this plan alone: not CUSTOM, with a default ACTIVE
+ flat-fee price, and either FREE or sold through a provider that captures payment
+ methods. Otherwise render selfServeCtaUrl.
+ """
+
+ self_serve_cta_url: str | None = Field(alias="selfServeCtaUrl")
+ """Where to send a customer who cannot subscribe alone ("Contact us")"""
+
+ trial_period_days: int | None = Field(alias="trialPeriodDays")
+ """Length of the free trial; null when the plan has none"""
+
+
+class PublicPlanEntitlement(KaitenModel):
+ description: str | None
+
+ display_order: int = Field(alias="displayOrder")
+
+ icon: str | None
+
+ limit_cap_exceeded_overage_percent: int | None = Field(alias="limitCapExceededOveragePercent")
+ """How far usage may exceed a NUMBER grant before it is refused: 0 is a hard limit, -1 is
+ unlimited; null for other types
+ """
+
+ name: str
+
+ sale_unit_factor: str | None = Field(alias="saleUnitFactor")
+
+ sale_unit_plural: str | None = Field(alias="saleUnitPlural")
+
+ sale_unit_singular: str | None = Field(alias="saleUnitSingular")
+
+ slug: str
+
+ type: EntitlementType | str
+
+ unit_plural: str | None = Field(alias="unitPlural")
+
+ unit_singular: str | None = Field(alias="unitSingular")
+
+ unlimited: bool
+ """A NUMBER grant with no limit"""
+
+ value: Any
+ """The granted value: a boolean, a number, or a configuration object"""
+
+
+class PublicPrice(KaitenModel):
+ billing_model: BillingModel | str = Field(alias="billingModel")
+ """FLAT_FEE: per billing period. USAGE_BASED: per sale unit of usage. OVERAGE: per sale
+ unit of usage above the limit. Never sum metered prices into a headline.
+ """
+
+ billing_period: BillingPeriod | str | None = Field(alias="billingPeriod")
+ """The period a FLAT_FEE price is charged for; null on metered prices"""
+
+ billing_timing: BillingTiming | str = Field(alias="billingTiming")
+
+ currency: str
+
+ display_label: str | None = Field(alias="displayLabel")
+ """The price's label; null when it has none"""
+
+ display_order: int = Field(alias="displayOrder")
+
+ id: str
+
+ is_default: bool = Field(alias="isDefault")
+ """The FLAT_FEE price shown for its billing period"""
+
+ metered: PublicMeter | None
+ """What a metered price measures; null on FLAT_FEE prices"""
+
+ unit_amount: int | None = Field(alias="unitAmount")
+ """The amount in minor units, when it is a whole number of them; null otherwise (render
+ unitAmountDecimal)
+ """
+
+ unit_amount_decimal: str = Field(alias="unitAmountDecimal")
+ """The amount in minor units, up to 12 decimal places"""
+
+
+class PublicSurface(KaitenModel):
+ enabled: bool
+ """Whether publishable keys and customer sessions work for the organization: billing is on
+ for it
+ """
+
+
+class PublishableKey(KaitenModel):
+ allowed_origins: list[str] = Field(alias="allowedOrigins")
+ """Browser origins allowed to send the key. Empty: no browser origin may; requests without
+ an Origin header (server-side rendering) still may.
+ """
+
+ anonymous_checkout: bool = Field(alias="anonymousCheckout")
+ """Whether the key may start an anonymous checkout (POST /public/checkout-sessions). Needs
+ a Turnstile site key and secret.
+ """
+
+ created_at: datetime = Field(alias="createdAt")
+
+ id: str
+ """Unique identifier of the key"""
+
+ key_hint: str = Field(alias="keyHint")
+ """The last four characters of the key"""
+
+ label: str
+ """What the key is for, to tell keys apart"""
+
+ last_used_at: datetime | None = Field(alias="lastUsedAt")
+ """When the key last authenticated a request, to the minute; null before its first"""
+
+ revoked_at: datetime | None = Field(alias="revokedAt")
+ """When the key was revoked; null while it is live"""
+
+ turnstile_secret_configured: bool = Field(alias="turnstileSecretConfigured")
+ """Whether a Turnstile secret is stored for the key. The secret itself is write-only."""
+
+ turnstile_site_key: str | None = Field(alias="turnstileSiteKey")
+ """The Turnstile site key the page renders its widget with; null without one. Public: the
+ catalogue hands it to the page.
+ """
+
+ updated_at: datetime = Field(alias="updatedAt")
+
+
+class PublishableKeyCreated(KaitenModel):
+ allowed_origins: list[str] = Field(alias="allowedOrigins")
+ """Browser origins allowed to send the key. Empty: no browser origin may; requests without
+ an Origin header (server-side rendering) still may.
+ """
+
+ anonymous_checkout: bool = Field(alias="anonymousCheckout")
+ """Whether the key may start an anonymous checkout (POST /public/checkout-sessions). Needs
+ a Turnstile site key and secret.
+ """
+
+ created_at: datetime = Field(alias="createdAt")
+
+ id: str
+ """Unique identifier of the key"""
+
+ key: str
+ """The key itself. Shown once: only its digest is stored."""
+
+ key_hint: str = Field(alias="keyHint")
+ """The last four characters of the key"""
+
+ label: str
+ """What the key is for, to tell keys apart"""
+
+ last_used_at: datetime | None = Field(alias="lastUsedAt")
+ """When the key last authenticated a request, to the minute; null before its first"""
+
+ revoked_at: datetime | None = Field(alias="revokedAt")
+ """When the key was revoked; null while it is live"""
+
+ turnstile_secret_configured: bool = Field(alias="turnstileSecretConfigured")
+ """Whether a Turnstile secret is stored for the key. The secret itself is write-only."""
+
+ turnstile_site_key: str | None = Field(alias="turnstileSiteKey")
+ """The Turnstile site key the page renders its widget with; null without one. Public: the
+ catalogue hands it to the page.
+ """
+
+ updated_at: datetime = Field(alias="updatedAt")
+
+
+class PublishableKeyEventDelivered(KaitenModel):
+ allowed_origins: list[str] = Field(alias="allowedOrigins")
+
+ anonymous_checkout: bool = Field(alias="anonymousCheckout")
+
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
+
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
+
+ id: str
+
+ key_hint: str = Field(alias="keyHint")
+ """The last four characters of the key"""
+
+ label: str
+
+ revoked_at: datetime | None = Field(alias="revokedAt")
+
+ turnstile_site_key: str | None = Field(alias="turnstileSiteKey")
+
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
+ """
+
+
+class PushFailures(KaitenModel):
+ count: int
+
+ oldest_failed_at: datetime | None = Field(alias="oldestFailedAt")
+
+
+class QueuedInvoice(KaitenModel):
+ boundary_at: datetime = Field(alias="boundaryAt")
+ """The boundary the invoice bills. With the kind, it identifies the invoice within its
+ subscription
+ """
+
+ collection_method: CollectionMethod | str = Field(alias="collectionMethod")
+
+ created_at: datetime = Field(alias="createdAt")
+
+ currency: str
+
+ customer_name: str = Field(alias="customerName")
+
+ customer_slug: str = Field(alias="customerSlug")
+ """As it was when the invoice was composed"""
+
+ days_until_due: int | None = Field(alias="daysUntilDue")
+
+ discount_total: int = Field(alias="discountTotal")
+ """Sum of the discounts, in minor units"""
+
+ due_at: datetime | None = Field(alias="dueAt")
+ """issuedAt + daysUntilDue"""
+
+ handoff: InvoiceHandoff
+
+ handoff_status: HandoffStatus | str = Field(alias="handoffStatus")
+ """PENDING: waiting in the handoff queue for the organization's accounting system"""
+
+ hold_reason: InvoiceHoldReason | str | None = Field(alias="holdReason")
+ """Why a DRAFT is held: the usage journal it was measured from failed a consistency check
+ (LEDGER_*), or its provider cannot take that many lines (PROVIDER_ITEM_LIMIT)
+ """
+
+ id: str
+ """The invoice's identifier: the key an accounting system deduplicates on"""
+
+ instance_name: str = Field(alias="instanceName")
+
+ instance_slug: str = Field(alias="instanceSlug")
+
+ issued_at: datetime | None = Field(alias="issuedAt")
+
+ kind: InvoiceKind | str
+ """ACTIVATION bills a subscription's first period, RENEWAL the period that ended and the
+ one that starts, FINAL what is left when a subscription ends
+ """
+
+ license_slug: str = Field(alias="licenseSlug")
+
+ overdue: bool
+ """Whether the invoice is overdue when read, the grace period of automatic collection
+ included: the predicate of ?overdue=true. Computed by the invoice list and the invoice
+ read; false in the answers of writes and in event payloads.
+ """
+
+ paid_at: datetime | None = Field(alias="paidAt")
+
+ provider_kind: BillingProviderKind | str = Field(alias="providerKind")
+ """Who collects it. NOOP: the organization itself, through the handoff queue"""
+
+ service_from: datetime = Field(alias="serviceFrom")
+ """Earliest start of its lines' service periods"""
+
+ service_to: datetime = Field(alias="serviceTo")
+ """Latest end of its lines' service periods"""
+
+ status: InvoiceStatus | str
+ """MANUAL: issued, for the organization to collect. PAID, UNCOLLECTIBLE and VOID are final.
+ DRAFT: held, waiting for a release or a recompose
+ """
+
+ subtotal: int
+ """Sum of the lines, in minor units"""
+
+ total: int
+ """subtotal − discountTotal, in minor units"""
+
+ updated_at: datetime = Field(alias="updatedAt")
+
+
+class Reconciliation(KaitenModel):
+ discounts: list[DiscountDifference]
+ """Allocations of DISCOUNT lines the provider applied with another amount, or not at all"""
+
+ due_date: DueDateDifference | None = Field(alias="dueDate")
+ """A SEND_INVOICE invoice the provider gives another due date than Kaiten's dueAt"""
+
+ extra_discounts: list[ExtraDiscount] = Field(alias="extraDiscounts")
+ """Discounts the provider applied that Kaiten did not create, such as a coupon added in its
+ dashboard
+ """
+
+ extra_in_provider: list[str] = Field(alias="extraInProvider")
+ """Provider lines Kaiten does not have"""
+
+ inclusive_tax: bool = Field(alias="inclusiveTax")
+ """Whether the provider's subtotal was compared, its tax being included in the amounts"""
+
+ lines: list[LineDifference]
+
+ missing_in_provider: list[str] = Field(alias="missingInProvider")
+ """Kaiten lines the provider does not have"""
+
+ totals: TotalsDifference
+
+ warnings: list[str] | None = None
+ """What the comparison noticed without calling it a mismatch (§12.7, V31-60)"""
+
+
+class Redemption(KaitenModel):
+ applications_count: int = Field(alias="applicationsCount")
+ """Invoices a PRICE voucher discounted"""
+
+ applications_max: int | None = Field(alias="applicationsMax")
+ """1 for ONE_TIME, durationInPeriods for REPEATING; null for FOREVER"""
+
+ code_hint: str = Field(alias="codeHint")
+
+ effective_expires_at: datetime | None = Field(alias="effectiveExpiresAt")
+ """When a boost stops applying; null for a PRICE voucher, counted in invoices, and for
+ FOREVER
+ """
+
+ effective_starts_at: datetime = Field(alias="effectiveStartsAt")
+
+ expired_at: datetime | None = Field(alias="expiredAt")
+
+ id: str
+
+ instance_slug: str = Field(alias="instanceSlug")
+
+ redeemed_at: datetime = Field(alias="redeemedAt")
+
+ revoked_at: datetime | None = Field(alias="revokedAt")
+
+ revoked_reason: str | None = Field(alias="revokedReason")
+
+ status: RedemptionStatus | str
+
+ voucher_id: str = Field(alias="voucherId")
+
+ voucher_name: str = Field(alias="voucherName")
+
+ voucher_type: VoucherType | str = Field(alias="voucherType")
+
+
+class RedemptionRules(KaitenModel):
+ annual_only: bool | None = Field(default=None, alias="annualOnly")
+ """Only for an instance with a live ANNUAL subscription"""
+
+ first_time_only: bool | None = Field(default=None, alias="firstTimeOnly")
+ """Only for a customer none of whose instances has paid an invoice"""
+
+ minimum_subscription_amount: MinimumAmount | None = Field(
+ default=None, alias="minimumSubscriptionAmount"
+ )
+ """Only for a live subscription whose base price is at least this. Strictly positive; omit
+ it for no minimum (0 is refused with .InvalidRedemptionRules).
+ """
+
+
+class Release(KaitenModel):
+ """An immutable, versioned set of components."""
+
+ component_ids: list[str] | None = Field(default=None, alias="componentIds")
+ """List of component IDs to link to this release"""
+
+ components: list[Component] | None = None
+ """List of components included in this release, fixed at creation"""
+
+ created_at: datetime = Field(alias="createdAt")
+ """Timestamp when the release was created"""
+
+ created_by: User = Field(alias="createdBy")
+ """User who created this release"""
+
+ description: str | None = None
+ """Description of the release"""
+
+ id: str
+ """Unique identifier for the release"""
+
+ slug: str | None = None
+ """Optional URL-friendly identifier, unique per organization. Auto-generated if not provided."""
+
+ version: str
+ """Version of the release"""
+
+
+class ReportedEntitlementUsage(KaitenModel):
+ current_period_end: datetime | None = Field(default=None, alias="currentPeriodEnd")
+ """End of the current usage window (exclusive). Null for a lifetime entitlement (no
+ configured reset period).
+ """
+
+ current_period_start: datetime | None = Field(default=None, alias="currentPeriodStart")
+ """Start of the current usage window (inclusive). Null for a lifetime entitlement (no
+ configured reset period).
+ """
+
+ entitlement_id: str = Field(alias="entitlementId")
+ """Unique identifier for the entitlement"""
+
+ entitlement_slug: str = Field(alias="entitlementSlug")
+ """URL-friendly identifier of the entitlement"""
+
+ license_id: str = Field(alias="licenseId")
+ """Unique identifier for the license to which this entitlement usage belongs"""
+
+ license_slug: str = Field(alias="licenseSlug")
+ """URL-friendly identifier of the license"""
+
+ limit: EntitlementValue | None = None
+ """The effective value this usage is measured against (the licence's grant, with the
+ instance's add-ons and boosts applied; source and provenance say which), discriminated
+ by the 'type' field. Absent when nothing grants the entitlement a value; -1 means
+ unlimited for a NUMBER entitlement.
+ """
+
+ limit_cap_exceeded_overage_percent: int | None = Field(
+ default=None, alias="limitCapExceededOveragePercent"
+ )
+ """How far above limit usage is still accepted, in percent: -1 unlimited, 0 a hard limit.
+ Absent for BOOLEAN and CONFIG.
+ """
+
+ provenance: Provenance | None
+ """What the licence, the add-ons and the boosts each contribute. Null on a replay
+ (Idempotent-Replayed): the journal does not store it.
+ """
+
+ source: EntitlementSource | str | None
+ """license: the licence grants it (add-ons, boosts and an exception may change it); addon:
+ an add-on attached does; exception: only the instance's exception does. Null on a replay
+ (Idempotent-Replayed): the journal does not store it.
+ """
+
+ transaction_id: str | None = Field(alias="transactionId")
+ """The Idempotency-Key this report was accepted under, echoed on the first answer and on
+ every replay; null when the request carried none. A client can tell from it that the
+ server honours the key.
+ """
+
+ value: EntitlementValue
+ """Resolved entitlement value, discriminated by the 'type' field"""
+
+
+class RolloutDateTargeting(KaitenModel):
+ """A targeting rule that shifts traffic between variants over a date range."""
+
+ end: RolloutStep
+
+ name: str
+
+ rule: str
+ """CEL expression for basicTargeting evaluation"""
+
+ start: RolloutStep
+
+ type: Literal["rollout_date"]
+
+
+class RolloutDateVariant(KaitenModel):
+ """A default variant that shifts traffic between variants over a date range."""
+
+ end: RolloutStep
+
+ start: RolloutStep
+
+ type: Literal["rollout_date"]
+
+
+class RolloutPercentageTargeting(KaitenModel):
+ """A targeting rule that splits the contexts it matches between variants."""
+
+ distribution: dict[str, int]
+
+ name: str
+
+ rule: str
+ """CEL expression for basicTargeting evaluation"""
+
+ type: Literal["rollout_percentage"]
+
+
+class RolloutPercentageVariant(KaitenModel):
+ """A default variant that splits traffic between variants by weight."""
+
+ distribution: dict[str, int]
+
+ type: Literal["rollout_percentage"]
+
+
+class RolloutStep(KaitenModel):
+ """One end of a date-based rollout."""
+
+ date: datetime
+
+ percentage: int
+
+ variant: str
+
+
+class ScheduledChange(KaitenModel):
+ effective_at: datetime = Field(alias="effectiveAt")
+ """The boundary it applies at: the current period's end"""
+
+ license_slug: str = Field(alias="licenseSlug")
+ """The licence version of that price"""
+
+ price: Price
+ """The FLAT_FEE price the subscription moves to"""
+
+ scheduled_at: datetime = Field(alias="scheduledAt")
+
+
+class ServiceAccount(KaitenModel):
+ """A machine identity inside an organization, owning API tokens."""
+
+ created_at: datetime | None = Field(default=None, alias="createdAt")
+
+ created_by: User | None = Field(default=None, alias="createdBy")
+
+ external_id: str | None = Field(default=None, alias="externalId")
+
+ id: str | None = None
+
+ name: str
+
+ slug: str | None = None
+ """Optional URL-friendly identifier, unique per organization. Auto-generated if not provided."""
+
+ tokens: list[Token] | None
+
+
+class SessionAddon(KaitenModel):
+ addon_slug: str = Field(alias="addonSlug")
+
+ family_slug: str = Field(alias="familySlug")
+
+ max_quantity: int | None = Field(alias="maxQuantity")
+ """The most one instance may hold; null when unbounded"""
+
+ name: str
+
+ pending_quantity: int | None = Field(alias="pendingQuantity")
+ """A decrease scheduled for the next renewal, 0 for a removal; null when nothing is scheduled"""
+
+ prices: list[PublicPrice]
+ """What it is billed: the flat fee of the subscription's period and the metered prices.
+ Quantity changes are billed from the next renewal, with no proration.
+ """
+
+ quantity: int
+ """0 when the add-on is not held"""
+
+
+class SessionAddonSelection(KaitenModel):
+ addon_slug: str = Field(alias="addonSlug")
+
+ quantity: int
+
+
+class SessionBoost(KaitenModel):
+ entitlement_slug: str = Field(alias="entitlementSlug")
+
+ modifier_type: GrantModifier | str = Field(alias="modifierType")
+
+ modifier_value: str | None = Field(default=None, alias="modifierValue")
+ """Absent for UNLIMITED, and on a BOOLEAN or CONFIG boost"""
+
+ value: bool | dict[str, Any] | None = None
+ """A BOOLEAN or CONFIG boost's value: true or false, or the document"""
+
+
+class SessionCheckout(KaitenModel):
+ activation_invoice: SessionInvoice | None = Field(default=None, alias="activationInvoice")
+ """subscribed: the invoice of the first period; absent during a trial"""
+
+ due_today: DueToday | None = Field(default=None, alias="dueToday")
+ """preview: what the checkout bills today; zero, with no lines, during a trial"""
+
+ expires_at: datetime | None = Field(default=None, alias="expiresAt")
+ """requires_payment_method: when the setup page expires"""
+
+ payment: Payment | None = None
+ """subscribed: how paying the activation invoice went"""
+
+ setup_session_id: str | None = Field(default=None, alias="setupSessionId")
+ """requires_payment_method: send it back as setupSessionId once the customer returns"""
+
+ setup_url: str | None = Field(default=None, alias="setupUrl")
+ """requires_payment_method: the provider's page where the customer saves a payment method"""
+
+ status: SessionCheckoutStatus | str
+
+ subscription: SessionSubscription | None = None
+ """subscribed: the subscription started"""
+
+ trial_ends_at: datetime | None = Field(default=None, alias="trialEndsAt")
+ """preview: when the trial would end, if there is one"""
+
+
+class SessionDiscount(KaitenModel):
+ applies_to: DiscountAppliesTo | str = Field(alias="appliesTo")
+
+ currency: str | None = None
+ """The currency of a FIXED_AMOUNT discount"""
+
+ type: DiscountType | str
+
+ value: str
+ """A percentage in (0, 100], or an amount in minor units of currency"""
+
+
+class SessionEventDelivered(KaitenModel):
+ billing_period: BillingPeriod | str = Field(alias="billingPeriod")
+
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
+
+ client_reference_id: str | None = Field(alias="clientReferenceId")
+ """The vendor's opaque reference, as given at the start"""
+
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
+
+ customer_slug: str | None = Field(alias="customerSlug")
+ """The customer the completion created; null before"""
+
+ email: str
+ """The address the visitor gave at the start"""
+
+ instance_slug: str | None = Field(alias="instanceSlug")
+ """The instance the completion created; null before"""
+
+ license_slug: str = Field(alias="licenseSlug")
+
+ session_id: str = Field(alias="sessionId")
+
+ status: CheckoutSessionEventStatus | str
+ """OPEN on started, COMPLETED on completed; on abandoned, ABANDONED (never completed, slugs
+ null) or EXPIRED (its INCOMPLETE subscription expired or was canceled unpaid, slugs set)
+ """
+
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
+ """
+
+
+class SessionInvoice(KaitenModel):
+ currency: str
+
+ discount_total: int = Field(alias="discountTotal")
+ """Minor units"""
+
+ due_at: datetime | None = Field(alias="dueAt")
+
+ external_reference: str | None = Field(alias="externalReference")
+ """The vendor's own number for an invoice it collects itself"""
+
+ hosted_invoice_url: str | None = Field(alias="hostedInvoiceUrl")
+ """Where the customer pays the invoice, or confirms a payment that needs authenticating"""
+
+ id: str
+
+ invoice_pdf_url: str | None = Field(alias="invoicePdfUrl")
+
+ issued_at: datetime | None = Field(alias="issuedAt")
+
+ kind: InvoiceKind | str
+
+ lines: list[SessionLine]
+
+ paid_at: datetime | None = Field(alias="paidAt")
+
+ provider_invoice_number: str | None = Field(alias="providerInvoiceNumber")
+
+ service_from: datetime = Field(alias="serviceFrom")
+
+ service_to: datetime = Field(alias="serviceTo")
+
+ status: SessionInvoiceStatus | str
+ """PUSHED: issued and awaiting payment. PAYMENT_FAILED: a charge failed; pay on
+ hostedInvoiceUrl. MANUAL: issued, the vendor collects it.
+ """
+
+ subtotal: int
+ """Minor units"""
+
+ total: int
+ """Minor units"""
+
+
+class SessionLine(KaitenModel):
+ amount: int
+ """Minor units; negative on a DISCOUNT line"""
+
+ description: str
+
+ label: str
+
+ quantity: str
+ """In sale units, a decimal string"""
+
+ seq: int
+
+ service_from: datetime = Field(alias="serviceFrom")
+
+ service_to: datetime = Field(alias="serviceTo")
+
+ type: InvoiceLineType | str
+
+ unit_amount_decimal: str | None = Field(alias="unitAmountDecimal")
+ """Minor units; null on a DISCOUNT line"""
+
+
+class SessionPortal(KaitenModel):
+ add_ons: list[SessionAddon] = Field(alias="addOns")
+ """The add-ons the instance holds"""
+
+ branding: PublicBranding
+
+ capabilities: PortalCapabilities
+
+ customer: PortalCustomer
+
+ entitlements: list[PortalEntitlement]
+ """The instance's effective entitlements: its licence's, raised or added by add-ons and
+ boosts
+ """
+
+ instance: PortalInstance | None
+ """The session's instance; null for a session bound to the customer only"""
+
+ instances: list[PortalInstanceSummary]
+ """The customer's instances, for a session bound to the customer only; empty otherwise"""
+
+ payment_method: PortalPaymentMethod | None = Field(alias="paymentMethod")
+ """The payment method the customer's invoices are charged to; null when none is saved"""
+
+ subscription: SessionSubscription | None
+ """The instance's subscription; null when it has none, or the session is bound to no instance"""
+
+ trial: PortalTrial | None
+ """TRIAL only: when the trial ends, when its reminder is due, what the conversion would
+ charge and to which card (V32-09); null otherwise
+ """
+
+ upcoming_invoice: PortalUpcomingInvoice | None = Field(alias="upcomingInvoice")
+ """What the next boundary will bill, on usage so far; null without a live subscription"""
+
+ updated_at: datetime = Field(alias="updatedAt")
+ """When this snapshot was read"""
+
+ vouchers: list[SessionRedemption]
+ """The instance's ACTIVE redemptions. Never a code."""
+
+
+class SessionRedemption(KaitenModel):
+ applications_count: int = Field(alias="applicationsCount")
+ """Invoices its PRICE part discounted; 0 without one"""
+
+ applications_max: int | None = Field(alias="applicationsMax")
+ """How many it discounts in all: 1 for ONE_TIME, its periods for REPEATING; null for
+ FOREVER and without a PRICE part
+ """
+
+ applies_from_invoice: Literal["next"] | None = Field(alias="appliesFromInvoice")
+ """next: a PRICE part that has not discounted an invoice yet -- it applies from the next
+ invoice composed, never to one already issued; null otherwise
+ """
+
+ effective_expires_at: datetime | None = Field(alias="effectiveExpiresAt")
+ """When a boost or a flag grant stops applying; null when it does not expire, and for a
+ PRICE voucher, counted in invoices
+ """
+
+ effective_starts_at: datetime = Field(alias="effectiveStartsAt")
+
+ name: str
+
+ status: RedemptionStatus | str
+
+ voucher_type: VoucherType | str = Field(alias="voucherType")
+
+
+class SessionScheduledChange(KaitenModel):
+ effective_at: datetime = Field(alias="effectiveAt")
+ """When it applies: the current period's end"""
+
+ price: PublicPrice
+ """The price the subscription moves to"""
+
+
+class SessionSubscription(KaitenModel):
+ base_price: PublicPrice = Field(alias="basePrice")
+
+ billing_period: BillingPeriod | str = Field(alias="billingPeriod")
+
+ cancel_at_period_end: bool = Field(alias="cancelAtPeriodEnd")
+ """The subscription ends at currentPeriodEnd; reactivate it before then to keep it"""
+
+ canceled_at: datetime | None = Field(alias="canceledAt")
+ """When the subscription was canceled"""
+
+ cancellation_reason: CancellationReason | str | None = Field(alias="cancellationReason")
+ """Why the system ended it; null when it did not, a vendor's own reason is never shown"""
+
+ collection_method: CollectionMethod | str = Field(alias="collectionMethod")
+
+ currency: str
+
+ current_period_end: datetime = Field(alias="currentPeriodEnd")
+ """When the current period ends: the renewal date, or the trial's end"""
+
+ current_period_start: datetime = Field(alias="currentPeriodStart")
+
+ incomplete_expires_at: datetime | None = Field(alias="incompleteExpiresAt")
+ """INCOMPLETE only: when the checkout ends unpaid"""
+
+ provider_kind: BillingProviderKind | str = Field(alias="providerKind")
+
+ scheduled_change: SessionScheduledChange | None = Field(alias="scheduledChange")
+ """A plan change the vendor scheduled for the next boundary"""
+
+ status: SubscriptionStatus | str
+
+ trial_ends_at: datetime | None = Field(alias="trialEndsAt")
+
+
+class SessionVoucher(KaitenModel):
+ boosts: list[SessionBoost] | None = None
+ """ENTITLEMENT_BOOST: what it raises"""
+
+ discount: SessionDiscount | None = None
+ """PRICE: what it takes off"""
+
+ duration: VoucherDuration | str
+ """PRICE: how many invoices it discounts (one, durationInPeriods, or every one).
+ ENTITLEMENT_BOOST and FLAG_GRANT: how long it lasts, in billing periods. COMPOSITE:
+ both, each for its part, which it carries each of.
+ """
+
+ duration_in_periods: int | None = Field(default=None, alias="durationInPeriods")
+
+ flag_grants: list[FlagGrant] | None = Field(default=None, alias="flagGrants")
+ """FLAG_GRANT: the flag variants it grants"""
+
+ name: str
+
+ voucher_type: VoucherType | str = Field(alias="voucherType")
+
+
+class SessionVoucherValidity(KaitenModel):
+ valid: bool
+
+ voucher: SessionVoucher | None = None
+ """What the voucher gives, when valid. Never its code."""
+
+
+class StartedSubscription(KaitenModel):
+ activation_invoice: InvoiceSummary | None = Field(default=None, alias="activationInvoice")
+ """The invoice of the first period, when the base price bills in advance; absent otherwise.
+ Charged automatically, it is pushed and charged before this answer when the provider
+ answers within 20 seconds: its status says how it went.
+ """
+
+ anchor_at: datetime = Field(alias="anchorAt")
+ """The instant its periods are counted from"""
+
+ base_price: Price = Field(alias="basePrice")
+ """The FLAT_FEE price the subscription is pinned to, deprecated or not"""
+
+ billing_period: BillingPeriod | str = Field(alias="billingPeriod")
+
+ cancel_at_period_end: bool = Field(alias="cancelAtPeriodEnd")
+
+ cancel_requested_at: datetime | None = Field(alias="cancelRequestedAt")
+
+ canceled_at: datetime | None = Field(alias="canceledAt")
+
+ cancellation_reason: str | None = Field(alias="cancellationReason")
+
+ collection_method: CollectionMethod | str = Field(alias="collectionMethod")
+ """Effective: the subscription's own, else the organization's default"""
+
+ collection_method_override: str | None = Field(alias="collectionMethodOverride")
+ """The subscription's own collection method; null when it takes the organization's"""
+
+ created_at: datetime = Field(alias="createdAt")
+
+ currency: str
+
+ current_period_end: datetime = Field(alias="currentPeriodEnd")
+ """The next boundary: the period closes, and its invoice is composed, after it"""
+
+ current_period_start: datetime = Field(alias="currentPeriodStart")
+
+ customer_name: str = Field(alias="customerName")
+
+ customer_slug: str = Field(alias="customerSlug")
+
+ days_until_due: int = Field(alias="daysUntilDue")
+ """Effective: the subscription's own, else the organization's default"""
+
+ days_until_due_override: int | None = Field(alias="daysUntilDueOverride")
+ """The subscription's own payment terms; null when it takes the organization's"""
+
+ id: str
+
+ incomplete_expires_at: datetime | None = Field(alias="incompleteExpiresAt")
+ """When a checkout waiting for its payment ends unpaid: INCOMPLETE, or a free subscription
+ that carries one
+ """
+
+ instance_name: str = Field(alias="instanceName")
+
+ instance_slug: str = Field(alias="instanceSlug")
+ """As it was at the subscription's last write"""
+
+ past_due_since: datetime | None = Field(alias="pastDueSince")
+
+ pending_license_slug: str | None = Field(alias="pendingLicenseSlug")
+ """The licence version a checkout waiting for its payment moves the instance to once paid"""
+
+ provider_kind: BillingProviderKind | str = Field(alias="providerKind")
+ """Who collects its invoices. NOOP: the organization itself, through the handoff queue"""
+
+ reminder_at: datetime | None = Field(alias="reminderAt")
+ """When trial_will_end is (or was) emitted: trialEndsAt minus the family's
+ trialReminderDays as it was when the trial started; set with trialEndsAt
+ """
+
+ scheduled_change: ScheduledChange | None = Field(alias="scheduledChange")
+ """A plan change waiting for the next boundary"""
+
+ started_at: datetime = Field(alias="startedAt")
+ """When billing started: usage before it is never billed"""
+
+ status: SubscriptionStatus | str
+
+ trial_ends_at: datetime | None = Field(alias="trialEndsAt")
+ """When the trial ends, or ended: usage before it is never billed"""
+
+ updated_at: datetime = Field(alias="updatedAt")
+
+
+class StatusChangeDelivered(KaitenModel):
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
+
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
+
+ from_: SubscriptionStatus | str = Field(alias="from")
+
+ instance_billing_id: str = Field(alias="instanceBillingId")
+
+ instance_slug: str = Field(alias="instanceSlug")
+
+ reason: SubscriptionStatusChangeReason | str
+
+ to: SubscriptionStatus | str
+
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
+ """
+
+
+class SyncFailureDelivered(KaitenModel):
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
+
+ consecutive_failures: int = Field(alias="consecutiveFailures")
+
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
+
+ last_sync_error: str = Field(alias="lastSyncError")
+
+ last_synced_at: datetime = Field(alias="lastSyncedAt")
+
+ provider_kind: str = Field(alias="providerKind")
+
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
+ """
+
+
+class SyncReport(KaitenModel):
+ providers: list[Outcome]
+
+
+class SystemTokenIssuance(KaitenModel):
+ """A platform credential minted a token inside this organization."""
+
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
+
+ credential_kind: str = Field(alias="credentialKind")
+ """Credential class that performed the mint; "platform" is the system:kaiten identity"""
+
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
+
+ expires_at: datetime | None = Field(default=None, alias="expiresAt")
+ """Absent when the minted credential does not expire"""
+
+ issued_at: datetime = Field(alias="issuedAt")
+ """When the credential was minted"""
+
+ issued_token_id: str = Field(alias="issuedTokenId")
+ """The credential that was minted"""
+
+ name: str
+ """Human-readable label the caller gave the minted credential"""
+
+ platform_token_id: str = Field(alias="platformTokenId")
+ """Platform credential that performed the mint; revoking it revokes everything it issued"""
+
+ scopes: list[str] | None
+ """Scopes granted to the minted credential"""
+
+ slug: str
+ """Server-assigned identifier of the minted credential"""
+
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
+ """
+
+
+class TargetingContext(KaitenModel):
+ roots: list[TargetingContextNode]
+ """Identifiers the server guarantees, and what is reachable below each. Not a closed set: a
+ host may target on its own attributes too
+ """
+
+
+class TargetingContextNode(KaitenModel):
+ description: str | None = None
+ """What this value means, shown by the editor"""
+
+ entries: list[TargetingContextNode] | None = None
+ """Shape of each known key, for a map whose entries differ. Named after the key"""
+
+ fields: list[TargetingContextNode] | None = None
+ """Named children, for an object"""
+
+ known_keys: list[str] | None = Field(default=None, alias="knownKeys")
+ """Keys this map currently has, for completion. Not a closed set"""
+
+ name: str
+ """Name as it is written in a rule"""
+
+ optional: bool | None = None
+ """Whether this value may be absent, in which case has() is worth using"""
+
+ type: TargetingFactType | str
+ """Shape of this node's value"""
+
+ values: TargetingContextNode | None = None
+ """Shape shared by every entry, for a map"""
+
+
+class TargetingRuleIssue(KaitenModel):
+ column: int
+ """1-based column the problem starts on, or 0 when it has no position"""
+
+ end_column: int | None = Field(default=None, alias="endColumn")
+ """1-based column just past the problem, or 0 when only a point is known"""
+
+ end_line: int | None = Field(default=None, alias="endLine")
+ """1-based line the problem ends on, or 0 when only a point is known"""
+
+ line: int
+ """1-based line the problem starts on, or 0 when it has no position"""
+
+ message: str
+ """What is wrong, in the same words the write path refuses it with"""
+
+
+class TargetingRuleRehearsal(KaitenModel):
+ evaluation_error: str | None = Field(default=None, alias="evaluationError")
+ """Why the engine could not answer, when it could not — e.g. the rule reads a host
+ attribute this context does not carry. At a real evaluation the rule would simply not
+ match
+ """
+
+ facts: dict[str, Any]
+ """The __kaiten namespace as enriched for this context — what a rule actually reads. Empty
+ sub-roots mean the context resolved nothing for them
+ """
+
+ issues: list[TargetingRuleIssue]
+ """Problems the lint found. The rule is only run when there are none"""
+
+ matched: bool
+ """Whether the rule matched this context. Meaningful only when valid and evaluationError is
+ empty
+ """
+
+ valid: bool
+ """Whether the rule lints clean"""
+
+
+class TargetingRuleVerdict(KaitenModel):
+ issues: list[TargetingRuleIssue]
+ """Every problem found, positioned where it is written. Empty when the rule is accepted"""
+
+ valid: bool
+ """Whether the write path would accept this rule"""
+
+
+class Token(KaitenModel):
+ """A token's metadata. Never its secret."""
+
+ created_at: datetime = Field(alias="createdAt")
+
+ created_by: User = Field(alias="createdBy")
+
+ expires_at: datetime | None = Field(default=None, alias="expiresAt")
+
+ id: str | None = None
+
+ name: str
+
+ revoked_at: datetime | None = Field(default=None, alias="revokedAt")
+
+ revoked_by: User | None = Field(default=None, alias="revokedBy")
- description: str
- """Detailed description of the license capabilities and restrictions"""
+ scopes: list[str] | None
- family_id: str | None = Field(default=None, alias="familyId")
- """Identifier of the license family this license is a version of. Stable across renames and
- new versions: two licenses sharing it are the same product. Always present in responses.
- On create, optional: names the family this license is a new version of, as familySlug
- does by slug; omit both to create a new family. On update, optional and must be the
- license's own family: a version cannot move between families
- (UpdateLicense.FamilyNotReassignable).
- """
+ service_account_id: str | None = Field(default=None, alias="serviceAccountId")
- family_slug: str | None = Field(default=None, alias="familySlug")
- """Write-only on create: slug of the family this license is a new version of. familyId
- names the same family by identifier; sent together they must agree. Omit both to create
- a new family, of which this license becomes version 1. Never returned -- see familyId.
- Refused on update: a version cannot move between families.
+ slug: str | None = None
+ """Optional URL-friendly identifier, unique per service account. Auto-generated if not
+ provided.
"""
- id: str
- """Unique identifier for the license"""
- is_default: bool = Field(alias="isDefault")
- """Whether this version is the one its family puts forward: at most one version per family,
- and only a PUBLISHED one (CreateLicense.DefaultMustBePublished,
- UpdateLicense.DefaultMustBePublished). Resolution hands it out ahead of the highest-
- numbered published version, so it keeps being served when a newer version is published.
- Setting it takes the flag from the family's previous default, which gets a
- LICENSE_UPDATED event of its own. On update, sending false unsets it; the default cannot
- be archived until another version takes its place or the flag is unset
- (ArchiveLicense.DefaultMustBePublished).
- """
+class TotalsDifference(KaitenModel):
+ kaiten_total: int = Field(alias="kaitenTotal")
- lifecycle_state: LicenseLifecycleState | str | None = Field(
- default=None, alias="lifecycleState"
- )
- """Whether this version may be served. Always present in responses. Optional on create,
- where it is DRAFT or PUBLISHED and PUBLISHED when omitted; ARCHIVED is refused
- (CreateLicense.LifecycleStateNotSettable), since a version is archived by withdrawing it
- from sale. After that it changes only through publish-license (DRAFT to PUBLISHED),
- archive-license (PUBLISHED to ARCHIVED) and unarchive-license (ARCHIVED to PUBLISHED);
- update accepts the stored state and refuses any other
- (UpdateLicense.LifecycleStateNotSettable). Only a PUBLISHED version can be the family's
- default. An ARCHIVED version cannot be assigned to an instance
- (CreateInstance.LicenseArchived, UpdateInstance.LicenseArchived); instances already on
- it keep it.
- """
+ provider_subtotal: int | None = Field(default=None, alias="providerSubtotal")
+
+ provider_total_discount: int | None = Field(default=None, alias="providerTotalDiscount")
+ """With inclusive tax: the provider's discounts, already taken off its subtotal"""
+
+ provider_total_excluding_tax: int = Field(alias="providerTotalExcludingTax")
+
+
+class TrialCharge(KaitenModel):
+ amount: int
+
+ billing_period: BillingPeriod | str = Field(alias="billingPeriod")
+
+ currency: str
+
+
+class TrialCustomer(KaitenModel):
+ billing_email: str | None = Field(alias="billingEmail")
+ """Personal data: the second payload that carries an e-mail (D-56)"""
name: str
- """Display name of the license"""
- slug: str | None = None
- """Optional URL-friendly identifier, unique per organization. When omitted, a new version
- of an existing family takes {familySlug}-v{version}, and any other license gets one
- generated from its name. A license that opens a new family gives the family its slug.
- """
+ slug: str
- type: LicenseType | str
- """Type of the license determining its capabilities and restrictions"""
- updated_at: datetime = Field(alias="updatedAt")
- """Timestamp when this license was last updated"""
+class TrialInstance(KaitenModel):
+ name: str
- version: str
- """Version of the license within its family, assigned by the server when the version is
- created and never changed afterwards: the version's derived slug
- ({familySlug}-v{version}, see slug) is built from it. A number is never reused within a
- family, even after its version is deleted. Create refuses any value
- (CreateLicense.VersionNotSettable); update accepts only the stored one, so a
- representation can be written back as it was read (UpdateLicense.VersionNotSettable).
- """
+ slug: str
- version_name: str | None = Field(default=None, alias="versionName")
- """Optional human-readable name for the license version. Defaults to "Version - "."""
+class TrialPaymentMethod(KaitenModel):
+ brand: str | None
-class LicenseEntitlement(KaitenModel):
- """A grant: the value a license gives one entitlement, and how it is enforced."""
+ exp_month: int | None = Field(alias="expMonth")
- created_at: datetime = Field(alias="createdAt")
- """Timestamp when this entitlement was created"""
+ exp_year: int | None = Field(alias="expYear")
- created_by: User = Field(alias="createdBy")
- """User who created this entitlement"""
+ last4: str | None
- entitlement_groups: list[EntitlementGroupSummary] | None = Field(
- default=None, alias="entitlementGroups"
- )
- """Groups associated with this entitlement"""
- entitlement_name: str = Field(alias="entitlementName")
- """Name of the entitlement feature or service"""
+class UsageLedger(KaitenModel):
+ retention_months: int | None = Field(alias="retentionMonths")
+ """How long usage reports are kept, in months; null when they are kept forever (ENV) or the
+ retention is unknown (UNKNOWN)
+ """
- entitlement_slug: str | None = Field(default=None, alias="entitlementSlug")
- """URL-friendly identifier for the entitlement"""
+ retention_source: RetentionSource | str = Field(alias="retentionSource")
+ """KBK: the organization's licence or an add-on; ENV: KAITEN_USAGE_LEDGER_RETENTION_MONTHS,
+ with no licensing authority; UNKNOWN: the licensing authority could not tell
+ """
- entitlement_type: LicenseEntitlementType | str | None = Field(
- default=None, alias="entitlementType"
- )
- """Type of entitlement (BOOLEAN, NUMBER, or CONFIG)"""
+
+class UsageReport(KaitenModel):
+ aggregation_method: str = Field(alias="aggregationMethod")
+ """The entitlement's aggregation method when the report was accepted"""
+
+ behavior: UsageBehavior | str
+ """append adds the value through the aggregation method; set overwrites the counter"""
+
+ delta: str
+ """valueAfter minus valueBefore; negative for a set that lowered the counter"""
+
+ entitlement_id: str = Field(alias="entitlementId")
+ """The entitlement the report was made for. It may since have been deleted."""
+
+ event_count_after: int = Field(alias="eventCountAfter")
+ """Reports counted in the window after this one"""
+
+ instance_id: str = Field(alias="instanceId")
+ """The instance the report was made for. It may since have been deleted."""
license_id: str = Field(alias="licenseId")
- """Unique identifier for the license this entitlement belongs to"""
+ """The instance's licence when the report was accepted"""
- license_slug: str = Field(alias="licenseSlug")
- """URL-friendly identifier for the license this entitlement belongs to"""
+ limit_value: str | None = Field(alias="limitValue")
+ """The limit in force when the report was accepted. Null when unlimited."""
- limit_cap_exceeded_overage_percent: int | None = Field(
- default=None, alias="limitCapExceededOveragePercent"
- )
- """Derives this grant's enforcement from its own numeric value: -1 when the value is
- unlimited (no cap, no overage); otherwise 0 rejects usage above the value (hard limit)
- and a positive percentage allows usage to exceed it by that much before being rejected
- (soft limit). Null for non-numeric (BOOLEAN/CONFIG) grants.
+ overage_delta: str = Field(alias="overageDelta")
+ """How much the usage above limitValue moved: max(0, valueAfter - limitValue) - max(0,
+ valueBefore - limitValue). 0 when unlimited.
"""
- updated_at: datetime = Field(alias="updatedAt")
- """Timestamp when this entitlement was last updated"""
+ overage_percent: int | None = Field(alias="overagePercent")
+ """The overage allowed above limitValue, in percent, when the report was accepted. Null
+ when unlimited.
+ """
- updated_by: User = Field(alias="updatedBy")
- """User who last updated this entitlement"""
+ properties: dict[str, Any] | None
+ """The report's metadata; null when none was stored"""
- value: EntitlementValue
- """Typed entitlement value. Supported shapes: {type:number,value:number},
- {type:boolean,value:boolean}, {type:object,value:{...}}
+ report_seq: int = Field(alias="reportSeq")
+ """Position of the report among the pair's accepted reports, from 1, without gaps"""
+
+ reported_at: datetime = Field(alias="reportedAt")
+ """When the server accepted the report (UTC, millisecond precision)"""
+
+ reported_value: str = Field(alias="reportedValue")
+ """The value as sent: a delta for append, an absolute value for set"""
+
+ transaction_id: str | None = Field(alias="transactionId")
+ """The report's idempotency key; null when it was sent without one"""
+
+ value_after: str = Field(alias="valueAfter")
+ """The counter after the report"""
+
+ value_before: str = Field(alias="valueBefore")
+ """The counter before the report, after any window reset"""
+
+ window_end: datetime | None = Field(alias="windowEnd")
+ """End of the usage window the report counted in (exclusive). Null for a lifetime
+ entitlement.
"""
+ window_start: datetime | None = Field(alias="windowStart")
+ """Start of the usage window the report counted in (inclusive). Null for a lifetime
+ entitlement.
+ """
-class LicenseFamilyView(KaitenModel):
- """A license family -- the product -- resolved to one of its versions."""
+
+class UsageReportPage(KaitenModel):
+ items: list[UsageReport]
+ """The reports, in reportSeq order"""
+
+ next_after_seq: int | None = Field(default=None, alias="nextAfterSeq")
+ """Pass as afterSeq to read the next page. Absent on the last page."""
+
+
+class User(KaitenModel):
+ """A reference to the user behind an action."""
+
+ id: str
+ """ID of the user"""
+
+ name: str | None = None
+ """Name of the user"""
+
+
+class Validity(KaitenModel):
+ reason: VoucherInvalidReason | str | None = None
+ """The first failing check. DISCOUNT_LIMIT_EXCEEDED: a line of the instance's invoices
+ would bear more than 20 discounts
+ """
+
+ rule: VoucherRule | str | None = None
+ """With NOT_ELIGIBLE: the eligibility rule"""
+
+ valid: bool
+
+ voucher: Voucher | None = None
+ """The voucher, without its code, when the code names one"""
+
+
+class Variant(KaitenModel):
+ """One possible value of a feature flag."""
+
+ description: str
+
+ name: str
+
+ value: Any
+
+
+class Voucher(KaitenModel):
+ applicable_addon_ids: list[str] = Field(alias="applicableAddonIds")
+ """Only instances holding one of these add-on versions can redeem it; any when empty"""
+
+ applicable_addon_price_ids: list[str] = Field(alias="applicableAddonPriceIds")
+
+ applicable_license_ids: list[str] = Field(alias="applicableLicenseIds")
+ """Only instances on these licence versions can redeem it; any when empty"""
+
+ applicable_license_price_ids: list[str] = Field(alias="applicableLicensePriceIds")
+
+ code: str | None = None
+ """The code to redeem. Returned to read:vouchers callers only; never in an event."""
+
+ code_hint: str = Field(alias="codeHint")
+ """The last four characters of the normalized code"""
created_at: datetime = Field(alias="createdAt")
- """Timestamp when this family was created"""
- current_version: License | None = Field(default=None, alias="currentVersion")
- """The version this read is about. By default the version the family resolves to: its
- default version if it has one, otherwise its highest-numbered PUBLISHED version. Under
- ?version=N it is the version asked for instead, whatever its lifecycle state. Absent
- when the family has no PUBLISHED version, which only a list or an include=versions read
- returns.
+ currency: str | None
+ """The currency of a FIXED_AMOUNT discount"""
+
+ description: str | None
+
+ duration: VoucherDuration | str
+ """PRICE: how many invoices it discounts (one, durationInPeriods, or every one). BOOST and
+ FLAG_GRANT: how long a redemption lasts, in the subscription's billing periods.
+ COMPOSITE: both, each for its part.
"""
+ duration_in_periods: int | None = Field(alias="durationInPeriods")
+
+ expires_at: datetime | None = Field(alias="expiresAt")
+
+ flag_grants: list[FlagGrant] = Field(alias="flagGrants")
+ """The flag variants a FLAG_GRANT grants"""
+
+ grants: list[Grant]
+ """What an ENTITLEMENT_BOOST changes"""
+
id: str
- """Unique identifier for the license family"""
- slug: str
- """URL-friendly identifier of the family, unique per organization and stable across renames
- and new versions. Deleting the family's last version deletes the family and frees the
- slug.
- """
+ max_redemptions: int | None = Field(alias="maxRedemptions")
+
+ name: str
+
+ price_applies_to: DiscountAppliesTo | str | None = Field(alias="priceAppliesTo")
+
+ price_discount_type: DiscountType | str | None = Field(alias="priceDiscountType")
+
+ price_discount_value: str | None = Field(alias="priceDiscountValue")
+ """A percentage in (0, 100], or an integer amount in minor units of currency"""
+
+ redemption_rules: RedemptionRules = Field(alias="redemptionRules")
+
+ redemptions_count: int = Field(alias="redemptionsCount")
+
+ restricted_customer_slug: str | None = Field(alias="restrictedCustomerSlug")
+ """Only this customer's instances can redeem it"""
+
+ starts_at: datetime | None = Field(alias="startsAt")
+
+ status: VoucherStatus | str
updated_at: datetime = Field(alias="updatedAt")
- """Timestamp when a version was last added to this family, or when the family was created.
- Renames, lifecycle moves and default changes update the versions' own updatedAt, not
- this one.
+
+ voucher_type: VoucherType | str = Field(alias="voucherType")
+
+
+class AddonDelivered(Addon):
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
+
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
+
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
"""
- version_count: int = Field(alias="versionCount")
- """Number of versions in this family, whatever their lifecycle state"""
- versions: list[License] | None = None
- """Every version of the family, oldest first. Only returned when explicitly requested with
- include=versions, and then never null: a family always has at least one version.
+class AddonGrantUpdateDelivered(AddonEntitlement):
+ addon_slug: str = Field(alias="addonSlug")
+ """The add-on version the grant belongs to"""
+
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
+
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
+
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
"""
-class ManifestEnvelope(KaitenModel):
- flags: list[ManifestFlag] | None
+class AddonPriceUpdateDelivered(Price):
+ addon_slug: str = Field(alias="addonSlug")
+ """The add-on version the price belongs to"""
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
-class ManifestFlag(KaitenModel):
- """One flag of the OpenFeature manifest."""
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
- default_value: Any = Field(alias="defaultValue")
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
+ """
- description: str | None = None
- key: str
+class AddonUpdateDelivered(Addon):
+ changed_fields: list[str] | None = Field(alias="changedFields")
+ """The members the change touched, as the API names them"""
- name: str | None = None
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
- type: str
+ compatible_license_families: list[str] | None = Field(alias="compatibleLicenseFamilies")
+ """The licence families the version fits after the change"""
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
-class MetadataField(KaitenModel):
- """A typed field an organization declares on its instances or deployment zones."""
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
+ """
- archived_at: datetime | None = Field(default=None, alias="archivedAt")
- """Timestamp when the field was archived (soft delete). Absent if active."""
- created_at: datetime = Field(alias="createdAt")
- """Timestamp when the field was created"""
+class BillingStartedDelivered(InstanceBilling):
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
- created_by: User = Field(alias="createdBy")
- """User who created this field"""
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
- display_order: int | None = Field(default=None, alias="displayOrder")
- """Sort order in the admin UI; ties are broken by createdAt ASC. Settable only through POST
- /metadata-fields/reorder; never echoed back on update.
+ resubscribed: bool
+ """Set when a CANCELED subscription was subscribed again"""
+
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
"""
- id: str
- """Unique identifier"""
- json_schema: dict[str, Any] = Field(alias="jsonSchema")
- """JSON Schema 2020-12 document describing the value space. Example:
- {"type":"string","enum":["a","b"]}
+class CancellationChangeDelivered(InstanceBilling):
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
+
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
+
+ effective_at: datetime = Field(alias="effectiveAt")
+ """The boundary the cancellation takes effect at"""
+
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
"""
- key: str
- """Stable identifier, unique per (organization, resource type) on non-archived rows.
- Immutable after creation; update must echo back the stored value.
+
+class CancellationDelivered(InstanceBilling):
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
+
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
+
+ final_invoice_id: str | None = Field(alias="finalInvoiceId")
+ """The FINAL invoice; null when the cancellation issued none (a trial)"""
+
+ mode: CancellationMode | str
+ """How it was canceled; a trial is canceled at once in either mode"""
+
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
"""
- label: str
- """Human-readable label shown in the admin UI"""
- resource_type: MetadataResourceType | str = Field(alias="resourceType")
- """Resource type this field applies to. Immutable after creation; update must echo back the
- stored value.
+class ComponentDelivered(Component):
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
+
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
+
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
"""
- updated_at: datetime = Field(alias="updatedAt")
- """Timestamp when the field was last updated"""
-
- updated_by: User = Field(alias="updatedBy")
- """User who last updated this field"""
+class CustomerEventDelivered(Customer):
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
-class MetadataFieldImpact(KaitenModel):
- """What a candidate metadata field schema would invalidate."""
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
- count: int
- """Number of resources whose stored value for this field would no longer satisfy the
- candidate schema
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
"""
- samples: list[MetadataFieldImpactSample] | None
- """Up to 5 impacted resources, for display"""
+class DeploymentZoneDelivered(DeploymentZone):
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
-class MetadataFieldImpactSample(KaitenModel):
- """A resource a metadata field schema change would invalidate."""
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
- name: str
- """Resource display name"""
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
+ """
- slug: str
- """Resource slug"""
+class EntitlementDelivered(Entitlement):
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
-class MetadataFieldReordered(KaitenModel):
- """Metadata fields were given a new display order."""
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
- ids: list[str] | None
- """Metadata field ids in their new display order"""
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
+ """
-class NumberEntitlementValue(KaitenModel):
- """A numeric entitlement value: a quota, or an amount consumed."""
+class EntitlementGroupDelivered(EntitlementGroup):
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
- event_count: int | None = None
- """Number of events accumulated (server-computed)"""
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
- type: Literal["number"]
- """Value type discriminator"""
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
+ """
- value: float
- """Numeric value"""
+class EntitlementUsageDelivered(EntitlementUsage):
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
-class PlainToken(KaitenModel):
- """A token together with its plaintext secret, which the API returns exactly once."""
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
- created_at: datetime = Field(alias="createdAt")
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
+ """
- created_by: User = Field(alias="createdBy")
- expires_at: datetime | None = Field(default=None, alias="expiresAt")
+class FeatureFlagDelivered(FeatureFlag):
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
- id: str | None = None
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
- name: str
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
+ """
- revoked_at: datetime | None = Field(default=None, alias="revokedAt")
- revoked_by: User | None = Field(default=None, alias="revokedBy")
+class HeldInvoiceDelivered(InvoiceSummary):
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
- scopes: list[str] | None
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
- service_account_id: str | None = Field(default=None, alias="serviceAccountId")
+ hold_detail: HoldDetail = Field(alias="holdDetail")
- slug: str | None = None
- """Optional URL-friendly identifier, unique per service account. Auto-generated if not
- provided.
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
"""
- token: str | None = None
+class InstanceAddonUpdateDelivered(InstanceAddon):
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
-class Problem(KaitenModel):
- """An RFC 9457 problem document, which every API error carries."""
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
- code: str | None = None
- """Stable, machine-readable error code."""
+ instance_slug: str = Field(alias="instanceSlug")
- detail: str | None = None
- """A human-readable explanation specific to this occurrence of the problem."""
+ previous_quantity: int | None = Field(default=None, alias="previousQuantity")
+ """The quantity before a quantity change"""
- error_id: str | None = Field(default=None, alias="errorId")
- """Correlation id for the server-side log entry describing the withheld cause."""
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
+ """
- errors: list[ErrorDetail] | None = None
- """Optional list of individual error details"""
- instance: str | None = None
- """A URI reference that identifies the specific occurrence of the problem."""
+class InstanceDelivered(Instance):
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
- status: int | None = None
- """HTTP status code"""
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
- title: str | None = None
- """A short, human-readable summary of the problem type. This value should not change
- between occurrences of the error.
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
"""
- type: str | None = None
- """A URI reference to human-readable documentation for the error."""
+class InstanceUpdateDelivered(Instance):
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
-class Release(KaitenModel):
- """An immutable, versioned set of components."""
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
- component_ids: list[str] | None = Field(default=None, alias="componentIds")
- """List of component IDs to link to this release"""
+ license_change_reason: Literal["BILLING_FALLBACK"] | None = Field(alias="licenseChangeReason")
+ """BILLING_FALLBACK: the subscription ended and the instance moved to its family's fallback
+ (V31-66). Null for any other change, the move a paid checkout applies included
+ """
- components: list[Component] | None = None
- """List of components included in this release, fixed at creation"""
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
+ """
- created_at: datetime = Field(alias="createdAt")
- """Timestamp when the release was created"""
- created_by: User = Field(alias="createdBy")
- """User who created this release"""
+class IssuedInvoiceDelivered(InvoiceSummary):
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
- description: str | None = None
- """Description of the release"""
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
- id: str
- """Unique identifier for the release"""
+ license_id: str = Field(alias="licenseId")
- slug: str | None = None
- """Optional URL-friendly identifier, unique per organization. Auto-generated if not provided."""
+ lines: list[InvoiceLine]
- version: str
- """Version of the release"""
+ lines_truncated: bool | None = Field(default=None, alias="linesTruncated")
+ """Set when the lines did not fit the event: read the invoice for all of them"""
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
+ """
-class RolloutDateTargeting(KaitenModel):
- """A targeting rule that shifts traffic between variants over a date range."""
- end: RolloutStep
+class LicenseDelivered(License):
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
- name: str
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
- rule: str
- """CEL expression for basicTargeting evaluation"""
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
+ """
- start: RolloutStep
- type: Literal["rollout_date"]
+class LicenseEntitlementDelivered(LicenseEntitlement):
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
-class RolloutDateVariant(KaitenModel):
- """A default variant that shifts traffic between variants over a date range."""
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
+ """
- end: RolloutStep
- start: RolloutStep
+class LicenseFamilyViewDelivered(LicenseFamilyView):
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
- type: Literal["rollout_date"]
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
+ """
-class RolloutPercentageTargeting(KaitenModel):
- """A targeting rule that splits the contexts it matches between variants."""
- distribution: dict[str, int]
+class LicensePriceEventDelivered(Price):
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
- name: str
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
- rule: str
- """CEL expression for basicTargeting evaluation"""
+ family_slug: str = Field(alias="familySlug")
+ """The family of that version"""
- type: Literal["rollout_percentage"]
+ license_slug: str = Field(alias="licenseSlug")
+ """The licence version the price belongs to"""
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
+ """
-class RolloutPercentageVariant(KaitenModel):
- """A default variant that splits traffic between variants by weight."""
- distribution: dict[str, int]
+class LicensePriceUpdatedEventDelivered(Price):
+ changed_fields: list[str] | None = Field(alias="changedFields")
+ """The members the update changed, as the API names them"""
- type: Literal["rollout_percentage"]
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
-class RolloutStep(KaitenModel):
- """One end of a date-based rollout."""
+ family_slug: str = Field(alias="familySlug")
+ """The family of that version"""
- date: datetime
+ license_slug: str = Field(alias="licenseSlug")
+ """The licence version the price belongs to"""
- percentage: int
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
+ """
- variant: str
+class MetadataFieldDelivered(MetadataField):
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
-class ServiceAccount(KaitenModel):
- """A machine identity inside an organization, owning API tokens."""
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
- created_at: datetime | None = Field(default=None, alias="createdAt")
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
+ """
- created_by: User | None = Field(default=None, alias="createdBy")
- external_id: str | None = Field(default=None, alias="externalId")
+class MismatchedInvoiceDelivered(InvoiceSummary):
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
- id: str | None = None
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
- name: str
+ reconciliation_detail: Reconciliation = Field(alias="reconciliationDetail")
- slug: str | None = None
- """Optional URL-friendly identifier, unique per organization. Auto-generated if not provided."""
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
+ """
- tokens: list[Token] | None
+class PaidInvoiceDelivered(InvoiceSummary):
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
-class SystemTokenIssuance(KaitenModel):
- """A platform credential minted a token inside this organization."""
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
- credential_kind: str = Field(alias="credentialKind")
- """Credential class that performed the mint; "platform" is the system:kaiten identity"""
+ external_reference: str | None = Field(default=None, alias="externalReference")
- expires_at: datetime | None = Field(default=None, alias="expiresAt")
- """Absent when the minted credential does not expire"""
+ note: str | None = None
+ """The note given when it was marked paid, kept only here"""
- issued_at: datetime = Field(alias="issuedAt")
- """When the credential was minted"""
+ source: PaymentSource | str
+ """MARK_PAID: recorded by the organization. ZERO_TOTAL: nothing was owed"""
- issued_token_id: str = Field(alias="issuedTokenId")
- """The credential that was minted"""
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
+ """
- name: str
- """Human-readable label the caller gave the minted credential"""
- platform_token_id: str = Field(alias="platformTokenId")
- """Platform credential that performed the mint; revoking it revokes everything it issued"""
+class PaymentFailedInvoiceDelivered(InvoiceSummary):
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
- scopes: list[str] | None
- """Scopes granted to the minted credential"""
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
- slug: str
- """Server-assigned identifier of the minted credential"""
+ failure_code: str = Field(alias="failureCode")
+ """The provider's decline or failure code, such as card_declined, expired_card,
+ authentication_required or no_payment_method
+ """
+ requires_action: bool = Field(alias="requiresAction")
+ """The customer must authenticate the payment on the invoice's hosted page"""
-class TargetingContext(KaitenModel):
- roots: list[TargetingContextNode]
- """Identifiers the server guarantees, and what is reachable below each. Not a closed set: a
- host may target on its own attributes too
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
"""
-class TargetingContextNode(KaitenModel):
- description: str | None = None
- """What this value means, shown by the editor"""
+class PushFailedInvoiceDelivered(InvoiceSummary):
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
- fields: list[TargetingContextNode] | None = None
- """Named children, for an object"""
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
- known_keys: list[str] | None = Field(default=None, alias="knownKeys")
- """Keys this map currently has, for completion. Not a closed set"""
+ last_push_error: str = Field(alias="lastPushError")
+ """The provider's code and message, never a request body"""
- name: str
- """Name as it is written in a rule"""
+ push_attempts: int = Field(alias="pushAttempts")
- optional: bool | None = None
- """Whether this value may be absent, in which case has() is worth using"""
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
+ """
- type: TargetingFactType | str
- """Shape of this node's value"""
- values: TargetingContextNode | None = None
- """Shape shared by every entry, for a map"""
+class PushedInvoiceDelivered(InvoiceSummary):
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
-class TargetingRuleIssue(KaitenModel):
- column: int
- """1-based column the problem starts on, or 0 when it has no position"""
+ external_invoice_id: str = Field(alias="externalInvoiceId")
- end_column: int | None = Field(default=None, alias="endColumn")
- """1-based column just past the problem, or 0 when only a point is known"""
+ provider_invoice_number: str | None = Field(default=None, alias="providerInvoiceNumber")
- end_line: int | None = Field(default=None, alias="endLine")
- """1-based line the problem ends on, or 0 when only a point is known"""
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
+ """
- line: int
- """1-based line the problem starts on, or 0 when it has no position"""
- message: str
- """What is wrong, in the same words the write path refuses it with"""
+class RedemptionDelivered(Redemption):
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
-class TargetingRuleRehearsal(KaitenModel):
- evaluation_error: str | None = Field(default=None, alias="evaluationError")
- """Why the engine could not answer, when it could not — e.g. the rule reads a host
- attribute this context does not carry. At a real evaluation the rule would simply not
- match
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
"""
- facts: dict[str, Any]
- """The __kaiten namespace as enriched for this context — what a rule actually reads. Empty
- sub-roots mean the context resolved nothing for them
+
+class ReleaseDelivered(Release):
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
+
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
+
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
"""
- issues: list[TargetingRuleIssue]
- """Problems the lint found. The rule is only run when there are none"""
- matched: bool
- """Whether the rule matched this context. Meaningful only when valid and evaluationError is
- empty
+class ReleasedInvoiceDelivered(InvoiceSummary):
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
+
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
+
+ hold_detail: HoldDetail = Field(alias="holdDetail")
+ """The hold it left, which the invoice no longer shows"""
+
+ release_reason: str = Field(alias="releaseReason")
+
+ released_by: str = Field(alias="releasedBy")
+ """The user who released it, or system when a later check found the journal sound"""
+
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
"""
- valid: bool
- """Whether the rule lints clean"""
+class TrialWillEndDelivered(InstanceBilling):
+ charge: TrialCharge | None
+ """What the conversion would charge, as upcoming-invoice previews it, discounts included
+ and before any tax the provider adds; null when nothing would be
+ """
-class TargetingRuleVerdict(KaitenModel):
- issues: list[TargetingRuleIssue]
- """Every problem found, positioned where it is written. Empty when the rule is accepted"""
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
- valid: bool
- """Whether the write path would accept this rule"""
+ customer: TrialCustomer
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
-class Token(KaitenModel):
- """A token's metadata. Never its secret."""
+ days_left: int = Field(alias="daysLeft")
+ """Whole days from the event to trialEndsAt, rounded up; 0 once it passed"""
- created_at: datetime = Field(alias="createdAt")
+ family_slug: str = Field(alias="familySlug")
+ """Its family"""
- created_by: User = Field(alias="createdBy")
+ instance: TrialInstance
- expires_at: datetime | None = Field(default=None, alias="expiresAt")
+ license_name: str = Field(alias="licenseName")
+ """The licence version of the base price"""
- id: str | None = None
+ payment_method: TrialPaymentMethod | None = Field(alias="paymentMethod")
+ """The ACTIVE card the conversion would be charged to; null without one, or on NOOP"""
- name: str
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
+ """
- revoked_at: datetime | None = Field(default=None, alias="revokedAt")
- revoked_by: User | None = Field(default=None, alias="revokedBy")
+class UncollectibleInvoiceDelivered(InvoiceSummary):
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
- scopes: list[str] | None
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
- service_account_id: str | None = Field(default=None, alias="serviceAccountId")
+ reason: str
- slug: str | None = None
- """Optional URL-friendly identifier, unique per service account. Auto-generated if not
- provided.
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
"""
-class User(KaitenModel):
- """A reference to the user behind an action."""
+class VoidedInvoiceDelivered(InvoiceSummary):
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
- id: str
- """ID of the user"""
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
- name: str | None = None
- """Name of the user"""
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
+ """
+ void_reason: str = Field(alias="voidReason")
-class Variant(KaitenModel):
- """One possible value of a feature flag."""
- description: str
+class VoucherDelivered(Voucher):
+ checkout_session_id: str | None = Field(default=None, alias="checkoutSessionId")
+ """With via checkout_session: the session"""
- name: str
+ customer_session_id: str | None = Field(default=None, alias="customerSessionId")
+ """With via customer_session: the session"""
- value: Any
+ via: EventVia | str | None = None
+ """customer_session: written through a kst_ customer session, under the vendor principal
+ that minted it. checkout_session: written by an anonymous checkout, under system:kaiten.
+ Absent otherwise
+ """
DefaultVariant = Annotated[
@@ -1607,32 +6238,78 @@ class Variant(KaitenModel):
for _model in (
+ Addon,
+ AddonEntitlement,
+ AddonFamily,
+ AuditActor,
AuditTrail,
BasicTargeting,
BasicVariant,
+ BillingCapabilities,
+ BillingFeatures,
+ BillingHealth,
+ BillingProvider,
+ BillingSettings,
BooleanEntitlementValue,
+ CanceledSubscription,
+ CheckoutConfirmation,
+ CheckoutPayment,
+ CheckoutSession,
+ CheckoutSessionAdmin,
+ CheckoutSessionStart,
+ CloseBacklog,
+ ClosePeriodsReport,
+ ClosedInvoice,
+ CompatibleLicenseFamilies,
+ CompletedPaymentMethodSession,
Component,
ConfigEntitlementValue,
+ ConnectionDelivered,
Connector,
ConnectorActivation,
ConnectorSettings,
ConnectorSettingsSchema,
ConnectorState,
+ CreatedCustomerSession,
Customer,
+ CustomerBilling,
CustomerCreationRejected,
+ CustomerInProvider,
CustomerIntegration,
CustomerIntegrationResource,
+ DeletedAddonDelivered,
Deployment,
DeploymentZone,
+ DiscountAllocation,
+ DiscountDifference,
+ DueDateDifference,
+ DueToday,
Entitlement,
+ EntitlementException,
EntitlementGroup,
EntitlementGroupSummary,
EntitlementGroupUsage,
EntitlementUsage,
ErrorDetail,
+ EventHeaders,
+ ExceptionEventDelivered,
+ ExhaustedEventDelivered,
+ ExpiredEventDelivered,
+ ExtraDiscount,
FeatureFlag,
FeatureFlagEvaluated,
+ FlagGrant,
+ Grant,
+ HandoffAcknowledgementDelivered,
+ HandoffBacklog,
+ HandoffClaim,
+ HeldInvoices,
+ HeldPair,
+ HoldDetail,
+ HoldRecord,
Instance,
+ InstanceAddon,
+ InstanceBilling,
InstanceEntitlementCapExceeded,
InstanceEntitlementUsagePeriodRolledOver,
InstanceEntitlementUsageReportAccepted,
@@ -1642,25 +6319,112 @@ class Variant(KaitenModel):
InstanceIntegrationResource,
InstanceLifecycleStageChanged,
InstanceStatusChanged,
+ Invoice,
+ InvoiceHandoff,
+ InvoiceHold,
+ InvoiceLine,
+ InvoiceLineDiscount,
+ InvoiceLineLedger,
+ InvoiceLineMetering,
+ InvoiceLineOverage,
+ InvoiceLineProvider,
+ InvoiceLineTrueUp,
+ InvoicePreview,
+ InvoiceSummary,
License,
LicenseEntitlement,
LicenseFamilyView,
+ LineDifference,
+ LineReportPage,
ManifestEnvelope,
ManifestFlag,
MetadataField,
MetadataFieldImpact,
MetadataFieldImpactSample,
MetadataFieldReordered,
+ MinimumAmount,
NumberEntitlementValue,
+ Outcome,
+ OverageLimit,
+ Payment,
+ PaymentMethodAttachedDelivered,
+ PaymentMethodDetachedDelivered,
+ PaymentMethodExpiringDelivered,
+ PaymentMethodLabels,
+ PaymentMethodSession,
PlainToken,
+ PlanChangeDelivered,
+ PlanChangeScheduleDelivered,
+ PortalCapabilities,
+ PortalCustomer,
+ PortalEntitlement,
+ PortalInstance,
+ PortalInstanceSummary,
+ PortalPaymentMethod,
+ PortalSession,
+ PortalTrial,
+ PortalTrialCharge,
+ PortalTrialPayment,
+ PortalUpcomingInvoice,
+ Price,
+ PriceMeter,
Problem,
+ Provenance,
+ ProvenanceAddon,
+ ProvenanceBoost,
+ ProvenanceException,
+ ProvenanceLicense,
+ ProvenanceNumber,
+ ProviderCapabilities,
+ ProviderChangeDelivered,
+ ProviderLineLimit,
+ ProviderRecord,
+ ProviderSync,
+ PublicAddOn,
+ PublicAddOnEntitlement,
+ PublicBranding,
+ PublicCatalog,
+ PublicCatalogCapabilities,
+ PublicMeter,
+ PublicPlan,
+ PublicPlanEntitlement,
+ PublicPrice,
+ PublicSurface,
+ PublishableKey,
+ PublishableKeyCreated,
+ PublishableKeyEventDelivered,
+ PushFailures,
+ QueuedInvoice,
+ Reconciliation,
+ Redemption,
+ RedemptionRules,
Release,
+ ReportedEntitlementUsage,
RolloutDateTargeting,
RolloutDateVariant,
RolloutPercentageTargeting,
RolloutPercentageVariant,
RolloutStep,
+ ScheduledChange,
ServiceAccount,
+ SessionAddon,
+ SessionAddonSelection,
+ SessionBoost,
+ SessionCheckout,
+ SessionDiscount,
+ SessionEventDelivered,
+ SessionInvoice,
+ SessionLine,
+ SessionPortal,
+ SessionRedemption,
+ SessionScheduledChange,
+ SessionSubscription,
+ SessionVoucher,
+ SessionVoucherValidity,
+ StartedSubscription,
+ StatusChangeDelivered,
+ SyncFailureDelivered,
+ SyncReport,
SystemTokenIssuance,
TargetingContext,
TargetingContextNode,
@@ -1668,8 +6432,55 @@ class Variant(KaitenModel):
TargetingRuleRehearsal,
TargetingRuleVerdict,
Token,
+ TotalsDifference,
+ TrialCharge,
+ TrialCustomer,
+ TrialInstance,
+ TrialPaymentMethod,
+ UsageLedger,
+ UsageReport,
+ UsageReportPage,
User,
+ Validity,
Variant,
+ Voucher,
+ AddonDelivered,
+ AddonGrantUpdateDelivered,
+ AddonPriceUpdateDelivered,
+ AddonUpdateDelivered,
+ BillingStartedDelivered,
+ CancellationChangeDelivered,
+ CancellationDelivered,
+ ComponentDelivered,
+ CustomerEventDelivered,
+ DeploymentZoneDelivered,
+ EntitlementDelivered,
+ EntitlementGroupDelivered,
+ EntitlementUsageDelivered,
+ FeatureFlagDelivered,
+ HeldInvoiceDelivered,
+ InstanceAddonUpdateDelivered,
+ InstanceDelivered,
+ InstanceUpdateDelivered,
+ IssuedInvoiceDelivered,
+ LicenseDelivered,
+ LicenseEntitlementDelivered,
+ LicenseFamilyViewDelivered,
+ LicensePriceEventDelivered,
+ LicensePriceUpdatedEventDelivered,
+ MetadataFieldDelivered,
+ MismatchedInvoiceDelivered,
+ PaidInvoiceDelivered,
+ PaymentFailedInvoiceDelivered,
+ PushFailedInvoiceDelivered,
+ PushedInvoiceDelivered,
+ RedemptionDelivered,
+ ReleaseDelivered,
+ ReleasedInvoiceDelivered,
+ TrialWillEndDelivered,
+ UncollectibleInvoiceDelivered,
+ VoidedInvoiceDelivered,
+ VoucherDelivered,
):
_model.model_rebuild()
del _model
diff --git a/src/kaitencloud/types/_params.py b/src/kaitencloud/types/_params.py
index e083959..9e08c89 100644
--- a/src/kaitencloud/types/_params.py
+++ b/src/kaitencloud/types/_params.py
@@ -24,6 +24,8 @@
"BasicVariantParam",
"DefaultVariantParam",
"IntegrationParam",
+ "MinimumAmountParam",
+ "RedemptionRulesParam",
"RolloutDateTargetingParam",
"RolloutDateVariantParam",
"RolloutPercentageTargetingParam",
@@ -31,6 +33,7 @@
"RolloutStepParam",
"TargetingParam",
"VariantParam",
+ "VoucherGrantParam",
]
@@ -124,3 +127,38 @@ class RolloutDateTargetingParam(TypedDict):
TargetingParam: TypeAlias = (
BasicTargetingParam | RolloutPercentageTargetingParam | RolloutDateTargetingParam
)
+
+
+class VoucherGrantParam(TypedDict, total=False):
+ """What an entitlement boost voucher grants on one entitlement."""
+
+ entitlementSlug: Required[str]
+ modifierType: Required[Literal["SET", "ADD", "MULTIPLY", "UNLIMITED"]]
+ """``SET``, ``ADD`` and ``MULTIPLY`` take a ``modifierValue``; a BOOLEAN or CONFIG
+ entitlement takes ``SET`` only, with a ``value``."""
+
+ modifierValue: str
+ """A NUMBER grant's decimal, as a string: ``"10"``."""
+
+ value: bool | dict[str, Any]
+ """What a BOOLEAN or CONFIG ``SET`` grant sets."""
+
+
+class MinimumAmountParam(TypedDict):
+ """An amount in minor units, in a currency."""
+
+ currency: str
+ unitAmountDecimal: str
+
+
+class RedemptionRulesParam(TypedDict, total=False):
+ """Who may redeem a voucher, beyond its dates and its redemption count."""
+
+ annualOnly: bool
+ """Only an instance with a live ``ANNUAL`` subscription."""
+
+ firstTimeOnly: bool
+ """Only a customer none of whose instances has paid an invoice."""
+
+ minimumSubscriptionAmount: MinimumAmountParam
+ """Only a live subscription whose base price is at least this."""
diff --git a/src/kaitencloud/types/_webhooks.py b/src/kaitencloud/types/_webhooks.py
index 0cc73bc..a20934c 100644
--- a/src/kaitencloud/types/_webhooks.py
+++ b/src/kaitencloud/types/_webhooks.py
@@ -14,17 +14,33 @@
from .._models import KaitenModel
from ._core import (
- Component,
- Customer,
+ AddonDelivered,
+ AddonGrantUpdateDelivered,
+ AddonPriceUpdateDelivered,
+ AddonUpdateDelivered,
+ BillingStartedDelivered,
+ CancellationChangeDelivered,
+ CancellationDelivered,
+ ComponentDelivered,
+ ConnectionDelivered,
CustomerCreationRejected,
+ CustomerEventDelivered,
+ DeletedAddonDelivered,
Deployment,
- DeploymentZone,
- Entitlement,
- EntitlementGroup,
- EntitlementUsage,
- FeatureFlag,
+ DeploymentZoneDelivered,
+ EntitlementDelivered,
+ EntitlementGroupDelivered,
+ EntitlementUsageDelivered,
+ EventHeaders,
+ ExceptionEventDelivered,
+ ExhaustedEventDelivered,
+ ExpiredEventDelivered,
+ FeatureFlagDelivered,
FeatureFlagEvaluated,
- Instance,
+ HandoffAcknowledgementDelivered,
+ HeldInvoiceDelivered,
+ InstanceAddonUpdateDelivered,
+ InstanceDelivered,
InstanceEntitlementCapExceeded,
InstanceEntitlementUsagePeriodRolledOver,
InstanceEntitlementUsageReportAccepted,
@@ -32,23 +48,68 @@
InstanceEntitlementValueGet,
InstanceLifecycleStageChanged,
InstanceStatusChanged,
- License,
- LicenseEntitlement,
- LicenseFamilyView,
- MetadataField,
+ InstanceUpdateDelivered,
+ IssuedInvoiceDelivered,
+ LicenseDelivered,
+ LicenseEntitlementDelivered,
+ LicenseFamilyViewDelivered,
+ LicensePriceEventDelivered,
+ LicensePriceUpdatedEventDelivered,
+ MetadataFieldDelivered,
MetadataFieldReordered,
- Release,
+ MismatchedInvoiceDelivered,
+ PaidInvoiceDelivered,
+ PaymentFailedInvoiceDelivered,
+ PaymentMethodAttachedDelivered,
+ PaymentMethodDetachedDelivered,
+ PaymentMethodExpiringDelivered,
+ PlanChangeDelivered,
+ PlanChangeScheduleDelivered,
+ ProviderChangeDelivered,
+ PublishableKeyEventDelivered,
+ PushedInvoiceDelivered,
+ PushFailedInvoiceDelivered,
+ RedemptionDelivered,
+ ReleaseDelivered,
+ ReleasedInvoiceDelivered,
+ SessionEventDelivered,
+ StatusChangeDelivered,
+ SyncFailureDelivered,
SystemTokenIssuance,
+ TrialWillEndDelivered,
+ UncollectibleInvoiceDelivered,
+ VoidedInvoiceDelivered,
+ VoucherDelivered,
)
__all__ = [
"WEBHOOK_EVENTS",
+ "AddonArchivedEvent",
+ "AddonCreatedEvent",
+ "AddonDeletedEvent",
+ "AddonEntitlementAssignedEvent",
+ "AddonEntitlementUnassignedEvent",
+ "AddonEntitlementUpdatedEvent",
+ "AddonPriceCreatedEvent",
+ "AddonPriceDeprecatedEvent",
+ "AddonPublishedEvent",
+ "AddonUnarchivedEvent",
+ "AddonUpdatedEvent",
+ "BillingProviderConnectedEvent",
+ "BillingProviderDisconnectedEvent",
+ "BillingProviderSyncFailedEvent",
+ "CheckoutSessionAbandonedEvent",
+ "CheckoutSessionCompletedEvent",
+ "CheckoutSessionStartedEvent",
"ComponentCreatedEvent",
"ComponentDeletedEvent",
"ComponentUpdatedEvent",
"CustomerCreatedEvent",
"CustomerCreationRejectedEvent",
"CustomerDeletedEvent",
+ "CustomerPaymentMethodAttachedEvent",
+ "CustomerPaymentMethodDetachedEvent",
+ "CustomerPaymentMethodExpiringEvent",
"CustomerUpdatedEvent",
"DeploymentZoneCreatedEvent",
"DeploymentZoneDeletedEvent",
@@ -63,20 +124,50 @@
"FeatureFlagDeletedEvent",
"FeatureFlagEvaluatedEvent",
"FeatureFlagUpdatedEvent",
+ "InstanceAddonAddedEvent",
+ "InstanceAddonQuantityChangedEvent",
+ "InstanceAddonRemovedEvent",
+ "InstanceBillingCanceledEvent",
+ "InstanceBillingCancellationRevertedEvent",
+ "InstanceBillingCancellationScheduledEvent",
+ "InstanceBillingPlanChangeCanceledEvent",
+ "InstanceBillingPlanChangeScheduledEvent",
+ "InstanceBillingPlanChangedEvent",
+ "InstanceBillingProviderChangedEvent",
+ "InstanceBillingStartedEvent",
+ "InstanceBillingStatusChangedEvent",
+ "InstanceBillingTrialWillEndEvent",
"InstanceCreatedEvent",
"InstanceDeletedEvent",
"InstanceDeployedEvent",
"InstanceEntitlementCapExceededEvent",
+ "InstanceEntitlementExceptionCreatedEvent",
+ "InstanceEntitlementExceptionExpiredEvent",
+ "InstanceEntitlementExceptionRevokedEvent",
"InstanceEntitlementUsagePeriodRolledOverEvent",
"InstanceEntitlementUsageReachedEvent",
"InstanceEntitlementUsageReportAcceptedEvent",
"InstanceEntitlementUsageReportRejectedEvent",
"InstanceEntitlementUsageWarningThresholdReachedEvent",
"InstanceEntitlementValueGetEvent",
+ "InstanceInvoiceHandoffAcknowledgedEvent",
+ "InstanceInvoiceHeldEvent",
+ "InstanceInvoiceIssuedEvent",
+ "InstanceInvoiceMarkedUncollectibleEvent",
+ "InstanceInvoicePaidEvent",
+ "InstanceInvoicePaymentFailedEvent",
+ "InstanceInvoicePushFailedEvent",
+ "InstanceInvoicePushedEvent",
+ "InstanceInvoiceReconciliationMismatchEvent",
+ "InstanceInvoiceReleasedEvent",
+ "InstanceInvoiceVoidedEvent",
"InstanceLifecycleStageChangedEvent",
"InstanceMigratedEvent",
"InstanceStatusChangedEvent",
"InstanceUpdatedEvent",
+ "InstanceVoucherExpiredEvent",
+ "InstanceVoucherRedeemedEvent",
+ "InstanceVoucherRevokedEvent",
"LicenseArchivedEvent",
"LicenseCreatedEvent",
"LicenseDeletedEvent",
@@ -86,6 +177,9 @@
"LicenseFamilyCreatedEvent",
"LicenseFamilyDeletedEvent",
"LicenseFamilyUpdatedEvent",
+ "LicensePriceCreatedEvent",
+ "LicensePriceDeprecatedEvent",
+ "LicensePriceUpdatedEvent",
"LicensePublishedEvent",
"LicenseUnarchivedEvent",
"LicenseUpdatedEvent",
@@ -94,14 +188,328 @@
"MetadataFieldReorderedEvent",
"MetadataFieldUnarchivedEvent",
"MetadataFieldUpdatedEvent",
+ "PublishableKeyCreatedEvent",
+ "PublishableKeyRevokedEvent",
"ReleaseCreatedEvent",
"ReleaseDeletedEvent",
"ReleaseDeployedEvent",
"SystemOrganizationTokenIssuedEvent",
+ "VoucherArchivedEvent",
+ "VoucherCreatedEvent",
+ "VoucherDeletedEvent",
+ "VoucherExhaustedEvent",
+ "VoucherExpiredEvent",
+ "VoucherPublishedEvent",
+ "VoucherUpdatedEvent",
"WebhookEvent",
]
+class AddonEntitlementAssignedEvent(KaitenModel):
+ """Triggered when an add-on version grants an entitlement. Delivered as
+ ``com.kaiten.addon.entitlement.v1.assigned``.
+ """
+
+ name: Literal["ADDON_ENTITLEMENT_ASSIGNED"]
+ """The event name."""
+
+ type: Literal["com.kaiten.addon.entitlement.v1.assigned"]
+ """The event type: what tells one event from another."""
+
+ data: AddonGrantUpdateDelivered
+ """What the event is about."""
+
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
+
+class AddonEntitlementUnassignedEvent(KaitenModel):
+ """Triggered when an add-on grant is removed. Delivered as
+ ``com.kaiten.addon.entitlement.v1.unassigned``.
+ """
+
+ name: Literal["ADDON_ENTITLEMENT_UNASSIGNED"]
+ """The event name."""
+
+ type: Literal["com.kaiten.addon.entitlement.v1.unassigned"]
+ """The event type: what tells one event from another."""
+
+ data: AddonGrantUpdateDelivered
+ """What the event is about."""
+
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
+
+class AddonEntitlementUpdatedEvent(KaitenModel):
+ """Triggered when an add-on grant changes. Delivered as
+ ``com.kaiten.addon.entitlement.v1.updated``.
+ """
+
+ name: Literal["ADDON_ENTITLEMENT_UPDATED"]
+ """The event name."""
+
+ type: Literal["com.kaiten.addon.entitlement.v1.updated"]
+ """The event type: what tells one event from another."""
+
+ data: AddonGrantUpdateDelivered
+ """What the event is about."""
+
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
+
+class AddonPriceCreatedEvent(KaitenModel):
+ """Triggered when a price is added to an add-on version. Delivered as
+ ``com.kaiten.addon.price.v1.created``.
+ """
+
+ name: Literal["ADDON_PRICE_CREATED"]
+ """The event name."""
+
+ type: Literal["com.kaiten.addon.price.v1.created"]
+ """The event type: what tells one event from another."""
+
+ data: AddonPriceUpdateDelivered
+ """What the event is about."""
+
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
+
+class AddonPriceDeprecatedEvent(KaitenModel):
+ """Triggered when an add-on price stops being offered. Delivered as
+ ``com.kaiten.addon.price.v1.deprecated``.
+ """
+
+ name: Literal["ADDON_PRICE_DEPRECATED"]
+ """The event name."""
+
+ type: Literal["com.kaiten.addon.price.v1.deprecated"]
+ """The event type: what tells one event from another."""
+
+ data: AddonPriceUpdateDelivered
+ """What the event is about."""
+
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
+
+class AddonArchivedEvent(KaitenModel):
+ """Triggered when an add-on version is withdrawn from sale. Delivered as
+ ``com.kaiten.addon.v1.archived``.
+ """
+
+ name: Literal["ADDON_ARCHIVED"]
+ """The event name."""
+
+ type: Literal["com.kaiten.addon.v1.archived"]
+ """The event type: what tells one event from another."""
+
+ data: AddonDelivered
+ """What the event is about."""
+
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
+
+class AddonCreatedEvent(KaitenModel):
+ """Triggered when an add-on version is created. Delivered as ``com.kaiten.addon.v1.created``."""
+
+ name: Literal["ADDON_CREATED"]
+ """The event name."""
+
+ type: Literal["com.kaiten.addon.v1.created"]
+ """The event type: what tells one event from another."""
+
+ data: AddonDelivered
+ """What the event is about."""
+
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
+
+class AddonDeletedEvent(KaitenModel):
+ """Triggered when an add-on version is deleted. Delivered as ``com.kaiten.addon.v1.deleted``."""
+
+ name: Literal["ADDON_DELETED"]
+ """The event name."""
+
+ type: Literal["com.kaiten.addon.v1.deleted"]
+ """The event type: what tells one event from another."""
+
+ data: DeletedAddonDelivered
+ """What the event is about."""
+
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
+
+class AddonPublishedEvent(KaitenModel):
+ """Triggered when a draft add-on version goes on sale. Delivered as
+ ``com.kaiten.addon.v1.published``.
+ """
+
+ name: Literal["ADDON_PUBLISHED"]
+ """The event name."""
+
+ type: Literal["com.kaiten.addon.v1.published"]
+ """The event type: what tells one event from another."""
+
+ data: AddonDelivered
+ """What the event is about."""
+
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
+
+class AddonUnarchivedEvent(KaitenModel):
+ """Triggered when an archived add-on version goes back on sale. Delivered as
+ ``com.kaiten.addon.v1.unarchived``.
+ """
+
+ name: Literal["ADDON_UNARCHIVED"]
+ """The event name."""
+
+ type: Literal["com.kaiten.addon.v1.unarchived"]
+ """The event type: what tells one event from another."""
+
+ data: AddonDelivered
+ """What the event is about."""
+
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
+
+class AddonUpdatedEvent(KaitenModel):
+ """Triggered when an add-on version, its compatibility or its family's visibility changes.
+ Delivered as ``com.kaiten.addon.v1.updated``.
+ """
+
+ name: Literal["ADDON_UPDATED"]
+ """The event name."""
+
+ type: Literal["com.kaiten.addon.v1.updated"]
+ """The event type: what tells one event from another."""
+
+ data: AddonUpdateDelivered
+ """What the event is about."""
+
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
+
+class BillingProviderConnectedEvent(KaitenModel):
+ """Triggered when an organization connects a payment provider (activates its connector).
+ Delivered as ``com.kaiten.billing_provider.v1.connected``.
+ """
+
+ name: Literal["BILLING_PROVIDER_CONNECTED"]
+ """The event name."""
+
+ type: Literal["com.kaiten.billing_provider.v1.connected"]
+ """The event type: what tells one event from another."""
+
+ data: ConnectionDelivered
+ """What the event is about."""
+
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
+
+class BillingProviderDisconnectedEvent(KaitenModel):
+ """Triggered when an organization disconnects a payment provider (deactivates its
+ connector). Delivered as ``com.kaiten.billing_provider.v1.disconnected``.
+ """
+
+ name: Literal["BILLING_PROVIDER_DISCONNECTED"]
+ """The event name."""
+
+ type: Literal["com.kaiten.billing_provider.v1.disconnected"]
+ """The event type: what tells one event from another."""
+
+ data: ConnectionDelivered
+ """What the event is about."""
+
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
+
+class BillingProviderSyncFailedEvent(KaitenModel):
+ """Triggered when syncing with a payment provider has failed three passes in a row, once
+ per streak. Delivered as ``com.kaiten.billing_provider.v1.sync_failed``.
+ """
+
+ name: Literal["BILLING_PROVIDER_SYNC_FAILED"]
+ """The event name."""
+
+ type: Literal["com.kaiten.billing_provider.v1.sync_failed"]
+ """The event type: what tells one event from another."""
+
+ data: SyncFailureDelivered
+ """What the event is about."""
+
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
+
+class CheckoutSessionAbandonedEvent(KaitenModel):
+ """Triggered when an anonymous checkout ends unpaid: still OPEN 24 h after its start
+ (status ABANDONED), or its INCOMPLETE subscription expired or was canceled (status
+ EXPIRED, with the customer and instance to clean up). Delivered as
+ ``com.kaiten.checkout_session.v1.abandoned``.
+ """
+
+ name: Literal["CHECKOUT_SESSION_ABANDONED"]
+ """The event name."""
+
+ type: Literal["com.kaiten.checkout_session.v1.abandoned"]
+ """The event type: what tells one event from another."""
+
+ data: SessionEventDelivered
+ """What the event is about."""
+
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
+
+class CheckoutSessionCompletedEvent(KaitenModel):
+ """Triggered when an anonymous checkout completes: its ACTIVATION invoice is paid, or
+ nothing was due. Carries the customer and instance it created; provision on this event,
+ never on instance.v1.created. Delivered as ``com.kaiten.checkout_session.v1.completed``.
+ """
+
+ name: Literal["CHECKOUT_SESSION_COMPLETED"]
+ """The event name."""
+
+ type: Literal["com.kaiten.checkout_session.v1.completed"]
+ """The event type: what tells one event from another."""
+
+ data: SessionEventDelivered
+ """What the event is about."""
+
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
+
+class CheckoutSessionStartedEvent(KaitenModel):
+ """Triggered when an anonymous checkout starts (POST /public/checkout-sessions). A dryRun
+ emits nothing. Delivered as ``com.kaiten.checkout_session.v1.started``.
+ """
+
+ name: Literal["CHECKOUT_SESSION_STARTED"]
+ """The event name."""
+
+ type: Literal["com.kaiten.checkout_session.v1.started"]
+ """The event type: what tells one event from another."""
+
+ data: SessionEventDelivered
+ """What the event is about."""
+
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
+
class ComponentCreatedEvent(KaitenModel):
"""Triggered when a new component is created. Delivered as
``com.kaiten.component.v1.created``.
@@ -113,9 +521,12 @@ class ComponentCreatedEvent(KaitenModel):
type: Literal["com.kaiten.component.v1.created"]
"""The event type: what tells one event from another."""
- data: Component
+ data: ComponentDelivered
"""What the event is about."""
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
class ComponentDeletedEvent(KaitenModel):
"""Triggered when a component is deleted. Delivered as ``com.kaiten.component.v1.deleted``."""
@@ -126,9 +537,12 @@ class ComponentDeletedEvent(KaitenModel):
type: Literal["com.kaiten.component.v1.deleted"]
"""The event type: what tells one event from another."""
- data: Component
+ data: ComponentDelivered
"""What the event is about."""
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
class ComponentUpdatedEvent(KaitenModel):
"""Triggered when a component is updated or auto-versioned. Delivered as
@@ -141,9 +555,67 @@ class ComponentUpdatedEvent(KaitenModel):
type: Literal["com.kaiten.component.v1.updated"]
"""The event type: what tells one event from another."""
- data: Component
+ data: ComponentDelivered
+ """What the event is about."""
+
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
+
+class CustomerPaymentMethodAttachedEvent(KaitenModel):
+ """Triggered when a customer's default payment method in a provider is set or replaced (a
+ completed setup session, or a change in the provider's portal). Carries no card data.
+ Delivered as ``com.kaiten.customer.payment_method.v1.attached``.
+ """
+
+ name: Literal["CUSTOMER_PAYMENT_METHOD_ATTACHED"]
+ """The event name."""
+
+ type: Literal["com.kaiten.customer.payment_method.v1.attached"]
+ """The event type: what tells one event from another."""
+
+ data: PaymentMethodAttachedDelivered
+ """What the event is about."""
+
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
+
+class CustomerPaymentMethodDetachedEvent(KaitenModel):
+ """Triggered when a customer no longer has a payment method in a provider. Delivered as
+ ``com.kaiten.customer.payment_method.v1.detached``.
+ """
+
+ name: Literal["CUSTOMER_PAYMENT_METHOD_DETACHED"]
+ """The event name."""
+
+ type: Literal["com.kaiten.customer.payment_method.v1.detached"]
+ """The event type: what tells one event from another."""
+
+ data: PaymentMethodDetachedDelivered
+ """What the event is about."""
+
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
+
+class CustomerPaymentMethodExpiringEvent(KaitenModel):
+ """Triggered once, 30 days before the end of the expiry month of a customer's payment
+ method. Delivered as ``com.kaiten.customer.payment_method.v1.expiring``.
+ """
+
+ name: Literal["CUSTOMER_PAYMENT_METHOD_EXPIRING"]
+ """The event name."""
+
+ type: Literal["com.kaiten.customer.payment_method.v1.expiring"]
+ """The event type: what tells one event from another."""
+
+ data: PaymentMethodExpiringDelivered
"""What the event is about."""
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
class CustomerCreatedEvent(KaitenModel):
"""Triggered when a new customer is created. Delivered as ``com.kaiten.customer.v1.created``."""
@@ -154,9 +626,12 @@ class CustomerCreatedEvent(KaitenModel):
type: Literal["com.kaiten.customer.v1.created"]
"""The event type: what tells one event from another."""
- data: Customer
+ data: CustomerEventDelivered
"""What the event is about."""
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
class CustomerCreationRejectedEvent(KaitenModel):
"""Triggered when a customer creation is refused because the organization has reached its
@@ -173,6 +648,9 @@ class CustomerCreationRejectedEvent(KaitenModel):
data: CustomerCreationRejected
"""What the event is about."""
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
class CustomerDeletedEvent(KaitenModel):
"""Triggered when a customer is deleted. Delivered as ``com.kaiten.customer.v1.deleted``."""
@@ -183,9 +661,12 @@ class CustomerDeletedEvent(KaitenModel):
type: Literal["com.kaiten.customer.v1.deleted"]
"""The event type: what tells one event from another."""
- data: Customer
+ data: CustomerEventDelivered
"""What the event is about."""
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
class CustomerUpdatedEvent(KaitenModel):
"""Triggered when a customer is updated. Delivered as ``com.kaiten.customer.v1.updated``."""
@@ -196,9 +677,12 @@ class CustomerUpdatedEvent(KaitenModel):
type: Literal["com.kaiten.customer.v1.updated"]
"""The event type: what tells one event from another."""
- data: Customer
+ data: CustomerEventDelivered
"""What the event is about."""
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
class DeploymentZoneCreatedEvent(KaitenModel):
"""Triggered when a new deployment zone is created. Delivered as
@@ -211,9 +695,12 @@ class DeploymentZoneCreatedEvent(KaitenModel):
type: Literal["com.kaiten.deployment_zone.v1.created"]
"""The event type: what tells one event from another."""
- data: DeploymentZone
+ data: DeploymentZoneDelivered
"""What the event is about."""
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
class DeploymentZoneDeletedEvent(KaitenModel):
"""Triggered when a deployment zone is deleted. Delivered as
@@ -226,9 +713,12 @@ class DeploymentZoneDeletedEvent(KaitenModel):
type: Literal["com.kaiten.deployment_zone.v1.deleted"]
"""The event type: what tells one event from another."""
- data: DeploymentZone
+ data: DeploymentZoneDelivered
"""What the event is about."""
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
class ReleaseDeployedEvent(KaitenModel):
"""Triggered when a release is deployed to a deployment zone, either at zone creation or
@@ -245,6 +735,9 @@ class ReleaseDeployedEvent(KaitenModel):
data: Deployment
"""What the event is about."""
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
class DeploymentZoneUpdatedEvent(KaitenModel):
"""Triggered when a deployment zone is updated. Delivered as
@@ -257,9 +750,12 @@ class DeploymentZoneUpdatedEvent(KaitenModel):
type: Literal["com.kaiten.deployment_zone.v1.updated"]
"""The event type: what tells one event from another."""
- data: DeploymentZone
+ data: DeploymentZoneDelivered
"""What the event is about."""
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
class EntitlementCreatedEvent(KaitenModel):
"""Triggered when a new entitlement is created. Delivered as
@@ -272,9 +768,12 @@ class EntitlementCreatedEvent(KaitenModel):
type: Literal["com.kaiten.entitlement.v1.created"]
"""The event type: what tells one event from another."""
- data: Entitlement
+ data: EntitlementDelivered
"""What the event is about."""
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
class EntitlementDeletedEvent(KaitenModel):
"""Triggered when an entitlement is deleted. Delivered as
@@ -287,9 +786,12 @@ class EntitlementDeletedEvent(KaitenModel):
type: Literal["com.kaiten.entitlement.v1.deleted"]
"""The event type: what tells one event from another."""
- data: Entitlement
+ data: EntitlementDelivered
"""What the event is about."""
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
class EntitlementUpdatedEvent(KaitenModel):
"""Triggered when an entitlement is updated. Delivered as
@@ -302,9 +804,12 @@ class EntitlementUpdatedEvent(KaitenModel):
type: Literal["com.kaiten.entitlement.v1.updated"]
"""The event type: what tells one event from another."""
- data: Entitlement
+ data: EntitlementDelivered
"""What the event is about."""
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
class EntitlementGroupCreatedEvent(KaitenModel):
"""Triggered when a new entitlement group is created. Delivered as
@@ -317,9 +822,12 @@ class EntitlementGroupCreatedEvent(KaitenModel):
type: Literal["com.kaiten.entitlement_group.v1.created"]
"""The event type: what tells one event from another."""
- data: EntitlementGroup
+ data: EntitlementGroupDelivered
"""What the event is about."""
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
class EntitlementGroupDeletedEvent(KaitenModel):
"""Triggered when an entitlement group is deleted. Delivered as
@@ -332,9 +840,12 @@ class EntitlementGroupDeletedEvent(KaitenModel):
type: Literal["com.kaiten.entitlement_group.v1.deleted"]
"""The event type: what tells one event from another."""
- data: EntitlementGroup
+ data: EntitlementGroupDelivered
"""What the event is about."""
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
class EntitlementGroupUpdatedEvent(KaitenModel):
"""Triggered when an entitlement group is updated. Delivered as
@@ -347,9 +858,12 @@ class EntitlementGroupUpdatedEvent(KaitenModel):
type: Literal["com.kaiten.entitlement_group.v1.updated"]
"""The event type: what tells one event from another."""
- data: EntitlementGroup
+ data: EntitlementGroupDelivered
"""What the event is about."""
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
class FeatureFlagCreatedEvent(KaitenModel):
"""Triggered when a new feature flag is created. Delivered as
@@ -362,183 +876,717 @@ class FeatureFlagCreatedEvent(KaitenModel):
type: Literal["com.kaiten.feature_flag.v1.created"]
"""The event type: what tells one event from another."""
- data: FeatureFlag
+ data: FeatureFlagDelivered
+ """What the event is about."""
+
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
+
+class FeatureFlagDeletedEvent(KaitenModel):
+ """Triggered when a feature flag is deleted. Delivered as
+ ``com.kaiten.feature_flag.v1.deleted``.
+ """
+
+ name: Literal["FEATURE_FLAG_DELETED"]
+ """The event name."""
+
+ type: Literal["com.kaiten.feature_flag.v1.deleted"]
+ """The event type: what tells one event from another."""
+
+ data: FeatureFlagDelivered
+ """What the event is about."""
+
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
+
+class FeatureFlagEvaluatedEvent(KaitenModel):
+ """Triggered on every flag evaluation. The payload carries no evaluation context, so no
+ targeting key or user attribute is exposed. Delivered as
+ ``com.kaiten.feature_flag.v1.evaluated``.
+ """
+
+ name: Literal["FEATURE_FLAG_EVALUATED"]
+ """The event name."""
+
+ type: Literal["com.kaiten.feature_flag.v1.evaluated"]
+ """The event type: what tells one event from another."""
+
+ data: FeatureFlagEvaluated
"""What the event is about."""
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
+
+class FeatureFlagUpdatedEvent(KaitenModel):
+ """Triggered when a feature flag is updated. Delivered as
+ ``com.kaiten.feature_flag.v1.updated``.
+ """
+
+ name: Literal["FEATURE_FLAG_UPDATED"]
+ """The event name."""
+
+ type: Literal["com.kaiten.feature_flag.v1.updated"]
+ """The event type: what tells one event from another."""
+
+ data: FeatureFlagDelivered
+ """What the event is about."""
+
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
+
+class SystemOrganizationTokenIssuedEvent(KaitenModel):
+ """Triggered when a platform credential mints an organization-scoped credential for the
+ `system:kaiten` platform identity inside this organization. Carries the platform
+ credential that performed the mint and the scopes granted, never the credential value.
+ Expect this at the rate an integration refreshes: 96 a day per process at a 15-minute
+ lifetime, more if it refreshes before expiry. Delivered as
+ ``com.kaiten.identity.v1.system_token_issued``.
+ """
+
+ name: Literal["SYSTEM_ORGANIZATION_TOKEN_ISSUED"]
+ """The event name."""
+
+ type: Literal["com.kaiten.identity.v1.system_token_issued"]
+ """The event type: what tells one event from another."""
+
+ data: SystemTokenIssuance
+ """What the event is about."""
+
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
+
+class InstanceAddonAddedEvent(KaitenModel):
+ """Triggered when an add-on is attached to an instance. Delivered as
+ ``com.kaiten.instance.addon.v1.added``.
+ """
+
+ name: Literal["INSTANCE_ADDON_ADDED"]
+ """The event name."""
+
+ type: Literal["com.kaiten.instance.addon.v1.added"]
+ """The event type: what tells one event from another."""
+
+ data: InstanceAddonUpdateDelivered
+ """What the event is about."""
+
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
+
+class InstanceAddonQuantityChangedEvent(KaitenModel):
+ """Triggered when the quantity of an instance's add-on changes. Delivered as
+ ``com.kaiten.instance.addon.v1.quantity_changed``.
+ """
+
+ name: Literal["INSTANCE_ADDON_QUANTITY_CHANGED"]
+ """The event name."""
+
+ type: Literal["com.kaiten.instance.addon.v1.quantity_changed"]
+ """The event type: what tells one event from another."""
+
+ data: InstanceAddonUpdateDelivered
+ """What the event is about."""
+
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
+
+class InstanceAddonRemovedEvent(KaitenModel):
+ """Triggered when an add-on is removed from an instance. Delivered as
+ ``com.kaiten.instance.addon.v1.removed``.
+ """
+
+ name: Literal["INSTANCE_ADDON_REMOVED"]
+ """The event name."""
+
+ type: Literal["com.kaiten.instance.addon.v1.removed"]
+ """The event type: what tells one event from another."""
+
+ data: InstanceAddonUpdateDelivered
+ """What the event is about."""
+
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
+
+class InstanceBillingCanceledEvent(KaitenModel):
+ """Triggered when a subscription is canceled: at its period's end, immediately, or during
+ its trial. Delivered as ``com.kaiten.instance.billing.v1.canceled``.
+ """
+
+ name: Literal["INSTANCE_BILLING_CANCELED"]
+ """The event name."""
+
+ type: Literal["com.kaiten.instance.billing.v1.canceled"]
+ """The event type: what tells one event from another."""
+
+ data: CancellationDelivered
+ """What the event is about."""
+
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
+
+class InstanceBillingCancellationRevertedEvent(KaitenModel):
+ """Triggered when a scheduled cancellation is reverted before the boundary. Delivered as
+ ``com.kaiten.instance.billing.v1.cancellation_reverted``.
+ """
+
+ name: Literal["INSTANCE_BILLING_CANCELLATION_REVERTED"]
+ """The event name."""
+
+ type: Literal["com.kaiten.instance.billing.v1.cancellation_reverted"]
+ """The event type: what tells one event from another."""
+
+ data: CancellationChangeDelivered
+ """What the event is about."""
+
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
+
+class InstanceBillingCancellationScheduledEvent(KaitenModel):
+ """Triggered when a subscription is set to cancel at the end of its period. Delivered as
+ ``com.kaiten.instance.billing.v1.cancellation_scheduled``.
+ """
+
+ name: Literal["INSTANCE_BILLING_CANCELLATION_SCHEDULED"]
+ """The event name."""
+
+ type: Literal["com.kaiten.instance.billing.v1.cancellation_scheduled"]
+ """The event type: what tells one event from another."""
+
+ data: CancellationChangeDelivered
+ """What the event is about."""
+
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
+
+class InstanceBillingPlanChangeCanceledEvent(KaitenModel):
+ """Triggered when a scheduled plan change is dropped: by request, or by a cancellation.
+ Delivered as ``com.kaiten.instance.billing.v1.plan_change_canceled``.
+ """
+
+ name: Literal["INSTANCE_BILLING_PLAN_CHANGE_CANCELED"]
+ """The event name."""
+
+ type: Literal["com.kaiten.instance.billing.v1.plan_change_canceled"]
+ """The event type: what tells one event from another."""
+
+ data: PlanChangeScheduleDelivered
+ """What the event is about."""
+
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
+
+class InstanceBillingPlanChangeScheduledEvent(KaitenModel):
+ """Triggered when a plan change is scheduled for the next boundary, or replaced. Delivered
+ as ``com.kaiten.instance.billing.v1.plan_change_scheduled``.
+ """
+
+ name: Literal["INSTANCE_BILLING_PLAN_CHANGE_SCHEDULED"]
+ """The event name."""
+
+ type: Literal["com.kaiten.instance.billing.v1.plan_change_scheduled"]
+ """The event type: what tells one event from another."""
+
+ data: PlanChangeScheduleDelivered
+ """What the event is about."""
+
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
+
+class InstanceBillingPlanChangedEvent(KaitenModel):
+ """Triggered when a scheduled plan change is applied at a boundary. Delivered as
+ ``com.kaiten.instance.billing.v1.plan_changed``.
+ """
+
+ name: Literal["INSTANCE_BILLING_PLAN_CHANGED"]
+ """The event name."""
+
+ type: Literal["com.kaiten.instance.billing.v1.plan_changed"]
+ """The event type: what tells one event from another."""
+
+ data: PlanChangeDelivered
+ """What the event is about."""
+
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
+
+class InstanceBillingProviderChangedEvent(KaitenModel):
+ """Triggered when a subscription moves to another payment provider, from its next invoice
+ on. Delivered as ``com.kaiten.instance.billing.v1.provider_changed``.
+ """
+
+ name: Literal["INSTANCE_BILLING_PROVIDER_CHANGED"]
+ """The event name."""
+
+ type: Literal["com.kaiten.instance.billing.v1.provider_changed"]
+ """The event type: what tells one event from another."""
+
+ data: ProviderChangeDelivered
+ """What the event is about."""
+
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
+
+class InstanceBillingStartedEvent(KaitenModel):
+ """Triggered when an instance is subscribed, or a CANCELED subscription subscribed again.
+ Delivered as ``com.kaiten.instance.billing.v1.started``.
+ """
+
+ name: Literal["INSTANCE_BILLING_STARTED"]
+ """The event name."""
+
+ type: Literal["com.kaiten.instance.billing.v1.started"]
+ """The event type: what tells one event from another."""
+
+ data: BillingStartedDelivered
+ """What the event is about."""
+
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
+
+class InstanceBillingStatusChangedEvent(KaitenModel):
+ """Triggered when a trial converts, or a subscription becomes PAST_DUE or leaves it.
+ Delivered as ``com.kaiten.instance.billing.v1.status_changed``.
+ """
+
+ name: Literal["INSTANCE_BILLING_STATUS_CHANGED"]
+ """The event name."""
+
+ type: Literal["com.kaiten.instance.billing.v1.status_changed"]
+ """The event type: what tells one event from another."""
+
+ data: StatusChangeDelivered
+ """What the event is about."""
+
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
+
+class InstanceBillingTrialWillEndEvent(KaitenModel):
+ """Triggered once per trial, at reminderAt: its family's trialReminderDays before
+ trialEndsAt (3 by default), or at once when the trial is shorter. Carries what a
+ reminder e-mail needs, read when it is emitted: the customer and its billing e-mail, the
+ instance, the plan, daysLeft, the charge the conversion would make and the card it would
+ be made to. Delivered as ``com.kaiten.instance.billing.v1.trial_will_end``.
+ """
+
+ name: Literal["INSTANCE_BILLING_TRIAL_WILL_END"]
+ """The event name."""
+
+ type: Literal["com.kaiten.instance.billing.v1.trial_will_end"]
+ """The event type: what tells one event from another."""
+
+ data: TrialWillEndDelivered
+ """What the event is about."""
+
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
+
+class InstanceEntitlementCapExceededEvent(KaitenModel):
+ """Triggered when a SOFT-enforcement entitlement's usage crosses from below the cap to
+ above it. Fires once per crossing. Delivered as
+ ``com.kaiten.instance.entitlement.v1.cap_exceeded``.
+ """
+
+ name: Literal["INSTANCE_ENTITLEMENT_CAP_EXCEEDED"]
+ """The event name."""
+
+ type: Literal["com.kaiten.instance.entitlement.v1.cap_exceeded"]
+ """The event type: what tells one event from another."""
+
+ data: InstanceEntitlementCapExceeded
+ """What the event is about."""
+
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
+
+class InstanceEntitlementUsagePeriodRolledOverEvent(KaitenModel):
+ """Triggered once per usage window closed by a report: the entitlement's own stored bucket,
+ and one synthetic event per window skipped entirely since the last report. Delivered as
+ ``com.kaiten.instance.entitlement.v1.usage_period_rolled_over``.
+ """
+
+ name: Literal["INSTANCE_ENTITLEMENT_USAGE_PERIOD_ROLLED_OVER"]
+ """The event name."""
+
+ type: Literal["com.kaiten.instance.entitlement.v1.usage_period_rolled_over"]
+ """The event type: what tells one event from another."""
+
+ data: InstanceEntitlementUsagePeriodRolledOver
+ """What the event is about."""
+
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
+
+class InstanceEntitlementUsageReachedEvent(KaitenModel):
+ """Triggered when an instance's entitlement usage reaches its threshold. Delivered as
+ ``com.kaiten.instance.entitlement.v1.usage_reached``.
+ """
+
+ name: Literal["INSTANCE_ENTITLEMENT_USAGE_REACHED"]
+ """The event name."""
+
+ type: Literal["com.kaiten.instance.entitlement.v1.usage_reached"]
+ """The event type: what tells one event from another."""
+
+ data: EntitlementUsageDelivered
+ """What the event is about."""
+
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
+
+class InstanceEntitlementUsageReportAcceptedEvent(KaitenModel):
+ """Triggered when a usage report is persisted, including a SOFT-enforcement overage.
+ Delivered as ``com.kaiten.instance.entitlement.v1.usage_report_accepted``.
+ """
+
+ name: Literal["ENTITLEMENT_USAGE_REPORT_ACCEPTED"]
+ """The event name."""
+
+ type: Literal["com.kaiten.instance.entitlement.v1.usage_report_accepted"]
+ """The event type: what tells one event from another."""
+
+ data: InstanceEntitlementUsageReportAccepted
+ """What the event is about."""
+
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
+
+class InstanceEntitlementUsageReportRejectedEvent(KaitenModel):
+ """Triggered when a usage report is refused because it would take a HARD-enforcement
+ entitlement past its cap. The usage is not persisted. Delivered as
+ ``com.kaiten.instance.entitlement.v1.usage_report_rejected``.
+ """
+
+ name: Literal["ENTITLEMENT_USAGE_REPORT_REJECTED"]
+ """The event name."""
+
+ type: Literal["com.kaiten.instance.entitlement.v1.usage_report_rejected"]
+ """The event type: what tells one event from another."""
+
+ data: InstanceEntitlementUsageReportAccepted
+ """What the event is about."""
+
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
+
+class InstanceEntitlementUsageWarningThresholdReachedEvent(KaitenModel):
+ """Triggered when an instance's entitlement usage crosses its configured early-warning
+ percentage boundary, before the cap is reached. Fires once per crossing. Delivered as
+ ``com.kaiten.instance.entitlement.v1.usage_warning_threshold_reached``.
+ """
+
+ name: Literal["INSTANCE_ENTITLEMENT_USAGE_WARNING_THRESHOLD_REACHED"]
+ """The event name."""
+
+ type: Literal["com.kaiten.instance.entitlement.v1.usage_warning_threshold_reached"]
+ """The event type: what tells one event from another."""
+
+ data: InstanceEntitlementUsageWarningThresholdReached
+ """What the event is about."""
+
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
+
+class InstanceEntitlementValueGetEvent(KaitenModel):
+ """Triggered on every read of an instance entitlement's usage value. This is a read audit
+ signal, not a state change. Delivered as
+ ``com.kaiten.instance.entitlement.v1.value_get``.
+ """
+
+ name: Literal["ENTITLEMENT_VALUE_GET"]
+ """The event name."""
+
+ type: Literal["com.kaiten.instance.entitlement.v1.value_get"]
+ """The event type: what tells one event from another."""
+
+ data: InstanceEntitlementValueGet
+ """What the event is about."""
+
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
+
+class InstanceEntitlementExceptionCreatedEvent(KaitenModel):
+ """Triggered when an exception is created on one entitlement of an instance. Emitted
+ whether or not billing is enabled. Delivered as
+ ``com.kaiten.instance.entitlement_exception.v1.created``.
+ """
+
+ name: Literal["INSTANCE_ENTITLEMENT_EXCEPTION_CREATED"]
+ """The event name."""
+
+ type: Literal["com.kaiten.instance.entitlement_exception.v1.created"]
+ """The event type: what tells one event from another."""
+
+ data: ExceptionEventDelivered
+ """What the event is about."""
+
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
+
+class InstanceEntitlementExceptionExpiredEvent(KaitenModel):
+ """Triggered once per exception, when its endsAt passes; never for a revoked one. Delivered
+ as ``com.kaiten.instance.entitlement_exception.v1.expired``.
+ """
+
+ name: Literal["INSTANCE_ENTITLEMENT_EXCEPTION_EXPIRED"]
+ """The event name."""
+
+ type: Literal["com.kaiten.instance.entitlement_exception.v1.expired"]
+ """The event type: what tells one event from another."""
+
+ data: ExceptionEventDelivered
+ """What the event is about."""
+
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
+
+class InstanceEntitlementExceptionRevokedEvent(KaitenModel):
+ """Triggered when an exception is revoked. Revoking one already revoked or ended emits
+ nothing. Delivered as ``com.kaiten.instance.entitlement_exception.v1.revoked``.
+ """
+
+ name: Literal["INSTANCE_ENTITLEMENT_EXCEPTION_REVOKED"]
+ """The event name."""
+
+ type: Literal["com.kaiten.instance.entitlement_exception.v1.revoked"]
+ """The event type: what tells one event from another."""
+
+ data: ExceptionEventDelivered
+ """What the event is about."""
+
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
-class FeatureFlagDeletedEvent(KaitenModel):
- """Triggered when a feature flag is deleted. Delivered as
- ``com.kaiten.feature_flag.v1.deleted``.
+class InstanceInvoiceHandoffAcknowledgedEvent(KaitenModel):
+ """Triggered when the organization's accounting system acknowledges an invoice from the
+ handoff queue, or the invoice is marked paid while still waiting there. Delivered as
+ ``com.kaiten.instance.invoice.v1.handoff_acknowledged``.
"""
- name: Literal["FEATURE_FLAG_DELETED"]
+ name: Literal["INSTANCE_INVOICE_HANDOFF_ACKNOWLEDGED"]
"""The event name."""
- type: Literal["com.kaiten.feature_flag.v1.deleted"]
+ type: Literal["com.kaiten.instance.invoice.v1.handoff_acknowledged"]
"""The event type: what tells one event from another."""
- data: FeatureFlag
+ data: HandoffAcknowledgementDelivered
"""What the event is about."""
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
-class FeatureFlagEvaluatedEvent(KaitenModel):
- """Triggered on every flag evaluation. The payload carries no evaluation context, so no
- targeting key or user attribute is exposed. Delivered as
- ``com.kaiten.feature_flag.v1.evaluated``.
+
+class InstanceInvoiceHeldEvent(KaitenModel):
+ """Triggered when a period closes into an invoice held as a DRAFT, because the usage
+ journal it was measured from failed a consistency check. It is neither issued nor handed
+ off until released or recomposed. Delivered as ``com.kaiten.instance.invoice.v1.held``.
"""
- name: Literal["FEATURE_FLAG_EVALUATED"]
+ name: Literal["INSTANCE_INVOICE_HELD"]
"""The event name."""
- type: Literal["com.kaiten.feature_flag.v1.evaluated"]
+ type: Literal["com.kaiten.instance.invoice.v1.held"]
"""The event type: what tells one event from another."""
- data: FeatureFlagEvaluated
+ data: HeldInvoiceDelivered
"""What the event is about."""
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
-class FeatureFlagUpdatedEvent(KaitenModel):
- """Triggered when a feature flag is updated. Delivered as
- ``com.kaiten.feature_flag.v1.updated``.
+
+class InstanceInvoiceIssuedEvent(KaitenModel):
+ """Triggered once per invoice with something owed, when it is issued. For an invoice the
+ organization collects itself, it is the signal to claim it from the handoff queue, which
+ stays the source of truth. Delivered as ``com.kaiten.instance.invoice.v1.issued``.
"""
- name: Literal["FEATURE_FLAG_UPDATED"]
+ name: Literal["INSTANCE_INVOICE_ISSUED"]
"""The event name."""
- type: Literal["com.kaiten.feature_flag.v1.updated"]
+ type: Literal["com.kaiten.instance.invoice.v1.issued"]
"""The event type: what tells one event from another."""
- data: FeatureFlag
+ data: IssuedInvoiceDelivered
"""What the event is about."""
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
-class SystemOrganizationTokenIssuedEvent(KaitenModel):
- """Triggered when a platform credential mints an organization-scoped credential for the
- `system:kaiten` platform identity inside this organization. Carries the platform
- credential that performed the mint and the scopes granted, never the credential value.
- Expect this at the rate an integration refreshes: roughly 120 per day per process at a
- 15-minute lifetime. Delivered as ``com.kaiten.identity.v1.system_token_issued``.
+
+class InstanceInvoiceMarkedUncollectibleEvent(KaitenModel):
+ """Triggered when an invoice is written off. Delivered as
+ ``com.kaiten.instance.invoice.v1.marked_uncollectible``.
"""
- name: Literal["SYSTEM_ORGANIZATION_TOKEN_ISSUED"]
+ name: Literal["INSTANCE_INVOICE_MARKED_UNCOLLECTIBLE"]
"""The event name."""
- type: Literal["com.kaiten.identity.v1.system_token_issued"]
+ type: Literal["com.kaiten.instance.invoice.v1.marked_uncollectible"]
"""The event type: what tells one event from another."""
- data: SystemTokenIssuance
+ data: UncollectibleInvoiceDelivered
"""What the event is about."""
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
-class InstanceEntitlementCapExceededEvent(KaitenModel):
- """Triggered when a SOFT-enforcement entitlement's usage crosses from below the cap to
- above it. Fires once per crossing. Delivered as
- ``com.kaiten.instance.entitlement.v1.cap_exceeded``.
+
+class InstanceInvoicePaidEvent(KaitenModel):
+ """Triggered when an invoice is paid: marked paid by the organization, or issued paid
+ because nothing was owed. Delivered as ``com.kaiten.instance.invoice.v1.paid``.
"""
- name: Literal["INSTANCE_ENTITLEMENT_CAP_EXCEEDED"]
+ name: Literal["INSTANCE_INVOICE_PAID"]
"""The event name."""
- type: Literal["com.kaiten.instance.entitlement.v1.cap_exceeded"]
+ type: Literal["com.kaiten.instance.invoice.v1.paid"]
"""The event type: what tells one event from another."""
- data: InstanceEntitlementCapExceeded
+ data: PaidInvoiceDelivered
"""What the event is about."""
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
-class InstanceEntitlementUsagePeriodRolledOverEvent(KaitenModel):
- """Triggered once per usage window closed by a report: the entitlement's own stored bucket,
- and one synthetic event per window skipped entirely since the last report. Delivered as
- ``com.kaiten.instance.entitlement.v1.usage_period_rolled_over``.
+
+class InstanceInvoicePaymentFailedEvent(KaitenModel):
+ """Triggered when the automatic charge of an invoice is refused, or needs the customer to
+ authenticate it on the invoice's hosted page. Delivered as
+ ``com.kaiten.instance.invoice.v1.payment_failed``.
"""
- name: Literal["INSTANCE_ENTITLEMENT_USAGE_PERIOD_ROLLED_OVER"]
+ name: Literal["INSTANCE_INVOICE_PAYMENT_FAILED"]
"""The event name."""
- type: Literal["com.kaiten.instance.entitlement.v1.usage_period_rolled_over"]
+ type: Literal["com.kaiten.instance.invoice.v1.payment_failed"]
"""The event type: what tells one event from another."""
- data: InstanceEntitlementUsagePeriodRolledOver
+ data: PaymentFailedInvoiceDelivered
"""What the event is about."""
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
-class InstanceEntitlementUsageReachedEvent(KaitenModel):
- """Triggered when an instance's entitlement usage reaches its threshold. Delivered as
- ``com.kaiten.instance.entitlement.v1.usage_reached``.
+
+class InstanceInvoicePushFailedEvent(KaitenModel):
+ """Triggered when pushing an invoice to its payment provider has failed as many times as
+ the alert threshold, then once a day while it keeps failing. Delivered as
+ ``com.kaiten.instance.invoice.v1.push_failed``.
"""
- name: Literal["INSTANCE_ENTITLEMENT_USAGE_REACHED"]
+ name: Literal["INSTANCE_INVOICE_PUSH_FAILED"]
"""The event name."""
- type: Literal["com.kaiten.instance.entitlement.v1.usage_reached"]
+ type: Literal["com.kaiten.instance.invoice.v1.push_failed"]
"""The event type: what tells one event from another."""
- data: EntitlementUsage
+ data: PushFailedInvoiceDelivered
"""What the event is about."""
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
-class InstanceEntitlementUsageReportAcceptedEvent(KaitenModel):
- """Triggered when a usage report is persisted, including a SOFT-enforcement overage.
- Delivered as ``com.kaiten.instance.entitlement.v1.usage_report_accepted``.
+
+class InstanceInvoicePushedEvent(KaitenModel):
+ """Triggered when an invoice's payment provider has issued it, right after
+ INSTANCE_INVOICE_ISSUED. Delivered as ``com.kaiten.instance.invoice.v1.pushed``.
"""
- name: Literal["ENTITLEMENT_USAGE_REPORT_ACCEPTED"]
+ name: Literal["INSTANCE_INVOICE_PUSHED"]
"""The event name."""
- type: Literal["com.kaiten.instance.entitlement.v1.usage_report_accepted"]
+ type: Literal["com.kaiten.instance.invoice.v1.pushed"]
"""The event type: what tells one event from another."""
- data: InstanceEntitlementUsageReportAccepted
+ data: PushedInvoiceDelivered
"""What the event is about."""
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
-class InstanceEntitlementUsageReportRejectedEvent(KaitenModel):
- """Triggered when a usage report is refused because it would take a HARD-enforcement
- entitlement past its cap. The usage is not persisted. Delivered as
- ``com.kaiten.instance.entitlement.v1.usage_report_rejected``.
+
+class InstanceInvoiceReconciliationMismatchEvent(KaitenModel):
+ """Triggered when an invoice's payment provider holds amounts that differ from Kaiten's.
+ Kaiten never corrects itself: void and recompose the invoice. Delivered as
+ ``com.kaiten.instance.invoice.v1.reconciliation_mismatch``.
"""
- name: Literal["ENTITLEMENT_USAGE_REPORT_REJECTED"]
+ name: Literal["INSTANCE_INVOICE_RECONCILIATION_MISMATCH"]
"""The event name."""
- type: Literal["com.kaiten.instance.entitlement.v1.usage_report_rejected"]
+ type: Literal["com.kaiten.instance.invoice.v1.reconciliation_mismatch"]
"""The event type: what tells one event from another."""
- data: InstanceEntitlementUsageReportAccepted
+ data: MismatchedInvoiceDelivered
"""What the event is about."""
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
-class InstanceEntitlementUsageWarningThresholdReachedEvent(KaitenModel):
- """Triggered when an instance's entitlement usage crosses its configured early-warning
- percentage boundary, before the cap is reached. Fires once per crossing. Delivered as
- ``com.kaiten.instance.entitlement.v1.usage_warning_threshold_reached``.
+
+class InstanceInvoiceReleasedEvent(KaitenModel):
+ """Triggered when a held invoice leaves its hold: released by the organization, recomposed
+ from a sound journal, or found sound by a later check. It is issued in the same
+ transaction. Delivered as ``com.kaiten.instance.invoice.v1.released``.
"""
- name: Literal["INSTANCE_ENTITLEMENT_USAGE_WARNING_THRESHOLD_REACHED"]
+ name: Literal["INSTANCE_INVOICE_RELEASED"]
"""The event name."""
- type: Literal["com.kaiten.instance.entitlement.v1.usage_warning_threshold_reached"]
+ type: Literal["com.kaiten.instance.invoice.v1.released"]
"""The event type: what tells one event from another."""
- data: InstanceEntitlementUsageWarningThresholdReached
+ data: ReleasedInvoiceDelivered
"""What the event is about."""
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
-class InstanceEntitlementValueGetEvent(KaitenModel):
- """Triggered on every read of an instance entitlement's usage value. This is a read audit
- signal, not a state change. Delivered as
- ``com.kaiten.instance.entitlement.v1.value_get``.
+
+class InstanceInvoiceVoidedEvent(KaitenModel):
+ """Triggered when an invoice is voided. Its boundary can then be recomposed into a
+ replacement. Delivered as ``com.kaiten.instance.invoice.v1.voided``.
"""
- name: Literal["ENTITLEMENT_VALUE_GET"]
+ name: Literal["INSTANCE_INVOICE_VOIDED"]
"""The event name."""
- type: Literal["com.kaiten.instance.entitlement.v1.value_get"]
+ type: Literal["com.kaiten.instance.invoice.v1.voided"]
"""The event type: what tells one event from another."""
- data: InstanceEntitlementValueGet
+ data: VoidedInvoiceDelivered
"""What the event is about."""
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
class InstanceCreatedEvent(KaitenModel):
"""Triggered when a new instance is created. Delivered as ``com.kaiten.instance.v1.created``."""
@@ -549,9 +1597,12 @@ class InstanceCreatedEvent(KaitenModel):
type: Literal["com.kaiten.instance.v1.created"]
"""The event type: what tells one event from another."""
- data: Instance
+ data: InstanceDelivered
"""What the event is about."""
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
class InstanceDeletedEvent(KaitenModel):
"""Triggered when an instance is deleted. Delivered as ``com.kaiten.instance.v1.deleted``."""
@@ -562,9 +1613,12 @@ class InstanceDeletedEvent(KaitenModel):
type: Literal["com.kaiten.instance.v1.deleted"]
"""The event type: what tells one event from another."""
- data: Instance
+ data: InstanceDelivered
"""What the event is about."""
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
class InstanceDeployedEvent(KaitenModel):
"""Triggered when an instance is deployed to a deployment zone. Delivered as
@@ -577,9 +1631,12 @@ class InstanceDeployedEvent(KaitenModel):
type: Literal["com.kaiten.instance.v1.deployed"]
"""The event type: what tells one event from another."""
- data: Instance
+ data: InstanceDelivered
"""What the event is about."""
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
class InstanceLifecycleStageChangedEvent(KaitenModel):
"""Triggered when an instance commercial lifecycle stage changes. Delivered as
@@ -595,6 +1652,9 @@ class InstanceLifecycleStageChangedEvent(KaitenModel):
data: InstanceLifecycleStageChanged
"""What the event is about."""
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
class InstanceMigratedEvent(KaitenModel):
"""Triggered when an instance is migrated from one deployment zone to another. Delivered as
@@ -607,9 +1667,12 @@ class InstanceMigratedEvent(KaitenModel):
type: Literal["com.kaiten.instance.v1.migrated"]
"""The event type: what tells one event from another."""
- data: Instance
+ data: InstanceDelivered
"""What the event is about."""
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
class InstanceStatusChangedEvent(KaitenModel):
"""Triggered when an instance operational status changes. Delivered as
@@ -625,9 +1688,15 @@ class InstanceStatusChangedEvent(KaitenModel):
data: InstanceStatusChanged
"""What the event is about."""
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
class InstanceUpdatedEvent(KaitenModel):
- """Triggered when an instance is updated. Delivered as ``com.kaiten.instance.v1.updated``."""
+ """Triggered when an instance is updated, by a write or by billing: the licence version a
+ paid checkout bought, or the fallback a subscription that ended moves it to
+ (licenseChangeReason BILLING_FALLBACK). Delivered as ``com.kaiten.instance.v1.updated``.
+ """
name: Literal["INSTANCE_UPDATED"]
"""The event name."""
@@ -635,9 +1704,66 @@ class InstanceUpdatedEvent(KaitenModel):
type: Literal["com.kaiten.instance.v1.updated"]
"""The event type: what tells one event from another."""
- data: Instance
+ data: InstanceUpdateDelivered
+ """What the event is about."""
+
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
+
+class InstanceVoucherExpiredEvent(KaitenModel):
+ """Triggered when a PRICE redemption has discounted its last invoice, or a boost's window
+ ends. Delivered as ``com.kaiten.instance.voucher.v1.expired``.
+ """
+
+ name: Literal["INSTANCE_VOUCHER_EXPIRED"]
+ """The event name."""
+
+ type: Literal["com.kaiten.instance.voucher.v1.expired"]
+ """The event type: what tells one event from another."""
+
+ data: RedemptionDelivered
+ """What the event is about."""
+
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
+
+class InstanceVoucherRedeemedEvent(KaitenModel):
+ """Triggered when an instance redeems a voucher. Delivered as
+ ``com.kaiten.instance.voucher.v1.redeemed``.
+ """
+
+ name: Literal["INSTANCE_VOUCHER_REDEEMED"]
+ """The event name."""
+
+ type: Literal["com.kaiten.instance.voucher.v1.redeemed"]
+ """The event type: what tells one event from another."""
+
+ data: RedemptionDelivered
+ """What the event is about."""
+
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
+
+class InstanceVoucherRevokedEvent(KaitenModel):
+ """Triggered when a redemption is revoked. Delivered as
+ ``com.kaiten.instance.voucher.v1.revoked``.
+ """
+
+ name: Literal["INSTANCE_VOUCHER_REVOKED"]
+ """The event name."""
+
+ type: Literal["com.kaiten.instance.voucher.v1.revoked"]
+ """The event type: what tells one event from another."""
+
+ data: RedemptionDelivered
"""What the event is about."""
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
class LicenseEntitlementAssignedEvent(KaitenModel):
"""Triggered when an entitlement is assigned to a license. Delivered as
@@ -650,9 +1776,12 @@ class LicenseEntitlementAssignedEvent(KaitenModel):
type: Literal["com.kaiten.license.entitlement.v1.assigned"]
"""The event type: what tells one event from another."""
- data: LicenseEntitlement
+ data: LicenseEntitlementDelivered
"""What the event is about."""
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
class LicenseEntitlementUnassignedEvent(KaitenModel):
"""Triggered when an entitlement is unassigned from a license. Delivered as
@@ -665,9 +1794,12 @@ class LicenseEntitlementUnassignedEvent(KaitenModel):
type: Literal["com.kaiten.license.entitlement.v1.unassigned"]
"""The event type: what tells one event from another."""
- data: LicenseEntitlement
+ data: LicenseEntitlementDelivered
"""What the event is about."""
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
class LicenseEntitlementUpdatedEvent(KaitenModel):
"""Triggered when a license entitlement is updated. Delivered as
@@ -680,9 +1812,66 @@ class LicenseEntitlementUpdatedEvent(KaitenModel):
type: Literal["com.kaiten.license.entitlement.v1.updated"]
"""The event type: what tells one event from another."""
- data: LicenseEntitlement
+ data: LicenseEntitlementDelivered
+ """What the event is about."""
+
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
+
+class LicensePriceCreatedEvent(KaitenModel):
+ """Triggered when a price is added to a licence version. Delivered as
+ ``com.kaiten.license.price.v1.created``.
+ """
+
+ name: Literal["LICENSE_PRICE_CREATED"]
+ """The event name."""
+
+ type: Literal["com.kaiten.license.price.v1.created"]
+ """The event type: what tells one event from another."""
+
+ data: LicensePriceEventDelivered
+ """What the event is about."""
+
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
+
+class LicensePriceDeprecatedEvent(KaitenModel):
+ """Triggered when a licence price is deprecated. Delivered as
+ ``com.kaiten.license.price.v1.deprecated``.
+ """
+
+ name: Literal["LICENSE_PRICE_DEPRECATED"]
+ """The event name."""
+
+ type: Literal["com.kaiten.license.price.v1.deprecated"]
+ """The event type: what tells one event from another."""
+
+ data: LicensePriceEventDelivered
+ """What the event is about."""
+
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
+
+class LicensePriceUpdatedEvent(KaitenModel):
+ """Triggered when a price of a DRAFT licence version is edited. Delivered as
+ ``com.kaiten.license.price.v1.updated``.
+ """
+
+ name: Literal["LICENSE_PRICE_UPDATED"]
+ """The event name."""
+
+ type: Literal["com.kaiten.license.price.v1.updated"]
+ """The event type: what tells one event from another."""
+
+ data: LicensePriceUpdatedEventDelivered
"""What the event is about."""
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
class LicenseArchivedEvent(KaitenModel):
"""Triggered when a published license version is archived and withdrawn from sale.
@@ -695,9 +1884,12 @@ class LicenseArchivedEvent(KaitenModel):
type: Literal["com.kaiten.license.v1.archived"]
"""The event type: what tells one event from another."""
- data: License
+ data: LicenseDelivered
"""What the event is about."""
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
class LicenseCreatedEvent(KaitenModel):
"""Triggered when a new license is created. Delivered as ``com.kaiten.license.v1.created``."""
@@ -708,9 +1900,12 @@ class LicenseCreatedEvent(KaitenModel):
type: Literal["com.kaiten.license.v1.created"]
"""The event type: what tells one event from another."""
- data: License
+ data: LicenseDelivered
"""What the event is about."""
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
class LicenseDeletedEvent(KaitenModel):
"""Triggered when a license is deleted. Delivered as ``com.kaiten.license.v1.deleted``."""
@@ -721,9 +1916,12 @@ class LicenseDeletedEvent(KaitenModel):
type: Literal["com.kaiten.license.v1.deleted"]
"""The event type: what tells one event from another."""
- data: License
+ data: LicenseDelivered
"""What the event is about."""
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
class LicensePublishedEvent(KaitenModel):
"""Triggered when a draft license version is published and goes on sale. Delivered as
@@ -736,9 +1934,12 @@ class LicensePublishedEvent(KaitenModel):
type: Literal["com.kaiten.license.v1.published"]
"""The event type: what tells one event from another."""
- data: License
+ data: LicenseDelivered
"""What the event is about."""
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
class LicenseUnarchivedEvent(KaitenModel):
"""Triggered when an archived license version is put back on sale. Delivered as
@@ -751,9 +1952,12 @@ class LicenseUnarchivedEvent(KaitenModel):
type: Literal["com.kaiten.license.v1.unarchived"]
"""The event type: what tells one event from another."""
- data: License
+ data: LicenseDelivered
"""What the event is about."""
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
class LicenseUpdatedEvent(KaitenModel):
"""Triggered when a license is updated. Delivered as ``com.kaiten.license.v1.updated``."""
@@ -764,9 +1968,12 @@ class LicenseUpdatedEvent(KaitenModel):
type: Literal["com.kaiten.license.v1.updated"]
"""The event type: what tells one event from another."""
- data: License
+ data: LicenseDelivered
"""What the event is about."""
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
class LicenseFamilyCreatedEvent(KaitenModel):
"""Triggered when a license is created without naming a family: its version 1 opens a new
@@ -781,9 +1988,12 @@ class LicenseFamilyCreatedEvent(KaitenModel):
type: Literal["com.kaiten.license_family.v1.created"]
"""The event type: what tells one event from another."""
- data: LicenseFamilyView
+ data: LicenseFamilyViewDelivered
"""What the event is about."""
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
class LicenseFamilyDeletedEvent(KaitenModel):
"""Triggered when a license family's last version is deleted, which deletes the family and
@@ -798,9 +2008,12 @@ class LicenseFamilyDeletedEvent(KaitenModel):
type: Literal["com.kaiten.license_family.v1.deleted"]
"""The event type: what tells one event from another."""
- data: LicenseFamilyView
+ data: LicenseFamilyViewDelivered
"""What the event is about."""
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
class LicenseFamilyUpdatedEvent(KaitenModel):
"""Triggered when a license family starts serving another version, or none: a newer version
@@ -818,9 +2031,12 @@ class LicenseFamilyUpdatedEvent(KaitenModel):
type: Literal["com.kaiten.license_family.v1.updated"]
"""The event type: what tells one event from another."""
- data: LicenseFamilyView
+ data: LicenseFamilyViewDelivered
"""What the event is about."""
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
class MetadataFieldArchivedEvent(KaitenModel):
"""Triggered when a metadata field is archived and leaves the active schema composition.
@@ -833,9 +2049,12 @@ class MetadataFieldArchivedEvent(KaitenModel):
type: Literal["com.kaiten.metadata_field.v1.archived"]
"""The event type: what tells one event from another."""
- data: MetadataField
+ data: MetadataFieldDelivered
"""What the event is about."""
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
class MetadataFieldCreatedEvent(KaitenModel):
"""Triggered when a metadata field is declared for a resource type. Delivered as
@@ -848,9 +2067,12 @@ class MetadataFieldCreatedEvent(KaitenModel):
type: Literal["com.kaiten.metadata_field.v1.created"]
"""The event type: what tells one event from another."""
- data: MetadataField
+ data: MetadataFieldDelivered
"""What the event is about."""
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
class MetadataFieldReorderedEvent(KaitenModel):
"""Triggered when the display order of a resource type's metadata fields is rewritten.
@@ -866,6 +2088,9 @@ class MetadataFieldReorderedEvent(KaitenModel):
data: MetadataFieldReordered
"""What the event is about."""
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
class MetadataFieldUnarchivedEvent(KaitenModel):
"""Triggered when an archived metadata field is restored to the active schema composition.
@@ -878,9 +2103,12 @@ class MetadataFieldUnarchivedEvent(KaitenModel):
type: Literal["com.kaiten.metadata_field.v1.unarchived"]
"""The event type: what tells one event from another."""
- data: MetadataField
+ data: MetadataFieldDelivered
"""What the event is about."""
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
class MetadataFieldUpdatedEvent(KaitenModel):
"""Triggered when a metadata field's label or JSON schema changes. A reordering emits
@@ -893,9 +2121,48 @@ class MetadataFieldUpdatedEvent(KaitenModel):
type: Literal["com.kaiten.metadata_field.v1.updated"]
"""The event type: what tells one event from another."""
- data: MetadataField
+ data: MetadataFieldDelivered
+ """What the event is about."""
+
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
+
+class PublishableKeyCreatedEvent(KaitenModel):
+ """Triggered when a publishable key is issued. The key is never in the payload, only its
+ hint. Delivered as ``com.kaiten.publishable_key.v1.created``.
+ """
+
+ name: Literal["PUBLISHABLE_KEY_CREATED"]
+ """The event name."""
+
+ type: Literal["com.kaiten.publishable_key.v1.created"]
+ """The event type: what tells one event from another."""
+
+ data: PublishableKeyEventDelivered
+ """What the event is about."""
+
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
+
+class PublishableKeyRevokedEvent(KaitenModel):
+ """Triggered once, when a publishable key is revoked. Delivered as
+ ``com.kaiten.publishable_key.v1.revoked``.
+ """
+
+ name: Literal["PUBLISHABLE_KEY_REVOKED"]
+ """The event name."""
+
+ type: Literal["com.kaiten.publishable_key.v1.revoked"]
+ """The event type: what tells one event from another."""
+
+ data: PublishableKeyEventDelivered
"""What the event is about."""
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
class ReleaseCreatedEvent(KaitenModel):
"""Triggered when a new release is created. Delivered as ``com.kaiten.release.v1.created``."""
@@ -906,9 +2173,12 @@ class ReleaseCreatedEvent(KaitenModel):
type: Literal["com.kaiten.release.v1.created"]
"""The event type: what tells one event from another."""
- data: Release
+ data: ReleaseDelivered
"""What the event is about."""
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
class ReleaseDeletedEvent(KaitenModel):
"""Triggered when a release is deleted. Delivered as ``com.kaiten.release.v1.deleted``."""
@@ -919,14 +2189,159 @@ class ReleaseDeletedEvent(KaitenModel):
type: Literal["com.kaiten.release.v1.deleted"]
"""The event type: what tells one event from another."""
- data: Release
+ data: ReleaseDelivered
+ """What the event is about."""
+
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
+
+class VoucherArchivedEvent(KaitenModel):
+ """Triggered when a voucher is archived. Delivered as ``com.kaiten.voucher.v1.archived``."""
+
+ name: Literal["VOUCHER_ARCHIVED"]
+ """The event name."""
+
+ type: Literal["com.kaiten.voucher.v1.archived"]
+ """The event type: what tells one event from another."""
+
+ data: VoucherDelivered
+ """What the event is about."""
+
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
+
+class VoucherCreatedEvent(KaitenModel):
+ """Triggered when a voucher is created. The code is never in the payload. Delivered as
+ ``com.kaiten.voucher.v1.created``.
+ """
+
+ name: Literal["VOUCHER_CREATED"]
+ """The event name."""
+
+ type: Literal["com.kaiten.voucher.v1.created"]
+ """The event type: what tells one event from another."""
+
+ data: VoucherDelivered
+ """What the event is about."""
+
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
+
+class VoucherDeletedEvent(KaitenModel):
+ """Triggered when a voucher no instance ever redeemed is deleted, with its last state.
+ Delivered as ``com.kaiten.voucher.v1.deleted``.
+ """
+
+ name: Literal["VOUCHER_DELETED"]
+ """The event name."""
+
+ type: Literal["com.kaiten.voucher.v1.deleted"]
+ """The event type: what tells one event from another."""
+
+ data: VoucherDelivered
+ """What the event is about."""
+
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
+
+class VoucherExhaustedEvent(KaitenModel):
+ """Triggered by the redemption that reaches maxRedemptions. Delivered as
+ ``com.kaiten.voucher.v1.exhausted``.
+ """
+
+ name: Literal["VOUCHER_EXHAUSTED"]
+ """The event name."""
+
+ type: Literal["com.kaiten.voucher.v1.exhausted"]
+ """The event type: what tells one event from another."""
+
+ data: ExhaustedEventDelivered
+ """What the event is about."""
+
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
+
+class VoucherExpiredEvent(KaitenModel):
+ """Triggered when an active voucher reaches its expiresAt and can no longer be redeemed.
+ Delivered as ``com.kaiten.voucher.v1.expired``.
+ """
+
+ name: Literal["VOUCHER_EXPIRED"]
+ """The event name."""
+
+ type: Literal["com.kaiten.voucher.v1.expired"]
+ """The event type: what tells one event from another."""
+
+ data: ExpiredEventDelivered
+ """What the event is about."""
+
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
+
+class VoucherPublishedEvent(KaitenModel):
+ """Triggered when a draft voucher becomes redeemable. Delivered as
+ ``com.kaiten.voucher.v1.published``.
+ """
+
+ name: Literal["VOUCHER_PUBLISHED"]
+ """The event name."""
+
+ type: Literal["com.kaiten.voucher.v1.published"]
+ """The event type: what tells one event from another."""
+
+ data: VoucherDelivered
+ """What the event is about."""
+
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
+
+class VoucherUpdatedEvent(KaitenModel):
+ """Triggered when a voucher changes. Delivered as ``com.kaiten.voucher.v1.updated``."""
+
+ name: Literal["VOUCHER_UPDATED"]
+ """The event name."""
+
+ type: Literal["com.kaiten.voucher.v1.updated"]
+ """The event type: what tells one event from another."""
+
+ data: VoucherDelivered
"""What the event is about."""
+ headers: EventHeaders | None = None
+ """The trace context of the request that produced the event."""
+
WebhookEvent = Annotated[
- ComponentCreatedEvent
+ AddonEntitlementAssignedEvent
+ | AddonEntitlementUnassignedEvent
+ | AddonEntitlementUpdatedEvent
+ | AddonPriceCreatedEvent
+ | AddonPriceDeprecatedEvent
+ | AddonArchivedEvent
+ | AddonCreatedEvent
+ | AddonDeletedEvent
+ | AddonPublishedEvent
+ | AddonUnarchivedEvent
+ | AddonUpdatedEvent
+ | BillingProviderConnectedEvent
+ | BillingProviderDisconnectedEvent
+ | BillingProviderSyncFailedEvent
+ | CheckoutSessionAbandonedEvent
+ | CheckoutSessionCompletedEvent
+ | CheckoutSessionStartedEvent
+ | ComponentCreatedEvent
| ComponentDeletedEvent
| ComponentUpdatedEvent
+ | CustomerPaymentMethodAttachedEvent
+ | CustomerPaymentMethodDetachedEvent
+ | CustomerPaymentMethodExpiringEvent
| CustomerCreatedEvent
| CustomerCreationRejectedEvent
| CustomerDeletedEvent
@@ -946,6 +2361,19 @@ class ReleaseDeletedEvent(KaitenModel):
| FeatureFlagEvaluatedEvent
| FeatureFlagUpdatedEvent
| SystemOrganizationTokenIssuedEvent
+ | InstanceAddonAddedEvent
+ | InstanceAddonQuantityChangedEvent
+ | InstanceAddonRemovedEvent
+ | InstanceBillingCanceledEvent
+ | InstanceBillingCancellationRevertedEvent
+ | InstanceBillingCancellationScheduledEvent
+ | InstanceBillingPlanChangeCanceledEvent
+ | InstanceBillingPlanChangeScheduledEvent
+ | InstanceBillingPlanChangedEvent
+ | InstanceBillingProviderChangedEvent
+ | InstanceBillingStartedEvent
+ | InstanceBillingStatusChangedEvent
+ | InstanceBillingTrialWillEndEvent
| InstanceEntitlementCapExceededEvent
| InstanceEntitlementUsagePeriodRolledOverEvent
| InstanceEntitlementUsageReachedEvent
@@ -953,6 +2381,20 @@ class ReleaseDeletedEvent(KaitenModel):
| InstanceEntitlementUsageReportRejectedEvent
| InstanceEntitlementUsageWarningThresholdReachedEvent
| InstanceEntitlementValueGetEvent
+ | InstanceEntitlementExceptionCreatedEvent
+ | InstanceEntitlementExceptionExpiredEvent
+ | InstanceEntitlementExceptionRevokedEvent
+ | InstanceInvoiceHandoffAcknowledgedEvent
+ | InstanceInvoiceHeldEvent
+ | InstanceInvoiceIssuedEvent
+ | InstanceInvoiceMarkedUncollectibleEvent
+ | InstanceInvoicePaidEvent
+ | InstanceInvoicePaymentFailedEvent
+ | InstanceInvoicePushFailedEvent
+ | InstanceInvoicePushedEvent
+ | InstanceInvoiceReconciliationMismatchEvent
+ | InstanceInvoiceReleasedEvent
+ | InstanceInvoiceVoidedEvent
| InstanceCreatedEvent
| InstanceDeletedEvent
| InstanceDeployedEvent
@@ -960,9 +2402,15 @@ class ReleaseDeletedEvent(KaitenModel):
| InstanceMigratedEvent
| InstanceStatusChangedEvent
| InstanceUpdatedEvent
+ | InstanceVoucherExpiredEvent
+ | InstanceVoucherRedeemedEvent
+ | InstanceVoucherRevokedEvent
| LicenseEntitlementAssignedEvent
| LicenseEntitlementUnassignedEvent
| LicenseEntitlementUpdatedEvent
+ | LicensePriceCreatedEvent
+ | LicensePriceDeprecatedEvent
+ | LicensePriceUpdatedEvent
| LicenseArchivedEvent
| LicenseCreatedEvent
| LicenseDeletedEvent
@@ -977,16 +2425,45 @@ class ReleaseDeletedEvent(KaitenModel):
| MetadataFieldReorderedEvent
| MetadataFieldUnarchivedEvent
| MetadataFieldUpdatedEvent
+ | PublishableKeyCreatedEvent
+ | PublishableKeyRevokedEvent
| ReleaseCreatedEvent
- | ReleaseDeletedEvent,
+ | ReleaseDeletedEvent
+ | VoucherArchivedEvent
+ | VoucherCreatedEvent
+ | VoucherDeletedEvent
+ | VoucherExhaustedEvent
+ | VoucherExpiredEvent
+ | VoucherPublishedEvent
+ | VoucherUpdatedEvent,
Field(discriminator="type"),
]
"""Any event Kaiten delivers to a webhook endpoint, discriminated by ``type``."""
WEBHOOK_EVENTS: dict[str, type[KaitenModel]] = {
+ "com.kaiten.addon.entitlement.v1.assigned": AddonEntitlementAssignedEvent,
+ "com.kaiten.addon.entitlement.v1.unassigned": AddonEntitlementUnassignedEvent,
+ "com.kaiten.addon.entitlement.v1.updated": AddonEntitlementUpdatedEvent,
+ "com.kaiten.addon.price.v1.created": AddonPriceCreatedEvent,
+ "com.kaiten.addon.price.v1.deprecated": AddonPriceDeprecatedEvent,
+ "com.kaiten.addon.v1.archived": AddonArchivedEvent,
+ "com.kaiten.addon.v1.created": AddonCreatedEvent,
+ "com.kaiten.addon.v1.deleted": AddonDeletedEvent,
+ "com.kaiten.addon.v1.published": AddonPublishedEvent,
+ "com.kaiten.addon.v1.unarchived": AddonUnarchivedEvent,
+ "com.kaiten.addon.v1.updated": AddonUpdatedEvent,
+ "com.kaiten.billing_provider.v1.connected": BillingProviderConnectedEvent,
+ "com.kaiten.billing_provider.v1.disconnected": BillingProviderDisconnectedEvent,
+ "com.kaiten.billing_provider.v1.sync_failed": BillingProviderSyncFailedEvent,
+ "com.kaiten.checkout_session.v1.abandoned": CheckoutSessionAbandonedEvent,
+ "com.kaiten.checkout_session.v1.completed": CheckoutSessionCompletedEvent,
+ "com.kaiten.checkout_session.v1.started": CheckoutSessionStartedEvent,
"com.kaiten.component.v1.created": ComponentCreatedEvent,
"com.kaiten.component.v1.deleted": ComponentDeletedEvent,
"com.kaiten.component.v1.updated": ComponentUpdatedEvent,
+ "com.kaiten.customer.payment_method.v1.attached": CustomerPaymentMethodAttachedEvent,
+ "com.kaiten.customer.payment_method.v1.detached": CustomerPaymentMethodDetachedEvent,
+ "com.kaiten.customer.payment_method.v1.expiring": CustomerPaymentMethodExpiringEvent,
"com.kaiten.customer.v1.created": CustomerCreatedEvent,
"com.kaiten.customer.v1.creation_rejected": CustomerCreationRejectedEvent,
"com.kaiten.customer.v1.deleted": CustomerDeletedEvent,
@@ -1006,6 +2483,19 @@ class ReleaseDeletedEvent(KaitenModel):
"com.kaiten.feature_flag.v1.evaluated": FeatureFlagEvaluatedEvent,
"com.kaiten.feature_flag.v1.updated": FeatureFlagUpdatedEvent,
"com.kaiten.identity.v1.system_token_issued": SystemOrganizationTokenIssuedEvent,
+ "com.kaiten.instance.addon.v1.added": InstanceAddonAddedEvent,
+ "com.kaiten.instance.addon.v1.quantity_changed": InstanceAddonQuantityChangedEvent,
+ "com.kaiten.instance.addon.v1.removed": InstanceAddonRemovedEvent,
+ "com.kaiten.instance.billing.v1.canceled": InstanceBillingCanceledEvent,
+ "com.kaiten.instance.billing.v1.cancellation_reverted": InstanceBillingCancellationRevertedEvent,
+ "com.kaiten.instance.billing.v1.cancellation_scheduled": InstanceBillingCancellationScheduledEvent,
+ "com.kaiten.instance.billing.v1.plan_change_canceled": InstanceBillingPlanChangeCanceledEvent,
+ "com.kaiten.instance.billing.v1.plan_change_scheduled": InstanceBillingPlanChangeScheduledEvent,
+ "com.kaiten.instance.billing.v1.plan_changed": InstanceBillingPlanChangedEvent,
+ "com.kaiten.instance.billing.v1.provider_changed": InstanceBillingProviderChangedEvent,
+ "com.kaiten.instance.billing.v1.started": InstanceBillingStartedEvent,
+ "com.kaiten.instance.billing.v1.status_changed": InstanceBillingStatusChangedEvent,
+ "com.kaiten.instance.billing.v1.trial_will_end": InstanceBillingTrialWillEndEvent,
"com.kaiten.instance.entitlement.v1.cap_exceeded": InstanceEntitlementCapExceededEvent,
"com.kaiten.instance.entitlement.v1.usage_period_rolled_over": InstanceEntitlementUsagePeriodRolledOverEvent,
"com.kaiten.instance.entitlement.v1.usage_reached": InstanceEntitlementUsageReachedEvent,
@@ -1013,6 +2503,20 @@ class ReleaseDeletedEvent(KaitenModel):
"com.kaiten.instance.entitlement.v1.usage_report_rejected": InstanceEntitlementUsageReportRejectedEvent,
"com.kaiten.instance.entitlement.v1.usage_warning_threshold_reached": InstanceEntitlementUsageWarningThresholdReachedEvent,
"com.kaiten.instance.entitlement.v1.value_get": InstanceEntitlementValueGetEvent,
+ "com.kaiten.instance.entitlement_exception.v1.created": InstanceEntitlementExceptionCreatedEvent,
+ "com.kaiten.instance.entitlement_exception.v1.expired": InstanceEntitlementExceptionExpiredEvent,
+ "com.kaiten.instance.entitlement_exception.v1.revoked": InstanceEntitlementExceptionRevokedEvent,
+ "com.kaiten.instance.invoice.v1.handoff_acknowledged": InstanceInvoiceHandoffAcknowledgedEvent,
+ "com.kaiten.instance.invoice.v1.held": InstanceInvoiceHeldEvent,
+ "com.kaiten.instance.invoice.v1.issued": InstanceInvoiceIssuedEvent,
+ "com.kaiten.instance.invoice.v1.marked_uncollectible": InstanceInvoiceMarkedUncollectibleEvent,
+ "com.kaiten.instance.invoice.v1.paid": InstanceInvoicePaidEvent,
+ "com.kaiten.instance.invoice.v1.payment_failed": InstanceInvoicePaymentFailedEvent,
+ "com.kaiten.instance.invoice.v1.push_failed": InstanceInvoicePushFailedEvent,
+ "com.kaiten.instance.invoice.v1.pushed": InstanceInvoicePushedEvent,
+ "com.kaiten.instance.invoice.v1.reconciliation_mismatch": InstanceInvoiceReconciliationMismatchEvent,
+ "com.kaiten.instance.invoice.v1.released": InstanceInvoiceReleasedEvent,
+ "com.kaiten.instance.invoice.v1.voided": InstanceInvoiceVoidedEvent,
"com.kaiten.instance.v1.created": InstanceCreatedEvent,
"com.kaiten.instance.v1.deleted": InstanceDeletedEvent,
"com.kaiten.instance.v1.deployed": InstanceDeployedEvent,
@@ -1020,9 +2524,15 @@ class ReleaseDeletedEvent(KaitenModel):
"com.kaiten.instance.v1.migrated": InstanceMigratedEvent,
"com.kaiten.instance.v1.status_changed": InstanceStatusChangedEvent,
"com.kaiten.instance.v1.updated": InstanceUpdatedEvent,
+ "com.kaiten.instance.voucher.v1.expired": InstanceVoucherExpiredEvent,
+ "com.kaiten.instance.voucher.v1.redeemed": InstanceVoucherRedeemedEvent,
+ "com.kaiten.instance.voucher.v1.revoked": InstanceVoucherRevokedEvent,
"com.kaiten.license.entitlement.v1.assigned": LicenseEntitlementAssignedEvent,
"com.kaiten.license.entitlement.v1.unassigned": LicenseEntitlementUnassignedEvent,
"com.kaiten.license.entitlement.v1.updated": LicenseEntitlementUpdatedEvent,
+ "com.kaiten.license.price.v1.created": LicensePriceCreatedEvent,
+ "com.kaiten.license.price.v1.deprecated": LicensePriceDeprecatedEvent,
+ "com.kaiten.license.price.v1.updated": LicensePriceUpdatedEvent,
"com.kaiten.license.v1.archived": LicenseArchivedEvent,
"com.kaiten.license.v1.created": LicenseCreatedEvent,
"com.kaiten.license.v1.deleted": LicenseDeletedEvent,
@@ -1037,7 +2547,16 @@ class ReleaseDeletedEvent(KaitenModel):
"com.kaiten.metadata_field.v1.reordered": MetadataFieldReorderedEvent,
"com.kaiten.metadata_field.v1.unarchived": MetadataFieldUnarchivedEvent,
"com.kaiten.metadata_field.v1.updated": MetadataFieldUpdatedEvent,
+ "com.kaiten.publishable_key.v1.created": PublishableKeyCreatedEvent,
+ "com.kaiten.publishable_key.v1.revoked": PublishableKeyRevokedEvent,
"com.kaiten.release.v1.created": ReleaseCreatedEvent,
"com.kaiten.release.v1.deleted": ReleaseDeletedEvent,
+ "com.kaiten.voucher.v1.archived": VoucherArchivedEvent,
+ "com.kaiten.voucher.v1.created": VoucherCreatedEvent,
+ "com.kaiten.voucher.v1.deleted": VoucherDeletedEvent,
+ "com.kaiten.voucher.v1.exhausted": VoucherExhaustedEvent,
+ "com.kaiten.voucher.v1.expired": VoucherExpiredEvent,
+ "com.kaiten.voucher.v1.published": VoucherPublishedEvent,
+ "com.kaiten.voucher.v1.updated": VoucherUpdatedEvent,
}
"""Every published event type, mapped to the model that parses it."""
diff --git a/tests/contract.py b/tests/contract.py
index ea8a8cb..0dd797a 100644
--- a/tests/contract.py
+++ b/tests/contract.py
@@ -121,7 +121,8 @@ def _write_registry(surface: str) -> Registry:
The API validates a request against the resource schema with its readOnly properties no
longer required -- and a client has no business sending one -- so readOnly properties are
dropped outright. Every body is ``additionalProperties: false``, so a request carrying one
- then fails exactly like a misspelled key.
+ then fails exactly like a misspelled key. And a nullable enum takes null, as the API's
+ validator does, though the document lists only its other values.
"""
writable = _without_read_only(copy.deepcopy(document(surface)))
resource = Resource.from_contents(writable, default_specification=DRAFT202012)
@@ -141,6 +142,14 @@ def _without_read_only(node: Any) -> Any:
del properties[name]
if isinstance(node.get("required"), list):
node["required"] = [name for name in node["required"] if name not in read_only]
+ kind, enum = node.get("type"), node.get("enum")
+ if (
+ isinstance(kind, list)
+ and "null" in kind
+ and isinstance(enum, list)
+ and None not in enum
+ ):
+ enum.append(None)
for value in node.values():
_without_read_only(value)
elif isinstance(node, list):
diff --git a/tests/test_billing.py b/tests/test_billing.py
new file mode 100644
index 0000000..3248b7e
--- /dev/null
+++ b/tests/test_billing.py
@@ -0,0 +1,200 @@
+# Copyright 2026 KAITEN INC
+# SPDX-License-Identifier: Apache-2.0
+
+"""Billing and the usage history: what the contract table cannot see.
+
+The public routes' credentials, the exports' bytes, the usage report's idempotency key and
+the history's ``afterSeq`` pages.
+"""
+
+from __future__ import annotations
+
+import asyncio
+from collections.abc import Callable
+from typing import Any
+
+import httpx
+import pytest
+from contract import sample
+from helpers import body, respond
+
+from kaitencloud import (
+ CredentialError,
+ PaginationError,
+ ServiceUnavailableError,
+ ThresholdExceededError,
+ TransactionIdReusedError,
+)
+
+REPORTED = sample({"$ref": "#/components/schemas/ReportedEntitlementUsage"}, "core")
+CATALOG = sample({"$ref": "#/components/schemas/PublicCatalog"}, "core")
+REPORT = sample({"$ref": "#/components/schemas/UsageReport"}, "core")
+HISTORY = "/api/instances/acme-production/entitlements/seats/usage/reports"
+
+
+def test_a_publishable_key_travels_in_its_own_header_never_as_a_bearer_token(
+ make_client: Callable[..., Any],
+) -> None:
+ client, recorder = make_client(respond(200, CATALOG), token="pk_live_3f6b")
+ client.billing.public.get_catalog(include_addons=False)
+
+ request = recorder.only
+ assert request.headers["x-kaiten-publishable-key"] == "pk_live_3f6b"
+ assert "authorization" not in request.headers
+ assert request.url.params["include"] == "none"
+
+
+@pytest.mark.parametrize(
+ ("token", "call"),
+ [
+ ("ksh_org", lambda c: c.billing.public.get_catalog()),
+ ("kst_session", lambda c: c.billing.public.create_checkout_session(turnstile_token="t")),
+ ("pk_live", lambda c: c.billing.session.get_portal()),
+ ("ksh_org", lambda c: c.billing.session.cancel()),
+ ("pk_live", lambda c: c.billing.public.confirm_checkout_session("cs_1")),
+ ],
+ ids=["catalog", "checkout-start", "session", "session-write", "checkout-confirm"],
+)
+def test_a_public_route_refuses_any_other_credential_before_sending(
+ make_client: Callable[..., Any], token: str, call: Callable[[Any], Any]
+) -> None:
+ client, recorder = make_client(respond(200, {}), token=token)
+ with pytest.raises(CredentialError, match="takes"):
+ call(client)
+ assert not recorder
+
+
+def test_invoice_statuses_are_repeated_query_parameters(make_client: Callable[..., Any]) -> None:
+ client, recorder = make_client(respond(200, {"items": [], "hasMore": False}))
+ client.billing.invoices.list(status=["MANUAL", "PAID"], overdue=True)
+
+ params = recorder.only.url.params
+ assert params.get_list("status") == ["MANUAL", "PAID"]
+ assert params["overdue"] == "true"
+
+
+@pytest.mark.parametrize(
+ ("export_format", "media_type"), [("csv", "text/csv"), ("json", "application/x-ndjson")]
+)
+def test_an_export_asks_for_its_format_and_returns_the_bytes(
+ make_client: Callable[..., Any], export_format: Any, media_type: str
+) -> None:
+ client, recorder = make_client(respond(200, "id,total\ninv_1,4900\n"))
+
+ exported = client.billing.invoices.export(format=export_format)
+
+ assert exported == b"id,total\ninv_1,4900\n"
+ assert recorder.only.headers["accept"] == media_type
+ assert recorder.only.url.params["format"] == export_format
+
+
+def test_a_limit_below_one_is_refused_before_sending(make_client: Callable[..., Any]) -> None:
+ client, recorder = make_client(respond(200, {"items": [], "hasMore": False}))
+ with pytest.raises(ValueError, match="at least 1"):
+ client.billing.invoices.list(limit=0)
+ assert not recorder
+
+
+def test_a_keyed_usage_report_sends_its_key_and_says_it_was_replayed(
+ make_client: Callable[..., Any],
+) -> None:
+ headers = {"Idempotent-Replayed": "true", "Kaiten-Metadata-Dropped": "too_large"}
+ client, recorder = make_client(respond(200, REPORTED, headers=headers))
+
+ result = client.instances.report_usage_detailed(
+ "acme-production", "tokens", 1200, transaction_id="llm-call-9f2c:tokens"
+ )
+
+ assert recorder.only.headers["idempotency-key"] == "llm-call-9f2c:tokens"
+ assert body(recorder.only) == {"value": {"type": "number", "value": 1200}, "behavior": "append"}
+ assert (result.replayed, result.metadata_dropped) == (True, True)
+ assert result.usage.entitlement_slug == REPORTED["entitlementSlug"]
+
+
+def test_a_usage_report_without_a_key_sends_none(make_client: Callable[..., Any]) -> None:
+ client, recorder = make_client(respond(200, REPORTED))
+
+ result = client.instances.report_usage_detailed("acme-production", "seats", 1)
+
+ assert "idempotency-key" not in recorder.only.headers
+ assert (result.replayed, result.metadata_dropped) == (False, False)
+
+
+@pytest.mark.parametrize(
+ ("code", "error"),
+ [
+ ("ReportEntitlementUsageMetric.TransactionIdReused", TransactionIdReusedError),
+ ("ReportEntitlementUsageMetric.ThresholdExceeded", ThresholdExceededError),
+ ],
+ ids=["key-reused", "threshold"],
+)
+def test_a_refused_usage_report_is_named_by_its_code(
+ make_client: Callable[..., Any], code: str, error: type[Exception]
+) -> None:
+ problem = {"title": "Conflict", "status": 409, "code": code}
+ client, _ = make_client(respond(409, problem))
+
+ with pytest.raises(error) as raised:
+ client.instances.report_usage_detailed("acme-production", "seats", 1, transaction_id="k1")
+ assert type(raised.value) is error
+
+
+def test_a_keyed_usage_report_is_never_retried(make_client: Callable[..., Any]) -> None:
+ # A Kaiten older than keys ignores the header, and would count a resent report twice.
+ client, recorder = make_client(respond(503), max_retries=2)
+ with pytest.raises(ServiceUnavailableError):
+ client.instances.report_usage_detailed("acme-production", "seats", 1, transaction_id="k1")
+ assert len(recorder) == 1
+
+
+def history(count: int, *, stuck: bool = False) -> Callable[[httpx.Request], httpx.Response]:
+ """A usage history endpoint paging by afterSeq, as Kaiten does; ``stuck`` never advances."""
+
+ def handler(request: httpx.Request) -> httpx.Response:
+ after = int(request.url.params.get("afterSeq", "0"))
+ limit = int(request.url.params["limit"])
+ rows = [
+ {**REPORT, "reportSeq": seq} for seq in range(after + 1, min(after + limit, count) + 1)
+ ]
+ page: dict[str, Any] = {"items": rows}
+ if rows and rows[-1]["reportSeq"] < count:
+ page["nextAfterSeq"] = after if stuck else rows[-1]["reportSeq"]
+ return httpx.Response(200, json=page)
+
+ return handler
+
+
+def test_the_usage_history_walks_every_page(make_client: Callable[..., Any]) -> None:
+ client, recorder = make_client(history(1200))
+
+ reports = client.instances.list_usage_reports("acme-production", "seats")
+
+ assert [report.report_seq for report in reports] == list(range(1, 1201))
+ assert [request.url.params.get("afterSeq") for request in recorder] == [None, "500", "1000"]
+ assert {request.url.params["limit"] for request in recorder} == {"500"}
+ assert {request.url.path for request in recorder} == {HISTORY}
+
+
+def test_the_usage_history_stops_at_its_ceiling(make_client: Callable[..., Any]) -> None:
+ client, recorder = make_client(history(1200))
+
+ reports = client.instances.list_usage_reports("acme-production", "seats", limit=3)
+
+ assert [report.report_seq for report in reports] == [1, 2, 3]
+ assert recorder.only.url.params["limit"] == "3"
+
+
+def test_a_history_page_that_does_not_advance_is_refused(make_client: Callable[..., Any]) -> None:
+ client, _ = make_client(history(1200, stuck=True))
+ with pytest.raises(PaginationError, match="afterSeq"):
+ client.instances.list_usage_reports("acme-production", "seats")
+
+
+def test_the_async_client_walks_the_history_too(make_async_client: Callable[..., Any]) -> None:
+ async def scenario() -> list[int]:
+ client, _ = make_async_client(history(700))
+ async with client:
+ reports = await client.billing.invoices.list_line_reports("inv_1", "line_1")
+ return [report.report_seq for report in reports]
+
+ assert asyncio.run(scenario()) == list(range(1, 701))
diff --git a/tests/test_contract.py b/tests/test_contract.py
index 6331982..3bacc84 100644
--- a/tests/test_contract.py
+++ b/tests/test_contract.py
@@ -14,7 +14,9 @@
is ``additionalProperties: false``, so a key too many and a required key missing end the
same way -- a 422 on every call, with no type error to catch it;
- the rules the schema cannot express hold: a slug an update refuses, a license version a
- create refuses.
+ create refuses;
+- the credential travels where the operation's security scheme says: a bearer token, or a
+ publishable key in its own header.
A ``-minimal`` variant calls the method with its required arguments only, which is where a
required-but-nullable key left out would show. Both clients run the whole table: the async
@@ -31,7 +33,7 @@
from typing import Any
import pytest
-from contract import contract_api, find_operation, write_errors
+from contract import Operation, contract_api, document, find_operation, write_errors
from helpers import ORG_ID, USER_ID, Recorder, body
from kaitencloud import targeting
@@ -48,6 +50,32 @@
"last_error": "timeout",
}
VARIANTS = [targeting.variant("on", True, description="Enabled"), targeting.variant("off", False)]
+VOUCHER: dict[str, Any] = {
+ "name": "Welcome",
+ "voucher_type": "COMPOSITE",
+ "duration": "REPEATING",
+ "code": "WELCOME-2026",
+ "description": "First three months at 20% off, and ten more seats",
+ "duration_in_periods": 3,
+ "starts_at": NOW,
+ "expires_at": LATER,
+ "max_redemptions": 100,
+ "restricted_customer_slug": "acme",
+ "price_discount_type": "PERCENTAGE",
+ "price_discount_value": "20",
+ "price_applies_to": "SELECTED_PRICES",
+ "applicable_license_ids": [ID],
+ "applicable_license_price_ids": [ID],
+ "applicable_addon_ids": [SAMPLE_ID],
+ "applicable_addon_price_ids": [SAMPLE_ID],
+ "grants": [{"entitlementSlug": "seats", "modifierType": "ADD", "modifierValue": "10"}],
+ "flag_grants": {"new-checkout": "on"},
+ "redemption_rules": {
+ "annualOnly": True,
+ "firstTimeOnly": True,
+ "minimumSubscriptionAmount": {"currency": "EUR", "unitAmountDecimal": "1000"},
+ },
+}
RULES = [
targeting.rule("Growth plan", "__kaiten.license.slug == 'growth'", "on"),
targeting.split_rule("Progressive rollout", "true", {"on": 20, "off": 80}),
@@ -66,6 +94,7 @@ class Case:
required: frozenset[str] = frozenset()
surface: str = "core"
name: str = ""
+ token: str | None = None
CASES = [
@@ -117,13 +146,18 @@ class Case:
external_customer_id="crm-42",
domain="acme.com",
integrations={"attio": INTEGRATION},
+ billing_email="billing@acme.com",
),
minimal=lambda c: c.customers.create(name="Acme"),
),
Case(
"update-customer",
lambda c: c.customers.update(
- "acme", name="Acme Corp", external_customer_id="crm-42", domain="acme.com"
+ "acme",
+ name="Acme Corp",
+ external_customer_id="crm-42",
+ domain="acme.com",
+ billing_email="billing@acme.com",
),
minimal=lambda c: c.customers.update("acme", name="Acme Corp"),
forbidden=frozenset({"slug", "integrations"}),
@@ -482,6 +516,10 @@ class Case:
family_slug="growth",
family_id=ID,
lifecycle_state="PUBLISHED",
+ pricing_type="PAID",
+ trial_period_days=14,
+ requires_payment_method=True,
+ self_serve_cta_url="https://acme.com/contact",
),
minimal=lambda c: c.licenses.create(name="Growth", type="PAID"),
forbidden=frozenset({"version"}),
@@ -504,6 +542,10 @@ class Case:
description="For scaling teams",
is_default=True,
version_name="Winter 2026",
+ pricing_type="CUSTOM",
+ trial_period_days=0,
+ requires_payment_method=False,
+ self_serve_cta_url="",
),
minimal=lambda c: c.licenses.update("growth-v2", name="Growth", type="PAID"),
# a version never changes family or number, and moves state only through the
@@ -539,7 +581,11 @@ class Case:
),
Case("delete-license-entitlement", lambda c: c.licenses.delete_entitlement("growth", "seats")),
# metadata fields
- Case("list-metadata-fields", lambda c: c.metadata_fields.list("INSTANCE")),
+ Case(
+ "list-metadata-fields",
+ lambda c: c.metadata_fields.list("INSTANCE", include_archived=True),
+ minimal=lambda c: c.metadata_fields.list("INSTANCE"),
+ ),
Case(
"create-metadata-field",
lambda c: c.metadata_fields.create(
@@ -612,6 +658,688 @@ class Case:
"delete-service-account-token",
lambda c: c.service_accounts.delete_token("billing-sync", "ci"),
),
+ # instances: keyed usage reports, the usage history, entitlement exceptions
+ Case(
+ "reportEntitlementUsageMetric",
+ lambda c: c.instances.report_usage_detailed(
+ "acme-production",
+ "seats",
+ 3,
+ behavior="set",
+ metadata={"source": "billing"},
+ transaction_id="llm-call-9f2c:tokens",
+ ),
+ minimal=lambda c: c.instances.report_usage_detailed("acme-production", "seats", 1),
+ name="reportEntitlementUsageMetric-detailed",
+ ),
+ Case(
+ "listUsageReports",
+ # A ceiling: the fake API answers every page with the same nextAfterSeq.
+ lambda c: c.instances.list_usage_reports(
+ "acme-production", "seats", from_=NOW, to=LATER, transaction_id="call-1", limit=1
+ ),
+ ),
+ Case(
+ "exportUsageReports",
+ lambda c: c.instances.export_usage_reports(
+ "acme-production", "seats", from_=NOW, to=LATER, format="json"
+ ),
+ minimal=lambda c: c.instances.export_usage_reports("acme-production", "seats"),
+ ),
+ Case(
+ "exportOrganizationUsageReports",
+ lambda c: c.instances.export_organization_usage_reports(
+ from_=NOW,
+ to=LATER,
+ format="json",
+ instance_slug="acme-production",
+ instance_id=ID,
+ entitlement_slug="seats",
+ entitlement_id=SAMPLE_ID,
+ ),
+ minimal=lambda c: c.instances.export_organization_usage_reports(),
+ ),
+ Case(
+ "listEntitlementExceptions",
+ lambda c: c.instances.list_entitlement_exceptions("acme-production", include_inactive=True),
+ minimal=lambda c: c.instances.list_entitlement_exceptions("acme-production"),
+ ),
+ Case(
+ "createEntitlementException",
+ lambda c: c.instances.create_entitlement_exception(
+ "acme-production",
+ "seats",
+ kind="SET",
+ reason="Pilot extension",
+ value=200,
+ starts_at=NOW,
+ ends_at=LATER,
+ ),
+ minimal=lambda c: c.instances.create_entitlement_exception(
+ "acme-production", "seats", kind="UNLIMITED", reason="Pilot extension"
+ ),
+ required=frozenset({"entitlementSlug", "kind", "reason"}),
+ ),
+ Case(
+ "revokeEntitlementException",
+ lambda c: c.instances.revoke_entitlement_exception("acme-production", ID),
+ ),
+ # license families
+ Case(
+ "updateLicenseFamily",
+ lambda c: c.license_families.update(
+ "growth",
+ is_public=True,
+ fallback_license_family_slug="free",
+ trial_end_without_payment_method="CANCEL",
+ trial_reminder_days=3,
+ ),
+ minimal=lambda c: c.license_families.update("growth"),
+ ),
+ # billing
+ Case("getBillingSettings", lambda c: c.billing.get_settings()),
+ Case(
+ "updateBillingSettings",
+ lambda c: c.billing.update_settings(
+ default_collection_method="SEND_INVOICE",
+ default_days_until_due=30,
+ handoff_stripe_invoices=False,
+ ),
+ ),
+ Case("getBillingCapabilities", lambda c: c.billing.get_capabilities()),
+ Case("getBillingHealth", lambda c: c.billing.get_health()),
+ Case("syncBillingProvider", lambda c: c.billing.sync()),
+ Case(
+ "closeBillingPeriods",
+ lambda c: c.billing.close_periods(instance_slug="acme-production"),
+ minimal=lambda c: c.billing.close_periods(),
+ ),
+ # billing: add-ons
+ Case(
+ "listAddons",
+ lambda c: c.billing.addons.list(lifecycle_state="PUBLISHED", family_slug="extra-seats"),
+ minimal=lambda c: c.billing.addons.list(),
+ ),
+ Case("getAddon", lambda c: c.billing.addons.get("extra-seats-v1")),
+ Case(
+ "createAddon",
+ lambda c: c.billing.addons.create(
+ name="Extra seats",
+ pricing_type="PAID",
+ description="Ten more seats",
+ slug="extra-seats-v2",
+ family_slug="extra-seats",
+ family_id=ID,
+ version_name="Winter 2026",
+ lifecycle_state="DRAFT",
+ is_default=False,
+ max_quantity=10,
+ increase_billing="TRUE_UP",
+ ),
+ minimal=lambda c: c.billing.addons.create(name="Extra seats", pricing_type="PAID"),
+ required=frozenset({"description"}),
+ ),
+ Case(
+ "updateAddon",
+ lambda c: c.billing.addons.update(
+ "extra-seats-v1",
+ name="Extra seats",
+ description="Ten more seats",
+ is_default=True,
+ version_name="Autumn 2026",
+ max_quantity=20,
+ ),
+ minimal=lambda c: c.billing.addons.update("extra-seats-v1", name="Extra seats"),
+ required=frozenset({"description", "isDefault"}),
+ ),
+ Case("deleteAddon", lambda c: c.billing.addons.delete("extra-seats-v1")),
+ Case("publishAddon", lambda c: c.billing.addons.publish("extra-seats-v1")),
+ Case("archiveAddon", lambda c: c.billing.addons.archive("extra-seats-v1")),
+ Case("unarchiveAddon", lambda c: c.billing.addons.unarchive("extra-seats-v1")),
+ Case("listAddonCompatibility", lambda c: c.billing.addons.list_compatibility("extra-seats-v1")),
+ Case(
+ "setAddonCompatibility",
+ lambda c: c.billing.addons.set_compatibility("extra-seats-v1", "growth"),
+ ),
+ Case(
+ "removeAddonCompatibility",
+ lambda c: c.billing.addons.remove_compatibility("extra-seats-v1", "growth"),
+ ),
+ Case("listAddonEntitlements", lambda c: c.billing.addons.list_entitlements("extra-seats-v1")),
+ Case(
+ "getAddonEntitlement",
+ lambda c: c.billing.addons.get_entitlement("extra-seats-v1", "seats"),
+ ),
+ Case(
+ "assignAddonEntitlement",
+ lambda c: c.billing.addons.assign_entitlement(
+ "extra-seats-v1", "seats", 10, override_behavior="ADD", overage_percent=0
+ ),
+ minimal=lambda c: c.billing.addons.assign_entitlement("extra-seats-v1", "sso", True),
+ required=frozenset({"entitlementSlug", "value"}),
+ ),
+ Case(
+ "updateAddonEntitlement",
+ lambda c: c.billing.addons.update_entitlement(
+ "extra-seats-v1", "seats", 20, override_behavior="MAX", overage_percent=10
+ ),
+ minimal=lambda c: c.billing.addons.update_entitlement("extra-seats-v1", "seats", 20),
+ forbidden=frozenset({"entitlementSlug"}),
+ ),
+ Case(
+ "unassignAddonEntitlement",
+ lambda c: c.billing.addons.unassign_entitlement("extra-seats-v1", "seats"),
+ ),
+ Case(
+ "listAddonPrices",
+ lambda c: c.billing.addons.list_prices("extra-seats-v1", status="ACTIVE"),
+ minimal=lambda c: c.billing.addons.list_prices("extra-seats-v1"),
+ ),
+ Case(
+ "createAddonPrice",
+ lambda c: c.billing.addons.create_price(
+ "extra-seats-v1",
+ billing_model="FLAT_FEE",
+ currency="EUR",
+ unit_amount_decimal="1500",
+ billing_period="MONTHLY",
+ billing_timing="ADVANCE",
+ is_default=True,
+ display_label="Extra seats",
+ display_order=1,
+ ),
+ minimal=lambda c: c.billing.addons.create_price(
+ "extra-seats-v1", billing_model="USAGE_BASED", currency="EUR", unit_amount_decimal="2"
+ ),
+ ),
+ Case(
+ "deprecateAddonPrice",
+ lambda c: c.billing.addons.deprecate_price("extra-seats-v1", ID),
+ ),
+ Case("listAddonFamilies", lambda c: c.billing.addons.list_families()),
+ Case("getAddonFamily", lambda c: c.billing.addons.get_family("extra-seats")),
+ Case(
+ "updateAddonFamily",
+ lambda c: c.billing.addons.update_family(
+ "extra-seats", is_public=True, increase_billing="NEXT_BOUNDARY"
+ ),
+ minimal=lambda c: c.billing.addons.update_family("extra-seats"),
+ ),
+ # billing: anonymous checkouts, customer sessions, publishable keys
+ Case(
+ "listCheckoutSessions",
+ lambda c: c.billing.checkout_sessions.list(
+ status=["COMPLETED", "ABANDONED"],
+ email="ada@acme.com",
+ client_reference_id="ref-1",
+ limit=10,
+ ),
+ minimal=lambda c: c.billing.checkout_sessions.list(),
+ ),
+ Case("getCheckoutSessionAdmin", lambda c: c.billing.checkout_sessions.get(ID)),
+ Case(
+ "createCustomerSession",
+ lambda c: c.billing.customer_sessions.create(
+ customer_slug="acme", instance_slug="acme-production", ttl_seconds=900
+ ),
+ minimal=lambda c: c.billing.customer_sessions.create(customer_slug="acme"),
+ ),
+ Case("revokeCustomerSession", lambda c: c.billing.customer_sessions.revoke(ID)),
+ Case(
+ "listPublishableKeys",
+ lambda c: c.billing.publishable_keys.list(include_revoked=True),
+ minimal=lambda c: c.billing.publishable_keys.list(),
+ ),
+ Case(
+ "createPublishableKey",
+ lambda c: c.billing.publishable_keys.create(
+ label="Pricing page",
+ allowed_origins=["https://acme.com"],
+ anonymous_checkout=True,
+ turnstile_site_key="0x4AAAAAAA",
+ turnstile_secret="0x4BBBBBBB",
+ ),
+ minimal=lambda c: c.billing.publishable_keys.create(
+ label="Pricing page", allowed_origins=[]
+ ),
+ required=frozenset({"allowedOrigins"}),
+ ),
+ Case(
+ "updatePublishableKey",
+ lambda c: c.billing.publishable_keys.update(
+ ID,
+ label="Pricing page",
+ allowed_origins=["https://acme.com"],
+ anonymous_checkout=False,
+ turnstile_site_key="",
+ turnstile_secret="",
+ ),
+ minimal=lambda c: c.billing.publishable_keys.update(ID),
+ ),
+ Case("revokePublishableKey", lambda c: c.billing.publishable_keys.revoke(ID)),
+ # billing: a customer's payment method and portal
+ Case("getCustomerBilling", lambda c: c.billing.customers.get("acme")),
+ Case("detachPaymentMethod", lambda c: c.billing.customers.detach_payment_method("acme")),
+ Case(
+ "createPaymentMethodSession",
+ lambda c: c.billing.customers.create_payment_method_session(
+ "acme", return_url="https://acme.com/billing", currency="EUR"
+ ),
+ minimal=lambda c: c.billing.customers.create_payment_method_session(
+ "acme", return_url="https://acme.com/billing"
+ ),
+ ),
+ Case(
+ "completePaymentMethodSession",
+ lambda c: c.billing.customers.complete_payment_method_session("acme", "seti_1"),
+ ),
+ Case(
+ "createPortalSession",
+ lambda c: c.billing.customers.create_portal_session(
+ "acme", return_url="https://acme.com/billing"
+ ),
+ ),
+ # billing: the accounting handoff
+ Case(
+ "listHandoff",
+ lambda c: c.billing.handoff.list(status="ACKNOWLEDGED", limit=10),
+ minimal=lambda c: c.billing.handoff.list(),
+ ),
+ Case(
+ "claimHandoff",
+ lambda c: c.billing.handoff.claim(limit=25, lease_seconds=900),
+ minimal=lambda c: c.billing.handoff.claim(),
+ ),
+ Case(
+ "ackHandoff",
+ lambda c: c.billing.handoff.ack(ID, lease_id=SAMPLE_ID, external_reference="INV-0042"),
+ minimal=lambda c: c.billing.handoff.ack(ID),
+ ),
+ # billing: an instance's add-ons and vouchers
+ Case(
+ "listInstanceAddons",
+ lambda c: c.billing.instance_addons.list("acme-production", include_removed=True),
+ minimal=lambda c: c.billing.instance_addons.list("acme-production"),
+ ),
+ Case(
+ "attachInstanceAddon",
+ lambda c: c.billing.instance_addons.attach("acme-production", "extra-seats-v1", quantity=2),
+ minimal=lambda c: c.billing.instance_addons.attach("acme-production", "extra-seats-v1"),
+ required=frozenset({"addonSlug"}),
+ ),
+ Case(
+ "setInstanceAddonQuantity",
+ lambda c: c.billing.instance_addons.set_quantity("acme-production", "extra-seats-v1", 3),
+ ),
+ Case(
+ "detachInstanceAddon",
+ lambda c: c.billing.instance_addons.detach("acme-production", "extra-seats-v1"),
+ ),
+ Case(
+ "listInstanceVouchers",
+ lambda c: c.billing.instance_vouchers.list("acme-production", status="ACTIVE"),
+ minimal=lambda c: c.billing.instance_vouchers.list("acme-production"),
+ ),
+ Case(
+ "redeemVoucher",
+ lambda c: c.billing.instance_vouchers.redeem("acme-production", "WELCOME-2026"),
+ ),
+ Case(
+ "revokeInstanceVoucher",
+ lambda c: c.billing.instance_vouchers.revoke(
+ "acme-production", ID, reason="Granted by mistake"
+ ),
+ ),
+ # billing: invoices
+ Case(
+ "listInvoices",
+ lambda c: c.billing.invoices.list(
+ instance_slug="acme-production",
+ customer_slug="acme",
+ status=["MANUAL", "PAID"],
+ kind="RENEWAL",
+ provider_kind="NOOP",
+ overdue=True,
+ held=False,
+ handoff_status="PENDING",
+ issued_from=NOW,
+ issued_to=LATER,
+ boundary_from=NOW,
+ boundary_to=LATER,
+ updated_since=NOW,
+ limit=10,
+ ),
+ minimal=lambda c: c.billing.invoices.list(),
+ ),
+ Case("getInvoice", lambda c: c.billing.invoices.get(ID)),
+ Case(
+ "exportInvoices",
+ lambda c: c.billing.invoices.export(
+ format="json",
+ granularity="invoice",
+ instance_slug="acme-production",
+ customer_slug="acme",
+ status=["PAID"],
+ kind="RENEWAL",
+ provider_kind="STRIPE",
+ overdue=False,
+ held=False,
+ handoff_status="ACKNOWLEDGED",
+ issued_from=NOW,
+ issued_to=LATER,
+ boundary_from=NOW,
+ boundary_to=LATER,
+ updated_since=NOW,
+ ),
+ minimal=lambda c: c.billing.invoices.export(),
+ ),
+ Case("voidInvoice", lambda c: c.billing.invoices.void(ID, reason="Duplicate")),
+ Case("recomposeInvoice", lambda c: c.billing.invoices.recompose(ID)),
+ Case(
+ "markInvoicePaid",
+ lambda c: c.billing.invoices.mark_paid(
+ ID, paid_at=NOW, external_reference="INV-0042", note="Wire 7731"
+ ),
+ minimal=lambda c: c.billing.invoices.mark_paid(ID),
+ ),
+ Case("writeOffInvoice", lambda c: c.billing.invoices.write_off(ID, reason="Bankrupt")),
+ Case(
+ "releaseInvoiceHold",
+ lambda c: c.billing.invoices.release_hold(ID, reason="Usage checked by hand"),
+ ),
+ Case("retryInvoicePush", lambda c: c.billing.invoices.retry_push(ID)),
+ Case("syncInvoice", lambda c: c.billing.invoices.sync(ID)),
+ Case(
+ "listInvoiceLineReports",
+ # A ceiling: the fake API answers every page with the same nextAfterSeq.
+ lambda c: c.billing.invoices.list_line_reports(ID, SAMPLE_ID, limit=1),
+ ),
+ # billing: license prices
+ Case(
+ "listLicensePrices",
+ lambda c: c.billing.license_prices.list(
+ "growth-v2", status="ACTIVE", billing_model="FLAT_FEE"
+ ),
+ minimal=lambda c: c.billing.license_prices.list("growth-v2"),
+ ),
+ Case("getLicensePrice", lambda c: c.billing.license_prices.get("growth-v2", ID)),
+ Case(
+ "createLicensePrice",
+ lambda c: c.billing.license_prices.create(
+ "growth-v2",
+ billing_model="FLAT_FEE",
+ currency="EUR",
+ unit_amount_decimal="4900",
+ billing_period="ANNUAL",
+ billing_timing="ADVANCE",
+ is_default=True,
+ display_label="Growth, yearly",
+ display_order=1,
+ ),
+ minimal=lambda c: c.billing.license_prices.create(
+ "growth-v2",
+ billing_model="OVERAGE",
+ currency="EUR",
+ unit_amount_decimal="2",
+ metered_entitlement_slug="api-calls",
+ ),
+ ),
+ Case(
+ "updateLicensePrice",
+ lambda c: c.billing.license_prices.update(
+ "growth-v2",
+ ID,
+ unit_amount_decimal="5900",
+ billing_period="MONTHLY",
+ billing_timing="ARREARS",
+ metered_entitlement_slug="api-calls",
+ is_default=False,
+ display_label="Growth",
+ display_order=2,
+ ),
+ minimal=lambda c: c.billing.license_prices.update("growth-v2", ID),
+ ),
+ Case("deprecateLicensePrice", lambda c: c.billing.license_prices.deprecate("growth-v2", ID)),
+ Case(
+ "previewLicenseInvoice",
+ lambda c: c.billing.license_prices.preview_invoice(
+ "growth-v2",
+ base_price_id=ID,
+ add_ons={"extra-seats-v1": 2},
+ sample_usage={"api-calls": "120000"},
+ instance_slug="acme-production",
+ voucher_code="WELCOME-2026",
+ ),
+ minimal=lambda c: c.billing.license_prices.preview_invoice("growth-v2"),
+ ),
+ # billing: what a publishable key and an anonymous checkout's token reach
+ Case(
+ "getPublicCatalog",
+ lambda c: c.billing.public.get_catalog(family_slug="growth", include_addons=False),
+ minimal=lambda c: c.billing.public.get_catalog(),
+ token="pk_test",
+ ),
+ Case(
+ "createCheckoutSession",
+ lambda c: c.billing.public.create_checkout_session(
+ turnstile_token="0.turnstile",
+ license_slug="growth-v2",
+ billing_period="MONTHLY",
+ add_ons={"extra-seats-v1": 1},
+ email="ada@acme.com",
+ return_url="https://acme.com/welcome",
+ voucher_code="WELCOME-2026",
+ trial=False,
+ client_reference_id="ref-1",
+ dry_run=True,
+ ),
+ minimal=lambda c: c.billing.public.create_checkout_session(
+ turnstile_token="0.turnstile", price_id=ID
+ ),
+ required=frozenset({"turnstileToken"}),
+ token="pk_test",
+ ),
+ Case(
+ "getCheckoutSession",
+ lambda c: c.billing.public.get_checkout_session(ID),
+ token="ksc_test",
+ ),
+ Case(
+ "confirmCheckoutSession",
+ lambda c: c.billing.public.confirm_checkout_session(ID),
+ token="ksc_test",
+ ),
+ # billing: what a customer session reaches
+ Case("getSessionPortal", lambda c: c.billing.session.get_portal(), token="kst_test"),
+ Case(
+ "listSessionInvoices",
+ lambda c: c.billing.session.list_invoices(limit=10),
+ minimal=lambda c: c.billing.session.list_invoices(),
+ token="kst_test",
+ ),
+ Case(
+ "createSessionCheckout",
+ lambda c: c.billing.session.checkout(
+ license_price_id=ID,
+ add_ons={"extra-seats-v1": 2},
+ voucher_code="WELCOME-2026",
+ billing_email="billing@acme.com",
+ return_url="https://acme.com/billing",
+ setup_session_id="seti_1",
+ trial=False,
+ dry_run=True,
+ ),
+ minimal=lambda c: c.billing.session.checkout(license_price_id=ID),
+ token="kst_test",
+ ),
+ Case(
+ "cancelSessionSubscription",
+ lambda c: c.billing.session.cancel(reason="Too expensive"),
+ minimal=lambda c: c.billing.session.cancel(),
+ token="kst_test",
+ ),
+ Case(
+ "reactivateSessionSubscription",
+ lambda c: c.billing.session.reactivate(),
+ token="kst_test",
+ ),
+ Case(
+ "setSessionAddonQuantity",
+ lambda c: c.billing.session.set_addon_quantity("extra-seats-v1", 0),
+ token="kst_test",
+ ),
+ Case(
+ "createSessionPaymentMethodSession",
+ lambda c: c.billing.session.create_payment_method_session(
+ return_url="https://acme.com/billing", currency="EUR"
+ ),
+ minimal=lambda c: c.billing.session.create_payment_method_session(
+ return_url="https://acme.com/billing"
+ ),
+ token="kst_test",
+ ),
+ Case(
+ "completeSessionPaymentMethodSession",
+ lambda c: c.billing.session.complete_payment_method_session("seti_1"),
+ token="kst_test",
+ ),
+ Case(
+ "createSessionPortalSession",
+ lambda c: c.billing.session.create_portal_session(return_url="https://acme.com/billing"),
+ token="kst_test",
+ ),
+ Case(
+ "validateSessionVoucher",
+ lambda c: c.billing.session.validate_voucher("WELCOME-2026", license_price_id=ID),
+ minimal=lambda c: c.billing.session.validate_voucher("WELCOME-2026"),
+ token="kst_test",
+ ),
+ Case(
+ "redeemSessionVoucher",
+ lambda c: c.billing.session.redeem_voucher("WELCOME-2026", dry_run=True),
+ minimal=lambda c: c.billing.session.redeem_voucher("WELCOME-2026"),
+ token="kst_test",
+ ),
+ # billing: subscriptions
+ Case("getInstanceBilling", lambda c: c.billing.subscriptions.get("acme-production")),
+ Case(
+ "subscribeInstance",
+ lambda c: c.billing.subscriptions.subscribe(
+ "acme-production",
+ base_price_id=ID,
+ add_ons={"extra-seats-v1": 2},
+ voucher_code="WELCOME-2026",
+ trial_days=14,
+ start_at=NOW,
+ provider_kind="STRIPE",
+ collection_method="CHARGE_AUTOMATICALLY",
+ days_until_due=30,
+ ),
+ minimal=lambda c: c.billing.subscriptions.subscribe("acme-production", base_price_id=ID),
+ ),
+ Case(
+ "updateInstanceBilling",
+ lambda c: c.billing.subscriptions.update(
+ "acme-production",
+ provider_kind="STRIPE",
+ collection_method="CHARGE_AUTOMATICALLY",
+ days_until_due=14,
+ ),
+ minimal=lambda c: c.billing.subscriptions.update("acme-production"),
+ ),
+ Case(
+ "updateInstanceBilling",
+ # None is sent as null: back to the organization's default.
+ lambda c: c.billing.subscriptions.update(
+ "acme-production", collection_method=None, days_until_due=None
+ ),
+ required=frozenset({"collectionMethod", "daysUntilDue"}),
+ name="updateInstanceBilling-defaults",
+ ),
+ Case(
+ "cancelSubscription",
+ lambda c: c.billing.subscriptions.cancel(
+ "acme-production", mode="IMMEDIATE", reason="Churned"
+ ),
+ minimal=lambda c: c.billing.subscriptions.cancel("acme-production"),
+ ),
+ Case("reactivateSubscription", lambda c: c.billing.subscriptions.reactivate("acme-production")),
+ Case(
+ "schedulePlanChange",
+ lambda c: c.billing.subscriptions.schedule_plan_change("acme-production", ID),
+ required=frozenset({"licensePriceId"}),
+ ),
+ Case(
+ "cancelPlanChange",
+ lambda c: c.billing.subscriptions.cancel_plan_change("acme-production"),
+ ),
+ Case(
+ "getUpcomingInvoice",
+ lambda c: c.billing.subscriptions.get_upcoming_invoice("acme-production"),
+ ),
+ Case(
+ "listInstanceInvoices",
+ lambda c: c.billing.subscriptions.list_invoices(
+ "acme-production",
+ customer_slug="acme",
+ status=["PUSHED"],
+ kind="ACTIVATION",
+ provider_kind="STRIPE",
+ overdue=True,
+ held=False,
+ handoff_status="NOT_REQUIRED",
+ issued_from=NOW,
+ issued_to=LATER,
+ boundary_from=NOW,
+ boundary_to=LATER,
+ updated_since=NOW,
+ limit=5,
+ ),
+ minimal=lambda c: c.billing.subscriptions.list_invoices("acme-production"),
+ ),
+ # billing: vouchers
+ Case(
+ "listVouchers",
+ lambda c: c.billing.vouchers.list(
+ status="ACTIVE", voucher_type="PRICE", restricted_customer_slug="acme"
+ ),
+ minimal=lambda c: c.billing.vouchers.list(),
+ ),
+ Case("getVoucher", lambda c: c.billing.vouchers.get(ID)),
+ Case(
+ "createVoucher",
+ lambda c: c.billing.vouchers.create(**VOUCHER),
+ minimal=lambda c: c.billing.vouchers.create(
+ name="Welcome", voucher_type="PRICE", duration="ONE_TIME"
+ ),
+ ),
+ Case(
+ "updateVoucher",
+ lambda c: c.billing.vouchers.update(ID, **VOUCHER),
+ minimal=lambda c: c.billing.vouchers.update(
+ ID, name="Welcome", voucher_type="PRICE", duration="FOREVER"
+ ),
+ ),
+ Case("deleteVoucher", lambda c: c.billing.vouchers.delete(ID)),
+ Case("publishVoucher", lambda c: c.billing.vouchers.publish(ID)),
+ Case("archiveVoucher", lambda c: c.billing.vouchers.archive(ID)),
+ Case(
+ "listVoucherRedemptions",
+ lambda c: c.billing.vouchers.list_redemptions(ID, limit=10),
+ minimal=lambda c: c.billing.vouchers.list_redemptions(ID),
+ ),
+ Case(
+ "lookupVoucher",
+ lambda c: c.billing.vouchers.lookup("WELCOME-2026"),
+ required=frozenset({"code"}),
+ ),
+ Case(
+ "validateVoucher",
+ lambda c: c.billing.vouchers.validate(
+ "WELCOME-2026", instance_slug="acme-production", license_price_id=ID
+ ),
+ minimal=lambda c: c.billing.vouchers.validate("WELCOME-2026"),
+ ),
# platform
Case("get-platform-me", lambda p: p.me(), surface="platform"),
Case(
@@ -654,6 +1382,12 @@ class Case:
surface="platform",
),
Case("delete-user", lambda p: p.users.delete(USER_ID), surface="platform"),
+ Case(
+ "platform-close-billing-periods",
+ lambda p: p.billing.close_periods(ORG_ID, instance_slug="acme-production"),
+ minimal=lambda p: p.billing.close_periods(ORG_ID),
+ surface="platform",
+ ),
]
@@ -673,7 +1407,12 @@ def check(case: Case, recorder: Recorder) -> None:
operation = find_operation(case.surface, request.method, request.url.path)
assert operation.operation_id == case.operation_id
assert set(request.url.params.keys()) <= operation.query_parameters
- assert request.headers["authorization"].startswith("Bearer ")
+ header = credential_header(case.surface, operation)
+ if header == "Authorization":
+ assert request.headers["authorization"].startswith("Bearer ")
+ else:
+ assert "authorization" not in request.headers, "the API refuses this credential there"
+ assert request.headers[header]
schema = operation.request_schema
if schema is None:
@@ -685,6 +1424,19 @@ def check(case: Case, recorder: Recorder) -> None:
assert case.required <= payload.keys(), f"missing {case.required - payload.keys()}"
+def credential_header(surface: str, operation: Operation) -> str:
+ """The header the operation's security scheme carries the credential in."""
+ contract = document(surface)
+ requirements = operation.definition.get("security", contract.get("security")) or [{}]
+ (scheme,) = {name for requirement in requirements for name in requirement}
+ definition = contract["components"]["securitySchemes"][scheme]
+ return str(definition["name"]) if definition["type"] == "apiKey" else "Authorization"
+
+
+def options(case: Case) -> dict[str, Any]:
+ return {"token": case.token} if case.token else {}
+
+
@pytest.mark.parametrize(("case", "call"), _invocations())
def test_sync_client_speaks_the_contract(
case: Case,
@@ -693,7 +1445,7 @@ def test_sync_client_speaks_the_contract(
make_platform_client: Callable[..., Any],
) -> None:
factory = make_platform_client if case.surface == "platform" else make_client
- client, recorder = factory(contract_api(case.surface))
+ client, recorder = factory(contract_api(case.surface), **options(case))
call(client)
check(case, recorder)
@@ -708,7 +1460,7 @@ def test_async_client_speaks_the_contract(
factory = make_async_platform_client if case.surface == "platform" else make_async_client
async def scenario() -> Recorder:
- client, recorder = factory(contract_api(case.surface))
+ client, recorder = factory(contract_api(case.surface), **options(case))
async with client:
await call(client)
return recorder
diff --git a/tests/test_coverage.py b/tests/test_coverage.py
index cbc29f7..394534f 100644
--- a/tests/test_coverage.py
+++ b/tests/test_coverage.py
@@ -25,6 +25,8 @@
KaitenClient,
KaitenPlatformClient,
)
+from kaitencloud._async.resources._base import AsyncResource
+from kaitencloud._sync.resources._base import Resource
SURFACES = ["core", "platform"]
LIFECYCLE = {"close", "with_options"}
@@ -66,14 +68,20 @@ def public_methods(client: Any) -> set[str]:
for name, _ in inspect.getmembers(type(client), inspect.isfunction)
if not name.startswith("_") and name not in LIFECYCLE
}
- for namespace, resource in vars(client).items():
- if namespace.startswith("_"):
- continue
+ # Namespaces nest -- client.billing.invoices -- so walk them all the way down.
+ namespaces = [(name, value) for name, value in vars(client).items() if not name.startswith("_")]
+ while namespaces:
+ path, resource = namespaces.pop()
exposed |= {
- f"{namespace}.{name}"
+ f"{path}.{name}"
for name, _ in inspect.getmembers(type(resource), inspect.isfunction)
if not name.startswith("_")
}
+ namespaces += [
+ (f"{path}.{name}", value)
+ for name, value in inspect.getmembers(resource)
+ if not name.startswith("_") and isinstance(value, (AsyncResource, Resource))
+ ]
return exposed
diff --git a/tests/test_models.py b/tests/test_models.py
index 245e7f8..f76d151 100644
--- a/tests/test_models.py
+++ b/tests/test_models.py
@@ -102,6 +102,7 @@ def test_discriminated_values_parse_into_their_own_class() -> None:
"entitlementSlug": "seats",
"licenseId": "l1",
"licenseSlug": "growth",
+ "source": "license",
"value": {"type": "number", "value": 7, "event_count": 3},
"limit": {"type": "number", "value": 10},
}
diff --git a/tests/test_readme.py b/tests/test_readme.py
index a0ce926..155db3a 100644
--- a/tests/test_readme.py
+++ b/tests/test_readme.py
@@ -34,7 +34,7 @@
}
LINK = re.compile(r"\[[^\]]+\]\((?!https?:)([^)#]*)(#[^)]+)?\)")
EXAMPLES = re.findall(r"```python\n(.*?)```", README, flags=re.DOTALL)
-CLIENT_CALL = re.compile(r"\b(client|platform)\.(\w+)(?:\.(\w+))?\(")
+CLIENT_CALL = re.compile(r"\b(client|platform)((?:\.\w+)+)\(")
HELPER_CALL = re.compile(r"\b(targeting|usage)\.(\w+)\(")
ENVIRONMENT_ASSUMED = "With `KAITEN_BASE_URL` and `KAITEN_AUTH_TOKEN` set"
@@ -68,14 +68,20 @@ def test_every_import_from_the_sdk_exists() -> None:
assert hasattr(module, alias.name), f"from {node.module} import {alias.name}"
+def resolve(clients: dict[str, object], owner: str, path: str) -> object:
+ """``client.billing.invoices.list`` and the like, attribute by attribute."""
+ target = clients[owner]
+ for attribute in path.strip(".").split("."):
+ assert hasattr(target, attribute), f"{owner}{path}"
+ target = getattr(target, attribute)
+ return target
+
+
def test_every_client_call_names_a_real_method(clients: dict[str, object]) -> None:
calls = [call for example in EXAMPLES for call in CLIENT_CALL.findall(example)]
assert calls
- for owner, attribute, method in calls:
- target = getattr(clients[owner], attribute)
- if method:
- target = getattr(target, method)
- assert callable(target), f"{owner}.{attribute}.{method}"
+ for owner, path in calls:
+ assert callable(resolve(clients, owner, path)), f"{owner}{path}"
def test_every_helper_call_names_a_real_function() -> None:
@@ -117,17 +123,14 @@ def test_the_error_tree_names_real_exceptions() -> None:
def test_the_api_reference_names_real_namespaces_and_methods(clients: dict[str, object]) -> None:
"""The reference is tables rather than ``python`` blocks, so the checks above skip it."""
reference = README[README.index("\n## API reference") : README.index("\n## How the SDK stays")]
- namespaces = re.findall(r"(client|platform)\.(\w+)", reference)
+ namespaces = re.findall(r"(client|platform)((?:\.\w+)+)", reference)
calls = CLIENT_CALL.findall(reference)
assert namespaces
assert calls
- for owner, attribute in namespaces:
- assert hasattr(clients[owner], attribute), f"{owner}.{attribute}"
- for owner, attribute, method in calls:
- target = getattr(clients[owner], attribute)
- if method:
- target = getattr(target, method)
- assert callable(target), f"{owner}.{attribute}.{method}"
+ for owner, path in namespaces:
+ resolve(clients, owner, path)
+ for owner, path in calls:
+ assert callable(resolve(clients, owner, path)), f"{owner}{path}"
def anchors(text: str) -> set[str]: