Usage metering + multi-rail billing as a shared capability — one account buys across products. METERING: ingests per-request usage (the provider-gateway usage feed C4 + stt/tts/etc), aggregates per (org, product, period), enforces plan quotas. BILLING: multi-rail (Dodo/PayPal/crypto/Razorpay; Stripe DORMANT), subscriptions, invoices, dunning. Metering NEVER blocks a product runtime (queue + reconcile). Payment secrets resolved server-side (Infisical), never in body/DB/logs. SELLER OF RECORD differs by rail (ADR-110 D3): dodo is a Merchant of Record — it is the legal seller and the registered taxpayer, so /v1/tax/quote SUPPRESSES our tax computation for it (quoting it would double-tax the customer). Every other rail is tax-exclusive: we are the seller and our tax is added. stripe is retained but DORMANT — it cannot settle for an India LLP (no PA-CB licence; invite-only) and refuses every charge/refund/capture with 422 regardless of credentials (ADR-110 D4).
- Group: Sellable plane
- Contract:
contracts/billing-metering/v1/openapi.yaml
- Console: internal — operated by Vagary Labs; no customer console page for this capability.
- Runbook: operational checklist for
billing-metering — internal (Vagary Labs ops; not part of this public site): docs/runbooks/capability-operations.md#billing-metering
- Public access: none — internal-only capability. It is NOT exposed on the public API gateway (
https://api.vagarylabs.com); it is reachable only inside the fleet (container/tailnet) by first-party callers. There is no customer-facing endpoint to call.
- Auth (internal):
bearerToken first-party bearer
Endpoints
| Method | Path | Summary |
|---|
PUT | /internal/voice/allocations/{allocation_id} | Authorize an immutable monthly internal Voice allocation |
PUT | /internal/voice/allocations/{allocation_id}/monthly-policy | Explicitly authorize, pause or resume a fixed recurring monthly internal ceiling |
GET | /internal/voice/allocations/lookup | Resolve internal allocation classification from a verified organization |
POST | /internal/voice/allocations/{allocation_id}/reservations | Reserve an internal demo session before provider initialization |
GET | /internal/voice/allocations/{allocation_id}/reservations/by-operation | Recover existing internal reservation evidence by exact operation binding |
GET | /internal/voice/allocations/{allocation_id}/reservations/{reservation_id} | Read an attributed internal session reservation |
POST | /internal/voice/allocations/{allocation_id}/reservations/{reservation_id}/attempts | Admit exactly one bounded provider attempt before external I/O |
POST | /internal/voice/allocations/{allocation_id}/reservations/{reservation_id}/usage | Record measured usage for an admitted internal session |
GET | /v1/voice/internal-profiles | List versioned internal execution profiles |
POST | /v1/voice/internal-profiles | Register an immutable internal execution profile version |
POST | /v1/voice/internal-allocations | Authorize a registry internal allocation for the current month |
GET | /v1/voice/internal-allocations/{allocation_id} | Read a registry allocation with its effective caps and the figures reserve enforces |
PATCH | /v1/voice/internal-allocations/{allocation_id} | Append a cap revision to a registry allocation |
POST | /internal/voice/allocations/{allocation_id}/reservations/{reservation_id}/commit | Retain the internal allocation debit after completion or uncertain I/O |
POST | /internal/voice/allocations/{allocation_id}/reservations/{reservation_id}/release | Release a hold only when no provider attempt was admitted |
GET | /v2/voice/status | Distinguish never-funded from active or closed paid history |
POST | /v2/voice/free/quota/reservations | Same legacy Free arithmetic with mandatory paid-schema and same-lock funding fence |
GET | /v2/voice/readiness | Revalidate original receipt without reserving again |
GET | /v2/voice/execution-decision | Resolve internal execution rights from receipt and frozen supported profile |
POST | /v2/voice/usage/attempts | Retain authenticated pending work before provider execution |
POST | /v2/voice/usage/attempts/{event_id}/complete | Retain measured or unknown completion on original attempt |
POST | /internal/refunds/reconcile-entitlements | Retry the access corrections that did not land after a succeeded refund |
POST | /internal/agreements/reconcile | Create the commercial agreement for every settled charge that lacks one |
POST | /internal/voice/settlements/confirm | Atomically record verified external receipt, exact Voice period and evidence outbox |
GET | /v2/voice/entitlement | Resolve the active paid Voice period from Billing's settled provenance |
POST | /v2/voice/quota/reservations | Reserve paid minutes without accepting a caller limit |
POST | /v2/voice/quota/reservations/{reservation_id}/finalize | Finalize actual usage on its original paid receipt and counter |
GET | /v2/voice/coverage-context | Resolve immutable coverage through an explicit tenant/session/reservation association |
POST | /v1/usage | Ingest a usage event (from provider-gateway / stt / tts / …) — queued, never blocks caller |
GET | /v1/usage/summary | aggregated usage per (org, product, period) |
POST | /v1/usage/erase-subject | GDPR Art-17 subject erasure by PSEUDONYMISATION (first-party admin only) |
GET | /v1/usage/quotas | C5 Wave-0 — batch quota-counter USED per metric for (org, period) in one call (the read a product dashboard consumes, e.g. voice getUsageQuotas). Returns USED only; the caller overlays its OWN tier->limit (I5). Core never dictates a limit here (org_plans defaults 'free'). |
| | |
POST | /v1/rails/select | Resolve which rail a customer rides and WHO THE SELLER OF RECORD IS, without charging anything (ADR-110 D3 + its E14 addendum). India routes to razorpay with Vagary Labs LLP as seller; rest of world routes to the dodo Merchant of Record, which becomes the legal seller, issues its own invoice under its own particulars, and collects and remits the customer's VAT/GST — so our tax computation is SUPPRESSED for it (tax_mode inclusive). |
A product calls this BEFORE sending anyone to checkout, because the answer changes what the checkout page must say: ADR-110's Consequences make that disclosure load-bearing ("Shipping the rail before the pages is publishing an incorrect statement of who the counterparty is"). Stateless, no rail call, no side effects. customer_country is optional — absent or unrecognised resolves to the MoR, the fail-safe direction. unattended_renewal_supported is FALSE on both rails: see RailSelection.
|
| POST | /v1/charge | Execute a charge on a rail (dodo/paypal/crypto/razorpay; stripe is DORMANT) — the capability PRIMITIVE. One-off or recurring (billing-metering runs dunning internally). Products build their own subscription/plan UX (product-face, I5) on top of this primitive; the capability never exposes SaaS packaging. dodo is the Merchant-of-Record rail: it creates a hosted checkout session and returns requires_action + a checkout link, and its verified webhook settles it. stripe returns 422 status: dormant for EVERY charge regardless of credentials (ADR-110 D4) — it is refusing on a legal constraint, not a missing key, and no dunning record is opened for it.
SEGMENT ROUTING (ADR-110 D3 + its E14 addendum). rail is OPTIONAL: send customer_country instead and the capability selects the rail — India to razorpay with Vagary Labs LLP as seller of record, rest of world to the dodo Merchant of Record. Rail choice is a TAX decision, so it is owned here rather than by product code (payment-rail-build-vs-buy.md §3 rules 4-5). When the capability selected the rail, the response carries a rail_selection block (schema RailSelection) naming the rail, the seller of record and the tax mode; on an explicit-rail charge that block is ABSENT and the response is unchanged. An EXPLICIT rail always wins and is NOT overridden by the country — D3's B2B/enterprise segment routes direct by human decision, not by the customer's country. Sending neither is still a 422; select_rail: true is the explicit opt-in for "apply the policy, country unknown", which resolves to the MoR.
|
| POST | /v1/charges/{rail}/{provider_charge_id}/capture | Capture a TWO-PHASE charge — complete an order/authorization that /v1/charge created and returned as requires_action (e.g. a PayPal order with intent=CAPTURE). The rail captures the funds server-side (secrets resolved from env, never in a body); when the settlement store holds the charge its state flips pending → succeeded (idempotent — reuses the verified-webhook mark path). A rail with no separate capture step (crypto settles via its inbound webhook) returns unsupported (422). organization_id is optional (I6 ownership when provided). Additive S2 lifecycle-convergence endpoint.
|
| GET | /v1/charges/{rail}/{provider_charge_id} | Read a charge's CURRENT settlement state by its rail provider id (the id /v1/charge recorded). This is the webhook-push model: it returns the settlement's cached pending/succeeded/failed state — it does NOT re-query the provider. A charge stays pending until its verified webhook (or a /capture) moves it. Serves the platform's PayPal get_order / crypto get_payment + check_payment lifecycle reads.
|
| GET | /v1/entitlement | What did this organization BUY? (ADR-120) The shared entitlement read: core is the single commercial settlement authority, so org_plans is where a purchase lands for every product, and this is the one route that answers what landed. Returns the PLAN and never a limit — I5 leaves the tier->limit policy with the product, which resolves a limit and passes it to /v1/quota/consume for atomic enforcement. A quotas field here would move that policy into core and reverse a ratified decision. TENANT-SCOPED: organization_id is resolved against the verified credential, not trusted verbatim. An org with no row reads as the default plan, because no purchase IS an entitlement — the free one — so callers get one shape always.
|
| POST | /v1/entitlement | Assign an org's plan directly — the WRITE half GET /v1/entitlement never had. set_plan (the only write to org_plans) previously had exactly two callers, both settlement-triggered (a charge SETTLING, or a full refund releasing it); there was no route for a trial grant, a negotiated enterprise contract, or an admin correction to land through. ADMIN-ONLY: requires a first-party SERVICE_TOKEN credential — a verified end-customer JWT is rejected even for its own org, because this route can grant a plan for free and "you are org X" must not mean "you may be org X's admin".
|
| GET | /v1/quota/check | is (org, metric) within quota? (read-only, NON-enforcing display). TENANT-SCOPED: organization_id is resolved against the verified credential, not trusted verbatim — a JWT caller asking for another org's counters is 403.
|
| POST | /v1/quota/consume | ATOMIC check-and-consume — the ENFORCING quota gate (body {organization_id, metric, amount?=1, product?, idempotency_key?, limit?}); use this, not /check, to gate a request. limit (optional, I5) — a caller that owns its own tier->limit policy (e.g. vagary-voice) passes the resolved per-tier limit and core enforces THAT atomically (negative=unlimited); absent -> the org plan quota. |
| POST | /v1/quota/reservations | Reserve Voice allowance with a durable quota-only receipt |
| POST | /v1/quota/reservations/{reservation_id}/finalize | Replace a Voice reservation with actual completed units in its original period |
| POST | /v1/webhooks/{rail} | Inbound payment-rail settlement webhook (dodo/stripe/paypal/crypto/razorpay). A SEPARATE ingress from the product-face: authed by the PROVIDER's signature over the RAW body (Stripe-Signature / X-CC-Webhook-Signature / X-Razorpay-Signature / PayPal transmission headers / Dodo's Standard-Webhooks webhook-id + webhook-timestamp + webhook-signature triple), NOT an api-key/bearer. The signature is VERIFIED per rail BEFORE the payload is trusted — a forged/unsigned POST is rejected (400) and can never flip a charge to paid. On a verified settlement event the charge's settlement state flips (pending → succeeded/failed); a failed recurring charge fires dunning. Idempotent: a redelivered event for an already-terminal charge is a 200 no-op.
|
| GET | /v1/settlements/{settlement_id} | Read a settlement record (its current pending/succeeded/failed state). TENANT-SCOPED: the record is looked up under the resolved organization, so a settlement owned by another tenant is a 404.
|
| GET | /v1/settlements | List an org's settlement records |
| POST | /v1/refunds | Reverse a SETTLED charge (full or partial), per-rail, idempotent. Reverses off the settlement recorded at charge-time: the settlement transitions succeeded → partially_refunded → refunded and accumulates refunded_amount_cents. Scoped to the caller's organization_id (I6 ownership — one org can never reverse another's charge). Omit amount_cents for a FULL refund of the remaining balance. idempotency_key makes a redelivered refund a no-op (the same key never double-reverses). Rail secrets resolved server-side. Rails whose refund API needs an id v1 doesn't persist (paypal capture id / razorpay payment id) return a deferred rail_status (422) rather than a wrong-id call; crypto on-chain charges are unsupported.
|
| GET | /v1/refunds | List an org's refund records |
| GET | /v1/refunds/{refund_id} | Read a refund record (its rail outcome + amount reversed). TENANT-SCOPED: a refund owned by another tenant is a 404.
|
| POST | /v1/tax/quote | Compute multi-jurisdiction tax for an amount (a stateless quote). A product quotes tax then charges the total via /v1/charge — tax is NOT folded into the charge primitive (charge stays byte-for-byte). No DB, no secrets: pure computation over the fleet's jurisdiction rate tables (US state sales tax incl. digital exemptions + country VAT/GST/consumption). Unsupported jurisdiction / product_type / rail → 422. RAIL-AWARE (ADR-110 D3): pass the OPTIONAL rail the charge will ride. On a Merchant-of-Record rail (dodo) the MoR is the legal seller and the registered taxpayer — it collects and remits the customer's tax itself — so OUR computation is suppressed: tax_cents is 0, total_cents equals the subtotal, and the response carries tax_collected_by: merchant_of_record plus the jurisdiction_tax_rate that WOULD have applied, so a suppressed quote is never mistaken for a 0% jurisdiction. Quoting our tax on an MoR charge would DOUBLE-TAX the customer. Omit rail for the unchanged tax-exclusive behaviour (we are the seller of record).
|
| GET | /v1/tax/jurisdictions | The jurisdictions (countries + US states) this capability can quote |
| POST | /v1/tax/validate-id | Validate the FORMAT of a customer tax ID (EU VAT / US EIN / AU ABN / IN GSTIN). Format-only — no registry lookup, no DB, no secrets. Never 422s on a bad id: returns {valid:false, format:unknown, ...} so the caller decides. Missing tax_id/country → 422.
|
| GET | /v1/dunning/{record_id} | Read a dunning record (failed-payment retry schedule + state). TENANT-SCOPED: a record owned by another tenant is a 404.
|
| GET | /v1/dunning | List an org's dunning records |
| POST | /v1/dunning/execute | Fire due dunning retries against the rail (ops-triggered internal executor). Body {"force": true} fires scheduled retries regardless of due_at (the normal schedule is exponential-backoff hours out).
|
| GET | /v1/rate-card | What we charge per unit — the customer-facing rate card (no org scope; identical for everyone) |
| GET | /v1/plans | The purchasable plans and their quota envelopes |
| GET | /v1/invoice | The org's metered usage for a period, rated into customer-visible invoice lines |
| POST | /v1/cost/estimate | Cost BEFORE spend — quote what {metric, quantity} would cost this org, before it is incurred |
| POST | /v1/organizations/{organization_id}/close | Stop billing for an org — the billing leg of the cross-plane org-delete cascade. IDEMPOTENT: success is asserted on live_rows_remaining == 0 and active_dunning_remaining == 0, never on released counts.
|
| GET | /v1/orgs/{org_id}/margin | Margin: revenue vs COGS per (product, capability, model, provider) for an org's current period. Revenue is re-rated from the CURRENT rate card (not stored), COGS = cost_cents (billable provider cost) + internal_cost_cents (fleet-key, non-billable). Emits a best-effort negative_margin notification (fire-and-forget POST to notifications; failure never blocks the response) when any line's margin is below zero.
|
| PUT | /v1/spend-domains/{spend_domain_id}/policy | Set or update the budget/quota/alert policy for a spend domain (upsert; returns the policy as stored). ADMIN-ONLY: spend_domain_policy is keyed by spend_domain_id alone (no organization_id column), so there is no stored owner to check a caller against — this fails CLOSED to a first-party SERVICE_TOKEN credential rather than trusting the id off the path (OW-1020). A verified end-customer JWT is rejected even for its own org.
|
| GET | /v1/spend-domains/{spend_domain_id}/policy | Read the budget/quota/alert policy for a spend domain. ADMIN-ONLY, same gate as PUT (OW-1020). |
| DELETE | /v1/spend-domains/{spend_domain_id}/policy | Delete the policy for a spend domain. ADMIN-ONLY, same gate as PUT (OW-1020). |
| GET | /v1/spend-domains/{spend_domain_id}/spend | Sum period-to-date cost_cents for a spend domain (the current calendar-month period; not caller-selectable). Optional ?project_id= scopes to one project's spend within the domain. ADMIN-ONLY, same gate as the policy routes (OW-1020) — the table has no organization_id to authorize a tenant caller against.
|
| GET | /v1/orgs/{org_id}/spend | Sum period-to-date cost_cents for an entire org (the current calendar-month period). |
| POST | /v1/contracts | Create or update the (single, active) enterprise contract for an org — negotiated quota overrides, overage behavior (hard_stop | soft_overage | grace_buffer) and commit terms. An org that already has a contract is updated in place (only the fields present in the body change); the first call for an org creates one. GATED OFF in this deployment (see the block comment above): _ENTERPRISE_STORES_ENABLED is hardcoded False, so this answers 503 on every authenticated call today; the 422/200 responses below are the real handler behavior once a durable backend exists and the gate is lifted.
|
| GET | /v1/contracts/{org_id} | Get the active enterprise contract for an org. GATED OFF in this deployment — see the block comment above /v1/contracts; this answers 503 on every authenticated call today.
|
| GET | /v1/contracts/{org_id}/quota-check | Contract-aware quota check for an org/metric: resolves the effective limit (contract override, or the plan-tier default) and, if over, the overage behavior's consequence (hard-stop denial / allow-with-grace / allow-with-overage-billing). GATED OFF in this deployment — see the block comment above /v1/contracts; this answers 503 on every authenticated call today.
|
| POST | /v1/invoices/persist | Persist rated invoice line items for an org/period (called at period-close). Replaces any existing lines for the same org/period — idempotent re-rate, not an append. GATED OFF in this deployment: this operation shares CreditStore's gate (_ENTERPRISE_STORES_ENABLED, hardcoded False — see the block comment above /v1/contracts), so it answers 503 with credit_store_unconfigured on every authenticated call today.
|
| GET | /v1/invoices/{org_id}/{period} | The customer-visible invoice for an org/period, INCLUDING usage credits netted against the persisted lines. GATED OFF in this deployment — same gate as POST /v1/invoices/persist; answers 503 with credit_store_unconfigured on every authenticated call today.
|
| POST | /v1/credits | Issue a usage credit against a specific (org, period, product, metric) invoice line — a support/ops dispute adjustment. Rejects (400) if the named line has no matching persisted invoice line, if credit_cents is not positive, or if reason/issued_by are blank (both required, logged for audit). GATED OFF in this deployment: same gate as invoices; answers 503 with credit_store_unconfigured on every authenticated call today.
|
| GET | /v1/credits/{org_id}/{period} | List all usage credits issued for an org/period. GATED OFF in this deployment — same gate as POST /v1/credits; answers 503 with credit_store_unconfigured on every authenticated call today.
|
| POST | /v1/reconciliation/run | Run vendor-invoice reconciliation for a period (ADR-130): parses vendor usage exports (Anthropic CSV / OpenAI usage JSON / Google billing CSV) per provider, compares them against this service's own metered summaries for the same period, and reports per-model variance plus alerts for any model exceeding the variance threshold. Accepts either a multi-provider vendor_csvs map or a single provider+csv pair (folded into the map if both are given). ADMIN-ONLY (first-party SERVICE_TOKEN caller; a verified end-customer JWT is rejected) — this aggregates metered cost across EVERY tenant for the period, so there is no single tenant to scope a customer credential to. See the block comment above /v1/contracts for a verified, currently unfixed defect in this admin-only call shape: no request can actually supply the organization_id this route's own auth chain requires, so this operation 422s for every caller today whenever BILLING_METERING_DSN IS configured (and 503s when it is not) — the 200 below is real logic, proven correct at the unit level against a live period's summaries, but not reachable over HTTP as currently wired.
|
| GET | /v1/reconciliation/risk-bucket | The exact=False (estimated) reconciliation risk for a period, by EVENT count — the share of metered events whose cost was a documented estimate rather than a provider-billed measurement. A narrower, standalone sibling of the risk_bucket embedded in POST /v1/reconciliation/run's response (see that operation's schema note for the difference). ADMIN-ONLY, same gate and same verified always-422 limitation as POST /v1/reconciliation/run — see that operation's summary and the block comment above /v1/contracts.
|
| POST | /v1/trials/start | Start a trial for an org (default plan "pro", default 14 days, per-metric trial caps default to DEFAULT_TRIAL_CAPS unless spend_cap_cents overrides them). Idempotent: an org with an already- ACTIVE trial gets that same trial back rather than a second one. Now answers a REAL 201 with the TrialSummary object — the Flask tuple-return bug (return <dict>, 201, which FastAPI serialized as a 200 whose body was the 2-element array [<object>, 201]) is fixed; verified in test/enterprise_routes_unit.py. GATED OFF in this deployment — see the block comment above /v1/contracts; answers 503 with trial_store_unconfigured on every authenticated call today.
|
| POST | /v1/trials/convert | Convert a trial to a paid plan and record the conversion event. source must be one of self_serve | sales_assisted | auto_convert (default self_serve). Now answers a REAL 201 with the conversion event object — same tuple-return fix as POST /v1/trials/start, verified in test/enterprise_routes_unit.py. A ValueError for "no trial found" is now mapped to 404 rather than an unhandled exception. GATED OFF in this deployment — see the block comment above /v1/contracts; answers 503 with trial_store_unconfigured on every authenticated call today.
|
| GET | /v1/trials/{org} | Get the current trial status for an org. Now calls the real format_trial_summary function (the code previously called a TrialStore.format_trial method that does not exist). GATED OFF in this deployment — see the block comment above /v1/contracts; answers 503 with trial_store_unconfigured on every authenticated call today.
|
| GET | /v1/trials/{org}/check | Intended to check whether an org's trial usage is within its trial cap. GENUINELY UNDETERMINABLE, reported rather than guessed: TrialStore has no check_quota method (the original call site's target); the closest match, check_trial_cap(org, metric, current_usage), needs a metric this route's own contract has never accepted, and trial_cap can hold several independently-capped metrics (see TrialStore.start_trial / DEFAULT_TRIAL_CAPS) — there is no single correct metric to default to, and inventing one would be a guess about money-adjacent state. This operation therefore answers a stable 501, not a wrong number and not a 500. Closing it for real needs either a metric (+ current_usage) query parameter added to this route's contract, or a different, all-metrics response shape this contract does not currently define — both are caller-facing decisions outside this pass's scope. GATED OFF in this deployment on top of that — see the block comment above /v1/contracts: _ENTERPRISE_STORES_ENABLED is checked BEFORE the 501 below, so this answers 503 with trial_store_unconfigured on every authenticated call today; the 501 is real (reachable with the store gate flipped, per test/enterprise_routes_unit.py) but currently masked by the 503.
|
| GET | /health | liveness |
| GET | /metrics | Prometheus |
Schemas
VoiceInternalIdentifier
VoiceInternalProfileHash
SHA-256 of the enabled server-owned immutable profile, never selected by an anonymous caller.
VoiceInternalAllocationRequest
| Field | Type | Description |
|---|
organization_id | object | |
project_id | object | |
environment_id | object | |
profile_hash | object | |
period | string | Current UTC month; renewal must be explicitly authorized. |
currency | string | |
cap_cents | integer | Internal allocation ceiling; not customer money or measured provider cost. |
authorized_by | object | |
authorization_ref | object | |
VoiceInternalMonthlyPolicyRequest
| Field | Type | Description |
|---|
organization_id | object | |
project_id | object | |
environment_id | object | |
profile_hash | object | |
currency | string | |
cap_cents | integer | |
enabled | boolean | False pauses future allocation and provider admission without deleting evidence. |
expected_revision | integer | Zero for initial authorization; current policy revision for a new change. |
authorized_by | object | |
authorization_ref | object | |
VoiceInternalReservationRequest
| Field | Type | Description |
|---|
organization_id | object | |
project_id | object | |
environment_id | object | |
profile_hash | object | |
operation_id | object | |
purpose | string | Defaults to visitor. Must match the allocation kind (acquisition=visitor, qualification=verification). |
correlation_id | object | |
publication_id | object | |
generation | integer | |
VoiceInternalUsageRequest
| Field | Type | Description |
|---|
organization_id | object | |
project_id | object | |
environment_id | object | |
profile_hash | object | |
operation_id | object | |
usage_id | object | |
capability | string | |
units | object | Measured units named as in the profile pricing rates, e.g. audio_ms, input_tokens, characters, media_ms, calls. |
correlation_id | object | |
VoiceInternalUsageResult
| Field | Type | Description |
|---|
wire_version | string | |
usage_id | string | |
allocation_id | string | |
reservation_id | string | |
operation_id | string | |
organization_id | string | |
profile_hash | object | |
capability | string | |
units | object | |
cost_micros | integer | Registry list-price rating in USD micros; not a vendor invoice. |
correlation_id | ['string', 'null'] | |
settled_cents | ['integer', 'null'] | |
cost_basis | string | |
billable | boolean | |
customer_invoice_excluded | boolean | |
VoiceInternalProfileRequest
| Field | Type | Description |
|---|
profile_id | string | |
profile_version | integer | |
profile | object | Hashed execution envelope: version, max_execution_ms and per-capability model, max_attempts, limits and totals. |
limits | object | |
pricing | object | |
created_by | object | |
Generated by scripts/gen-capability-docs.py from contracts/billing-metering/v1/openapi.yaml — the contract IS the source of truth; edit the contract, not this page. The Edge route table section is synced from the live edge catalog snapshot (v1) — re-sync with python3 scripts/sync-edge-catalog.py.