Download OpenAPI specification:
Actions available to tenant admins within their own tenant. Some are also available to Qvyon Operations.
Create a new user and send an Auth0 invitation email.
The user is created in PENDING status. An async job sends the Auth0 invitation. Once the user accepts the invite and logs in, their status transitions to ACTIVE.
Authorization (requires mgmt:write:users scope):
Returns 409 if a user with the same email already exists in this tenant.
| X-Tenant-ID | string Auth0 Organization ID of the target tenant (e.g., 'org_abc123'). Required for Operations Users on tenant-scoped operations. |
| email required | string Email address of the user. Must be unique within the tenant. |
| name required | string Display name of the user. |
| role required | string Enum: "admin" "member" "operations" Role to assign to the user. |
{- "email": "string",
- "name": "string",
- "role": "admin"
}{- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z",
- "id": "string",
- "tenant_id": "string",
- "auth0_id": "string",
- "email": "string",
- "name": "string",
- "role": "admin",
- "status": "active",
- "invited_at": "2019-08-24T14:15:22Z",
- "invite_expires_at": "2019-08-24T14:15:22Z"
}Resend the Auth0 invitation email for a user in PENDING or INVITE_FAILED status.
Deletes any existing Auth0 invitation and creates a new one with a fresh expiration. Updates invited_at timestamp.
Authorization (requires mgmt:write:users scope):
Returns 404 if user not found. Returns 409 if user is not in PENDING or INVITE_FAILED status.
| userId required | string |
| X-Tenant-ID | string Auth0 Organization ID of the target tenant (e.g., 'org_abc123'). Required for Operations Users on tenant-scoped operations. |
{- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z",
- "id": "string",
- "tenant_id": "string",
- "auth0_id": "string",
- "email": "string",
- "name": "string",
- "role": "admin",
- "status": "active",
- "invited_at": "2019-08-24T14:15:22Z",
- "invite_expires_at": "2019-08-24T14:15:22Z"
}Retrieve a single tenant by slug.
Authorization: a tenant user reading their own tenant (mgmt:read:tenant),
or an Operations User reading any tenant (mgmt:read:tenants_mgmt).
Holding the first scope is not enough to read someone else's tenant: the
handler compares the caller's organization to the tenant's and returns
403 otherwise.
Returns 404 if not found.
| slug required | string |
{- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z",
- "public_id": "eb3ce6c7-ce7e-4638-a072-e5054706f601",
- "auth0_org_id": "string",
- "selected_plan_key": "string",
- "slug": "string",
- "name": "string",
- "description": "string",
- "distributorCode": "string",
- "tenant_type": "regular",
- "status": "active",
- "status_updated_at": "2019-08-24T14:15:22Z"
}Records a customer's interest and tells Operations about it. Nothing is provisioned and no contract exists until Operations records one.
Accepted rather than created: the answer a customer is waiting for is a conversation with sales, not a resource. Submitting a second time is allowed - a request with more detail than the last one is worth having, and refusing it would leave the customer with no way to correct a mistake.
| slug required | string |
| organizationSize required | string <= 200 characters How large the organization is, as the customer described it. |
| sequenceVolume required | string <= 200 characters How much sequence data they expect to process. |
| seatRequirement required | string <= 200 characters How many seats they expect to need. |
| deploymentType required | string <= 200 characters Cloud, on-premise or hybrid, as the customer described it. |
| specialRequirements required | string <= 4000 characters Anything else they want sales to know. May be empty. |
{- "organizationSize": "string",
- "sequenceVolume": "string",
- "seatRequirement": "string",
- "deploymentType": "string",
- "specialRequirements": "string"
}{- "errors": [
- {
- "detail": "string",
- "pointer": "string"
}
], - "type": "string",
- "title": "string",
- "status": 0,
- "detail": "string",
- "instance": "string"
}{- "tenantSlug": "string",
- "plan": {
- "key": "string",
- "name": "string",
- "priceCents": 0,
- "billingInterval": "string",
- "yearlyPriceCents": 0,
- "tokenLimit": 0,
- "seatLimit": 0,
- "isCustom": true,
- "topupMicrosPerToken": 0,
- "isActive": true
}, - "status": "trialing",
- "isTrial": true,
- "isCustom": true,
- "hasPaidSubscription": true,
- "hasBillingAccount": true,
- "currentPeriodStart": "2019-08-24T14:15:22Z",
- "currentPeriodEnd": "2019-08-24T14:15:22Z",
- "billingInterval": "month",
- "cancelAtPeriodEnd": true,
- "pendingChange": {
- "planKey": "string",
- "planName": "string",
- "billingInterval": "month",
- "effectiveAt": "2019-08-24T14:15:22Z"
}
}| slug required | string |
{- "name": "string",
- "email": "string",
- "taxId": "string",
- "address": {
- "line1": "string",
- "line2": "string",
- "city": "string",
- "state": "string",
- "postalCode": "string",
- "country": "string"
}
}Writes directly to the Stripe Customer backing this tenant's subscription — there is no local copy.
| slug required | string |
| name required | string |
| email required | string |
| taxId required | string |
required | object (ManagementResources.BillingAddress) A tenant's billing address, stored on the Stripe Customer. |
{- "name": "string",
- "email": "string",
- "taxId": "string",
- "address": {
- "line1": "string",
- "line2": "string",
- "city": "string",
- "state": "string",
- "postalCode": "string",
- "country": "string"
}
}{- "name": "string",
- "email": "string",
- "taxId": "string",
- "address": {
- "line1": "string",
- "line2": "string",
- "city": "string",
- "state": "string",
- "postalCode": "string",
- "country": "string"
}
}Cancels at period end rather than immediately: the customer has paid for the period already, and access should run to the end of it.
The local row is not deleted. subscriptions_tenant_unique means one subscription per tenant, so the row has to survive for the tenant to ever subscribe again; Stripe's customer.subscription.deleted webhook moves it to 'canceled' when the period actually closes. Until then the subscription stays 'active' with cancelAtPeriodEnd set, which is what the customer sees.
Idempotent: cancelling an already-cancelling subscription returns the same state rather than erroring.
| slug required | string |
{- "tenantSlug": "string",
- "plan": {
- "key": "string",
- "name": "string",
- "priceCents": 0,
- "billingInterval": "string",
- "yearlyPriceCents": 0,
- "tokenLimit": 0,
- "seatLimit": 0,
- "isCustom": true,
- "topupMicrosPerToken": 0,
- "isActive": true
}, - "status": "trialing",
- "isTrial": true,
- "isCustom": true,
- "hasPaidSubscription": true,
- "hasBillingAccount": true,
- "currentPeriodStart": "2019-08-24T14:15:22Z",
- "currentPeriodEnd": "2019-08-24T14:15:22Z",
- "billingInterval": "month",
- "cancelAtPeriodEnd": true,
- "pendingChange": {
- "planKey": "string",
- "planName": "string",
- "billingInterval": "month",
- "effectiveAt": "2019-08-24T14:15:22Z"
}
}Changing the plan tier within the same billing interval takes effect immediately: Stripe prorates the difference and invoices it against the saved payment method. Capacity the customer asked for today should not wait a month.
Changing the billing interval - monthly to yearly or back - is scheduled
for the end of the current period instead. Doing it immediately would
refund part of a month already used and start the new period on an
arbitrary date, and no allowance is credited on a mid-cycle change, so the
customer would pay the annual price and receive nothing until the year was
up. The response carries subscription.pendingChange and no client
secret, because nothing is charged today.
| slug required | string |
| planKey required | string |
| billingInterval required | string (ManagementResources.BillingInterval) Enum: "month" "year" Which cadence a subscription is billed on. |
{- "planKey": "string",
- "billingInterval": "month"
}{- "subscription": {
- "tenantSlug": "string",
- "plan": {
- "key": "string",
- "name": "string",
- "priceCents": 0,
- "billingInterval": "string",
- "yearlyPriceCents": 0,
- "tokenLimit": 0,
- "seatLimit": 0,
- "isCustom": true,
- "topupMicrosPerToken": 0,
- "isActive": true
}, - "status": "trialing",
- "isTrial": true,
- "isCustom": true,
- "hasPaidSubscription": true,
- "hasBillingAccount": true,
- "currentPeriodStart": "2019-08-24T14:15:22Z",
- "currentPeriodEnd": "2019-08-24T14:15:22Z",
- "billingInterval": "month",
- "cancelAtPeriodEnd": true,
- "pendingChange": {
- "planKey": "string",
- "planName": "string",
- "billingInterval": "month",
- "effectiveAt": "2019-08-24T14:15:22Z"
}
}, - "clientSecret": "string"
} Drops a pending billing-interval change, leaving the subscription on what
it is already paying for. Only meaningful while pendingChange is set;
once the period has turned over the change is the subscription and
reversing it is an ordinary change in the other direction.
| slug required | string |
{- "tenantSlug": "string",
- "plan": {
- "key": "string",
- "name": "string",
- "priceCents": 0,
- "billingInterval": "string",
- "yearlyPriceCents": 0,
- "tokenLimit": 0,
- "seatLimit": 0,
- "isCustom": true,
- "topupMicrosPerToken": 0,
- "isActive": true
}, - "status": "trialing",
- "isTrial": true,
- "isCustom": true,
- "hasPaidSubscription": true,
- "hasBillingAccount": true,
- "currentPeriodStart": "2019-08-24T14:15:22Z",
- "currentPeriodEnd": "2019-08-24T14:15:22Z",
- "billingInterval": "month",
- "cancelAtPeriodEnd": true,
- "pendingChange": {
- "planKey": "string",
- "planName": "string",
- "billingInterval": "month",
- "effectiveAt": "2019-08-24T14:15:22Z"
}
}| slug required | string |
{- "items": [
- {
- "id": "string",
- "createdAt": "2019-08-24T14:15:22Z",
- "status": "string",
- "kind": "subscription",
- "amountCents": 0,
- "hostedInvoiceUrl": "string",
- "invoicePdfUrl": "string"
}
]
}Creates the Stripe Customer + Subscription server-side (payment_behavior: default_incomplete) and returns a client secret for the frontend to confirm the card directly with Stripe. Card data never passes through our backend.
| slug required | string |
| planKey required | string |
{- "planKey": "string"
}{- "subscriptionId": "string",
- "clientSecret": "string"
}Append-only audit trail of every token movement for this tenant — grants, expiries, purchases, and job-driven debits from recorded usage events. Default sort: created_at DESC (newest first).
| slug required | string |
| limit | integer <int32> [ 1 .. 200 ] Default: 50 Maximum items to return. Server default: 50. Server maximum: 200. |
| cursor | string Opaque continuation token from previous response. |
{- "items": [
- {
- "id": "string",
- "transactionType": "reservation",
- "amount": 0,
- "balanceAfter": 0,
- "referenceType": "job",
- "referenceId": "string",
- "reason": "string",
- "createdAt": "2019-08-24T14:15:22Z"
}
], - "page": {
- "next": "string"
}
}Creates a Stripe PaymentIntent for a one-off token purchase and returns its client secret, so the browser confirms the card directly with Stripe and no card data reaches this service.
Tokens are NOT credited here. The balance moves only once Stripe confirms
the charge, when payment_intent.succeeded arrives at the webhook and
writes a purchase entry to the ledger. A caller who never confirms, or
whose card is declined, leaves no ledger movement at all - which is why
this returns an intent rather than a balance.
| slug required | string |
| tokens required | integer <int32> >= 1 How many tokens to buy. The charge is priced server-side from the configured top-up rate - the client never states what it should be charged, so a tampered request can buy fewer tokens but never cheaper ones. |
{- "tokens": 1
}{- "clientSecret": "string",
- "tokens": 0,
- "amountCents": 0,
- "currency": "string"
}What tokens cost to buy on top of the plan allotment, so a client can show the price while the customer chooses an amount.
For display only. A purchase is always priced again server-side from the same configuration, so a stale or tampered copy of this cannot change what a card is charged.
| slug required | string |
{- "microsPerToken": 0,
- "currency": "string",
- "minTokens": 0,
- "maxTokens": 0
}Aggregates recorded usage events per user, so a tenant admin can see who spent the organisation's tokens rather than only that the organisation did.
Lifetime totals, for the same reason usage-summary is: usage_events carries no period marker.
Attribution only exists from the point mimic-api began reporting a submitter. Earlier consumption is real and is still counted, in a single entry with no user attached, so the entries continue to sum to the tenant total rather than quietly disagreeing with it.
| slug required | string |
{- "entries": [
- {
- "userId": "string",
- "name": "string",
- "email": "string",
- "tokens": 0,
- "eventCount": 0
}
], - "totalTokens": 0
}Token consumption bucketed over time, for the usage chart. Hourly buckets for the 24h range, daily for the rest.
Bucketed on when the work completed rather than when the event was recorded, so a backlog drained after an outage lands on the hours the jobs actually ran. Events that report no completion time fall back to their record time.
| slug required | string |
| range | string (ManagementResources.UsageHistoryRange) Enum: "24h" "7d" "30d" "90d" How far back a usage history chart reaches. |
{- "range": "24h",
- "points": [
- {
- "periodStart": "2019-08-24T14:15:22Z",
- "tokens": 0
}
]
}Aggregates recorded usage events into per-category totals, so a dashboard can show where a tenant's tokens went rather than only how many are left.
Totals are lifetime, not per billing period: usage_events carries no period marker, and deriving one from the subscription's current period would silently exclude history from before the current plan.
| slug required | string |
{- "entries": [
- {
- "usageType": "string",
- "tokens": 0,
- "eventCount": 0
}
], - "totalTokens": 0
}List users within the target tenant.
Authorization (requires mgmt:read:users scope):
Default sort: created_at DESC, email ASC (newest first, stable tie-breaker).
| limit | integer <int32> [ 1 .. 200 ] Default: 50 Maximum items to return. Server default: 50. Server maximum: 200. |
| cursor | string Opaque continuation token from previous response. |
| X-Tenant-ID | string Auth0 Organization ID of the target tenant (e.g., 'org_abc123'). Required for Operations Users on tenant-scoped operations. |
{- "items": [
- {
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z",
- "id": "string",
- "tenant_id": "string",
- "auth0_id": "string",
- "email": "string",
- "name": "string",
- "role": "admin",
- "status": "active",
- "invited_at": "2019-08-24T14:15:22Z",
- "invite_expires_at": "2019-08-24T14:15:22Z"
}
], - "page": {
- "next": "string"
}
}Retrieve a single user by ID.
Authorization (requires mgmt:read:users scope):
Returns 404 if user not found or not in target tenant.
| userId required | string |
| X-Tenant-ID | string Auth0 Organization ID of the target tenant (e.g., 'org_abc123'). Required for Operations Users on tenant-scoped operations. |
{- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z",
- "id": "string",
- "tenant_id": "string",
- "auth0_id": "string",
- "email": "string",
- "name": "string",
- "role": "admin",
- "status": "active",
- "invited_at": "2019-08-24T14:15:22Z",
- "invite_expires_at": "2019-08-24T14:15:22Z"
}Update an existing user's name, role, or status.
Authorization (requires mgmt:write:users scope):
Note: Cannot transition status to PENDING or INVITE_FAILED via this endpoint.
Returns 404 if user not found or not in target tenant.
| userId required | string |
| X-Tenant-ID | string Auth0 Organization ID of the target tenant (e.g., 'org_abc123'). Required for Operations Users on tenant-scoped operations. |
| name | string Display name of the user. Omit to leave unchanged. |
| role | string Enum: "admin" "member" "operations" Role of the user within the tenant. Omit to leave unchanged. |
| status | string Enum: "active" "deactivated" "suspended" "pending" "invite_failed" Current status of the user. Omit to leave unchanged. Cannot transition to PENDING or INVITE_FAILED. |
{- "name": "string",
- "role": "admin",
- "status": "active"
}{- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z",
- "id": "string",
- "tenant_id": "string",
- "auth0_id": "string",
- "email": "string",
- "name": "string",
- "role": "admin",
- "status": "active",
- "invited_at": "2019-08-24T14:15:22Z",
- "invite_expires_at": "2019-08-24T14:15:22Z"
}Soft-delete a user (sets status to DEACTIVATED).
Authorization (requires mgmt:write:users scope):
Idempotent: returns 204 even if already deactivated or not found.
| userId required | string |
| X-Tenant-ID | string Auth0 Organization ID of the target tenant (e.g., 'org_abc123'). Required for Operations Users on tenant-scoped operations. |
{- "type": "string",
- "title": "string",
- "status": 0,
- "detail": "string",
- "instance": "string"
}Revokes the user's Auth0 sessions and refresh tokens, so any access token already issued to them stops working at its next silent refresh rather than lasting until it expires on its own.
A support action, not a state change: the user stays active and may log straight back in. To stop someone logging in at all, suspend them instead, which blocks the account and revokes access together.
Authorization (requires mgmt:write:users scope):
Returns 409 for a user who has never logged in, since a pending invitation has no session to revoke.
| userId required | string |
| X-Tenant-ID | string Auth0 Organization ID of the target tenant (e.g., 'org_abc123'). Required for Operations Users on tenant-scoped operations. |
{- "type": "string",
- "title": "string",
- "status": 0,
- "detail": "string",
- "instance": "string"
}The Operations log. Newest first.
Cross-tenant by definition, so it carries no tenant header and no slug:
the point of the log is to see across tenants, and the scope restricts it
to the Operations organization. Narrow it to one tenant with tenantSlug.
Paged on (occurred_at, id) rather than by offset. The log grows continuously, so an offset page shifts under the reader as rows arrive above it.
Scope: mgmt:read:tenants_mgmt, Operations Users only.
| limit | integer <int32> [ 1 .. 200 ] Default: 50 Maximum items to return. Server default: 50. Server maximum: 200. |
| cursor | string Opaque continuation token from previous response. |
| category | string (ManagementResources.AuditCategory) Enum: "audit" "security" "communication" "events" Show only this category. Omit for all. |
| activityType | string Show only this kind of activity - Independent of |
| tenantSlug | string Show only events concerning this tenant. Omit for all tenants. |
{- "items": [
- {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "occurredAt": "2019-08-24T14:15:22Z",
- "category": "audit",
- "actorType": "admin",
- "actor": "string",
- "activity": "string",
- "activityType": "string",
- "targetType": "Organization",
- "target": "string",
- "tenantSlug": "string",
- "detail": {
- "property1": null,
- "property2": null
}
}
], - "page": {
- "next": "string"
}
}Soonest to run out first, because the view exists to answer which renewals are coming up.
Each row carries the organization it belongs to, joined server-side rather than fetched per row - the same N+1 the ops registry had to fix.
{- "items": [
- {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "tenantSlug": "string",
- "organization": "string",
- "valueCents": 0,
- "currency": "string",
- "billingFrequency": "monthly",
- "tokenPool": 0,
- "seats": 0,
- "startsOn": "2019-08-24",
- "endsOn": "2019-08-24",
- "discountBasisPoints": 0,
- "seatPriceCents": 0,
- "deployment": "cloud",
- "licencePriceCents": 0,
- "tokenBonus": 0,
- "unitPriceMicros": 0,
- "tokenDiscountBasisPoints": 0,
- "licenseDiscountBasisPoints": 0,
- "periods": [
- {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "position": 0,
- "startsOn": "2019-08-24",
- "endsOn": "2019-08-24",
- "committedTokens": 0,
- "committedSpendCents": 0,
- "unitPriceMicros": 0,
- "discountBasisPoints": 0
}
], - "notes": "string",
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}
]
}| slug required | string |
{- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "tenantSlug": "string",
- "organization": "string",
- "valueCents": 0,
- "currency": "string",
- "billingFrequency": "monthly",
- "tokenPool": 0,
- "seats": 0,
- "startsOn": "2019-08-24",
- "endsOn": "2019-08-24",
- "discountBasisPoints": 0,
- "seatPriceCents": 0,
- "deployment": "cloud",
- "licencePriceCents": 0,
- "tokenBonus": 0,
- "unitPriceMicros": 0,
- "tokenDiscountBasisPoints": 0,
- "licenseDiscountBasisPoints": 0,
- "periods": [
- {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "position": 0,
- "startsOn": "2019-08-24",
- "endsOn": "2019-08-24",
- "committedTokens": 0,
- "committedSpendCents": 0,
- "unitPriceMicros": 0,
- "discountBasisPoints": 0
}
], - "notes": "string",
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}One contract per organization. Sending this for an organization that already has one revises those terms; a renegotiation is the same contract on new terms, not a second one.
Recording terms does not provision anything. The tenant's plan, seats and token balance still come from its subscription, which is granted separately - so a contract can be drafted and corrected before any capacity is handed out, and a typo here cannot spend real compute.
| slug required | string |
| valueCents required | integer <int64> |
| currency required | string |
| billingFrequency required | string (ManagementResources.ContractBillingFrequency) Enum: "monthly" "quarterly" "annually" How often an enterprise customer is invoiced for their contract. |
| tokenPool required | integer <int64> |
| seats required | integer <int32> |
| startsOn required | string <date> |
| endsOn required | string <date> |
| discountBasisPoints required | integer <int32> [ 0 .. 10000 ] Discount on the whole contract, in basis points (1250 = 12.5%), applied after the component discounts. Zero when none was agreed. |
| seatPriceCents required | integer <int64> >= 0 Agreed price of one seat for the term, in whole cents. Zero when licences are not priced separately. |
| tokenDiscountBasisPoints required | integer <int32> [ 0 .. 10000 ] Discount on the token component, in basis points. Zero when none was agreed. |
| licenseDiscountBasisPoints required | integer <int32> [ 0 .. 10000 ] Discount on the Mimic on-prem licence, in basis points. Never applies to user seats. |
| deployment required | string (ManagementResources.ContractDeployment) Enum: "cloud" "on_prem" Where a contract runs. On-prem buys the Mimic licence; cloud does not. |
| licencePriceCents required | integer <int64> >= 0 Yearly price of the Mimic on-prem licence, in whole cents. Zero on a cloud contract. |
| tokenBonus required | integer <int64> >= 0 One-time free tokens credited at the start of the term. |
| unitPriceMicros required | integer <int64> >= 0 Rate per token for a contract with no ramp schedule, in micros. |
required | Array of objects (ManagementResources.UpsertContractPeriodRequest) The ramp schedule, replaced wholesale. Empty for a flat contract. Sent as a set rather than diffed: steps are added, removed and reordered together, and a half-applied edit would leave a gap or an overlap in the term. |
| notes required | string |
{- "valueCents": 0,
- "currency": "string",
- "billingFrequency": "monthly",
- "tokenPool": 0,
- "seats": 0,
- "startsOn": "2019-08-24",
- "endsOn": "2019-08-24",
- "discountBasisPoints": 10000,
- "seatPriceCents": 0,
- "tokenDiscountBasisPoints": 10000,
- "licenseDiscountBasisPoints": 10000,
- "deployment": "cloud",
- "licencePriceCents": 0,
- "tokenBonus": 0,
- "unitPriceMicros": 0,
- "periods": [
- {
- "startsOn": "2019-08-24",
- "endsOn": "2019-08-24",
- "committedTokens": 0,
- "committedSpendCents": 0,
- "unitPriceMicros": 0,
- "discountBasisPoints": 10000
}
], - "notes": "string"
}{- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "tenantSlug": "string",
- "organization": "string",
- "valueCents": 0,
- "currency": "string",
- "billingFrequency": "monthly",
- "tokenPool": 0,
- "seats": 0,
- "startsOn": "2019-08-24",
- "endsOn": "2019-08-24",
- "discountBasisPoints": 0,
- "seatPriceCents": 0,
- "deployment": "cloud",
- "licencePriceCents": 0,
- "tokenBonus": 0,
- "unitPriceMicros": 0,
- "tokenDiscountBasisPoints": 0,
- "licenseDiscountBasisPoints": 0,
- "periods": [
- {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "position": 0,
- "startsOn": "2019-08-24",
- "endsOn": "2019-08-24",
- "committedTokens": 0,
- "committedSpendCents": 0,
- "unitPriceMicros": 0,
- "discountBasisPoints": 0
}
], - "notes": "string",
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}Removes the commercial record. The tenant, its users and its subscription are untouched.
| slug required | string |
{- "type": "string",
- "title": "string",
- "status": 0,
- "detail": "string",
- "instance": "string"
}Create a new Operations user and send an Auth0 invitation email into the internal Qvyon Operations organization.
Unlike UserInvitesAPI, there is no tenant to target — this always invites into the Operations organization, so no X-Tenant-ID header is accepted.
Authorization: Operations Users only (requires mgmt:write:tenants_mgmt scope).
Admin Users receive 403 Forbidden.
Returns 409 if a user with the same email already exists in the Operations organization.
| email required | string Email address of the user. Must be unique within the Operations organization. |
| name required | string Display name of the user. |
{- "email": "string",
- "name": "string"
}{- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z",
- "id": "string",
- "tenant_id": "string",
- "auth0_id": "string",
- "email": "string",
- "name": "string",
- "role": "admin",
- "status": "active",
- "invited_at": "2019-08-24T14:15:22Z",
- "invite_expires_at": "2019-08-24T14:15:22Z"
} Creating a plan with a key that already exists is a conflict, not an
update. Changing one is update, which is the call that knows a price
change has to reach Stripe.
| key required | string |
| name required | string |
| priceCents required | integer <int32> |
| billingInterval required | string or null |
| yearlyPriceCents | integer or null <int32> Price for a full year. Null when this plan has no yearly billing option. |
| tokenLimit required | integer <int64> |
| seatLimit required | integer <int32> |
| isCustom required | boolean |
| topupMicrosPerToken | integer or null <int64> Per-token price for this tier outside its allowance, in millionths of a dollar. Null or omitted falls back to the catalogue default. |
| isActive | boolean Whether to offer the plan immediately. Defaults to true when omitted. |
{- "key": "string",
- "name": "string",
- "priceCents": 0,
- "billingInterval": "string",
- "yearlyPriceCents": 0,
- "tokenLimit": 0,
- "seatLimit": 0,
- "isCustom": true,
- "topupMicrosPerToken": 0,
- "isActive": true
}{- "key": "string",
- "name": "string",
- "priceCents": 0,
- "billingInterval": "string",
- "yearlyPriceCents": 0,
- "tokenLimit": 0,
- "seatLimit": 0,
- "isCustom": true,
- "topupMicrosPerToken": 0,
- "isActive": true
}The catalogue as Operations has to see it to edit it, which includes the plans no longer offered. The public list hides those, and editing a catalogue through a view that omits part of it is how a retired plan gets recreated under a second key.
{- "items": [
- {
- "key": "string",
- "name": "string",
- "priceCents": 0,
- "billingInterval": "string",
- "yearlyPriceCents": 0,
- "tokenLimit": 0,
- "seatLimit": 0,
- "isCustom": true,
- "topupMicrosPerToken": 0,
- "isActive": true
}
]
}Changes the catalogue entry and, when an amount changed, the Stripe Price behind it.
A Stripe Price is immutable, so a new amount means a new Price, which this creates and points the plan at. Without that the pricing page would show one figure and the card would be charged another - the stored amount is display only, and checkout bills whatever Price the plan points to.
Subscribers already on the old Price keep it until their next plan change. That is Stripe's own behaviour for a subscription item and is deliberate here too: repricing an existing contract without telling the customer is a commercial decision, not a side effect of editing a catalogue.
| key required | string |
| name required | string |
| priceCents required | integer <int32> |
| billingInterval required | string or null |
| yearlyPriceCents required | integer or null <int32> |
| tokenLimit required | integer <int64> |
| seatLimit required | integer <int32> |
| isCustom required | boolean |
| topupMicrosPerToken required | integer or null <int64> |
| isActive required | boolean |
{- "name": "string",
- "priceCents": 0,
- "billingInterval": "string",
- "yearlyPriceCents": 0,
- "tokenLimit": 0,
- "seatLimit": 0,
- "isCustom": true,
- "topupMicrosPerToken": 0,
- "isActive": true
}{- "key": "string",
- "name": "string",
- "priceCents": 0,
- "billingInterval": "string",
- "yearlyPriceCents": 0,
- "tokenLimit": 0,
- "seatLimit": 0,
- "isCustom": true,
- "topupMicrosPerToken": 0,
- "isActive": true
}Create a new tenant and corresponding Auth0 Organization.
Authorization: Operations Users only (requires mgmt:write:tenants_mgmt scope).
Admin Users receive 403 Forbidden.
Returns 409 if a tenant with the same slug already exists.
| slug required | string Unique slug identifier for this tenant. Must be URL-safe. |
| name required | string Human-readable name of the tenant. |
| description | string Optional description of the tenant. |
{- "slug": "string",
- "name": "string",
- "description": "string"
}{- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z",
- "public_id": "eb3ce6c7-ce7e-4638-a072-e5054706f601",
- "auth0_org_id": "string",
- "selected_plan_key": "string",
- "slug": "string",
- "name": "string",
- "description": "string",
- "distributorCode": "string",
- "tenant_type": "regular",
- "status": "active",
- "status_updated_at": "2019-08-24T14:15:22Z"
}List all tenants.
Authorization: Operations Users only (requires mgmt:read:tenants_mgmt scope).
Admin Users receive 403 Forbidden.
Default sort: created_at DESC, slug ASC (newest first, stable tie-breaker).
| limit | integer <int32> [ 1 .. 200 ] Default: 50 Maximum items to return. Server default: 50. Server maximum: 200. |
| cursor | string Opaque continuation token from previous response. |
{- "items": [
- {
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z",
- "public_id": "eb3ce6c7-ce7e-4638-a072-e5054706f601",
- "auth0_org_id": "string",
- "selected_plan_key": "string",
- "slug": "string",
- "name": "string",
- "description": "string",
- "distributorCode": "string",
- "tenant_type": "regular",
- "status": "active",
- "status_updated_at": "2019-08-24T14:15:22Z"
}
], - "page": {
- "next": "string"
}
}Resolves the org_id carried in a user's access token to the tenant it belongs to.
Exists for services that authorise by tenant and keep a local cache of them: on a cache miss they fetch the tenant here rather than depending on having received an event for it. A cache miss is self-correcting, whereas a missed event leaves a tenant permanently invisible with nothing to notice or repair it.
Returns 404 when no tenant carries that organization ID, which is a meaningful answer - the caller holds a token for an organization this platform does not know.
The organization id is a query parameter rather than a path segment: a literal segment under /v1/tenants is ambiguous against /v1/tenants/{slug}/... in Go's router, which refuses to register both.
| auth0OrgId required | string |
{- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z",
- "public_id": "eb3ce6c7-ce7e-4638-a072-e5054706f601",
- "auth0_org_id": "string",
- "selected_plan_key": "string",
- "slug": "string",
- "name": "string",
- "description": "string",
- "distributorCode": "string",
- "tenant_type": "regular",
- "status": "active",
- "status_updated_at": "2019-08-24T14:15:22Z"
} Same tenants as list, joined server-side with each tenant's current
plan, subscription status, and occupied seat count — for the ops
organization registry view. Avoids per-tenant follow-up calls.
Authorization: Operations Users only (requires mgmt:read:tenants_mgmt scope).
| limit | integer <int32> [ 1 .. 200 ] Default: 50 Maximum items to return. Server default: 50. Server maximum: 200. |
| cursor | string Opaque continuation token from previous response. |
{- "items": [
- {
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z",
- "public_id": "eb3ce6c7-ce7e-4638-a072-e5054706f601",
- "auth0_org_id": "string",
- "selected_plan_key": "string",
- "slug": "string",
- "name": "string",
- "description": "string",
- "distributorCode": "string",
- "tenant_type": "regular",
- "status": "active",
- "status_updated_at": "2019-08-24T14:15:22Z",
- "plan": {
- "key": "string",
- "name": "string",
- "seatLimit": 0,
- "tokenLimit": 0
}, - "subscriptionStatus": "trialing",
- "currentPeriodStart": "2019-08-24T14:15:22Z",
- "currentPeriodEnd": "2019-08-24T14:15:22Z",
- "occupiedSeats": 0
}
], - "page": {
- "next": "string"
}
}Update an existing tenant.
Authorization: Operations Users only (requires mgmt:write:tenants_mgmt scope).
Admin Users receive 403 Forbidden.
Returns 404 if tenant not found.
| slug required | string |
| name | string Human-readable name of the tenant. |
| description | string Optional description of the tenant. |
| status | string Enum: "active" "deactivated" Current status of the tenant. |
| distributorCode | string Distributor this customer came through. Send an empty string to clear it. Absent and empty mean different things: a request that omits this leaves the code as it was, which is what lets a rename not wipe it. |
{- "name": "string",
- "description": "string",
- "status": "active",
- "distributorCode": "string"
}{- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z",
- "public_id": "eb3ce6c7-ce7e-4638-a072-e5054706f601",
- "auth0_org_id": "string",
- "selected_plan_key": "string",
- "slug": "string",
- "name": "string",
- "description": "string",
- "distributorCode": "string",
- "tenant_type": "regular",
- "status": "active",
- "status_updated_at": "2019-08-24T14:15:22Z"
}Soft-delete a tenant (sets status to deactivated).
Authorization: Operations Users only (requires mgmt:write:tenants_mgmt scope).
Admin Users receive 403 Forbidden.
Idempotent: returns 204 even if already deleted or not found.
| slug required | string |
{- "type": "string",
- "title": "string",
- "status": 0,
- "detail": "string",
- "instance": "string"
}| slug required | string |
| planKey required | string |
| currentPeriodEnd required | string or null <date-time> |
| reason required | string |
| trialDays | integer <int32> [ 1 .. 365 ] Start the subscription as a trial running this many days from now, rather than as an active license. Mutually exclusive with currentPeriodEnd, which sets the end date directly. |
{- "planKey": "string",
- "currentPeriodEnd": "2019-08-24T14:15:22Z",
- "reason": "string",
- "trialDays": 1
}{- "tenantSlug": "string",
- "plan": {
- "key": "string",
- "name": "string",
- "priceCents": 0,
- "billingInterval": "string",
- "yearlyPriceCents": 0,
- "tokenLimit": 0,
- "seatLimit": 0,
- "isCustom": true,
- "topupMicrosPerToken": 0,
- "isActive": true
}, - "status": "trialing",
- "isTrial": true,
- "isCustom": true,
- "hasPaidSubscription": true,
- "hasBillingAccount": true,
- "currentPeriodStart": "2019-08-24T14:15:22Z",
- "currentPeriodEnd": "2019-08-24T14:15:22Z",
- "billingInterval": "month",
- "cancelAtPeriodEnd": true,
- "pendingChange": {
- "planKey": "string",
- "planName": "string",
- "billingInterval": "month",
- "effectiveAt": "2019-08-24T14:15:22Z"
}
}Ends a trial now by setting its period end to the current time.
The subscription stays in the trialing status rather than moving to canceled: the tenant was on a trial and it finished, which is what the registry already renders as "Expired". Marking it canceled would claim a paid subscription was cancelled, which never happened.
Idempotent - expiring an already-expired trial returns the same result rather than moving the date backwards again.
| slug required | string |
{- "tenantSlug": "string",
- "plan": {
- "key": "string",
- "name": "string",
- "priceCents": 0,
- "billingInterval": "string",
- "yearlyPriceCents": 0,
- "tokenLimit": 0,
- "seatLimit": 0,
- "isCustom": true,
- "topupMicrosPerToken": 0,
- "isActive": true
}, - "status": "trialing",
- "isTrial": true,
- "isCustom": true,
- "hasPaidSubscription": true,
- "hasBillingAccount": true,
- "currentPeriodStart": "2019-08-24T14:15:22Z",
- "currentPeriodEnd": "2019-08-24T14:15:22Z",
- "billingInterval": "month",
- "cancelAtPeriodEnd": true,
- "pendingChange": {
- "planKey": "string",
- "planName": "string",
- "billingInterval": "month",
- "effectiveAt": "2019-08-24T14:15:22Z"
}
}Pushes a trial's end date out by the requested number of days.
Counted from the existing end date rather than from today, so extending a trial that still has time left adds to it instead of shortening it, and two extensions add up.
Only a trialing subscription can be extended - moving the period end of a paid subscription would silently disagree with Stripe, which owns that date.
| slug required | string |
| days required | integer <int32> [ 1 .. 365 ] Days to add to the current trial end. Counted from the existing end date, so extending twice adds both. |
| reason required | string Why the trial is being extended. Recorded for traceability. |
{- "days": 1,
- "reason": "string"
}{- "tenantSlug": "string",
- "plan": {
- "key": "string",
- "name": "string",
- "priceCents": 0,
- "billingInterval": "string",
- "yearlyPriceCents": 0,
- "tokenLimit": 0,
- "seatLimit": 0,
- "isCustom": true,
- "topupMicrosPerToken": 0,
- "isActive": true
}, - "status": "trialing",
- "isTrial": true,
- "isCustom": true,
- "hasPaidSubscription": true,
- "hasBillingAccount": true,
- "currentPeriodStart": "2019-08-24T14:15:22Z",
- "currentPeriodEnd": "2019-08-24T14:15:22Z",
- "billingInterval": "month",
- "cancelAtPeriodEnd": true,
- "pendingChange": {
- "planKey": "string",
- "planName": "string",
- "billingInterval": "month",
- "effectiveAt": "2019-08-24T14:15:22Z"
}
}Records an admin_action entry against the tenant's token ledger, for credits and manual corrections. Distinct from job-driven debits, which arrive through POST /v1/usage/events and are priced from usage_rates.
| slug required | string |
| amount required | integer <int64> Signed amount to apply. Positive credits tokens, negative debits them. Refused if it would take the balance outside what a 64-bit count can hold. A grant that could not be represented used to wrap to a negative balance and still report success. |
| reason required | string |
{- "amount": 0,
- "reason": "string"
}{- "id": "string",
- "transactionType": "reservation",
- "amount": 0,
- "balanceAfter": 0,
- "referenceType": "job",
- "referenceId": "string",
- "reason": "string",
- "createdAt": "2019-08-24T14:15:22Z"
}The default a tenant falls back to when its plan sets no rate of its own, which is every tenant with no plan at all.
Distinct from the per-tenant topup-pricing call, which answers "what does
this tenant pay" and resolves the plan rate first. This one answers "what
is the default configured as", which is the question the editor asks.
{- "microsPerToken": 0,
- "currency": "string",
- "minTokens": 0,
- "maxTokens": 0
}| microsPerToken required | integer <int64> |
| minTokens required | integer <int32> |
| maxTokens required | integer <int32> |
{- "microsPerToken": 0,
- "minTokens": 0,
- "maxTokens": 0
}{- "microsPerToken": 0,
- "currency": "string",
- "minTokens": 0,
- "maxTokens": 0
}Every configured rate, in tokens per unit: compute per hour, storage per terabyte-month, reconstruction and exportation per frame, actor import per actor.
These are what the platform charges. A usage event is priced at ingestion from the rate in force at that moment, so changing one here decides what the next job costs and never restates what a tenant has already been charged.
{- "items": [
- {
- "category": "string",
- "rateKey": "string",
- "instanceType": "string",
- "tokenAmount": 0.1,
- "unit": "string"
}
]
}Addressed by category, rate key and instance type, which is how the table is keyed. A rate has no id a person would ever hold.
| category required | string |
| rateKey required | string |
| instanceType required | string |
| tokenAmount required | number <double> >= 0 |
{- "category": "string",
- "rateKey": "string",
- "instanceType": "string",
- "tokenAmount": 0.1
}{- "category": "string",
- "rateKey": "string",
- "instanceType": "string",
- "tokenAmount": 0.1,
- "unit": "string"
}Retrieve information about the currently authenticated user.
Returns the user's profile, their tenant, and OAuth2 scopes from the JWT.
Authorization: Any authenticated user (valid JWT required, no additional scope needed).
Returns 404 if the authenticated user does not exist in the database. This can occur if:
{- "user": {
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z",
- "id": "string",
- "tenant_id": "string",
- "auth0_id": "string",
- "email": "string",
- "name": "string",
- "role": "admin",
- "status": "active",
- "invited_at": "2019-08-24T14:15:22Z",
- "invite_expires_at": "2019-08-24T14:15:22Z"
}, - "tenant": {
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z",
- "public_id": "eb3ce6c7-ce7e-4638-a072-e5054706f601",
- "auth0_org_id": "string",
- "selected_plan_key": "string",
- "slug": "string",
- "name": "string",
- "description": "string",
- "distributorCode": "string",
- "tenant_type": "regular",
- "status": "active",
- "status_updated_at": "2019-08-24T14:15:22Z"
}
}The plan catalogue is public. It backs the plan picker on the public signup page, where the visitor has no account yet and so no token to authenticate with — requiring one made the picker render empty and left Register permanently disabled.
Nothing here is sensitive: these are the same prices, token and seat limits the marketing site publishes. Creating or changing a plan still requires mgmt:write:billing_mgmt.
{- "items": [
- {
- "key": "string",
- "name": "string",
- "priceCents": 0,
- "billingInterval": "string",
- "yearlyPriceCents": 0,
- "tokenLimit": 0,
- "seatLimit": 0,
- "isCustom": true,
- "topupMicrosPerToken": 0,
- "isActive": true
}
]
}Any authenticated user can see what is available. Platforms are whatever the publisher has declared, so a new packaging format appears here without a deploy.
{- "items": [
- {
- "platform": "string",
- "version": "string",
- "sha256": "string"
}
]
}Returns a presigned URL the browser fetches directly from storage. The bucket is private and no credentials reach the client.
Minted per request rather than returned with the list: the link expires in minutes, so signing every platform on page load would hand out links that are dead before anyone clicks one.
| platform required | string |
{- "platform": "string",
- "version": "string",
- "sha256": "string",
- "url": "string",
- "expiresAt": "2019-08-24T14:15:22Z"
}Records consumption facts (job duration, frame/actor counts) and turns each into a token debit against the reporting tenant, priced from usage_rates at ingestion time.
Not tenant-scoped: the caller is a service (the executor, or a script acting on its behalf) reporting on behalf of whichever tenant each event names, so tenant_slug lives in the request body rather than X-Tenant-ID.
Idempotent per event_id - a retried delivery reports 'duplicate' rather than debiting twice. Always returns 202 with a per-event result, so one bad event in a batch doesn't force retrying the whole batch.
required | Array of objects (ManagementResources.UsageEventInput) |
{- "events": [
- {
- "eventId": "string",
- "tenantSlug": "string",
- "jobId": "string",
- "usageType": "string",
- "reservationId": "string",
- "userAuth0Id": "string",
- "instanceType": "string",
- "startedAt": "2019-08-24T14:15:22Z",
- "completedAt": "2019-08-24T14:15:22Z",
- "durationSeconds": 0,
- "frameCount": 0,
- "actorCount": 0,
- "storedBytes": 0,
- "outcome": "completed"
}
]
}{- "results": [
- {
- "eventId": "string",
- "status": "recorded",
- "error": "string"
}
]
}Decides whether a job may start, and holds its estimated cost so a concurrent job cannot spend the same tokens.
This exists because reconstruction of N scenes launches N jobs at once. A caller that merely reads the balance before starting each one gets the same answer N times and starts all of them, so a tenant with room for one job can overdraw by N. Reserving writes a hold under a per-tenant lock, so requests are decided one at a time and the second sees what the first took.
Always 200. granted says whether the job may run; a refusal is a normal
answer to a fair question, not an error, and it reports what was
available so the caller can tell the user how short they were.
The hold is released when the job's usage event arrives carrying the same reservationId, at which point the real cost is debited instead. Billing is never the estimate.
| reservationId required | string Caller-generated id. The usage event for this job carries it back, which is what pairs the real cost with the hold. |
| tenantSlug required | string |
| usageType required | string |
| instanceType | string Compute instance the job will run on, if it bills compute time. |
| frameCount | integer <int32> |
| actorCount | integer <int32> |
| estimatedDurationSeconds | integer <int32> How long the caller expects the job to run. Omit it and a conservative configured default for this usage type is used instead. Only ever used to decide whether the job may start. Billing uses the real duration reported when the job finishes, so an inaccurate estimate costs the tenant nothing. |
{- "reservationId": "string",
- "tenantSlug": "string",
- "usageType": "string",
- "instanceType": "string",
- "frameCount": 0,
- "actorCount": 0,
- "estimatedDurationSeconds": 0
}{- "reservationId": "string",
- "granted": true,
- "reservedTokens": 0,
- "availableTokens": 0,
- "refusalReason": "insufficient_tokens"
}