fix: migration conflicts
2
.github/workflows/build.yml
vendored
@@ -26,7 +26,7 @@ env:
|
||||
# staging repo (autumn-staging) -> us-east-1
|
||||
# Branches allowed to deploy to staging via workflow_dispatch with tag=deploy-staging.
|
||||
# Add short-lived PR branches here when you need staging without merging to dev.
|
||||
STAGING_DEPLOY_BRANCH_ALLOWLIST: fix-health-check-redis-disabled-detection feat/track-rate-limit-redis feat/events-hourly-rollup fix/analytics-tz-bucket-offset
|
||||
STAGING_DEPLOY_BRANCH_ALLOWLIST: ""
|
||||
|
||||
jobs:
|
||||
checks:
|
||||
|
||||
1
.gitignore
vendored
@@ -22,6 +22,7 @@ supabase.sh
|
||||
tests/
|
||||
!server/tests
|
||||
!packages/mcp/tests
|
||||
!packages/ai-sdk/tests
|
||||
!apps/leaf/tests
|
||||
!vite/tests
|
||||
.secrets
|
||||
|
||||
78
apps/docs/api-reference-generator/balances/trackTokens.mdx
Normal file
@@ -0,0 +1,78 @@
|
||||
---
|
||||
title: "Track Token Usage"
|
||||
openapi: "openapi POST /v1/balances.track_tokens"
|
||||
---
|
||||
|
||||
import { DynamicParamField } from "/snippets/dynamic-param-field.jsx";
|
||||
import { DynamicResponseField } from "/snippets/dynamic-response-field.jsx";
|
||||
import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
|
||||
<Note>
|
||||
Track AI token usage against a customer's AI credit system balance. Converts token counts to a dollar cost using [Models.dev](https://models.dev) pricing and your configured markup, then deducts from the customer's credit balance.
|
||||
</Note>
|
||||
|
||||
The `model_id` must use `provider/model` format, matching the provider and model keys from [Models.dev](https://models.dev). For providers with nested model paths (like OpenRouter), include the full path: `openrouter/anthropic/claude-opus-4.6`. The first path segment is the provider key used for `providerMarkups` lookup.
|
||||
|
||||
### Common Use Cases
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```typescript Anthropic
|
||||
await autumn.balances.trackTokens({
|
||||
customerId: "cus_123",
|
||||
modelId: "anthropic/claude-opus-4-6",
|
||||
inputTokens: 1000,
|
||||
outputTokens: 500
|
||||
});
|
||||
```
|
||||
|
||||
```typescript With cache + reasoning
|
||||
await autumn.balances.trackTokens({
|
||||
customerId: "cus_123",
|
||||
modelId: "anthropic/claude-opus-4-6",
|
||||
inputTokens: 800, // excludes the cached tokens below
|
||||
outputTokens: 350, // excludes the reasoning tokens below
|
||||
cacheReadTokens: 1000,
|
||||
cacheWriteTokens: 200,
|
||||
reasoningTokens: 150
|
||||
});
|
||||
```
|
||||
|
||||
```typescript OpenRouter (nested path)
|
||||
await autumn.balances.trackTokens({
|
||||
customerId: "cus_123",
|
||||
modelId: "openrouter/anthropic/claude-opus-4-6",
|
||||
inputTokens: 2000,
|
||||
outputTokens: 1000
|
||||
});
|
||||
```
|
||||
|
||||
```typescript With explicit feature
|
||||
await autumn.balances.trackTokens({
|
||||
customerId: "cus_123",
|
||||
featureId: "ai_credits",
|
||||
modelId: "anthropic/claude-haiku-4-5",
|
||||
inputTokens: 2000,
|
||||
outputTokens: 1000
|
||||
});
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
### Token Pools
|
||||
|
||||
Each token parameter is an exclusive pool — no token should be counted in more than one. `input_tokens` is non-cached text input only (cached tokens go in `cache_read_tokens` / `cache_write_tokens`), and `output_tokens` is text output only (reasoning tokens go in `reasoning_tokens`, audio in `audio_input_tokens` / `audio_output_tokens`). Each pool is billed at the model's published rate for that pool, falling back to the text input/output rate when the model has none.
|
||||
|
||||
<Warning>
|
||||
If you pass a provider's raw totals (e.g. OpenAI's `prompt_tokens` and `completion_tokens`), subtract the cache and reasoning counts first — otherwise those tokens are billed twice. The [`@useautumn/ai-sdk` wrapper](/documentation/external-providers/ai-sdk) does this normalization for you.
|
||||
</Warning>
|
||||
|
||||
### Markup Resolution
|
||||
|
||||
Markups are optional — the credit system's default markup applies unless overridden per provider or per model. With no markups set, the Models.dev base cost is charged as-is. A markup of `-100` makes the model free — the usage event is still recorded, but nothing is deducted. See [AI Credit Systems](/documentation/modelling-pricing/credit-systems#ai-credit-systems) for configuration.
|
||||
|
||||
The recorded event's `properties` include the full pricing breakdown: `cost`, `base_cost`, `markup`, `markup_source` (`model`, `provider`, or `default`), `tier_applied` (whether large-context tier pricing applied), and the per-pool `rates` used.
|
||||
|
||||
<Tip>
|
||||
`feature_id` is auto-detected when the customer has exactly one AI credit system. The request fails if the customer has none, or has more than one and `feature_id` is omitted.
|
||||
</Tip>
|
||||
206
apps/docs/mintlify/api-reference/balances/trackTokens.mdx
Normal file
@@ -0,0 +1,206 @@
|
||||
---
|
||||
title: "Track Token Usage"
|
||||
openapi: "openapi POST /v1/balances.track_tokens"
|
||||
---
|
||||
|
||||
import { DynamicParamField } from "/snippets/dynamic-param-field.jsx";
|
||||
import { DynamicResponseField } from "/snippets/dynamic-response-field.jsx";
|
||||
import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
|
||||
<Note>
|
||||
Track AI token usage against a customer's AI credit system balance. Converts token counts to a dollar cost using [Models.dev](https://models.dev) pricing and your configured markup, then deducts from the customer's credit balance.
|
||||
</Note>
|
||||
|
||||
The `model_id` must use `provider/model` format, matching the provider and model keys from [Models.dev](https://models.dev). For providers with nested model paths (like OpenRouter), include the full path: `openrouter/anthropic/claude-opus-4.6`. The first path segment is the provider key used for provider-level markup lookup.
|
||||
|
||||
### Common Use Cases
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```typescript Anthropic
|
||||
await autumn.balances.trackTokens({
|
||||
customerId: "cus_123",
|
||||
modelId: "anthropic/claude-opus-4-6",
|
||||
inputTokens: 1000,
|
||||
outputTokens: 500
|
||||
});
|
||||
```
|
||||
|
||||
```typescript With cache + reasoning
|
||||
await autumn.balances.trackTokens({
|
||||
customerId: "cus_123",
|
||||
modelId: "anthropic/claude-opus-4-6",
|
||||
inputTokens: 800, // excludes the cached tokens below
|
||||
outputTokens: 350, // excludes the reasoning tokens below
|
||||
cacheReadTokens: 1000,
|
||||
cacheWriteTokens: 200,
|
||||
reasoningTokens: 150
|
||||
});
|
||||
```
|
||||
|
||||
```typescript OpenRouter (nested path)
|
||||
await autumn.balances.trackTokens({
|
||||
customerId: "cus_123",
|
||||
modelId: "openrouter/anthropic/claude-opus-4.6",
|
||||
inputTokens: 2000,
|
||||
outputTokens: 1000
|
||||
});
|
||||
```
|
||||
|
||||
```typescript With explicit feature
|
||||
await autumn.balances.trackTokens({
|
||||
customerId: "cus_123",
|
||||
featureId: "ai_credits",
|
||||
modelId: "anthropic/claude-haiku-4-5",
|
||||
inputTokens: 2000,
|
||||
outputTokens: 1000
|
||||
});
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
### Token Pools
|
||||
|
||||
Each token parameter is an exclusive pool — no token should be counted in more than one. Each pool is billed at the model's published rate for that pool, falling back to the text input/output rate when the model has none.
|
||||
|
||||
<Warning>
|
||||
If you pass a provider's raw totals (e.g. OpenAI's `prompt_tokens` and `completion_tokens`), subtract the cache and reasoning counts first — otherwise those tokens are billed twice. The [`@useautumn/ai-sdk` wrapper](/documentation/external-providers/ai-sdk) does this normalization for you.
|
||||
</Warning>
|
||||
|
||||
### Markup Resolution
|
||||
|
||||
Markups are optional — the credit system's default markup applies unless overridden per provider or per model. With no markups set, the Models.dev base cost is charged as-is. A markup of `-100` makes the model free — the usage event is still recorded, but nothing is deducted. See [AI Credit Systems](/documentation/modelling-pricing/credit-systems#ai-credit-systems) for configuration.
|
||||
|
||||
<Tip>
|
||||
`feature_id` is auto-detected when the customer has exactly one AI credit system. The request fails if the customer has none, or has more than one and `feature_id` is omitted.
|
||||
</Tip>
|
||||
|
||||
### Body Parameters
|
||||
|
||||
<DynamicParamField body="customer_id" type="string" required>
|
||||
The ID of the customer.
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="model_id" type="string" required>
|
||||
The AI model in `provider/model` format, matching keys from [Models.dev](https://models.dev) (e.g., `anthropic/claude-opus-4-6`, `openai/gpt-4o`, `openrouter/anthropic/claude-opus-4.6`).
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="input_tokens" type="number" required>
|
||||
Number of non-cached text input tokens consumed. Exclusive of the cache and audio token pools.
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="output_tokens" type="number" required>
|
||||
Number of text output tokens consumed. Exclusive of the reasoning and audio output pools.
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="cache_read_tokens" type="number">
|
||||
Number of cached input tokens read, billed at the model's cache read rate.
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="cache_write_tokens" type="number">
|
||||
Number of input tokens written to the cache, billed at the model's cache write rate.
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="reasoning_tokens" type="number">
|
||||
Number of reasoning tokens generated, billed at the model's reasoning rate (falls back to the output rate).
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="audio_input_tokens" type="number">
|
||||
Number of audio input tokens consumed, billed at the model's audio input rate (falls back to the input rate).
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="audio_output_tokens" type="number">
|
||||
Number of audio output tokens generated, billed at the model's audio output rate (falls back to the output rate).
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="feature_id" type="string">
|
||||
The ID of the AI credit system feature. If omitted, automatically detects the customer's AI credit system feature. Required when the customer has more than one.
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="entity_id" type="string">
|
||||
The ID of the entity for entity-scoped balances.
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="properties" type="object">
|
||||
Additional properties to attach to this usage event. The token counts and a pricing breakdown (`cost`, `base_cost`, `markup`, `markup_source`, `tier_applied`, `rates`) are automatically included.
|
||||
</DynamicParamField>
|
||||
|
||||
### Response
|
||||
|
||||
<DynamicResponseField name="customer_id" type="string">
|
||||
The ID of the customer whose token usage was tracked.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="value" type="number">
|
||||
The dollar cost that was deducted from the customer's AI credit balance.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="balance" type="object | null">
|
||||
The updated balance for the AI credit system feature.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="feature_id" type="string">
|
||||
The feature ID this balance is for.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="granted" type="number">
|
||||
Total balance granted (included + prepaid).
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="remaining" type="number">
|
||||
Remaining balance available for use.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="usage" type="number">
|
||||
Total usage consumed in the current period.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="unlimited" type="boolean">
|
||||
Whether this feature has unlimited usage.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="overage_allowed" type="boolean">
|
||||
Whether usage beyond the granted balance is allowed.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="next_reset_at" type="number | null">
|
||||
Timestamp when the balance will reset, or null for no reset.
|
||||
</DynamicResponseField>
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
|
||||
<ResponseExample>
|
||||
```json 200
|
||||
{
|
||||
"customer_id": "cus_123",
|
||||
"value": 0.06,
|
||||
"balance": {
|
||||
"feature_id": "ai_credits",
|
||||
"granted": 10.00,
|
||||
"remaining": 9.94,
|
||||
"usage": 0.06,
|
||||
"unlimited": false,
|
||||
"overage_allowed": false,
|
||||
"next_reset_at": 1773851121437,
|
||||
"breakdown": [
|
||||
{
|
||||
"id": "cus_ent_abc123",
|
||||
"plan_id": "pro_plan",
|
||||
"included_grant": 10.00,
|
||||
"prepaid_grant": 0,
|
||||
"remaining": 9.94,
|
||||
"usage": 0.06,
|
||||
"unlimited": false,
|
||||
"reset": {
|
||||
"interval": "month",
|
||||
"resets_at": 1773851121437
|
||||
},
|
||||
"price": null,
|
||||
"expires_at": null
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
</ResponseExample>
|
||||
@@ -132,7 +132,7 @@ This is useful for attaching custom metadata to the Stripe subscription created
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="items" type="object[]">
|
||||
Override the items in the plan (PUT-style — replaces all existing items). Mutually exclusive with add_items / remove_items / update_items.
|
||||
Override the items in the plan (PUT-style — replaces all existing items). Mutually exclusive with add_items / remove_items / deprecated update_items.
|
||||
<Expandable title="properties">
|
||||
<DynamicParamField body="feature_id" type="string" required>
|
||||
The ID of the feature to configure.
|
||||
@@ -197,8 +197,8 @@ This is useful for attaching custom metadata to the Stripe subscription created
|
||||
'prepaid' for upfront payment (seats), 'usage_based' for pay-as-you-go.
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="max_purchase" type="number">
|
||||
Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total.
|
||||
<DynamicParamField body="max_purchase" type="number | null">
|
||||
Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total. Null for no limit.
|
||||
</DynamicParamField>
|
||||
|
||||
</Expandable>
|
||||
@@ -309,8 +309,8 @@ This is useful for attaching custom metadata to the Stripe subscription created
|
||||
'prepaid' for upfront payment (seats), 'usage_based' for pay-as-you-go.
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="max_purchase" type="number">
|
||||
Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total.
|
||||
<DynamicParamField body="max_purchase" type="number | null">
|
||||
Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total. Null for no limit.
|
||||
</DynamicParamField>
|
||||
|
||||
</Expandable>
|
||||
@@ -367,7 +367,11 @@ This is useful for attaching custom metadata to the Stripe subscription created
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="interval" type="'one_off' | 'week' | 'month' | 'quarter' | 'semi_annual' | 'year'">
|
||||
Match items with this interval.
|
||||
Match items with this interval. Accepts either a BillingInterval (price-side) or a ResetInterval (reset-side, includes day/hour/minute) so price-less items keyed by reset.interval can be disambiguated.
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="interval_count" type="integer">
|
||||
Match items with this interval_count. Disambiguates between items that share an interval but differ in count.
|
||||
</DynamicParamField>
|
||||
|
||||
</Expandable>
|
||||
|
||||
@@ -99,7 +99,7 @@ const response = await autumn.billing.update({
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="items" type="object[]">
|
||||
Override the items in the plan (PUT-style — replaces all existing items). Mutually exclusive with add_items / remove_items / update_items.
|
||||
Override the items in the plan (PUT-style — replaces all existing items). Mutually exclusive with add_items / remove_items / deprecated update_items.
|
||||
<Expandable title="properties">
|
||||
<DynamicParamField body="feature_id" type="string" required>
|
||||
The ID of the feature to configure.
|
||||
@@ -164,8 +164,8 @@ const response = await autumn.billing.update({
|
||||
'prepaid' for upfront payment (seats), 'usage_based' for pay-as-you-go.
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="max_purchase" type="number">
|
||||
Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total.
|
||||
<DynamicParamField body="max_purchase" type="number | null">
|
||||
Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total. Null for no limit.
|
||||
</DynamicParamField>
|
||||
|
||||
</Expandable>
|
||||
@@ -276,8 +276,8 @@ const response = await autumn.billing.update({
|
||||
'prepaid' for upfront payment (seats), 'usage_based' for pay-as-you-go.
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="max_purchase" type="number">
|
||||
Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total.
|
||||
<DynamicParamField body="max_purchase" type="number | null">
|
||||
Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total. Null for no limit.
|
||||
</DynamicParamField>
|
||||
|
||||
</Expandable>
|
||||
@@ -334,7 +334,11 @@ const response = await autumn.billing.update({
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="interval" type="'one_off' | 'week' | 'month' | 'quarter' | 'semi_annual' | 'year'">
|
||||
Match items with this interval.
|
||||
Match items with this interval. Accepts either a BillingInterval (price-side) or a ResetInterval (reset-side, includes day/hour/minute) so price-less items keyed by reset.interval can be disambiguated.
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="interval_count" type="integer">
|
||||
Match items with this interval_count. Disambiguates between items that share an interval but differ in count.
|
||||
</DynamicParamField>
|
||||
|
||||
</Expandable>
|
||||
|
||||
@@ -118,7 +118,7 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="customize" type="object">
|
||||
Customize the plan to schedule. Can override the price, items, or both.
|
||||
Customize the plan to schedule. Can override price, replace items, or patch items with add_items, remove_items, and update_items.
|
||||
<Expandable title="properties">
|
||||
<DynamicParamField body="price" type="object | null">
|
||||
Base price configuration for a plan.
|
||||
@@ -139,7 +139,7 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="items" type="object[]">
|
||||
Override the items in the plan.
|
||||
Override the items in the plan (PUT-style — replaces all existing items). Mutually exclusive with add_items / remove_items / deprecated update_items.
|
||||
<Expandable title="properties">
|
||||
<DynamicParamField body="feature_id" type="string" required>
|
||||
The ID of the feature to configure.
|
||||
@@ -204,8 +204,8 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
'prepaid' for upfront payment (seats), 'usage_based' for pay-as-you-go.
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="max_purchase" type="number">
|
||||
Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total.
|
||||
<DynamicParamField body="max_purchase" type="number | null">
|
||||
Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total. Null for no limit.
|
||||
</DynamicParamField>
|
||||
|
||||
</Expandable>
|
||||
@@ -250,6 +250,140 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
</Expandable>
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="add_items" type="object[]">
|
||||
Items to add to the plan.
|
||||
<Expandable title="properties">
|
||||
<DynamicParamField body="feature_id" type="string" required>
|
||||
The ID of the feature to configure.
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="included" type="number">
|
||||
Number of free units included. Balance resets to this each interval for consumable features.
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="unlimited" type="boolean">
|
||||
If true, customer has unlimited access to this feature.
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="reset" type="object">
|
||||
Reset configuration for consumable features. Omit for non-consumable features like seats.
|
||||
<Expandable title="properties">
|
||||
<DynamicParamField body="interval" type="'one_off' | 'minute' | 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'semi_annual' | 'year'" required>
|
||||
Interval at which balance resets (e.g. 'month', 'year'). For consumable features only.
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="interval_count" type="number">
|
||||
Number of intervals between resets. Defaults to 1.
|
||||
</DynamicParamField>
|
||||
|
||||
</Expandable>
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="price" type="object">
|
||||
Pricing for usage beyond included units. Omit for free features.
|
||||
<Expandable title="properties">
|
||||
<DynamicParamField body="amount" type="number">
|
||||
Price per billing_units after included usage. Either 'amount' or 'tiers' is required.
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="tiers" type="object[]">
|
||||
Tiered pricing. Either 'amount' or 'tiers' is required.
|
||||
<Expandable title="properties">
|
||||
<DynamicParamField body="to" type="any" />
|
||||
|
||||
<DynamicParamField body="amount" type="any" />
|
||||
|
||||
<DynamicParamField body="flat_amount" type="any" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="tier_behavior" type="'graduated' | 'volume'" />
|
||||
|
||||
<DynamicParamField body="interval" type="'one_off' | 'week' | 'month' | 'quarter' | 'semi_annual' | 'year'" required>
|
||||
Billing interval. For consumable features, should match reset.interval.
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="interval_count" type="number">
|
||||
Number of intervals per billing cycle. Defaults to 1.
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="billing_units" type="number">
|
||||
Units per price increment. Usage is rounded UP when billed (e.g. billing_units=100 means 101 rounds to 200).
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="billing_method" type="'prepaid' | 'usage_based'" required>
|
||||
'prepaid' for upfront payment (seats), 'usage_based' for pay-as-you-go.
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="max_purchase" type="number | null">
|
||||
Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total. Null for no limit.
|
||||
</DynamicParamField>
|
||||
|
||||
</Expandable>
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="proration" type="object">
|
||||
Proration settings for prepaid features. Controls mid-cycle quantity change billing.
|
||||
<Expandable title="properties">
|
||||
<DynamicParamField body="on_increase" type="'bill_immediately' | 'prorate_immediately' | 'prorate_next_cycle' | 'bill_next_cycle'" required>
|
||||
Billing behavior when quantity increases mid-cycle.
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="on_decrease" type="'prorate' | 'prorate_immediately' | 'prorate_next_cycle' | 'none' | 'no_prorations'" required>
|
||||
Credit behavior when quantity decreases mid-cycle.
|
||||
</DynamicParamField>
|
||||
|
||||
</Expandable>
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="rollover" type="object">
|
||||
Rollover config for unused units. If set, unused included units carry over.
|
||||
<Expandable title="properties">
|
||||
<DynamicParamField body="max" type="number">
|
||||
Max rollover units. Omit for unlimited rollover.
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="max_percentage" type="number">
|
||||
Maximum rollover as a percentage (0-100) of included + prepaid grant. Mutually exclusive with max.
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="expiry_duration_type" type="'month' | 'forever'" required>
|
||||
When rolled over units expire.
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="expiry_duration_length" type="number">
|
||||
Number of periods before expiry.
|
||||
</DynamicParamField>
|
||||
|
||||
</Expandable>
|
||||
</DynamicParamField>
|
||||
|
||||
</Expandable>
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="remove_items" type="object[]">
|
||||
Filters selecting items to remove from the plan.
|
||||
<Expandable title="properties">
|
||||
<DynamicParamField body="feature_id" type="string">
|
||||
Match items linked to this feature.
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="billing_method" type="'prepaid' | 'usage_based'">
|
||||
Match items with this billing method (prepaid or usage_based).
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="interval" type="'one_off' | 'week' | 'month' | 'quarter' | 'semi_annual' | 'year'">
|
||||
Match items with this interval. Accepts either a BillingInterval (price-side) or a ResetInterval (reset-side, includes day/hour/minute) so price-less items keyed by reset.interval can be disambiguated.
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="interval_count" type="integer">
|
||||
Match items with this interval_count. Disambiguates between items that share an interval but differ in count.
|
||||
</DynamicParamField>
|
||||
|
||||
</Expandable>
|
||||
</DynamicParamField>
|
||||
|
||||
</Expandable>
|
||||
</DynamicParamField>
|
||||
|
||||
|
||||
@@ -111,8 +111,8 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
'prepaid' for upfront payment (seats), 'usage_based' for pay-as-you-go.
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="max_purchase" type="number">
|
||||
Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total.
|
||||
<DynamicParamField body="max_purchase" type="number | null">
|
||||
Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total. Null for no limit.
|
||||
</DynamicParamField>
|
||||
|
||||
</Expandable>
|
||||
|
||||
@@ -65,7 +65,7 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="items" type="object[]">
|
||||
Override the items in the plan (PUT-style — replaces all existing items). Mutually exclusive with add_items / remove_items / update_items.
|
||||
Override the items in the plan (PUT-style — replaces all existing items). Mutually exclusive with add_items / remove_items / deprecated update_items.
|
||||
<Expandable title="properties">
|
||||
<DynamicParamField body="feature_id" type="string" required>
|
||||
The ID of the feature to configure.
|
||||
@@ -130,8 +130,8 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
'prepaid' for upfront payment (seats), 'usage_based' for pay-as-you-go.
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="max_purchase" type="number">
|
||||
Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total.
|
||||
<DynamicParamField body="max_purchase" type="number | null">
|
||||
Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total. Null for no limit.
|
||||
</DynamicParamField>
|
||||
|
||||
</Expandable>
|
||||
@@ -242,8 +242,8 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
'prepaid' for upfront payment (seats), 'usage_based' for pay-as-you-go.
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="max_purchase" type="number">
|
||||
Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total.
|
||||
<DynamicParamField body="max_purchase" type="number | null">
|
||||
Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total. Null for no limit.
|
||||
</DynamicParamField>
|
||||
|
||||
</Expandable>
|
||||
@@ -300,7 +300,11 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="interval" type="'one_off' | 'week' | 'month' | 'quarter' | 'semi_annual' | 'year'">
|
||||
Match items with this interval.
|
||||
Match items with this interval. Accepts either a BillingInterval (price-side) or a ResetInterval (reset-side, includes day/hour/minute) so price-less items keyed by reset.interval can be disambiguated.
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="interval_count" type="integer">
|
||||
Match items with this interval_count. Disambiguates between items that share an interval but differ in count.
|
||||
</DynamicParamField>
|
||||
|
||||
</Expandable>
|
||||
@@ -757,7 +761,7 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
The name of the feature.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="type" type="'static' | 'boolean' | 'single_use' | 'continuous_use' | 'credit_system'">
|
||||
<DynamicResponseField name="type" type="'static' | 'boolean' | 'single_use' | 'continuous_use' | 'credit_system' | 'ai_credit_system'">
|
||||
The type of the feature
|
||||
</DynamicResponseField>
|
||||
|
||||
@@ -825,8 +829,16 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
Price per billing_units after included usage is consumed. Mutually exclusive with tiers.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tiers" type="any[]">
|
||||
<DynamicResponseField name="tiers" type="object[]">
|
||||
Tiered pricing configuration. Each tier's 'to' INCLUDES the included amount. Either 'tiers' or 'amount' is required.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="to" type="number" />
|
||||
|
||||
<DynamicResponseField name="amount" type="number" />
|
||||
|
||||
<DynamicResponseField name="flat_amount" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tier_behavior" type="'graduated' | 'volume'" />
|
||||
@@ -1086,7 +1098,7 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
The name of the feature.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="type" type="'static' | 'boolean' | 'single_use' | 'continuous_use' | 'credit_system'">
|
||||
<DynamicResponseField name="type" type="'static' | 'boolean' | 'single_use' | 'continuous_use' | 'credit_system' | 'ai_credit_system'">
|
||||
The type of the feature
|
||||
</DynamicResponseField>
|
||||
|
||||
@@ -1154,8 +1166,16 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
Price per billing_units after included usage is consumed. Mutually exclusive with tiers.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tiers" type="any[]">
|
||||
<DynamicResponseField name="tiers" type="object[]">
|
||||
Tiered pricing configuration. Each tier's 'to' INCLUDES the included amount. Either 'tiers' or 'amount' is required.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="to" type="number" />
|
||||
|
||||
<DynamicResponseField name="amount" type="number" />
|
||||
|
||||
<DynamicResponseField name="flat_amount" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tier_behavior" type="'graduated' | 'volume'" />
|
||||
|
||||
@@ -111,8 +111,8 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
'prepaid' for upfront payment (seats), 'usage_based' for pay-as-you-go.
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="max_purchase" type="number">
|
||||
Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total.
|
||||
<DynamicParamField body="max_purchase" type="number | null">
|
||||
Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total. Null for no limit.
|
||||
</DynamicParamField>
|
||||
|
||||
</Expandable>
|
||||
@@ -788,7 +788,7 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
The name of the feature.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="type" type="'static' | 'boolean' | 'single_use' | 'continuous_use' | 'credit_system'">
|
||||
<DynamicResponseField name="type" type="'static' | 'boolean' | 'single_use' | 'continuous_use' | 'credit_system' | 'ai_credit_system'">
|
||||
The type of the feature
|
||||
</DynamicResponseField>
|
||||
|
||||
@@ -856,8 +856,16 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
Price per billing_units after included usage is consumed. Mutually exclusive with tiers.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tiers" type="any[]">
|
||||
<DynamicResponseField name="tiers" type="object[]">
|
||||
Tiered pricing configuration. Each tier's 'to' INCLUDES the included amount. Either 'tiers' or 'amount' is required.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="to" type="number" />
|
||||
|
||||
<DynamicResponseField name="amount" type="number" />
|
||||
|
||||
<DynamicResponseField name="flat_amount" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tier_behavior" type="'graduated' | 'volume'" />
|
||||
@@ -1117,7 +1125,7 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
The name of the feature.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="type" type="'static' | 'boolean' | 'single_use' | 'continuous_use' | 'credit_system'">
|
||||
<DynamicResponseField name="type" type="'static' | 'boolean' | 'single_use' | 'continuous_use' | 'credit_system' | 'ai_credit_system'">
|
||||
The type of the feature
|
||||
</DynamicResponseField>
|
||||
|
||||
@@ -1185,8 +1193,16 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
Price per billing_units after included usage is consumed. Mutually exclusive with tiers.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tiers" type="any[]">
|
||||
<DynamicResponseField name="tiers" type="object[]">
|
||||
Tiered pricing configuration. Each tier's 'to' INCLUDES the included amount. Either 'tiers' or 'amount' is required.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="to" type="number" />
|
||||
|
||||
<DynamicResponseField name="amount" type="number" />
|
||||
|
||||
<DynamicResponseField name="flat_amount" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tier_behavior" type="'graduated' | 'volume'" />
|
||||
|
||||
@@ -65,7 +65,7 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="items" type="object[]">
|
||||
Override the items in the plan (PUT-style — replaces all existing items). Mutually exclusive with add_items / remove_items / update_items.
|
||||
Override the items in the plan (PUT-style — replaces all existing items). Mutually exclusive with add_items / remove_items / deprecated update_items.
|
||||
<Expandable title="properties">
|
||||
<DynamicParamField body="feature_id" type="string" required>
|
||||
The ID of the feature to configure.
|
||||
@@ -130,8 +130,8 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
'prepaid' for upfront payment (seats), 'usage_based' for pay-as-you-go.
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="max_purchase" type="number">
|
||||
Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total.
|
||||
<DynamicParamField body="max_purchase" type="number | null">
|
||||
Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total. Null for no limit.
|
||||
</DynamicParamField>
|
||||
|
||||
</Expandable>
|
||||
@@ -242,8 +242,8 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
'prepaid' for upfront payment (seats), 'usage_based' for pay-as-you-go.
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="max_purchase" type="number">
|
||||
Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total.
|
||||
<DynamicParamField body="max_purchase" type="number | null">
|
||||
Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total. Null for no limit.
|
||||
</DynamicParamField>
|
||||
|
||||
</Expandable>
|
||||
@@ -300,7 +300,11 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="interval" type="'one_off' | 'week' | 'month' | 'quarter' | 'semi_annual' | 'year'">
|
||||
Match items with this interval.
|
||||
Match items with this interval. Accepts either a BillingInterval (price-side) or a ResetInterval (reset-side, includes day/hour/minute) so price-less items keyed by reset.interval can be disambiguated.
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="interval_count" type="integer">
|
||||
Match items with this interval_count. Disambiguates between items that share an interval but differ in count.
|
||||
</DynamicParamField>
|
||||
|
||||
</Expandable>
|
||||
@@ -690,7 +694,7 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
The name of the feature.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="type" type="'static' | 'boolean' | 'single_use' | 'continuous_use' | 'credit_system'">
|
||||
<DynamicResponseField name="type" type="'static' | 'boolean' | 'single_use' | 'continuous_use' | 'credit_system' | 'ai_credit_system'">
|
||||
The type of the feature
|
||||
</DynamicResponseField>
|
||||
|
||||
@@ -758,8 +762,16 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
Price per billing_units after included usage is consumed. Mutually exclusive with tiers.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tiers" type="any[]">
|
||||
<DynamicResponseField name="tiers" type="object[]">
|
||||
Tiered pricing configuration. Each tier's 'to' INCLUDES the included amount. Either 'tiers' or 'amount' is required.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="to" type="number" />
|
||||
|
||||
<DynamicResponseField name="amount" type="number" />
|
||||
|
||||
<DynamicResponseField name="flat_amount" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tier_behavior" type="'graduated' | 'volume'" />
|
||||
@@ -1019,7 +1031,7 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
The name of the feature.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="type" type="'static' | 'boolean' | 'single_use' | 'continuous_use' | 'credit_system'">
|
||||
<DynamicResponseField name="type" type="'static' | 'boolean' | 'single_use' | 'continuous_use' | 'credit_system' | 'ai_credit_system'">
|
||||
The type of the feature
|
||||
</DynamicResponseField>
|
||||
|
||||
@@ -1087,8 +1099,16 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
Price per billing_units after included usage is consumed. Mutually exclusive with tiers.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tiers" type="any[]">
|
||||
<DynamicResponseField name="tiers" type="object[]">
|
||||
Tiered pricing configuration. Each tier's 'to' INCLUDES the included amount. Either 'tiers' or 'amount' is required.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="to" type="number" />
|
||||
|
||||
<DynamicResponseField name="amount" type="number" />
|
||||
|
||||
<DynamicResponseField name="flat_amount" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tier_behavior" type="'graduated' | 'volume'" />
|
||||
|
||||
@@ -65,7 +65,7 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="items" type="object[]">
|
||||
Override the items in the plan (PUT-style — replaces all existing items). Mutually exclusive with add_items / remove_items / update_items.
|
||||
Override the items in the plan (PUT-style — replaces all existing items). Mutually exclusive with add_items / remove_items / deprecated update_items.
|
||||
<Expandable title="properties">
|
||||
<DynamicParamField body="feature_id" type="string" required>
|
||||
The ID of the feature to configure.
|
||||
@@ -130,8 +130,8 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
'prepaid' for upfront payment (seats), 'usage_based' for pay-as-you-go.
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="max_purchase" type="number">
|
||||
Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total.
|
||||
<DynamicParamField body="max_purchase" type="number | null">
|
||||
Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total. Null for no limit.
|
||||
</DynamicParamField>
|
||||
|
||||
</Expandable>
|
||||
@@ -242,8 +242,8 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
'prepaid' for upfront payment (seats), 'usage_based' for pay-as-you-go.
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="max_purchase" type="number">
|
||||
Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total.
|
||||
<DynamicParamField body="max_purchase" type="number | null">
|
||||
Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total. Null for no limit.
|
||||
</DynamicParamField>
|
||||
|
||||
</Expandable>
|
||||
@@ -300,7 +300,11 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="interval" type="'one_off' | 'week' | 'month' | 'quarter' | 'semi_annual' | 'year'">
|
||||
Match items with this interval.
|
||||
Match items with this interval. Accepts either a BillingInterval (price-side) or a ResetInterval (reset-side, includes day/hour/minute) so price-less items keyed by reset.interval can be disambiguated.
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="interval_count" type="integer">
|
||||
Match items with this interval_count. Disambiguates between items that share an interval but differ in count.
|
||||
</DynamicParamField>
|
||||
|
||||
</Expandable>
|
||||
|
||||
@@ -124,8 +124,8 @@ const { allowed } = await autumn.check({
|
||||
Human-readable name displayed in the dashboard and billing UI.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="type" type="'boolean' | 'metered' | 'credit_system'">
|
||||
Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools.
|
||||
<DynamicResponseField name="type" type="'boolean' | 'metered' | 'credit_system' | 'ai_credit_system'">
|
||||
Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="consumable" type="boolean">
|
||||
@@ -150,6 +150,30 @@ const { allowed } = await autumn.check({
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="model_markups.{key}" type="object | null">
|
||||
Per-model markup overrides for AI credit systems.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="markup" type="number" />
|
||||
|
||||
<DynamicResponseField name="input_cost" type="number" />
|
||||
|
||||
<DynamicResponseField name="output_cost" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="default_markup" type="number">
|
||||
Default percentage markup for AI credit systems. Use -100 to make usage free.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="provider_markups.{key}" type="object | null">
|
||||
Per-provider default markup percentages for AI credit systems.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="markup" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="display" type="object">
|
||||
Display names for the feature in billing UI and customer-facing components.
|
||||
<Expandable title="properties">
|
||||
@@ -255,8 +279,16 @@ const { allowed } = await autumn.check({
|
||||
The per-unit price amount.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tiers" type="any[]">
|
||||
<DynamicResponseField name="tiers" type="object[]">
|
||||
Tiered pricing configuration if applicable.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="to" type="number" />
|
||||
|
||||
<DynamicResponseField name="amount" type="number" />
|
||||
|
||||
<DynamicResponseField name="flat_amount" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tier_behavior" type="'graduated' | 'volume'">
|
||||
@@ -320,8 +352,8 @@ const { allowed } = await autumn.check({
|
||||
Human-readable name displayed in the dashboard and billing UI.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="type" type="'boolean' | 'metered' | 'credit_system'">
|
||||
Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools.
|
||||
<DynamicResponseField name="type" type="'boolean' | 'metered' | 'credit_system' | 'ai_credit_system'">
|
||||
Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="consumable" type="boolean">
|
||||
@@ -346,6 +378,30 @@ const { allowed } = await autumn.check({
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="model_markups.{key}" type="object | null">
|
||||
Per-model markup overrides for AI credit systems.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="markup" type="number" />
|
||||
|
||||
<DynamicResponseField name="input_cost" type="number" />
|
||||
|
||||
<DynamicResponseField name="output_cost" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="default_markup" type="number">
|
||||
Default percentage markup for AI credit systems. Use -100 to make usage free.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="provider_markups.{key}" type="object | null">
|
||||
Per-provider default markup percentages for AI credit systems.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="markup" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="display" type="object">
|
||||
Display names for the feature in billing UI and customer-facing components.
|
||||
<Expandable title="properties">
|
||||
@@ -451,8 +507,16 @@ const { allowed } = await autumn.check({
|
||||
The per-unit price amount.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tiers" type="any[]">
|
||||
<DynamicResponseField name="tiers" type="object[]">
|
||||
Tiered pricing configuration if applicable.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="to" type="number" />
|
||||
|
||||
<DynamicResponseField name="amount" type="number" />
|
||||
|
||||
<DynamicResponseField name="flat_amount" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tier_behavior" type="'graduated' | 'volume'">
|
||||
@@ -528,8 +592,8 @@ const { allowed } = await autumn.check({
|
||||
Human-readable name displayed in the dashboard and billing UI.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="type" type="'boolean' | 'metered' | 'credit_system'">
|
||||
Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools.
|
||||
<DynamicResponseField name="type" type="'boolean' | 'metered' | 'credit_system' | 'ai_credit_system'">
|
||||
Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="consumable" type="boolean">
|
||||
@@ -554,6 +618,30 @@ const { allowed } = await autumn.check({
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="model_markups.{key}" type="object | null">
|
||||
Per-model markup overrides for AI credit systems.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="markup" type="number" />
|
||||
|
||||
<DynamicResponseField name="input_cost" type="number" />
|
||||
|
||||
<DynamicResponseField name="output_cost" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="default_markup" type="number">
|
||||
Default percentage markup for AI credit systems. Use -100 to make usage free.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="provider_markups.{key}" type="object | null">
|
||||
Per-provider default markup percentages for AI credit systems.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="markup" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="display" type="object">
|
||||
Display names for the feature in billing UI and customer-facing components.
|
||||
<Expandable title="properties">
|
||||
|
||||
@@ -135,8 +135,8 @@ await autumn.track({
|
||||
Human-readable name displayed in the dashboard and billing UI.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="type" type="'boolean' | 'metered' | 'credit_system'">
|
||||
Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools.
|
||||
<DynamicResponseField name="type" type="'boolean' | 'metered' | 'credit_system' | 'ai_credit_system'">
|
||||
Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="consumable" type="boolean">
|
||||
@@ -161,6 +161,30 @@ await autumn.track({
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="model_markups.{key}" type="object | null">
|
||||
Per-model markup overrides for AI credit systems.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="markup" type="number" />
|
||||
|
||||
<DynamicResponseField name="input_cost" type="number" />
|
||||
|
||||
<DynamicResponseField name="output_cost" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="default_markup" type="number">
|
||||
Default percentage markup for AI credit systems. Use -100 to make usage free.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="provider_markups.{key}" type="object | null">
|
||||
Per-provider default markup percentages for AI credit systems.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="markup" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="display" type="object">
|
||||
Display names for the feature in billing UI and customer-facing components.
|
||||
<Expandable title="properties">
|
||||
@@ -266,8 +290,16 @@ await autumn.track({
|
||||
The per-unit price amount.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tiers" type="any[]">
|
||||
<DynamicResponseField name="tiers" type="object[]">
|
||||
Tiered pricing configuration if applicable.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="to" type="number" />
|
||||
|
||||
<DynamicResponseField name="amount" type="number" />
|
||||
|
||||
<DynamicResponseField name="flat_amount" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tier_behavior" type="'graduated' | 'volume'">
|
||||
@@ -331,8 +363,8 @@ await autumn.track({
|
||||
Human-readable name displayed in the dashboard and billing UI.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="type" type="'boolean' | 'metered' | 'credit_system'">
|
||||
Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools.
|
||||
<DynamicResponseField name="type" type="'boolean' | 'metered' | 'credit_system' | 'ai_credit_system'">
|
||||
Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="consumable" type="boolean">
|
||||
@@ -357,6 +389,30 @@ await autumn.track({
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="model_markups.{key}" type="object | null">
|
||||
Per-model markup overrides for AI credit systems.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="markup" type="number" />
|
||||
|
||||
<DynamicResponseField name="input_cost" type="number" />
|
||||
|
||||
<DynamicResponseField name="output_cost" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="default_markup" type="number">
|
||||
Default percentage markup for AI credit systems. Use -100 to make usage free.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="provider_markups.{key}" type="object | null">
|
||||
Per-provider default markup percentages for AI credit systems.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="markup" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="display" type="object">
|
||||
Display names for the feature in billing UI and customer-facing components.
|
||||
<Expandable title="properties">
|
||||
@@ -462,8 +518,16 @@ await autumn.track({
|
||||
The per-unit price amount.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tiers" type="any[]">
|
||||
<DynamicResponseField name="tiers" type="object[]">
|
||||
Tiered pricing configuration if applicable.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="to" type="number" />
|
||||
|
||||
<DynamicResponseField name="amount" type="number" />
|
||||
|
||||
<DynamicResponseField name="flat_amount" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tier_behavior" type="'graduated' | 'volume'">
|
||||
|
||||
622
apps/docs/mintlify/api-reference/core/trackTokens.mdx
Normal file
@@ -0,0 +1,622 @@
|
||||
---
|
||||
title: "Track Tokens"
|
||||
openapi: "openapi POST /v1/balances.track_tokens"
|
||||
---
|
||||
|
||||
import { DynamicParamField } from "/snippets/dynamic-param-field.jsx";
|
||||
import { DynamicResponseField } from "/snippets/dynamic-response-field.jsx";
|
||||
import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
|
||||
### Body Parameters
|
||||
|
||||
<DynamicParamField body="customer_id" type="string" required>
|
||||
The ID of the customer.
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="entity_id" type="string">
|
||||
The ID of the entity for entity-scoped balances.
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="feature_id" type="string">
|
||||
The ID of the AI credit system feature. Auto-detected from the customer's entitlements if omitted — only required when a customer has multiple AI credit systems.
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="model_id" type="string" required>
|
||||
The AI model as '<provider>/<model>' (e.g. 'anthropic/claude-opus-4-8', 'openrouter/openai/gpt-4o'). The provider is the first path segment and must match a provider + model key in models.dev.
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="input_tokens" type="integer" required>
|
||||
Number of non-cached text input tokens consumed. Exclusive of cache and audio token pools.
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="output_tokens" type="integer" required>
|
||||
Number of text output tokens consumed. Exclusive of the reasoning and audio output pools.
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="cache_read_tokens" type="integer">
|
||||
Number of cached input tokens read.
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="cache_write_tokens" type="integer">
|
||||
Number of input tokens written to the cache.
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="audio_input_tokens" type="integer">
|
||||
Number of audio input tokens consumed.
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="audio_output_tokens" type="integer">
|
||||
Number of audio output tokens generated.
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="reasoning_tokens" type="integer">
|
||||
Number of reasoning tokens generated.
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="properties" type="object">
|
||||
Additional properties to attach to this usage event.
|
||||
</DynamicParamField>
|
||||
|
||||
|
||||
### Response
|
||||
|
||||
<DynamicResponseField name="customer_id" type="string">
|
||||
The ID of the customer whose usage was tracked.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="entity_id" type="string">
|
||||
The ID of the entity, if entity-scoped tracking was performed.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="event_name" type="string">
|
||||
The event name that was tracked, if event_name was used instead of feature_id.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="value" type="number">
|
||||
The amount of usage that was recorded.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="balance" type="object | null">
|
||||
The updated balance for the tracked feature. Null if tracking by event_name that affects multiple features.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="feature_id" type="string">
|
||||
The feature ID this balance is for.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="feature" type="object">
|
||||
The full feature object if expanded.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="id" type="string">
|
||||
The unique identifier for this feature, used in /check and /track calls.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="name" type="string">
|
||||
Human-readable name displayed in the dashboard and billing UI.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="type" type="'boolean' | 'metered' | 'credit_system' | 'ai_credit_system'">
|
||||
Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="consumable" type="boolean">
|
||||
For metered features: true if usage resets periodically (API calls, credits), false if allocated persistently (seats, storage).
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="event_names" type="string[]">
|
||||
Event names that trigger this feature's balance. Allows multiple features to respond to a single event.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="credit_schema" type="object[]">
|
||||
For credit_system features: maps metered features to their credit costs.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="metered_feature_id" type="string">
|
||||
ID of the metered feature that draws from this credit system.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="credit_cost" type="number">
|
||||
Credits consumed per unit of the metered feature.
|
||||
</DynamicResponseField>
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="model_markups.{key}" type="object | null">
|
||||
Per-model markup overrides for AI credit systems.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="markup" type="number" />
|
||||
|
||||
<DynamicResponseField name="input_cost" type="number" />
|
||||
|
||||
<DynamicResponseField name="output_cost" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="default_markup" type="number">
|
||||
Default percentage markup for AI credit systems. Use -100 to make usage free.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="provider_markups.{key}" type="object | null">
|
||||
Per-provider default markup percentages for AI credit systems.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="markup" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="display" type="object">
|
||||
Display names for the feature in billing UI and customer-facing components.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="singular" type="string | null">
|
||||
Singular form for UI display (e.g., 'API call', 'seat').
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="plural" type="string | null">
|
||||
Plural form for UI display (e.g., 'API calls', 'seats').
|
||||
</DynamicResponseField>
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="archived" type="boolean">
|
||||
Whether the feature is archived and hidden from the dashboard.
|
||||
</DynamicResponseField>
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="granted" type="number">
|
||||
Total balance granted (included + prepaid).
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="remaining" type="number">
|
||||
Remaining balance available for use.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="usage" type="number">
|
||||
Total usage consumed in the current period.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="unlimited" type="boolean">
|
||||
Whether this feature has unlimited usage.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="overage_allowed" type="boolean">
|
||||
Whether usage beyond the granted balance is allowed (with overage charges).
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="max_purchase" type="number | null">
|
||||
Maximum quantity that can be purchased as a top-up, or null for unlimited.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="next_reset_at" type="number | null">
|
||||
Timestamp when the balance will reset, or null for no reset.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="breakdown" type="object[]">
|
||||
Detailed breakdown of balance sources when stacking multiple plans or grants.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="id" type="string">
|
||||
The unique identifier for this balance breakdown.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="plan_id" type="string | null">
|
||||
The plan ID this balance originates from, or null for standalone balances.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="included_grant" type="number">
|
||||
Amount granted from the plan's included usage.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="prepaid_grant" type="number">
|
||||
Amount granted from prepaid purchases or top-ups.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="remaining" type="number">
|
||||
Remaining balance available for use.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="usage" type="number">
|
||||
Amount consumed in the current period.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="unlimited" type="boolean">
|
||||
Whether this balance has unlimited usage.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="reset" type="object | null">
|
||||
Reset configuration for this balance, or null if no reset.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="interval" type="'one_off' | 'minute' | 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'semi_annual' | 'year'">
|
||||
The reset interval (hour, day, week, month, etc.) or 'multiple' if combined from different intervals.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="interval_count" type="number">
|
||||
Number of intervals between resets (eg. 2 for bi-monthly).
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="resets_at" type="number | null">
|
||||
Timestamp when the balance will next reset.
|
||||
</DynamicResponseField>
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="price" type="object | null">
|
||||
Pricing configuration if this balance has usage-based pricing.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="amount" type="number">
|
||||
The per-unit price amount.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tiers" type="object[]">
|
||||
Tiered pricing configuration if applicable.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="to" type="number" />
|
||||
|
||||
<DynamicResponseField name="amount" type="number" />
|
||||
|
||||
<DynamicResponseField name="flat_amount" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tier_behavior" type="'graduated' | 'volume'">
|
||||
How tiers are applied: graduated (split across bands) or volume (flat rate for the matched tier).
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="billing_units" type="number">
|
||||
The number of units per billing increment (eg. $9 / 250 units).
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="billing_method" type="'prepaid' | 'usage_based'">
|
||||
Whether usage is prepaid or billed pay-per-use.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="max_purchase" type="number | null">
|
||||
Maximum quantity that can be purchased, or null for unlimited.
|
||||
</DynamicResponseField>
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="expires_at" type="number | null">
|
||||
Timestamp when this balance expires, or null for no expiration.
|
||||
</DynamicResponseField>
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="rollovers" type="object[]">
|
||||
Rollover balances carried over from previous periods.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="balance" type="number">
|
||||
Amount of balance rolled over from a previous period.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="expires_at" type="number">
|
||||
Timestamp when the rollover balance expires.
|
||||
</DynamicResponseField>
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="balances.{key}" type="object | null">
|
||||
Map of feature_id to updated balance for the tracked feature and any related features (e.g. linked credit systems). Value is null when the customer has no balance for that feature.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="feature_id" type="string">
|
||||
The feature ID this balance is for.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="feature" type="object">
|
||||
The full feature object if expanded.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="id" type="string">
|
||||
The unique identifier for this feature, used in /check and /track calls.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="name" type="string">
|
||||
Human-readable name displayed in the dashboard and billing UI.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="type" type="'boolean' | 'metered' | 'credit_system' | 'ai_credit_system'">
|
||||
Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="consumable" type="boolean">
|
||||
For metered features: true if usage resets periodically (API calls, credits), false if allocated persistently (seats, storage).
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="event_names" type="string[]">
|
||||
Event names that trigger this feature's balance. Allows multiple features to respond to a single event.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="credit_schema" type="object[]">
|
||||
For credit_system features: maps metered features to their credit costs.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="metered_feature_id" type="string">
|
||||
ID of the metered feature that draws from this credit system.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="credit_cost" type="number">
|
||||
Credits consumed per unit of the metered feature.
|
||||
</DynamicResponseField>
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="model_markups.{key}" type="object | null">
|
||||
Per-model markup overrides for AI credit systems.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="markup" type="number" />
|
||||
|
||||
<DynamicResponseField name="input_cost" type="number" />
|
||||
|
||||
<DynamicResponseField name="output_cost" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="default_markup" type="number">
|
||||
Default percentage markup for AI credit systems. Use -100 to make usage free.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="provider_markups.{key}" type="object | null">
|
||||
Per-provider default markup percentages for AI credit systems.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="markup" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="display" type="object">
|
||||
Display names for the feature in billing UI and customer-facing components.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="singular" type="string | null">
|
||||
Singular form for UI display (e.g., 'API call', 'seat').
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="plural" type="string | null">
|
||||
Plural form for UI display (e.g., 'API calls', 'seats').
|
||||
</DynamicResponseField>
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="archived" type="boolean">
|
||||
Whether the feature is archived and hidden from the dashboard.
|
||||
</DynamicResponseField>
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="granted" type="number">
|
||||
Total balance granted (included + prepaid).
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="remaining" type="number">
|
||||
Remaining balance available for use.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="usage" type="number">
|
||||
Total usage consumed in the current period.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="unlimited" type="boolean">
|
||||
Whether this feature has unlimited usage.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="overage_allowed" type="boolean">
|
||||
Whether usage beyond the granted balance is allowed (with overage charges).
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="max_purchase" type="number | null">
|
||||
Maximum quantity that can be purchased as a top-up, or null for unlimited.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="next_reset_at" type="number | null">
|
||||
Timestamp when the balance will reset, or null for no reset.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="breakdown" type="object[]">
|
||||
Detailed breakdown of balance sources when stacking multiple plans or grants.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="id" type="string">
|
||||
The unique identifier for this balance breakdown.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="plan_id" type="string | null">
|
||||
The plan ID this balance originates from, or null for standalone balances.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="included_grant" type="number">
|
||||
Amount granted from the plan's included usage.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="prepaid_grant" type="number">
|
||||
Amount granted from prepaid purchases or top-ups.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="remaining" type="number">
|
||||
Remaining balance available for use.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="usage" type="number">
|
||||
Amount consumed in the current period.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="unlimited" type="boolean">
|
||||
Whether this balance has unlimited usage.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="reset" type="object | null">
|
||||
Reset configuration for this balance, or null if no reset.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="interval" type="'one_off' | 'minute' | 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'semi_annual' | 'year'">
|
||||
The reset interval (hour, day, week, month, etc.) or 'multiple' if combined from different intervals.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="interval_count" type="number">
|
||||
Number of intervals between resets (eg. 2 for bi-monthly).
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="resets_at" type="number | null">
|
||||
Timestamp when the balance will next reset.
|
||||
</DynamicResponseField>
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="price" type="object | null">
|
||||
Pricing configuration if this balance has usage-based pricing.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="amount" type="number">
|
||||
The per-unit price amount.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tiers" type="object[]">
|
||||
Tiered pricing configuration if applicable.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="to" type="number" />
|
||||
|
||||
<DynamicResponseField name="amount" type="number" />
|
||||
|
||||
<DynamicResponseField name="flat_amount" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tier_behavior" type="'graduated' | 'volume'">
|
||||
How tiers are applied: graduated (split across bands) or volume (flat rate for the matched tier).
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="billing_units" type="number">
|
||||
The number of units per billing increment (eg. $9 / 250 units).
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="billing_method" type="'prepaid' | 'usage_based'">
|
||||
Whether usage is prepaid or billed pay-per-use.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="max_purchase" type="number | null">
|
||||
Maximum quantity that can be purchased, or null for unlimited.
|
||||
</DynamicResponseField>
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="expires_at" type="number | null">
|
||||
Timestamp when this balance expires, or null for no expiration.
|
||||
</DynamicResponseField>
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="rollovers" type="object[]">
|
||||
Rollover balances carried over from previous periods.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="balance" type="number">
|
||||
Amount of balance rolled over from a previous period.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="expires_at" type="number">
|
||||
Timestamp when the rollover balance expires.
|
||||
</DynamicResponseField>
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="deductions" type="object[]">
|
||||
Per-balance breakdown of what this event deducted. A single event can consume from multiple balance rows when credit systems or rollovers are involved; this surfaces each one so callers can build per-feature usage views without polling.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="balance_id" type="string">
|
||||
ID of the underlying balance row that was deducted from (customer_entitlement or rollover).
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="feature_id" type="string">
|
||||
The feature this balance belongs to.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="plan_id" type="string | null">
|
||||
ID of the plan/product this balance belongs to. Null when the balance can't be attributed to a single plan (e.g. it spans multiple).
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="reset" type="object | null">
|
||||
Reset configuration for the balance this deduction came from, or null if the balance doesn't reset.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="interval" type="'one_off' | 'minute' | 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'semi_annual' | 'year'">
|
||||
The reset interval (hour, day, week, month, etc.) or 'multiple' if combined from different intervals.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="interval_count" type="number">
|
||||
Number of intervals between resets (eg. 2 for bi-monthly).
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="resets_at" type="number | null">
|
||||
Timestamp when the balance will next reset.
|
||||
</DynamicResponseField>
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="value" type="number">
|
||||
Amount deducted from this balance. Positive when usage was consumed, negative when credit was restored (e.g. a refund via negative track value).
|
||||
</DynamicResponseField>
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
|
||||
<ResponseExample>
|
||||
```json 200
|
||||
{
|
||||
"customer_id": "cus_123",
|
||||
"value": 0.006,
|
||||
"balance": {
|
||||
"feature_id": "ai_credits",
|
||||
"granted": 10,
|
||||
"remaining": 9.994,
|
||||
"usage": 0.006,
|
||||
"unlimited": false,
|
||||
"overage_allowed": false,
|
||||
"max_purchase": null,
|
||||
"next_reset_at": 1773851121437,
|
||||
"breakdown": [
|
||||
{
|
||||
"id": "cus_ent_39qmLooixXLAqMywgXywjAz96rV",
|
||||
"plan_id": "pro_plan",
|
||||
"included_grant": 100,
|
||||
"prepaid_grant": 0,
|
||||
"remaining": 72,
|
||||
"usage": 28,
|
||||
"unlimited": false,
|
||||
"reset": {
|
||||
"interval": "month",
|
||||
"resets_at": 1773851121437
|
||||
},
|
||||
"price": null,
|
||||
"expires_at": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"deductions": [
|
||||
{
|
||||
"balance_id": "cus_ent_3DdSDoyFmoA9Neecl2a2Gc507X2",
|
||||
"feature_id": "ai_credits",
|
||||
"plan_id": "pro",
|
||||
"reset": {
|
||||
"interval": "month",
|
||||
"resets_at": 1781288736881
|
||||
},
|
||||
"value": 0.006
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
</ResponseExample>
|
||||
@@ -252,7 +252,7 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
The name of the feature.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="type" type="'static' | 'boolean' | 'single_use' | 'continuous_use' | 'credit_system'">
|
||||
<DynamicResponseField name="type" type="'static' | 'boolean' | 'single_use' | 'continuous_use' | 'credit_system' | 'ai_credit_system'">
|
||||
The type of the feature
|
||||
</DynamicResponseField>
|
||||
|
||||
@@ -320,8 +320,16 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
Price per billing_units after included usage is consumed. Mutually exclusive with tiers.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tiers" type="any[]">
|
||||
<DynamicResponseField name="tiers" type="object[]">
|
||||
Tiered pricing configuration. Each tier's 'to' INCLUDES the included amount. Either 'tiers' or 'amount' is required.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="to" type="number" />
|
||||
|
||||
<DynamicResponseField name="amount" type="number" />
|
||||
|
||||
<DynamicResponseField name="flat_amount" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tier_behavior" type="'graduated' | 'volume'" />
|
||||
@@ -603,7 +611,7 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
The name of the feature.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="type" type="'static' | 'boolean' | 'single_use' | 'continuous_use' | 'credit_system'">
|
||||
<DynamicResponseField name="type" type="'static' | 'boolean' | 'single_use' | 'continuous_use' | 'credit_system' | 'ai_credit_system'">
|
||||
The type of the feature
|
||||
</DynamicResponseField>
|
||||
|
||||
@@ -671,8 +679,16 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
Price per billing_units after included usage is consumed. Mutually exclusive with tiers.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tiers" type="any[]">
|
||||
<DynamicResponseField name="tiers" type="object[]">
|
||||
Tiered pricing configuration. Each tier's 'to' INCLUDES the included amount. Either 'tiers' or 'amount' is required.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="to" type="number" />
|
||||
|
||||
<DynamicResponseField name="amount" type="number" />
|
||||
|
||||
<DynamicResponseField name="flat_amount" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tier_behavior" type="'graduated' | 'volume'" />
|
||||
@@ -856,8 +872,8 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
Human-readable name displayed in the dashboard and billing UI.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="type" type="'boolean' | 'metered' | 'credit_system'">
|
||||
Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools.
|
||||
<DynamicResponseField name="type" type="'boolean' | 'metered' | 'credit_system' | 'ai_credit_system'">
|
||||
Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="consumable" type="boolean">
|
||||
@@ -882,6 +898,30 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="model_markups.{key}" type="object | null">
|
||||
Per-model markup overrides for AI credit systems.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="markup" type="number" />
|
||||
|
||||
<DynamicResponseField name="input_cost" type="number" />
|
||||
|
||||
<DynamicResponseField name="output_cost" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="default_markup" type="number">
|
||||
Default percentage markup for AI credit systems. Use -100 to make usage free.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="provider_markups.{key}" type="object | null">
|
||||
Per-provider default markup percentages for AI credit systems.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="markup" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="display" type="object">
|
||||
Display names for the feature in billing UI and customer-facing components.
|
||||
<Expandable title="properties">
|
||||
@@ -987,8 +1027,16 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
The per-unit price amount.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tiers" type="any[]">
|
||||
<DynamicResponseField name="tiers" type="object[]">
|
||||
Tiered pricing configuration if applicable.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="to" type="number" />
|
||||
|
||||
<DynamicResponseField name="amount" type="number" />
|
||||
|
||||
<DynamicResponseField name="flat_amount" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tier_behavior" type="'graduated' | 'volume'">
|
||||
@@ -1064,8 +1112,8 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
Human-readable name displayed in the dashboard and billing UI.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="type" type="'boolean' | 'metered' | 'credit_system'">
|
||||
Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools.
|
||||
<DynamicResponseField name="type" type="'boolean' | 'metered' | 'credit_system' | 'ai_credit_system'">
|
||||
Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="consumable" type="boolean">
|
||||
@@ -1090,6 +1138,30 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="model_markups.{key}" type="object | null">
|
||||
Per-model markup overrides for AI credit systems.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="markup" type="number" />
|
||||
|
||||
<DynamicResponseField name="input_cost" type="number" />
|
||||
|
||||
<DynamicResponseField name="output_cost" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="default_markup" type="number">
|
||||
Default percentage markup for AI credit systems. Use -100 to make usage free.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="provider_markups.{key}" type="object | null">
|
||||
Per-provider default markup percentages for AI credit systems.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="markup" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="display" type="object">
|
||||
Display names for the feature in billing UI and customer-facing components.
|
||||
<Expandable title="properties">
|
||||
|
||||
@@ -406,7 +406,7 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
The name of the feature.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="type" type="'static' | 'boolean' | 'single_use' | 'continuous_use' | 'credit_system'">
|
||||
<DynamicResponseField name="type" type="'static' | 'boolean' | 'single_use' | 'continuous_use' | 'credit_system' | 'ai_credit_system'">
|
||||
The type of the feature
|
||||
</DynamicResponseField>
|
||||
|
||||
@@ -474,8 +474,16 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
Price per billing_units after included usage is consumed. Mutually exclusive with tiers.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tiers" type="any[]">
|
||||
<DynamicResponseField name="tiers" type="object[]">
|
||||
Tiered pricing configuration. Each tier's 'to' INCLUDES the included amount. Either 'tiers' or 'amount' is required.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="to" type="number" />
|
||||
|
||||
<DynamicResponseField name="amount" type="number" />
|
||||
|
||||
<DynamicResponseField name="flat_amount" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tier_behavior" type="'graduated' | 'volume'" />
|
||||
@@ -757,7 +765,7 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
The name of the feature.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="type" type="'static' | 'boolean' | 'single_use' | 'continuous_use' | 'credit_system'">
|
||||
<DynamicResponseField name="type" type="'static' | 'boolean' | 'single_use' | 'continuous_use' | 'credit_system' | 'ai_credit_system'">
|
||||
The type of the feature
|
||||
</DynamicResponseField>
|
||||
|
||||
@@ -825,8 +833,16 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
Price per billing_units after included usage is consumed. Mutually exclusive with tiers.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tiers" type="any[]">
|
||||
<DynamicResponseField name="tiers" type="object[]">
|
||||
Tiered pricing configuration. Each tier's 'to' INCLUDES the included amount. Either 'tiers' or 'amount' is required.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="to" type="number" />
|
||||
|
||||
<DynamicResponseField name="amount" type="number" />
|
||||
|
||||
<DynamicResponseField name="flat_amount" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tier_behavior" type="'graduated' | 'volume'" />
|
||||
@@ -1010,8 +1026,8 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
Human-readable name displayed in the dashboard and billing UI.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="type" type="'boolean' | 'metered' | 'credit_system'">
|
||||
Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools.
|
||||
<DynamicResponseField name="type" type="'boolean' | 'metered' | 'credit_system' | 'ai_credit_system'">
|
||||
Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="consumable" type="boolean">
|
||||
@@ -1036,6 +1052,30 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="model_markups.{key}" type="object | null">
|
||||
Per-model markup overrides for AI credit systems.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="markup" type="number" />
|
||||
|
||||
<DynamicResponseField name="input_cost" type="number" />
|
||||
|
||||
<DynamicResponseField name="output_cost" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="default_markup" type="number">
|
||||
Default percentage markup for AI credit systems. Use -100 to make usage free.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="provider_markups.{key}" type="object | null">
|
||||
Per-provider default markup percentages for AI credit systems.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="markup" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="display" type="object">
|
||||
Display names for the feature in billing UI and customer-facing components.
|
||||
<Expandable title="properties">
|
||||
@@ -1141,8 +1181,16 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
The per-unit price amount.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tiers" type="any[]">
|
||||
<DynamicResponseField name="tiers" type="object[]">
|
||||
Tiered pricing configuration if applicable.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="to" type="number" />
|
||||
|
||||
<DynamicResponseField name="amount" type="number" />
|
||||
|
||||
<DynamicResponseField name="flat_amount" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tier_behavior" type="'graduated' | 'volume'">
|
||||
@@ -1218,8 +1266,8 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
Human-readable name displayed in the dashboard and billing UI.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="type" type="'boolean' | 'metered' | 'credit_system'">
|
||||
Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools.
|
||||
<DynamicResponseField name="type" type="'boolean' | 'metered' | 'credit_system' | 'ai_credit_system'">
|
||||
Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="consumable" type="boolean">
|
||||
@@ -1244,6 +1292,30 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="model_markups.{key}" type="object | null">
|
||||
Per-model markup overrides for AI credit systems.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="markup" type="number" />
|
||||
|
||||
<DynamicResponseField name="input_cost" type="number" />
|
||||
|
||||
<DynamicResponseField name="output_cost" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="default_markup" type="number">
|
||||
Default percentage markup for AI credit systems. Use -100 to make usage free.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="provider_markups.{key}" type="object | null">
|
||||
Per-provider default markup percentages for AI credit systems.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="markup" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="display" type="object">
|
||||
Display names for the feature in billing UI and customer-facing components.
|
||||
<Expandable title="properties">
|
||||
|
||||
@@ -277,7 +277,7 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
The name of the feature.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="type" type="'static' | 'boolean' | 'single_use' | 'continuous_use' | 'credit_system'">
|
||||
<DynamicResponseField name="type" type="'static' | 'boolean' | 'single_use' | 'continuous_use' | 'credit_system' | 'ai_credit_system'">
|
||||
The type of the feature
|
||||
</DynamicResponseField>
|
||||
|
||||
@@ -345,8 +345,16 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
Price per billing_units after included usage is consumed. Mutually exclusive with tiers.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tiers" type="any[]">
|
||||
<DynamicResponseField name="tiers" type="object[]">
|
||||
Tiered pricing configuration. Each tier's 'to' INCLUDES the included amount. Either 'tiers' or 'amount' is required.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="to" type="number" />
|
||||
|
||||
<DynamicResponseField name="amount" type="number" />
|
||||
|
||||
<DynamicResponseField name="flat_amount" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tier_behavior" type="'graduated' | 'volume'" />
|
||||
@@ -628,7 +636,7 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
The name of the feature.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="type" type="'static' | 'boolean' | 'single_use' | 'continuous_use' | 'credit_system'">
|
||||
<DynamicResponseField name="type" type="'static' | 'boolean' | 'single_use' | 'continuous_use' | 'credit_system' | 'ai_credit_system'">
|
||||
The type of the feature
|
||||
</DynamicResponseField>
|
||||
|
||||
@@ -696,8 +704,16 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
Price per billing_units after included usage is consumed. Mutually exclusive with tiers.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tiers" type="any[]">
|
||||
<DynamicResponseField name="tiers" type="object[]">
|
||||
Tiered pricing configuration. Each tier's 'to' INCLUDES the included amount. Either 'tiers' or 'amount' is required.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="to" type="number" />
|
||||
|
||||
<DynamicResponseField name="amount" type="number" />
|
||||
|
||||
<DynamicResponseField name="flat_amount" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tier_behavior" type="'graduated' | 'volume'" />
|
||||
@@ -881,8 +897,8 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
Human-readable name displayed in the dashboard and billing UI.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="type" type="'boolean' | 'metered' | 'credit_system'">
|
||||
Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools.
|
||||
<DynamicResponseField name="type" type="'boolean' | 'metered' | 'credit_system' | 'ai_credit_system'">
|
||||
Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="consumable" type="boolean">
|
||||
@@ -907,6 +923,30 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="model_markups.{key}" type="object | null">
|
||||
Per-model markup overrides for AI credit systems.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="markup" type="number" />
|
||||
|
||||
<DynamicResponseField name="input_cost" type="number" />
|
||||
|
||||
<DynamicResponseField name="output_cost" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="default_markup" type="number">
|
||||
Default percentage markup for AI credit systems. Use -100 to make usage free.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="provider_markups.{key}" type="object | null">
|
||||
Per-provider default markup percentages for AI credit systems.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="markup" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="display" type="object">
|
||||
Display names for the feature in billing UI and customer-facing components.
|
||||
<Expandable title="properties">
|
||||
@@ -1012,8 +1052,16 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
The per-unit price amount.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tiers" type="any[]">
|
||||
<DynamicResponseField name="tiers" type="object[]">
|
||||
Tiered pricing configuration if applicable.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="to" type="number" />
|
||||
|
||||
<DynamicResponseField name="amount" type="number" />
|
||||
|
||||
<DynamicResponseField name="flat_amount" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tier_behavior" type="'graduated' | 'volume'">
|
||||
@@ -1089,8 +1137,8 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
Human-readable name displayed in the dashboard and billing UI.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="type" type="'boolean' | 'metered' | 'credit_system'">
|
||||
Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools.
|
||||
<DynamicResponseField name="type" type="'boolean' | 'metered' | 'credit_system' | 'ai_credit_system'">
|
||||
Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="consumable" type="boolean">
|
||||
@@ -1115,6 +1163,30 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="model_markups.{key}" type="object | null">
|
||||
Per-model markup overrides for AI credit systems.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="markup" type="number" />
|
||||
|
||||
<DynamicResponseField name="input_cost" type="number" />
|
||||
|
||||
<DynamicResponseField name="output_cost" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="default_markup" type="number">
|
||||
Default percentage markup for AI credit systems. Use -100 to make usage free.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="provider_markups.{key}" type="object | null">
|
||||
Per-provider default markup percentages for AI credit systems.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="markup" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="display" type="object">
|
||||
Display names for the feature in billing UI and customer-facing components.
|
||||
<Expandable title="properties">
|
||||
|
||||
@@ -394,7 +394,7 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
The name of the feature.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="type" type="'static' | 'boolean' | 'single_use' | 'continuous_use' | 'credit_system'">
|
||||
<DynamicResponseField name="type" type="'static' | 'boolean' | 'single_use' | 'continuous_use' | 'credit_system' | 'ai_credit_system'">
|
||||
The type of the feature
|
||||
</DynamicResponseField>
|
||||
|
||||
@@ -462,8 +462,16 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
Price per billing_units after included usage is consumed. Mutually exclusive with tiers.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tiers" type="any[]">
|
||||
<DynamicResponseField name="tiers" type="object[]">
|
||||
Tiered pricing configuration. Each tier's 'to' INCLUDES the included amount. Either 'tiers' or 'amount' is required.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="to" type="number" />
|
||||
|
||||
<DynamicResponseField name="amount" type="number" />
|
||||
|
||||
<DynamicResponseField name="flat_amount" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tier_behavior" type="'graduated' | 'volume'" />
|
||||
@@ -745,7 +753,7 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
The name of the feature.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="type" type="'static' | 'boolean' | 'single_use' | 'continuous_use' | 'credit_system'">
|
||||
<DynamicResponseField name="type" type="'static' | 'boolean' | 'single_use' | 'continuous_use' | 'credit_system' | 'ai_credit_system'">
|
||||
The type of the feature
|
||||
</DynamicResponseField>
|
||||
|
||||
@@ -813,8 +821,16 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
Price per billing_units after included usage is consumed. Mutually exclusive with tiers.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tiers" type="any[]">
|
||||
<DynamicResponseField name="tiers" type="object[]">
|
||||
Tiered pricing configuration. Each tier's 'to' INCLUDES the included amount. Either 'tiers' or 'amount' is required.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="to" type="number" />
|
||||
|
||||
<DynamicResponseField name="amount" type="number" />
|
||||
|
||||
<DynamicResponseField name="flat_amount" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tier_behavior" type="'graduated' | 'volume'" />
|
||||
@@ -998,8 +1014,8 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
Human-readable name displayed in the dashboard and billing UI.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="type" type="'boolean' | 'metered' | 'credit_system'">
|
||||
Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools.
|
||||
<DynamicResponseField name="type" type="'boolean' | 'metered' | 'credit_system' | 'ai_credit_system'">
|
||||
Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="consumable" type="boolean">
|
||||
@@ -1024,6 +1040,30 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="model_markups.{key}" type="object | null">
|
||||
Per-model markup overrides for AI credit systems.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="markup" type="number" />
|
||||
|
||||
<DynamicResponseField name="input_cost" type="number" />
|
||||
|
||||
<DynamicResponseField name="output_cost" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="default_markup" type="number">
|
||||
Default percentage markup for AI credit systems. Use -100 to make usage free.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="provider_markups.{key}" type="object | null">
|
||||
Per-provider default markup percentages for AI credit systems.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="markup" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="display" type="object">
|
||||
Display names for the feature in billing UI and customer-facing components.
|
||||
<Expandable title="properties">
|
||||
@@ -1129,8 +1169,16 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
The per-unit price amount.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tiers" type="any[]">
|
||||
<DynamicResponseField name="tiers" type="object[]">
|
||||
Tiered pricing configuration if applicable.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="to" type="number" />
|
||||
|
||||
<DynamicResponseField name="amount" type="number" />
|
||||
|
||||
<DynamicResponseField name="flat_amount" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tier_behavior" type="'graduated' | 'volume'">
|
||||
@@ -1206,8 +1254,8 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
Human-readable name displayed in the dashboard and billing UI.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="type" type="'boolean' | 'metered' | 'credit_system'">
|
||||
Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools.
|
||||
<DynamicResponseField name="type" type="'boolean' | 'metered' | 'credit_system' | 'ai_credit_system'">
|
||||
Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="consumable" type="boolean">
|
||||
@@ -1232,6 +1280,30 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="model_markups.{key}" type="object | null">
|
||||
Per-model markup overrides for AI credit systems.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="markup" type="number" />
|
||||
|
||||
<DynamicResponseField name="input_cost" type="number" />
|
||||
|
||||
<DynamicResponseField name="output_cost" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="default_markup" type="number">
|
||||
Default percentage markup for AI credit systems. Use -100 to make usage free.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="provider_markups.{key}" type="object | null">
|
||||
Per-provider default markup percentages for AI credit systems.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="markup" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="display" type="object">
|
||||
Display names for the feature in billing UI and customer-facing components.
|
||||
<Expandable title="properties">
|
||||
|
||||
@@ -359,7 +359,7 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
The name of the feature.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="type" type="'static' | 'boolean' | 'single_use' | 'continuous_use' | 'credit_system'">
|
||||
<DynamicResponseField name="type" type="'static' | 'boolean' | 'single_use' | 'continuous_use' | 'credit_system' | 'ai_credit_system'">
|
||||
The type of the feature
|
||||
</DynamicResponseField>
|
||||
|
||||
@@ -427,8 +427,16 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
Price per billing_units after included usage is consumed. Mutually exclusive with tiers.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tiers" type="any[]">
|
||||
<DynamicResponseField name="tiers" type="object[]">
|
||||
Tiered pricing configuration. Each tier's 'to' INCLUDES the included amount. Either 'tiers' or 'amount' is required.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="to" type="number" />
|
||||
|
||||
<DynamicResponseField name="amount" type="number" />
|
||||
|
||||
<DynamicResponseField name="flat_amount" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tier_behavior" type="'graduated' | 'volume'" />
|
||||
@@ -709,7 +717,7 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
The name of the feature.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="type" type="'static' | 'boolean' | 'single_use' | 'continuous_use' | 'credit_system'">
|
||||
<DynamicResponseField name="type" type="'static' | 'boolean' | 'single_use' | 'continuous_use' | 'credit_system' | 'ai_credit_system'">
|
||||
The type of the feature
|
||||
</DynamicResponseField>
|
||||
|
||||
@@ -777,8 +785,16 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
Price per billing_units after included usage is consumed. Mutually exclusive with tiers.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tiers" type="any[]">
|
||||
<DynamicResponseField name="tiers" type="object[]">
|
||||
Tiered pricing configuration. Each tier's 'to' INCLUDES the included amount. Either 'tiers' or 'amount' is required.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="to" type="number" />
|
||||
|
||||
<DynamicResponseField name="amount" type="number" />
|
||||
|
||||
<DynamicResponseField name="flat_amount" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tier_behavior" type="'graduated' | 'volume'" />
|
||||
@@ -961,8 +977,8 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
Human-readable name displayed in the dashboard and billing UI.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="type" type="'boolean' | 'metered' | 'credit_system'">
|
||||
Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools.
|
||||
<DynamicResponseField name="type" type="'boolean' | 'metered' | 'credit_system' | 'ai_credit_system'">
|
||||
Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="consumable" type="boolean">
|
||||
@@ -987,6 +1003,30 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="model_markups.{key}" type="object | null">
|
||||
Per-model markup overrides for AI credit systems.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="markup" type="number" />
|
||||
|
||||
<DynamicResponseField name="input_cost" type="number" />
|
||||
|
||||
<DynamicResponseField name="output_cost" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="default_markup" type="number">
|
||||
Default percentage markup for AI credit systems. Use -100 to make usage free.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="provider_markups.{key}" type="object | null">
|
||||
Per-provider default markup percentages for AI credit systems.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="markup" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="display" type="object">
|
||||
Display names for the feature in billing UI and customer-facing components.
|
||||
<Expandable title="properties">
|
||||
@@ -1092,8 +1132,16 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
The per-unit price amount.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tiers" type="any[]">
|
||||
<DynamicResponseField name="tiers" type="object[]">
|
||||
Tiered pricing configuration if applicable.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="to" type="number" />
|
||||
|
||||
<DynamicResponseField name="amount" type="number" />
|
||||
|
||||
<DynamicResponseField name="flat_amount" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tier_behavior" type="'graduated' | 'volume'">
|
||||
@@ -1168,8 +1216,8 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
Human-readable name displayed in the dashboard and billing UI.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="type" type="'boolean' | 'metered' | 'credit_system'">
|
||||
Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools.
|
||||
<DynamicResponseField name="type" type="'boolean' | 'metered' | 'credit_system' | 'ai_credit_system'">
|
||||
Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="consumable" type="boolean">
|
||||
@@ -1194,6 +1242,30 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="model_markups.{key}" type="object | null">
|
||||
Per-model markup overrides for AI credit systems.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="markup" type="number" />
|
||||
|
||||
<DynamicResponseField name="input_cost" type="number" />
|
||||
|
||||
<DynamicResponseField name="output_cost" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="default_markup" type="number">
|
||||
Default percentage markup for AI credit systems. Use -100 to make usage free.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="provider_markups.{key}" type="object | null">
|
||||
Per-provider default markup percentages for AI credit systems.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="markup" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="display" type="object">
|
||||
Display names for the feature in billing UI and customer-facing components.
|
||||
<Expandable title="properties">
|
||||
|
||||
@@ -131,7 +131,7 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
The name of the feature.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="type" type="'static' | 'boolean' | 'single_use' | 'continuous_use' | 'credit_system'">
|
||||
<DynamicResponseField name="type" type="'static' | 'boolean' | 'single_use' | 'continuous_use' | 'credit_system' | 'ai_credit_system'">
|
||||
The type of the feature
|
||||
</DynamicResponseField>
|
||||
|
||||
@@ -199,8 +199,16 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
Price per billing_units after included usage is consumed. Mutually exclusive with tiers.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tiers" type="any[]">
|
||||
<DynamicResponseField name="tiers" type="object[]">
|
||||
Tiered pricing configuration. Each tier's 'to' INCLUDES the included amount. Either 'tiers' or 'amount' is required.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="to" type="number" />
|
||||
|
||||
<DynamicResponseField name="amount" type="number" />
|
||||
|
||||
<DynamicResponseField name="flat_amount" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tier_behavior" type="'graduated' | 'volume'" />
|
||||
@@ -481,7 +489,7 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
The name of the feature.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="type" type="'static' | 'boolean' | 'single_use' | 'continuous_use' | 'credit_system'">
|
||||
<DynamicResponseField name="type" type="'static' | 'boolean' | 'single_use' | 'continuous_use' | 'credit_system' | 'ai_credit_system'">
|
||||
The type of the feature
|
||||
</DynamicResponseField>
|
||||
|
||||
@@ -549,8 +557,16 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
Price per billing_units after included usage is consumed. Mutually exclusive with tiers.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tiers" type="any[]">
|
||||
<DynamicResponseField name="tiers" type="object[]">
|
||||
Tiered pricing configuration. Each tier's 'to' INCLUDES the included amount. Either 'tiers' or 'amount' is required.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="to" type="number" />
|
||||
|
||||
<DynamicResponseField name="amount" type="number" />
|
||||
|
||||
<DynamicResponseField name="flat_amount" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tier_behavior" type="'graduated' | 'volume'" />
|
||||
@@ -733,8 +749,8 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
Human-readable name displayed in the dashboard and billing UI.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="type" type="'boolean' | 'metered' | 'credit_system'">
|
||||
Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools.
|
||||
<DynamicResponseField name="type" type="'boolean' | 'metered' | 'credit_system' | 'ai_credit_system'">
|
||||
Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="consumable" type="boolean">
|
||||
@@ -759,6 +775,30 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="model_markups.{key}" type="object | null">
|
||||
Per-model markup overrides for AI credit systems.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="markup" type="number" />
|
||||
|
||||
<DynamicResponseField name="input_cost" type="number" />
|
||||
|
||||
<DynamicResponseField name="output_cost" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="default_markup" type="number">
|
||||
Default percentage markup for AI credit systems. Use -100 to make usage free.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="provider_markups.{key}" type="object | null">
|
||||
Per-provider default markup percentages for AI credit systems.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="markup" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="display" type="object">
|
||||
Display names for the feature in billing UI and customer-facing components.
|
||||
<Expandable title="properties">
|
||||
@@ -864,8 +904,16 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
The per-unit price amount.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tiers" type="any[]">
|
||||
<DynamicResponseField name="tiers" type="object[]">
|
||||
Tiered pricing configuration if applicable.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="to" type="number" />
|
||||
|
||||
<DynamicResponseField name="amount" type="number" />
|
||||
|
||||
<DynamicResponseField name="flat_amount" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tier_behavior" type="'graduated' | 'volume'">
|
||||
@@ -940,8 +988,8 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
Human-readable name displayed in the dashboard and billing UI.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="type" type="'boolean' | 'metered' | 'credit_system'">
|
||||
Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools.
|
||||
<DynamicResponseField name="type" type="'boolean' | 'metered' | 'credit_system' | 'ai_credit_system'">
|
||||
Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="consumable" type="boolean">
|
||||
@@ -966,6 +1014,30 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="model_markups.{key}" type="object | null">
|
||||
Per-model markup overrides for AI credit systems.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="markup" type="number" />
|
||||
|
||||
<DynamicResponseField name="input_cost" type="number" />
|
||||
|
||||
<DynamicResponseField name="output_cost" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="default_markup" type="number">
|
||||
Default percentage markup for AI credit systems. Use -100 to make usage free.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="provider_markups.{key}" type="object | null">
|
||||
Per-provider default markup percentages for AI credit systems.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="markup" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="display" type="object">
|
||||
Display names for the feature in billing UI and customer-facing components.
|
||||
<Expandable title="properties">
|
||||
|
||||
@@ -160,7 +160,7 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
The name of the feature.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="type" type="'static' | 'boolean' | 'single_use' | 'continuous_use' | 'credit_system'">
|
||||
<DynamicResponseField name="type" type="'static' | 'boolean' | 'single_use' | 'continuous_use' | 'credit_system' | 'ai_credit_system'">
|
||||
The type of the feature
|
||||
</DynamicResponseField>
|
||||
|
||||
@@ -228,8 +228,16 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
Price per billing_units after included usage is consumed. Mutually exclusive with tiers.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tiers" type="any[]">
|
||||
<DynamicResponseField name="tiers" type="object[]">
|
||||
Tiered pricing configuration. Each tier's 'to' INCLUDES the included amount. Either 'tiers' or 'amount' is required.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="to" type="number" />
|
||||
|
||||
<DynamicResponseField name="amount" type="number" />
|
||||
|
||||
<DynamicResponseField name="flat_amount" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tier_behavior" type="'graduated' | 'volume'" />
|
||||
@@ -510,7 +518,7 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
The name of the feature.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="type" type="'static' | 'boolean' | 'single_use' | 'continuous_use' | 'credit_system'">
|
||||
<DynamicResponseField name="type" type="'static' | 'boolean' | 'single_use' | 'continuous_use' | 'credit_system' | 'ai_credit_system'">
|
||||
The type of the feature
|
||||
</DynamicResponseField>
|
||||
|
||||
@@ -578,8 +586,16 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
Price per billing_units after included usage is consumed. Mutually exclusive with tiers.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tiers" type="any[]">
|
||||
<DynamicResponseField name="tiers" type="object[]">
|
||||
Tiered pricing configuration. Each tier's 'to' INCLUDES the included amount. Either 'tiers' or 'amount' is required.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="to" type="number" />
|
||||
|
||||
<DynamicResponseField name="amount" type="number" />
|
||||
|
||||
<DynamicResponseField name="flat_amount" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tier_behavior" type="'graduated' | 'volume'" />
|
||||
@@ -762,8 +778,8 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
Human-readable name displayed in the dashboard and billing UI.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="type" type="'boolean' | 'metered' | 'credit_system'">
|
||||
Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools.
|
||||
<DynamicResponseField name="type" type="'boolean' | 'metered' | 'credit_system' | 'ai_credit_system'">
|
||||
Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="consumable" type="boolean">
|
||||
@@ -788,6 +804,30 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="model_markups.{key}" type="object | null">
|
||||
Per-model markup overrides for AI credit systems.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="markup" type="number" />
|
||||
|
||||
<DynamicResponseField name="input_cost" type="number" />
|
||||
|
||||
<DynamicResponseField name="output_cost" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="default_markup" type="number">
|
||||
Default percentage markup for AI credit systems. Use -100 to make usage free.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="provider_markups.{key}" type="object | null">
|
||||
Per-provider default markup percentages for AI credit systems.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="markup" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="display" type="object">
|
||||
Display names for the feature in billing UI and customer-facing components.
|
||||
<Expandable title="properties">
|
||||
@@ -893,8 +933,16 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
The per-unit price amount.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tiers" type="any[]">
|
||||
<DynamicResponseField name="tiers" type="object[]">
|
||||
Tiered pricing configuration if applicable.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="to" type="number" />
|
||||
|
||||
<DynamicResponseField name="amount" type="number" />
|
||||
|
||||
<DynamicResponseField name="flat_amount" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tier_behavior" type="'graduated' | 'volume'">
|
||||
@@ -969,8 +1017,8 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
Human-readable name displayed in the dashboard and billing UI.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="type" type="'boolean' | 'metered' | 'credit_system'">
|
||||
Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools.
|
||||
<DynamicResponseField name="type" type="'boolean' | 'metered' | 'credit_system' | 'ai_credit_system'">
|
||||
Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="consumable" type="boolean">
|
||||
@@ -995,6 +1043,30 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="model_markups.{key}" type="object | null">
|
||||
Per-model markup overrides for AI credit systems.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="markup" type="number" />
|
||||
|
||||
<DynamicResponseField name="input_cost" type="number" />
|
||||
|
||||
<DynamicResponseField name="output_cost" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="default_markup" type="number">
|
||||
Default percentage markup for AI credit systems. Use -100 to make usage free.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="provider_markups.{key}" type="object | null">
|
||||
Per-provider default markup percentages for AI credit systems.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="markup" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="display" type="object">
|
||||
Display names for the feature in billing UI and customer-facing components.
|
||||
<Expandable title="properties">
|
||||
|
||||
@@ -195,7 +195,7 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
The name of the feature.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="type" type="'static' | 'boolean' | 'single_use' | 'continuous_use' | 'credit_system'">
|
||||
<DynamicResponseField name="type" type="'static' | 'boolean' | 'single_use' | 'continuous_use' | 'credit_system' | 'ai_credit_system'">
|
||||
The type of the feature
|
||||
</DynamicResponseField>
|
||||
|
||||
@@ -263,8 +263,16 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
Price per billing_units after included usage is consumed. Mutually exclusive with tiers.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tiers" type="any[]">
|
||||
<DynamicResponseField name="tiers" type="object[]">
|
||||
Tiered pricing configuration. Each tier's 'to' INCLUDES the included amount. Either 'tiers' or 'amount' is required.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="to" type="number" />
|
||||
|
||||
<DynamicResponseField name="amount" type="number" />
|
||||
|
||||
<DynamicResponseField name="flat_amount" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tier_behavior" type="'graduated' | 'volume'" />
|
||||
@@ -545,7 +553,7 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
The name of the feature.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="type" type="'static' | 'boolean' | 'single_use' | 'continuous_use' | 'credit_system'">
|
||||
<DynamicResponseField name="type" type="'static' | 'boolean' | 'single_use' | 'continuous_use' | 'credit_system' | 'ai_credit_system'">
|
||||
The type of the feature
|
||||
</DynamicResponseField>
|
||||
|
||||
@@ -613,8 +621,16 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
Price per billing_units after included usage is consumed. Mutually exclusive with tiers.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tiers" type="any[]">
|
||||
<DynamicResponseField name="tiers" type="object[]">
|
||||
Tiered pricing configuration. Each tier's 'to' INCLUDES the included amount. Either 'tiers' or 'amount' is required.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="to" type="number" />
|
||||
|
||||
<DynamicResponseField name="amount" type="number" />
|
||||
|
||||
<DynamicResponseField name="flat_amount" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tier_behavior" type="'graduated' | 'volume'" />
|
||||
@@ -797,8 +813,8 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
Human-readable name displayed in the dashboard and billing UI.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="type" type="'boolean' | 'metered' | 'credit_system'">
|
||||
Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools.
|
||||
<DynamicResponseField name="type" type="'boolean' | 'metered' | 'credit_system' | 'ai_credit_system'">
|
||||
Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="consumable" type="boolean">
|
||||
@@ -823,6 +839,30 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="model_markups.{key}" type="object | null">
|
||||
Per-model markup overrides for AI credit systems.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="markup" type="number" />
|
||||
|
||||
<DynamicResponseField name="input_cost" type="number" />
|
||||
|
||||
<DynamicResponseField name="output_cost" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="default_markup" type="number">
|
||||
Default percentage markup for AI credit systems. Use -100 to make usage free.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="provider_markups.{key}" type="object | null">
|
||||
Per-provider default markup percentages for AI credit systems.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="markup" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="display" type="object">
|
||||
Display names for the feature in billing UI and customer-facing components.
|
||||
<Expandable title="properties">
|
||||
@@ -928,8 +968,16 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
The per-unit price amount.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tiers" type="any[]">
|
||||
<DynamicResponseField name="tiers" type="object[]">
|
||||
Tiered pricing configuration if applicable.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="to" type="number" />
|
||||
|
||||
<DynamicResponseField name="amount" type="number" />
|
||||
|
||||
<DynamicResponseField name="flat_amount" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tier_behavior" type="'graduated' | 'volume'">
|
||||
@@ -1004,8 +1052,8 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
Human-readable name displayed in the dashboard and billing UI.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="type" type="'boolean' | 'metered' | 'credit_system'">
|
||||
Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools.
|
||||
<DynamicResponseField name="type" type="'boolean' | 'metered' | 'credit_system' | 'ai_credit_system'">
|
||||
Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="consumable" type="boolean">
|
||||
@@ -1030,6 +1078,30 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="model_markups.{key}" type="object | null">
|
||||
Per-model markup overrides for AI credit systems.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="markup" type="number" />
|
||||
|
||||
<DynamicResponseField name="input_cost" type="number" />
|
||||
|
||||
<DynamicResponseField name="output_cost" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="default_markup" type="number">
|
||||
Default percentage markup for AI credit systems. Use -100 to make usage free.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="provider_markups.{key}" type="object | null">
|
||||
Per-provider default markup percentages for AI credit systems.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="markup" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="display" type="object">
|
||||
Display names for the feature in billing UI and customer-facing components.
|
||||
<Expandable title="properties">
|
||||
|
||||
@@ -13,7 +13,7 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
The name of the feature.
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="type" type="'boolean' | 'metered' | 'credit_system'" required>
|
||||
<DynamicParamField body="type" type="'boolean' | 'metered' | 'credit_system' | 'ai_credit_system'" required>
|
||||
The type of the feature. 'single_use' features are consumed, like API calls, tokens, or messages. 'continuous_use' features are allocated, like seats, workspaces, or projects. 'credit_system' features are schemas that unify multiple 'single_use' features into a single credit system.
|
||||
</DynamicParamField>
|
||||
|
||||
@@ -32,7 +32,7 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="credit_schema" type="object[]">
|
||||
A schema that maps 'single_use' feature IDs to credit costs. Applicable only for 'credit_system' features.
|
||||
A schema that maps 'single_use' feature IDs to credit costs. For classic credit systems only — AI credit systems use model_markups instead.
|
||||
<Expandable title="properties">
|
||||
<DynamicParamField body="metered_feature_id" type="string" required />
|
||||
|
||||
@@ -41,6 +41,30 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
</Expandable>
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="model_markups.{key}" type="object | null">
|
||||
Per-model markup overrides for AI credit systems. Maps model IDs to their markup configuration.
|
||||
<Expandable title="properties">
|
||||
<DynamicParamField body="markup" type="number" />
|
||||
|
||||
<DynamicParamField body="input_cost" type="number" />
|
||||
|
||||
<DynamicParamField body="output_cost" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="default_markup" type="number">
|
||||
Default percentage markup for this AI credit system. Used when no model or provider markup applies. Use -100 to make usage free.
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="provider_markups.{key}" type="object | null">
|
||||
Per-provider default markup percentages for AI credit systems. Provider keys match the first segment of model_id.
|
||||
<Expandable title="properties">
|
||||
<DynamicParamField body="markup" type="number" required />
|
||||
|
||||
</Expandable>
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="event_names" type="string[]" />
|
||||
|
||||
<DynamicParamField body="feature_id" type="string" required>
|
||||
@@ -58,8 +82,8 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
Human-readable name displayed in the dashboard and billing UI.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="type" type="'boolean' | 'metered' | 'credit_system'">
|
||||
Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools.
|
||||
<DynamicResponseField name="type" type="'boolean' | 'metered' | 'credit_system' | 'ai_credit_system'">
|
||||
Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="consumable" type="boolean">
|
||||
@@ -84,6 +108,30 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="model_markups.{key}" type="object | null">
|
||||
Per-model markup overrides for AI credit systems.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="markup" type="number" />
|
||||
|
||||
<DynamicResponseField name="input_cost" type="number" />
|
||||
|
||||
<DynamicResponseField name="output_cost" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="default_markup" type="number">
|
||||
Default percentage markup for AI credit systems. Use -100 to make usage free.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="provider_markups.{key}" type="object | null">
|
||||
Per-provider default markup percentages for AI credit systems.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="markup" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="display" type="object">
|
||||
Display names for the feature in billing UI and customer-facing components.
|
||||
<Expandable title="properties">
|
||||
|
||||
@@ -24,8 +24,8 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
Human-readable name displayed in the dashboard and billing UI.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="type" type="'boolean' | 'metered' | 'credit_system'">
|
||||
Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools.
|
||||
<DynamicResponseField name="type" type="'boolean' | 'metered' | 'credit_system' | 'ai_credit_system'">
|
||||
Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="consumable" type="boolean">
|
||||
@@ -50,6 +50,30 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="model_markups.{key}" type="object | null">
|
||||
Per-model markup overrides for AI credit systems.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="markup" type="number" />
|
||||
|
||||
<DynamicResponseField name="input_cost" type="number" />
|
||||
|
||||
<DynamicResponseField name="output_cost" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="default_markup" type="number">
|
||||
Default percentage markup for AI credit systems. Use -100 to make usage free.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="provider_markups.{key}" type="object | null">
|
||||
Per-provider default markup percentages for AI credit systems.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="markup" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="display" type="object">
|
||||
Display names for the feature in billing UI and customer-facing components.
|
||||
<Expandable title="properties">
|
||||
|
||||
@@ -20,8 +20,8 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
Human-readable name displayed in the dashboard and billing UI.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="type" type="'boolean' | 'metered' | 'credit_system'">
|
||||
Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools.
|
||||
<DynamicResponseField name="type" type="'boolean' | 'metered' | 'credit_system' | 'ai_credit_system'">
|
||||
Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="consumable" type="boolean">
|
||||
@@ -46,6 +46,30 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="model_markups.{key}" type="object | null">
|
||||
Per-model markup overrides for AI credit systems.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="markup" type="number" />
|
||||
|
||||
<DynamicResponseField name="input_cost" type="number" />
|
||||
|
||||
<DynamicResponseField name="output_cost" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="default_markup" type="number">
|
||||
Default percentage markup for AI credit systems. Use -100 to make usage free.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="provider_markups.{key}" type="object | null">
|
||||
Per-provider default markup percentages for AI credit systems.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="markup" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="display" type="object">
|
||||
Display names for the feature in billing UI and customer-facing components.
|
||||
<Expandable title="properties">
|
||||
|
||||
@@ -13,7 +13,7 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
The name of the feature.
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="type" type="'boolean' | 'metered' | 'credit_system'">
|
||||
<DynamicParamField body="type" type="'boolean' | 'metered' | 'credit_system' | 'ai_credit_system'">
|
||||
The type of the feature. 'single_use' features are consumed, like API calls, tokens, or messages. 'continuous_use' features are allocated, like seats, workspaces, or projects. 'credit_system' features are schemas that unify multiple 'single_use' features into a single credit system.
|
||||
</DynamicParamField>
|
||||
|
||||
@@ -32,7 +32,7 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="credit_schema" type="object[]">
|
||||
A schema that maps 'single_use' feature IDs to credit costs. Applicable only for 'credit_system' features.
|
||||
A schema that maps 'single_use' feature IDs to credit costs. For classic credit systems only — AI credit systems use model_markups instead.
|
||||
<Expandable title="properties">
|
||||
<DynamicParamField body="metered_feature_id" type="string" required />
|
||||
|
||||
@@ -41,6 +41,30 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
</Expandable>
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="model_markups.{key}" type="object | null">
|
||||
Per-model markup overrides for AI credit systems. Maps model IDs to their markup configuration.
|
||||
<Expandable title="properties">
|
||||
<DynamicParamField body="markup" type="number" />
|
||||
|
||||
<DynamicParamField body="input_cost" type="number" />
|
||||
|
||||
<DynamicParamField body="output_cost" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="default_markup" type="number">
|
||||
Default percentage markup for this AI credit system. Used when no model or provider markup applies. Use -100 to make usage free.
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="provider_markups.{key}" type="object | null">
|
||||
Per-provider default markup percentages for AI credit systems. Provider keys match the first segment of model_id.
|
||||
<Expandable title="properties">
|
||||
<DynamicParamField body="markup" type="number" required />
|
||||
|
||||
</Expandable>
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="event_names" type="string[]" />
|
||||
|
||||
<DynamicParamField body="archived" type="boolean">
|
||||
@@ -66,8 +90,8 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
Human-readable name displayed in the dashboard and billing UI.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="type" type="'boolean' | 'metered' | 'credit_system'">
|
||||
Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools.
|
||||
<DynamicResponseField name="type" type="'boolean' | 'metered' | 'credit_system' | 'ai_credit_system'">
|
||||
Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="consumable" type="boolean">
|
||||
@@ -92,6 +116,30 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="model_markups.{key}" type="object | null">
|
||||
Per-model markup overrides for AI credit systems.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="markup" type="number" />
|
||||
|
||||
<DynamicResponseField name="input_cost" type="number" />
|
||||
|
||||
<DynamicResponseField name="output_cost" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="default_markup" type="number">
|
||||
Default percentage markup for AI credit systems. Use -100 to make usage free.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="provider_markups.{key}" type="object | null">
|
||||
Per-provider default markup percentages for AI credit systems.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="markup" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="display" type="object">
|
||||
Display names for the feature in billing UI and customer-facing components.
|
||||
<Expandable title="properties">
|
||||
|
||||
@@ -248,8 +248,8 @@ await autumn.plans.create({
|
||||
'prepaid' for upfront payment (seats), 'usage_based' for pay-as-you-go.
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="max_purchase" type="number">
|
||||
Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total.
|
||||
<DynamicParamField body="max_purchase" type="number | null">
|
||||
Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total. Null for no limit.
|
||||
</DynamicParamField>
|
||||
|
||||
</Expandable>
|
||||
@@ -409,7 +409,7 @@ await autumn.plans.create({
|
||||
The name of the feature.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="type" type="'static' | 'boolean' | 'single_use' | 'continuous_use' | 'credit_system'">
|
||||
<DynamicResponseField name="type" type="'static' | 'boolean' | 'single_use' | 'continuous_use' | 'credit_system' | 'ai_credit_system'">
|
||||
The type of the feature
|
||||
</DynamicResponseField>
|
||||
|
||||
@@ -477,8 +477,16 @@ await autumn.plans.create({
|
||||
Price per billing_units after included usage is consumed. Mutually exclusive with tiers.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tiers" type="any[]">
|
||||
<DynamicResponseField name="tiers" type="object[]">
|
||||
Tiered pricing configuration. Each tier's 'to' INCLUDES the included amount. Either 'tiers' or 'amount' is required.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="to" type="number" />
|
||||
|
||||
<DynamicResponseField name="amount" type="number" />
|
||||
|
||||
<DynamicResponseField name="flat_amount" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tier_behavior" type="'graduated' | 'volume'" />
|
||||
|
||||
@@ -119,7 +119,7 @@ const plan = await autumn.plans.get({
|
||||
The name of the feature.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="type" type="'static' | 'boolean' | 'single_use' | 'continuous_use' | 'credit_system'">
|
||||
<DynamicResponseField name="type" type="'static' | 'boolean' | 'single_use' | 'continuous_use' | 'credit_system' | 'ai_credit_system'">
|
||||
The type of the feature
|
||||
</DynamicResponseField>
|
||||
|
||||
@@ -187,8 +187,16 @@ const plan = await autumn.plans.get({
|
||||
Price per billing_units after included usage is consumed. Mutually exclusive with tiers.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tiers" type="any[]">
|
||||
<DynamicResponseField name="tiers" type="object[]">
|
||||
Tiered pricing configuration. Each tier's 'to' INCLUDES the included amount. Either 'tiers' or 'amount' is required.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="to" type="number" />
|
||||
|
||||
<DynamicResponseField name="amount" type="number" />
|
||||
|
||||
<DynamicResponseField name="flat_amount" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tier_behavior" type="'graduated' | 'volume'" />
|
||||
|
||||
@@ -136,7 +136,7 @@ const plans = await autumn.plans.list({
|
||||
The name of the feature.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="type" type="'static' | 'boolean' | 'single_use' | 'continuous_use' | 'credit_system'">
|
||||
<DynamicResponseField name="type" type="'static' | 'boolean' | 'single_use' | 'continuous_use' | 'credit_system' | 'ai_credit_system'">
|
||||
The type of the feature
|
||||
</DynamicResponseField>
|
||||
|
||||
@@ -204,8 +204,16 @@ const plans = await autumn.plans.list({
|
||||
Price per billing_units after included usage is consumed. Mutually exclusive with tiers.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tiers" type="any[]">
|
||||
<DynamicResponseField name="tiers" type="object[]">
|
||||
Tiered pricing configuration. Each tier's 'to' INCLUDES the included amount. Either 'tiers' or 'amount' is required.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="to" type="number" />
|
||||
|
||||
<DynamicResponseField name="amount" type="number" />
|
||||
|
||||
<DynamicResponseField name="flat_amount" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tier_behavior" type="'graduated' | 'volume'" />
|
||||
|
||||
@@ -175,8 +175,8 @@ await autumn.plans.update({
|
||||
'prepaid' for upfront payment (seats), 'usage_based' for pay-as-you-go.
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="max_purchase" type="number">
|
||||
Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total.
|
||||
<DynamicParamField body="max_purchase" type="number | null">
|
||||
Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total. Null for no limit.
|
||||
</DynamicParamField>
|
||||
|
||||
</Expandable>
|
||||
@@ -263,6 +263,8 @@ await autumn.plans.update({
|
||||
The new ID to use for the plan. Can only be updated if the plan has not been used by any customers.
|
||||
</DynamicParamField>
|
||||
|
||||
<DynamicParamField body="disable_version" type="boolean" />
|
||||
|
||||
|
||||
### Response
|
||||
|
||||
@@ -344,7 +346,7 @@ await autumn.plans.update({
|
||||
The name of the feature.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="type" type="'static' | 'boolean' | 'single_use' | 'continuous_use' | 'credit_system'">
|
||||
<DynamicResponseField name="type" type="'static' | 'boolean' | 'single_use' | 'continuous_use' | 'credit_system' | 'ai_credit_system'">
|
||||
The type of the feature
|
||||
</DynamicResponseField>
|
||||
|
||||
@@ -412,8 +414,16 @@ await autumn.plans.update({
|
||||
Price per billing_units after included usage is consumed. Mutually exclusive with tiers.
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tiers" type="any[]">
|
||||
<DynamicResponseField name="tiers" type="object[]">
|
||||
Tiered pricing configuration. Each tier's 'to' INCLUDES the included amount. Either 'tiers' or 'amount' is required.
|
||||
<Expandable title="properties">
|
||||
<DynamicResponseField name="to" type="number" />
|
||||
|
||||
<DynamicResponseField name="amount" type="number" />
|
||||
|
||||
<DynamicResponseField name="flat_amount" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</DynamicResponseField>
|
||||
|
||||
<DynamicResponseField name="tier_behavior" type="'graduated' | 'volume'" />
|
||||
|
||||
@@ -97,6 +97,168 @@ openapi: "api/openapi.yml webhook billing.updated"
|
||||
The ID of the feature that was added or removed.
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="item" type="object" required>
|
||||
The item snapshot that was added or removed.
|
||||
<Expandable title="properties">
|
||||
<ParamField body="feature_id" type="string" required>
|
||||
The ID of the feature this item configures.
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="feature" type="object">
|
||||
The full feature object if expanded.
|
||||
<Expandable title="properties">
|
||||
<ParamField body="id" type="string" required>
|
||||
The ID of the feature, used to refer to it in other API calls like /track or /check.
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="name" type="string | null">
|
||||
The name of the feature.
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="type" type="'static' | 'boolean' | 'single_use' | 'continuous_use' | 'credit_system' | 'ai_credit_system'" required>
|
||||
The type of the feature
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="display" type="object | null">
|
||||
Singular and plural display names for the feature.
|
||||
<Expandable title="properties">
|
||||
<ParamField body="singular" type="string" required>
|
||||
The singular display name for the feature.
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="plural" type="string" required>
|
||||
The plural display name for the feature.
|
||||
</ParamField>
|
||||
|
||||
</Expandable>
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="credit_schema" type="object[] | null">
|
||||
Credit cost schema for credit system features.
|
||||
<Expandable title="properties">
|
||||
<ParamField body="metered_feature_id" type="string" required>
|
||||
The ID of the metered feature (should be a single_use feature).
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="credit_cost" type="number" required>
|
||||
The credit cost of the metered feature.
|
||||
</ParamField>
|
||||
|
||||
</Expandable>
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="archived" type="boolean | null">
|
||||
Whether or not the feature is archived.
|
||||
</ParamField>
|
||||
|
||||
</Expandable>
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="included" type="number" required>
|
||||
Number of free units included. For consumable features, balance resets to this number each interval.
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="unlimited" type="boolean" required>
|
||||
Whether the customer has unlimited access to this feature.
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="reset" type="object | null" required>
|
||||
Reset configuration for consumable features. Null for non-consumable features like seats where usage persists across billing cycles.
|
||||
<Expandable title="properties">
|
||||
<ParamField body="interval" type="'one_off' | 'minute' | 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'semi_annual' | 'year'" required>
|
||||
The interval at which the feature balance resets (e.g. 'month', 'year'). For consumable features, usage resets to 0 and included units are restored.
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="interval_count" type="number">
|
||||
Number of intervals between resets. Defaults to 1.
|
||||
</ParamField>
|
||||
|
||||
</Expandable>
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="price" type="object | null" required>
|
||||
Pricing configuration for usage beyond included units. Null if feature is entirely free.
|
||||
<Expandable title="properties">
|
||||
<ParamField body="amount" type="number">
|
||||
Price per billing_units after included usage is consumed. Mutually exclusive with tiers.
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="tiers" type="object[]">
|
||||
Tiered pricing configuration. Each tier's 'to' INCLUDES the included amount. Either 'tiers' or 'amount' is required.
|
||||
<Expandable title="properties">
|
||||
<ParamField body="to" type="number" required />
|
||||
|
||||
<ParamField body="amount" type="number" required />
|
||||
|
||||
<ParamField body="flat_amount" type="number" />
|
||||
|
||||
</Expandable>
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="tier_behavior" type="'graduated' | 'volume'" />
|
||||
|
||||
<ParamField body="interval" type="'one_off' | 'week' | 'month' | 'quarter' | 'semi_annual' | 'year'" required>
|
||||
Billing interval for this price. For consumable features, should match reset.interval.
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="interval_count" type="number">
|
||||
Number of intervals per billing cycle. Defaults to 1.
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="billing_units" type="number" required>
|
||||
Number of units per price increment. Usage is rounded UP to the nearest billing_units when billed (e.g. billing_units=100 means 101 usage rounds to 200).
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="billing_method" type="'prepaid' | 'usage_based'" required>
|
||||
'prepaid' for features like seats where customers pay upfront, 'usage_based' for pay-as-you-go after included usage.
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="max_purchase" type="number | null" required>
|
||||
Maximum units a customer can purchase beyond included. E.g. if included=100 and max_purchase=300, customer can use up to 400 total before usage is capped. Null for no limit.
|
||||
</ParamField>
|
||||
|
||||
</Expandable>
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="display" type="object">
|
||||
Display text for showing this item in pricing pages.
|
||||
<Expandable title="properties">
|
||||
<ParamField body="primary_text" type="string" required>
|
||||
Main display text (e.g. '$10' or '100 messages').
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="secondary_text" type="string">
|
||||
Secondary display text (e.g. 'per month' or 'then $0.5 per 100').
|
||||
</ParamField>
|
||||
|
||||
</Expandable>
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="rollover" type="object">
|
||||
Rollover configuration for unused units. If set, unused included units roll over to the next period.
|
||||
<Expandable title="properties">
|
||||
<ParamField body="max" type="number | null" required>
|
||||
Maximum rollover units. Null for unlimited rollover.
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="max_percentage" type="number | null">
|
||||
Maximum rollover as a percentage (0-100) of included + prepaid grant. Mutually exclusive with max.
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="expiry_duration_type" type="'month' | 'forever'" required>
|
||||
When rolled over units expire.
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="expiry_duration_length" type="number">
|
||||
Number of periods before expiry.
|
||||
</ParamField>
|
||||
|
||||
</Expandable>
|
||||
</ParamField>
|
||||
|
||||
</Expandable>
|
||||
</ParamField>
|
||||
|
||||
</Expandable>
|
||||
</ParamField>
|
||||
|
||||
|
||||
@@ -133,6 +133,7 @@
|
||||
"documentation/slack-discord-notifications",
|
||||
"documentation/fail-open",
|
||||
"documentation/rate-limits",
|
||||
"documentation/external-providers/ai-sdk",
|
||||
"documentation/external-providers/convex",
|
||||
"documentation/external-providers/revenuecat",
|
||||
"documentation/external-providers/vercel-marketplace"
|
||||
@@ -205,6 +206,7 @@
|
||||
"pages": [
|
||||
"api-reference/core/check",
|
||||
"api-reference/core/track",
|
||||
"api-reference/balances/trackTokens",
|
||||
"api-reference/core/batchTrack",
|
||||
"api-reference/balances/createBalance",
|
||||
"api-reference/balances/updateBalance",
|
||||
|
||||
@@ -131,6 +131,85 @@ curl -X POST "https://api.useautumn.com/v1/balances/update" \
|
||||
can reset or override incremental usage recorded through events.
|
||||
</Warning>
|
||||
|
||||
## Tracking AI Token Usage
|
||||
|
||||
If you're using an [AI credit system](/examples/monetary-credits), you can track token usage directly with `trackTokens`. This automatically converts token counts to a dollar cost using [Models.dev](https://models.dev) pricing and your configured markup, then deducts from the customer's credit balance.
|
||||
|
||||
The `modelId` must be in `provider/model` format, matching the provider and model keys from [Models.dev](https://models.dev). For example:
|
||||
- `anthropic/claude-sonnet-4-5-20250514`
|
||||
- `openai/gpt-4o`
|
||||
- `google/gemini-2.5-pro`
|
||||
|
||||
For providers with nested model paths (like OpenRouter), include the full path after the provider: `openrouter/anthropic/claude-opus-4.6`.
|
||||
|
||||
Token counts are **exclusive pools**: `inputTokens` should exclude cached tokens (pass those as `cacheReadTokens` / `cacheWriteTokens`) and `outputTokens` should exclude reasoning tokens (pass those as `reasoningTokens`). Audio tokens go in `audioInputTokens` / `audioOutputTokens`. See the [API reference](/api-reference/balances/trackTokens) for the full parameter list.
|
||||
|
||||
<Note>
|
||||
`autumn.balances.trackTokens` requires an autumn-js release that includes
|
||||
the method. On older versions, call the REST endpoint directly — see the
|
||||
cURL tab below.
|
||||
</Note>
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```typescript TypeScript
|
||||
import { Autumn } from "autumn-js";
|
||||
|
||||
const autumn = new Autumn({ secretKey: "am_sk_test_1234" });
|
||||
|
||||
await autumn.balances.trackTokens({
|
||||
customerId: "user_123",
|
||||
modelId: "anthropic/claude-opus-4-6",
|
||||
inputTokens: 1000,
|
||||
outputTokens: 500,
|
||||
});
|
||||
```
|
||||
|
||||
```python Python
|
||||
from autumn_sdk import Autumn
|
||||
|
||||
autumn = Autumn("am_sk_test_1234")
|
||||
|
||||
await autumn.balances.track_tokens(
|
||||
customer_id="user_123",
|
||||
model_id="anthropic/claude-opus-4-6",
|
||||
input_tokens=1000,
|
||||
output_tokens=500,
|
||||
)
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl -X POST "https://api.useautumn.com/v1/balances.track_tokens" \
|
||||
-H "Authorization: Bearer am_sk_test_1234" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"customer_id": "user_123",
|
||||
"model_id": "anthropic/claude-opus-4-6",
|
||||
"input_tokens": 1000,
|
||||
"output_tokens": 500
|
||||
}'
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
<Tip>
|
||||
If the customer has exactly one AI credit system feature, you can omit the
|
||||
`featureId` parameter — it will be auto-detected. The request fails with an
|
||||
error if the customer has no AI credit system, or has more than one and no
|
||||
`featureId` is provided.
|
||||
</Tip>
|
||||
|
||||
### Vercel AI SDK integration
|
||||
|
||||
If you're using the [Vercel AI SDK](https://sdk.vercel.ai), the `@useautumn/ai-sdk` package can automatically track token usage for every `generateText` or `streamText` call — no manual `trackTokens` calls needed.
|
||||
|
||||
<Card
|
||||
title="Vercel AI SDK Integration"
|
||||
horizontal
|
||||
href="/documentation/external-providers/ai-sdk"
|
||||
icon="wand-magic-sparkles"
|
||||
/>
|
||||
|
||||
## Using Event Names
|
||||
|
||||
In the above examples, we used the `featureId` to identify the feature. You can instead use the `eventName` parameter to link balances to different events in your application. This can be useful when:
|
||||
|
||||
145
apps/docs/mintlify/documentation/external-providers/ai-sdk.mdx
Normal file
@@ -0,0 +1,145 @@
|
||||
---
|
||||
title: "Vercel AI SDK"
|
||||
description: "Automatically track AI token usage with the Vercel AI SDK"
|
||||
---
|
||||
|
||||
The `@useautumn/ai-sdk` package integrates Autumn with the [Vercel AI SDK](https://sdk.vercel.ai), automatically tracking token usage for every `generateText` or `streamText` call. No manual `trackTokens` calls needed.
|
||||
|
||||
## Setup
|
||||
|
||||
#### 1. Install the package
|
||||
|
||||
<CodeGroup>
|
||||
```bash npm
|
||||
npm install @useautumn/ai-sdk
|
||||
```
|
||||
|
||||
```bash pnpm
|
||||
pnpm add @useautumn/ai-sdk
|
||||
```
|
||||
|
||||
```bash yarn
|
||||
yarn add @useautumn/ai-sdk
|
||||
```
|
||||
|
||||
```bash bun
|
||||
bun add @useautumn/ai-sdk
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
<Note>
|
||||
Requires `autumn-js` and `ai` (v6+) as peer dependencies.
|
||||
</Note>
|
||||
|
||||
#### 2. Wrap your model
|
||||
|
||||
Use `withAutumn` to wrap any AI SDK language model. It intercepts generate and stream calls, reads the token usage from the response, and reports it to Autumn automatically.
|
||||
|
||||
```typescript
|
||||
import { Autumn } from "autumn-js";
|
||||
import { anthropic } from "@ai-sdk/anthropic";
|
||||
import { withAutumn } from "@useautumn/ai-sdk";
|
||||
|
||||
const autumn = new Autumn({ secretKey: "am_sk_test_1234" });
|
||||
|
||||
const model = withAutumn({
|
||||
autumn,
|
||||
model: anthropic("claude-sonnet-4-5-20250514"),
|
||||
customerId: "user_123",
|
||||
});
|
||||
```
|
||||
|
||||
#### 3. Use as normal
|
||||
|
||||
The wrapped model works exactly like a regular AI SDK model. Token usage is tracked in the background after each call.
|
||||
|
||||
```typescript
|
||||
import { generateText, streamText } from "ai";
|
||||
|
||||
// Generate — usage tracked automatically
|
||||
const { text } = await generateText({
|
||||
model,
|
||||
prompt: "Explain quantum computing in one paragraph",
|
||||
});
|
||||
|
||||
// Stream — usage tracked when the stream finishes
|
||||
const result = streamText({
|
||||
model,
|
||||
prompt: "Write a short poem about recursion",
|
||||
});
|
||||
|
||||
for await (const chunk of result.textStream) {
|
||||
process.stdout.write(chunk);
|
||||
}
|
||||
```
|
||||
|
||||
## Token pools
|
||||
|
||||
The wrapper normalizes the AI SDK's usage object into the exclusive token pools that [trackTokens](/api-reference/balances/trackTokens) expects: text input (excluding cached tokens), text output (excluding reasoning tokens), cache reads, cache writes, and reasoning tokens. Each pool is billed at the model's published rate, so cached and reasoning-heavy requests are priced correctly without any extra work.
|
||||
|
||||
## Model ID format
|
||||
|
||||
The wrapped model constructs the `modelId` sent to Autumn using `provider/model` format, derived from the AI SDK model's `provider` and `modelId` fields. This must match a valid provider and model key from [Models.dev](https://models.dev).
|
||||
|
||||
For example:
|
||||
- `@ai-sdk/anthropic` → `anthropic/claude-sonnet-4-5-20250514`
|
||||
- `@ai-sdk/openai` → `openai/gpt-4o`
|
||||
- `@ai-sdk/google` → `google/gemini-2.5-pro`
|
||||
|
||||
If the AI SDK provider name doesn't match the Models.dev provider key, use the `providerId` option to override it:
|
||||
|
||||
```typescript
|
||||
import { createOpenRouter } from "@openrouter/ai-sdk-provider";
|
||||
|
||||
const openrouter = createOpenRouter();
|
||||
|
||||
const model = withAutumn({
|
||||
autumn,
|
||||
model: openrouter("anthropic/claude-opus-4.6"),
|
||||
customerId: "user_123",
|
||||
providerId: "openrouter", // Override provider prefix
|
||||
});
|
||||
// Sends modelId as "openrouter/anthropic/claude-opus-4.6"
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| `autumn` | `Autumn` | Yes | Your Autumn SDK client instance |
|
||||
| `model` | `LanguageModelV3` | Yes | The AI SDK language model to wrap |
|
||||
| `customerId` | `string` | Yes | The Autumn customer ID to attribute usage to |
|
||||
| `providerId` | `string` | No | Override the provider prefix in the model name sent to Autumn. Falls back to the model's `provider` field |
|
||||
| `featureId` | `string` | No | Target a specific AI credit system feature. Auto-detected if you only have one |
|
||||
| `entityId` | `string` | No | Entity ID for entity-scoped balance tracking |
|
||||
| `properties` | `Record<string, unknown>` | No | Additional properties to attach to each usage event |
|
||||
|
||||
## Full example
|
||||
|
||||
```typescript
|
||||
import { Autumn } from "autumn-js";
|
||||
import { openai } from "@ai-sdk/openai";
|
||||
import { generateText } from "ai";
|
||||
import { withAutumn } from "@useautumn/ai-sdk";
|
||||
|
||||
const autumn = new Autumn({ secretKey: process.env.AUTUMN_SECRET_KEY! });
|
||||
|
||||
async function chat(customerId: string, message: string) {
|
||||
const model = withAutumn({
|
||||
autumn,
|
||||
model: openai("gpt-4o"),
|
||||
customerId,
|
||||
});
|
||||
|
||||
const { text } = await generateText({
|
||||
model,
|
||||
prompt: message,
|
||||
});
|
||||
|
||||
return text;
|
||||
}
|
||||
```
|
||||
|
||||
<Tip>
|
||||
Tracking failures are caught and logged to the console — they won't break your AI features. Check your server logs if usage isn't appearing in Autumn.
|
||||
</Tip>
|
||||
@@ -271,3 +271,137 @@ See the credits pricing guide for a more detailed example of setting up a moneta
|
||||
href="/examples/monetary-credits"
|
||||
icon="money-bills"
|
||||
/>
|
||||
|
||||
## AI Credit Systems
|
||||
|
||||
For AI applications that need to track token usage with per-model pricing, you can create an AI credit system. This lets you define markup percentages for each model and automatically calculate costs based on input/output tokens.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="CLI">
|
||||
|
||||
Markups are optional. `defaultMarkup` applies to every model unless overridden — by `providerMarkups` (keyed by the first segment of the model ID, e.g. `openrouter`), or by `modelMarkups` for a specific model, which takes highest priority. With no markups set, models are billed at their Models.dev base cost.
|
||||
|
||||
A markup of `-100` makes the model free: usage events are still recorded, but nothing is deducted from the balance.
|
||||
|
||||
```ts Simplest setup — one markup for everything
|
||||
export const aiCredits = feature({
|
||||
id: 'ai_credits',
|
||||
name: 'AI Credits',
|
||||
type: 'ai_credit_system',
|
||||
defaultMarkup: 30, // every model billed at models.dev cost + 30%
|
||||
});
|
||||
```
|
||||
|
||||
Or mix the levels for finer control:
|
||||
|
||||
```ts autumn.config.ts
|
||||
import { feature, item, plan } from 'atmn';
|
||||
|
||||
export const aiCredits = feature({
|
||||
id: 'ai_credits',
|
||||
name: 'AI Credits',
|
||||
type: 'ai_credit_system',
|
||||
// Global fallback markup
|
||||
defaultMarkup: 30,
|
||||
// Per-provider defaults
|
||||
providerMarkups: {
|
||||
openrouter: { markup: 25 },
|
||||
},
|
||||
// Per-model overrides (highest priority)
|
||||
modelMarkups: {
|
||||
'anthropic/claude-opus-4-5': { markup: 20 },
|
||||
'anthropic/claude-sonnet-4-5': { markup: 15 },
|
||||
'openai/gpt-4o-mini': { markup: -100 }, // free for customers
|
||||
// For custom/self-hosted models, specify input/output costs in $/M tokens
|
||||
'custom/my-model': { markup: 25, inputCost: 0.01, outputCost: 0.03 },
|
||||
},
|
||||
});
|
||||
|
||||
export const pro = plan({
|
||||
id: 'pro',
|
||||
name: 'Pro',
|
||||
price: { amount: 50, interval: 'month' },
|
||||
items: [
|
||||
item({
|
||||
featureId: aiCredits.id,
|
||||
included: 10, // $10 worth of AI credits
|
||||
reset: { interval: 'month' },
|
||||
}),
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
Push changes with `atmn push`.
|
||||
|
||||
</Tab>
|
||||
<Tab title="Dashboard">
|
||||
|
||||
1. Navigate to the features page, under Plans.
|
||||
2. Click "Create Credit System"
|
||||
3. Toggle "AI Credit System" to enable model-based pricing
|
||||
4. Set a default markup %, and optionally add providers with their own default markups
|
||||
5. Add the models you want to support, overriding the markup per model where needed
|
||||
6. For custom models, also specify input/output costs per million tokens
|
||||
7. Click "Create"
|
||||
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
### Model ID Format
|
||||
|
||||
Model IDs follow the `provider/model` format:
|
||||
- Standard models: `anthropic/claude-opus-4-5`, `openai/gpt-4o`
|
||||
- OpenRouter models: `openrouter/anthropic/claude-opus-4.6`
|
||||
- Custom models: `custom/my-model-name`
|
||||
|
||||
For standard models, pricing is automatically fetched from models.dev, including separate rates for cache reads/writes, reasoning, and audio tokens where the model publishes them, plus large-context tier pricing (e.g. above 200k input tokens) when applicable.
|
||||
|
||||
For custom models, you must specify both `inputCost` and `outputCost` in dollars per million tokens — tracking fails if either is missing. Custom models bill input and output tokens only; cache, reasoning, and audio pools are ignored.
|
||||
|
||||
### Tracking Token Usage
|
||||
|
||||
Use the `trackTokens` endpoint to deduct credits based on token usage:
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```typescript TypeScript
|
||||
import { Autumn } from "autumn-js";
|
||||
|
||||
const autumn = new Autumn({ secretKey: "am_sk_test_1234" });
|
||||
|
||||
await autumn.balances.trackTokens({
|
||||
customerId: "user_123",
|
||||
modelId: "anthropic/claude-opus-4-5",
|
||||
inputTokens: 1500,
|
||||
outputTokens: 500,
|
||||
});
|
||||
```
|
||||
|
||||
```python Python
|
||||
from autumn_sdk import Autumn
|
||||
|
||||
autumn = Autumn("am_sk_test_1234")
|
||||
|
||||
await autumn.balances.track_tokens(
|
||||
customer_id="user_123",
|
||||
model_id="anthropic/claude-opus-4-5",
|
||||
input_tokens=1500,
|
||||
output_tokens=500,
|
||||
)
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl -X POST "https://api.useautumn.com/v1/balances.track_tokens" \
|
||||
-H "Authorization: Bearer am_sk_test_1234" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"customer_id": "user_123",
|
||||
"model_id": "anthropic/claude-opus-4-5",
|
||||
"input_tokens": 1500,
|
||||
"output_tokens": 500
|
||||
}'
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
The cost is calculated automatically based on the model's pricing plus your configured markup percentage.
|
||||
|
||||
@@ -25,6 +25,10 @@ Autumn enforces rate limits to ensure reliable performance for all users. Limits
|
||||
|
||||
When a rate limit is exceeded, the API returns a `429 Too Many Requests` response. Your application should back off and retry after the rate limit window resets.
|
||||
|
||||
## Service overload (503)
|
||||
|
||||
Under heavy load, customer-state endpoints (`/customers.get_or_create`, `/customers.get`, `/entities.get`) may briefly return a `503` with code `service_unavailable` and a `Retry-After` header (seconds). This is transient and unrelated to your request volume -- retry after the indicated delay. `check` and `track` are never shed this way (see [Fail-Open Defaults](/documentation/fail-open)).
|
||||
|
||||
## Preview endpoints are not rate limited
|
||||
|
||||
Preview endpoints like `/v1/attach/preview`, `/v1/billing.preview_attach`, and `/v1/billing.preview_update` are **not** subject to rate limits. You can call these freely to display pricing previews to your users.
|
||||
|
||||
@@ -105,12 +105,12 @@ export default async function BlogPostPage({ params }: { params: BlogParams }) {
|
||||
</header>
|
||||
|
||||
{post.image && (
|
||||
<div className="relative w-full aspect-[2/1] overflow-hidden border border-[#292929] mb-12">
|
||||
<div className="relative w-full aspect-[2/1] overflow-hidden border border-[#292929] bg-[#080808] mb-12">
|
||||
<Image
|
||||
src={post.image}
|
||||
alt={post.title}
|
||||
fill
|
||||
className="object-cover"
|
||||
className="object-contain"
|
||||
priority
|
||||
sizes="(max-width: 768px) 100vw, 720px"
|
||||
/>
|
||||
|
||||
124
apps/website/content/blog/active-active-redis-cache.mdx
Normal file
@@ -0,0 +1,124 @@
|
||||
---
|
||||
title: "Active-active fixed our counters and broke everything else"
|
||||
description: "Why we moved Autumn to a multi-region architecture, the tradeoffs we hit with active-active Redis, and why we eventually returned to a simpler single-region setup."
|
||||
date: "2026-06-08"
|
||||
author: "John, Autumn Co-Founder"
|
||||
slug: "active-active-redis-cache"
|
||||
image: "/images/blog/multi-region-initial-architecture.png"
|
||||
---
|
||||
|
||||
Last year we started to onboard companies with a global customer base. With our own users starting to appear in more regions, we decided to build a multi-region architecture to reduce latency times globally.
|
||||
|
||||
Initially, our services were isolated to one region, us-west.
|
||||
|
||||
Our aim was to reduce latency in two regions to start, us-west and us-east, and targeted a round trip latency of under 50ms. The main difficulty was that this applied to both reads and writes, so simply using DB read replicas weren’t an option. Ultimately, there were two major considerations:
|
||||
|
||||
- How to spin up our server in multiple regions
|
||||
- More crucially though, how to make data reads and writes low latency across regions
|
||||
|
||||
## Spinning up our server in multiple regions
|
||||
|
||||
There were two options here. Either we went serverless with something like Cloudflare Workers, or we manually spun up stateful servers in different regions. We went with the latter for a couple reasons:
|
||||
|
||||
1. The whole point of this was to reduce latency. With serverless, we were afraid of inconsistent latencies due to cold startup times, which we benchmarked and proved to be true.
|
||||
2. Our server was already stateful, and going serverless would’ve broken patterns we relied on. Event batching, for one, gets painful when every request runs in an isolated session.
|
||||
|
||||
This blog from [Unkey](https://www.unkey.com/blog/serverless-exit) was really helpful when we made our decision. Now our next challenge was deciding on a provider. Our requirements were simple:
|
||||
|
||||
- Latency should be as low as possible
|
||||
- Spinning up multi-region servers should be as simple as possible
|
||||
|
||||
Surprisingly, we tried almost every provider we could find and none of them fit perfectly. We ultimately chose AWS ECS, managed through Flightcontrol, where we spun up an ECS service in us-west and us-east, then used Route53 to route requests based on region.
|
||||
|
||||

|
||||
|
||||
To explain why we came to this decision, it’s worth walking through the other top contenders.
|
||||
|
||||
**[Render](https://render.com/)**
|
||||
|
||||
We were originally on Render so this seemed like the obvious choice. However, Render doesn’t natively support multi-region, so to set this up we had to manually create instances in each region. More annoyingly though, the only way to have a single domain route to different instances was to use Cloudflare’s load balancer.
|
||||
|
||||

|
||||
|
||||
Ultimately, we chose AWS over Render because we found that Cloudflare's Load Balancer introduced additional latency compared to Route53, which resolved at the DNS layer. With Render, there were also multiple hops involved as Render itself uses Cloudflare in front of their services.
|
||||
|
||||
**[Railway](https://railway.com/)**
|
||||
|
||||
Railway was extremely compelling because they supported multi-region natively. That meant that you could spin up a single service, have it replicated across different regions, and they would handle load balancing, provisioning, and more for you. The DX was unmatched. Unfortunately though, Railway’s infra isn’t on AWS. They build their own machines. This means a couple things:
|
||||
|
||||
- Our database, cache, and other data stores wouldn’t be co-located with our server, unless we used Railway for those as well, which was too limiting for us
|
||||
- Most of our users were also hosted on AWS so their servers wouldn’t be as close to ours
|
||||
|
||||

|
||||
|
||||
Ultimately, with both providers, the decision came down to latency. AWS consistently provided the lowest latencies in our benchmarks.
|
||||
|
||||

|
||||
|
||||
That said, ECS came with a bunch of maintenance overhead, especially coming from Render. Even with Flightcontrol, we had to build an internal dashboard to build and deploy across regions at once. Moreover, application and load balancer logs were an absolute pain to set up. But today I’m very glad we made the tradeoff. Having lower-level control over our infra has been useful, and AI has made things much easier too.
|
||||
|
||||
## Making data reads and writes multi-region
|
||||
|
||||
The bigger challenge we faced was with data access: making both reads and writes fast across regions. Think of us as a complex rate limiter. Before a request is allowed through, we often need to update usage counters atomically and decide whether the customer still has access.
|
||||
|
||||
For example, when you send a message to Cursor, they may deduct an estimated number of credits before accepting your message, then reconcile the actual usage afterwards. Since these writes sit on the hot path, they need to be real-time and fast. We considered several approaches to solving this.
|
||||
|
||||
1. **A master database per region**
|
||||
|
||||
We’d spin up a Postgres database in each region, completely isolated from each other, and let our users pick which region their data lives in, so it sits closest to their server. The catch, beyond running multiple databases, is that our user’s own customers might be spread across regions. For example, if they’re running Cloudflare Workers, pinning a whole account to one region doesn’t hold up.
|
||||
|
||||

|
||||
|
||||
2. **A region per customer**
|
||||
|
||||
Instead of pinning our user, we could pin a customer: our user’s user. Each customer is tied to a region, and all their reads and writes happen there. We'd keep a record mapping customers to regions, and route each request accordingly.
|
||||
|
||||

|
||||
|
||||
Now trying to do this with Postgres sounded like a headache. Imagine trying to JOIN data across different databases. We could simplify this with a read/write cache in each region instead of fully separate Postgres databases, but we still ruled it out because of the routing layer. We'd need yet another cache for the customer-to-region mapping, itself replicated across regions, and getting every request to the right region felt like way too much overhead.
|
||||
|
||||
3. **Active-active Redis database**
|
||||
|
||||
The final approach, which we ended up going with, was using an Active-Active database from Redis Cloud. You spin up Redis caches in multiple regions, all fully synced, and you can write to any of them. When concurrent writes hit the same key in different regions, Redis Cloud resolves the conflict using CRDTs: Conflict-free Replicated Data Types.
|
||||
|
||||
Using a counter as an example: two concurrent increment operations merge into their sum rather than overwriting each other. This fit our use case perfectly. Each server connects to its own Redis cache in the cluster, and since our writes are just increments, the conflicts get resolved for us.
|
||||
|
||||
## Why we went back
|
||||
|
||||
We chose the Active-Active Redis database for simplicity, and while it definitely created the least infra overhead, I think it wasn’t really the right solution for us, which led to more complexity than it was worth.
|
||||
|
||||
1. **Race conditions**
|
||||
|
||||
First of all, with the active-active database, even though it solved that counter case perfectly, we found ourselves running into a bunch of race conditions. Take the following example:
|
||||
|
||||
- We store each customer as a JSON blob with `customer_id` as the key
|
||||
- Your customer performs an upgrade on us-east so we append to their `subscriptions` array
|
||||
- At the same time, Stripe sends an `invoice.paid` webhook to our us-west server and we append to the customer’s `invoices` array
|
||||
|
||||
Now, both of these append operations happen on the same key and are done via a read-update-set operation. Since they happen in different regions, Redis resolves the conflict through a Last-Write-Win strategy. So either the invoices or subscriptions array will be missing an item.
|
||||
|
||||
To solve these types of issues, we’d often have to normalize the data. For instance, we might store the subscriptions and invoices array as separate keys, `customer_id:subscriptions` and `customer_id:invoices`. Ultimately though, we ran into these issues more often than we’d hoped, especially since it was hard to replicate a multi-region setup locally.
|
||||
|
||||
2. **Infra overhead**
|
||||
|
||||
The second issue we kept running into was infra overhead. It wasn’t just slowing us down; it was starting to affect reliability too.
|
||||
|
||||
A couple of months ago, we had a user run a cron job every hour that spiked our Redis CPU and degraded the server. The quick fix would’ve been to spin up a separate Redis database for that user, so their load wouldn’t impact everyone else. But because of our multi-region architecture, what should have been a simple isolation fix became much more complex and delayed.
|
||||
|
||||
Reliability matters more to us than latency. So when our architecture made it harder to ship reliability fixes quickly, that was a strong signal that the tradeoff no longer made sense.
|
||||
|
||||
Ultimately, the thing that pushed us to move back to a single-region architecture was noticing that traffic was split roughly 95:5 between us-east and us-west. Taking on all of that complexity and giving up speed and reliability for this small slice of traffic didn’t feel worth it.
|
||||
|
||||
## Conclusion
|
||||
|
||||
Ever since we’ve moved back to a single-region architecture, we’ve been way more confident in our infra and reliability, and have been able to make changes, introduce new services, and ship features way faster too. Focusing on optimizing a smaller scope has felt like a huge difference. So generally, we’re very happy about our decision. Now, two concluding thoughts:
|
||||
|
||||
**Don’t “move fast and break things” with infra**
|
||||
|
||||
I think the mistake we made with our multi-region setup was optimizing for simplicity and speed rather than choosing the architecture that would hold up best long term. Infra is a little counterintuitive to the usual “ship fast” startup advice. These decisions affect reliability directly, and they’re often some of the hardest decisions to reverse later. So while speed still matters, infra choices deserve more upfront thought than your average product decision.
|
||||
|
||||
**The “smart” choice isn’t always the best one**
|
||||
|
||||
With our original approach, I think we convinced ourselves that an Active-Active Redis database would be a silver bullet, and that choosing it was the “smart” move. But infra is all about tradeoffs. There’s a reason writable database replicas aren’t common: they add a lot of complexity, and that complexity has to show up somewhere.
|
||||
|
||||
We’ll definitely go back to multi-region at some point. But when we do, I think we’ll take a “less hacky” approach: route each customer to a single home region, and keep their data and traffic there. It’s much easier to reason about, and probably a lot more reliable.
|
||||
@@ -20,6 +20,20 @@ const nextConfig = {
|
||||
// falling back to WebP. Next.js negotiates via Accept header automatically.
|
||||
formats: ["image/avif", "image/webp"],
|
||||
},
|
||||
async redirects() {
|
||||
return [
|
||||
{
|
||||
source: "/docs",
|
||||
destination: "https://docs.useautumn.com",
|
||||
permanent: false,
|
||||
},
|
||||
{
|
||||
source: "/blog/how-we-built-a-multi-region-architecture-and-why-we-went-back",
|
||||
destination: "/blog/active-active-redis-cache",
|
||||
permanent: true,
|
||||
},
|
||||
];
|
||||
},
|
||||
async headers() {
|
||||
if (!isProd) return [];
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 3.0 MiB |
|
After Width: | Height: | Size: 1.0 MiB |
@@ -0,0 +1,75 @@
|
||||
<svg width="2048" height="430" viewBox="0 0 2048 430" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="2048" height="430" fill="#FFFFFF"/>
|
||||
<rect x="0" y="0" width="2048" height="430" fill="#FFFFFF"/>
|
||||
<line x1="34" y1="88" x2="2014" y2="88" stroke="#E2E7EE" stroke-width="1.5"/>
|
||||
<line x1="34" y1="202" x2="2014" y2="202" stroke="#E2E7EE" stroke-width="1.5"/>
|
||||
<line x1="34" y1="316" x2="2014" y2="316" stroke="#E2E7EE" stroke-width="1.5"/>
|
||||
<text x="64" y="58" fill="#526070" font-family="Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" font-size="17" font-weight="500" letter-spacing="0.2">MONITOR</text>
|
||||
<text x="766" y="58" fill="#526070" font-family="Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" font-size="17" font-weight="500" letter-spacing="0.2">TYPE</text>
|
||||
<text x="876" y="58" fill="#526070" font-family="Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" font-size="17" font-weight="500" letter-spacing="0.2">LAST 24 HRS</text>
|
||||
<text x="1242" y="58" fill="#526070" font-family="Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" font-size="17" font-weight="500" letter-spacing="0.2">UPTIME</text>
|
||||
<text x="1386" y="58" fill="#526070" font-family="Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" font-size="17" font-weight="500" letter-spacing="0.2">SUCCESS</text>
|
||||
<text x="1552" y="58" fill="#526070" font-family="Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" font-size="17" font-weight="500" letter-spacing="0.2">P99</text>
|
||||
<text x="1680" y="58" fill="#526070" font-family="Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" font-size="17" font-weight="500" letter-spacing="0.2">RESPONSE</text>
|
||||
<text x="1830" y="58" fill="#526070" font-family="Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" font-size="17" font-weight="500" letter-spacing="0.2">INTERVAL</text>
|
||||
|
||||
<g transform="translate(64 116)">
|
||||
<circle cx="28" cy="28" r="28" fill="#63CE6C"/>
|
||||
<path d="M17 29.5L25 37.5L41 18.5" stroke="white" stroke-width="4.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<text x="106" y="24" fill="#202632" font-family="Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" font-size="26" font-weight="700">AWS ECS Get Customer (US East)</text>
|
||||
<text x="106" y="62" fill="#718093" font-family="Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" font-size="21" font-weight="400">1 minute ago</text>
|
||||
<rect x="702" y="16" width="45" height="34" rx="4" fill="#263140"/>
|
||||
<text x="712" y="38" fill="#FFFFFF" font-family="'SF Mono', ui-monospace, Menlo, monospace" font-size="15" font-weight="700">API</text>
|
||||
<g fill="#60C96A" transform="translate(812 11)">
|
||||
<rect x="0" y="30" width="7" height="35"/><rect x="18" y="22" width="7" height="43"/><rect x="36" y="42" width="7" height="23"/><rect x="54" y="18" width="7" height="47"/><rect x="72" y="54" width="7" height="11"/><rect x="90" y="32" width="7" height="33"/><rect x="108" y="12" width="7" height="53"/><rect x="126" y="36" width="7" height="29"/><rect x="144" y="45" width="7" height="20"/><rect x="162" y="38" width="7" height="27"/><rect x="180" y="29" width="7" height="36"/><rect x="198" y="44" width="7" height="21"/><rect x="216" y="52" width="7" height="13"/><rect x="234" y="26" width="7" height="39"/><rect x="252" y="48" width="7" height="17"/><rect x="270" y="42" width="7" height="23"/><rect x="288" y="18" width="7" height="47"/>
|
||||
</g>
|
||||
<text x="1178" y="43" fill="#202632" font-family="Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" font-size="25" font-weight="400">100 %</text>
|
||||
<text x="1330" y="43" fill="#202632" font-family="Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" font-size="25" font-weight="400">100 %</text>
|
||||
<text x="1492" y="43" fill="#202632" font-family="Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" font-size="25" font-weight="400">55</text>
|
||||
<text x="1526" y="43" fill="#202632" font-family="Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" font-size="18" font-weight="400"> ms</text>
|
||||
<text x="1638" y="43" fill="#202632" font-family="Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" font-size="25" font-weight="400">88</text>
|
||||
<text x="1672" y="43" fill="#202632" font-family="Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" font-size="18" font-weight="400"> ms</text>
|
||||
<text x="1788" y="43" fill="#202632" font-family="Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" font-size="25" font-weight="400">1 min</text>
|
||||
<circle cx="1908" cy="23" r="3.5" fill="#68778A"/><circle cx="1908" cy="39" r="3.5" fill="#68778A"/><circle cx="1908" cy="55" r="3.5" fill="#68778A"/>
|
||||
</g>
|
||||
|
||||
<g transform="translate(64 230)">
|
||||
<circle cx="28" cy="28" r="28" fill="#63CE6C"/>
|
||||
<path d="M17 29.5L25 37.5L41 18.5" stroke="white" stroke-width="4.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<text x="106" y="24" fill="#202632" font-family="Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" font-size="26" font-weight="700">Railway Get Customer (US East)</text>
|
||||
<text x="106" y="62" fill="#718093" font-family="Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" font-size="21" font-weight="400">less than a minute ago</text>
|
||||
<rect x="702" y="16" width="45" height="34" rx="4" fill="#263140"/>
|
||||
<text x="712" y="38" fill="#FFFFFF" font-family="'SF Mono', ui-monospace, Menlo, monospace" font-size="15" font-weight="700">API</text>
|
||||
<g fill="#60C96A" transform="translate(812 11)">
|
||||
<rect x="0" y="20" width="7" height="45"/><rect x="18" y="42" width="7" height="23"/><rect x="36" y="51" width="7" height="14"/><rect x="54" y="48" width="7" height="17"/><rect x="72" y="34" width="7" height="31"/><rect x="90" y="45" width="7" height="20"/><rect x="108" y="41" width="7" height="24"/><rect x="126" y="39" width="7" height="26"/><rect x="144" y="56" width="7" height="9"/><rect x="162" y="47" width="7" height="18"/><rect x="180" y="37" width="7" height="28"/><rect x="198" y="35" width="7" height="30"/><rect x="216" y="43" width="7" height="22"/><rect x="234" y="51" width="7" height="14"/><rect x="252" y="28" width="7" height="37"/><rect x="270" y="38" width="7" height="27"/><rect x="288" y="14" width="7" height="51"/>
|
||||
</g>
|
||||
<text x="1178" y="43" fill="#202632" font-family="Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" font-size="25" font-weight="400">100 %</text>
|
||||
<text x="1330" y="43" fill="#202632" font-family="Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" font-size="25" font-weight="400">100 %</text>
|
||||
<text x="1492" y="43" fill="#202632" font-family="Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" font-size="25" font-weight="400">120</text>
|
||||
<text x="1538" y="43" fill="#202632" font-family="Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" font-size="18" font-weight="400"> ms</text>
|
||||
<text x="1638" y="43" fill="#202632" font-family="Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" font-size="25" font-weight="400">178</text>
|
||||
<text x="1684" y="43" fill="#202632" font-family="Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" font-size="18" font-weight="400"> ms</text>
|
||||
<text x="1788" y="43" fill="#202632" font-family="Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" font-size="25" font-weight="400">1 min</text>
|
||||
<circle cx="1908" cy="23" r="3.5" fill="#68778A"/><circle cx="1908" cy="39" r="3.5" fill="#68778A"/><circle cx="1908" cy="55" r="3.5" fill="#68778A"/>
|
||||
</g>
|
||||
|
||||
<g transform="translate(64 344)">
|
||||
<circle cx="28" cy="28" r="28" fill="#63CE6C"/>
|
||||
<path d="M17 29.5L25 37.5L41 18.5" stroke="white" stroke-width="4.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<text x="106" y="24" fill="#202632" font-family="Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" font-size="26" font-weight="700">Render Get Customer (US East)</text>
|
||||
<text x="106" y="62" fill="#718093" font-family="Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" font-size="21" font-weight="400">less than a minute ago</text>
|
||||
<rect x="702" y="16" width="45" height="34" rx="4" fill="#263140"/>
|
||||
<text x="712" y="38" fill="#FFFFFF" font-family="'SF Mono', ui-monospace, Menlo, monospace" font-size="15" font-weight="700">API</text>
|
||||
<g fill="#60C96A" transform="translate(812 11)">
|
||||
<rect x="0" y="44" width="7" height="21"/><rect x="18" y="54" width="7" height="11"/><rect x="36" y="48" width="7" height="17"/><rect x="54" y="5" width="7" height="60"/><rect x="72" y="53" width="7" height="12"/><rect x="90" y="42" width="7" height="23"/><rect x="108" y="38" width="7" height="27"/><rect x="126" y="36" width="7" height="29"/><rect x="144" y="41" width="7" height="24"/><rect x="162" y="46" width="7" height="19"/><rect x="180" y="45" width="7" height="20"/><rect x="198" y="19" width="7" height="46"/><rect x="216" y="51" width="7" height="14"/><rect x="234" y="36" width="7" height="29"/><rect x="252" y="47" width="7" height="18"/><rect x="270" y="30" width="7" height="35"/><rect x="288" y="40" width="7" height="25"/>
|
||||
</g>
|
||||
<text x="1178" y="43" fill="#202632" font-family="Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" font-size="25" font-weight="400">100 %</text>
|
||||
<text x="1330" y="43" fill="#202632" font-family="Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" font-size="25" font-weight="400">100 %</text>
|
||||
<text x="1492" y="43" fill="#202632" font-family="Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" font-size="25" font-weight="400">106</text>
|
||||
<text x="1538" y="43" fill="#202632" font-family="Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" font-size="18" font-weight="400"> ms</text>
|
||||
<text x="1638" y="43" fill="#202632" font-family="Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" font-size="25" font-weight="400">154</text>
|
||||
<text x="1684" y="43" fill="#202632" font-family="Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" font-size="18" font-weight="400"> ms</text>
|
||||
<text x="1788" y="43" fill="#202632" font-family="Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" font-size="25" font-weight="400">1 min</text>
|
||||
<circle cx="1908" cy="23" r="3.5" fill="#68778A"/><circle cx="1908" cy="39" r="3.5" fill="#68778A"/><circle cx="1908" cy="55" r="3.5" fill="#68778A"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
34
bun.lock
@@ -182,6 +182,22 @@
|
||||
"typescript": "^6.0.2",
|
||||
},
|
||||
},
|
||||
"packages/ai-sdk": {
|
||||
"name": "@useautumn/ai-sdk",
|
||||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"@ai-sdk/provider": "^3.0.0",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.9.1",
|
||||
"tsup": "^8.4.0",
|
||||
"typescript": "^5.8.3",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"ai": "^6.0.116",
|
||||
"autumn-js": "*",
|
||||
},
|
||||
},
|
||||
"packages/atmn": {
|
||||
"name": "atmn",
|
||||
"version": "1.1.8",
|
||||
@@ -666,13 +682,11 @@
|
||||
"unrs-resolver",
|
||||
],
|
||||
"overrides": {
|
||||
"@better-auth/core": "1.6.5",
|
||||
"@better-auth/passkey": "1.6.5",
|
||||
"@isaacs/brace-expansion": "5.0.1",
|
||||
"@modelcontextprotocol/sdk": "1.29.0",
|
||||
"@smithy/config-resolver": "^4.4.0",
|
||||
"@types/pg": "8.20.0",
|
||||
"better-auth": "1.6.5",
|
||||
"diff": "8.0.3",
|
||||
"esbuild": "0.25.0",
|
||||
"fast-xml-parser": "5.3.4",
|
||||
@@ -2780,6 +2794,8 @@
|
||||
|
||||
"@upstash/redis": ["@upstash/redis@1.38.0", "", { "dependencies": { "uncrypto": "^0.1.3" } }, "sha512-wu+dZBptlLy0+MCUEoHmzrY/TnmgDey3+c7EbIGwrLqAvkP8yi5MWZHYGIFtAygmL4Bkz2TdFu+eU0vFPncIcg=="],
|
||||
|
||||
"@useautumn/ai-sdk": ["@useautumn/ai-sdk@workspace:packages/ai-sdk"],
|
||||
|
||||
"@useautumn/sdk": ["@useautumn/sdk@workspace:packages/sdk"],
|
||||
|
||||
"@vdemedes/prettier-config": ["@vdemedes/prettier-config@2.0.1", "", {}, "sha512-lcHyyLfS2ro282qsXKpxw+canUkOlFIGoanxt3BaNCm5K1NR8k4hGvYbFO54/+QWq12d0y/EYRz68yNQkqWFrw=="],
|
||||
@@ -6340,6 +6356,10 @@
|
||||
|
||||
"@babel/helper-create-class-features-plugin/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||
|
||||
"@better-auth/cli/@better-auth/core": ["@better-auth/core@1.4.21", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "zod": "^4.3.5" }, "peerDependencies": { "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "1.1.21", "better-call": "1.1.8", "jose": "^6.1.0", "kysely": "^0.28.5", "nanostores": "^1.0.1" } }, "sha512-R4s7pwShkqB21fZ599QASbXxqFcoxanLyz7DHSX6SJPNYV748wBLsm3xM9VrjfvWMpS+cQUErOCt9yWT1hMn6w=="],
|
||||
|
||||
"@better-auth/cli/better-auth": ["better-auth@1.4.21", "", { "dependencies": { "@better-auth/core": "1.4.21", "@better-auth/telemetry": "1.4.21", "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "1.1.21", "@noble/ciphers": "^2.0.0", "@noble/hashes": "^2.0.0", "better-call": "1.1.8", "defu": "^6.1.4", "jose": "^6.1.0", "kysely": "^0.28.5", "nanostores": "^1.0.1", "zod": "^4.3.5" }, "peerDependencies": { "@lynx-js/react": "*", "@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0", "@sveltejs/kit": "^2.0.0", "@tanstack/react-start": "^1.0.0", "@tanstack/solid-start": "^1.0.0", "better-sqlite3": "^12.0.0", "drizzle-kit": ">=0.31.4", "drizzle-orm": ">=0.41.0", "mongodb": "^6.0.0 || ^7.0.0", "mysql2": "^3.0.0", "next": "^14.0.0 || ^15.0.0 || ^16.0.0", "pg": "^8.0.0", "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0", "solid-js": "^1.0.0", "svelte": "^4.0.0 || ^5.0.0", "vitest": "^2.0.0 || ^3.0.0 || ^4.0.0", "vue": "^3.0.0" }, "optionalPeers": ["@lynx-js/react", "@prisma/client", "@sveltejs/kit", "@tanstack/react-start", "@tanstack/solid-start", "better-sqlite3", "drizzle-kit", "drizzle-orm", "mongodb", "mysql2", "next", "pg", "prisma", "react", "react-dom", "solid-js", "svelte", "vitest", "vue"] }, "sha512-qdrIZS7xnGF2HPBV5wYNPWTkPojhauOOjz1+MhLvwFy+zXpgLofQmWsI5I9DY+ef845NKt93XcgpyAc4RPPT9A=="],
|
||||
|
||||
"@better-auth/cli/commander": ["commander@12.1.0", "", {}, "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA=="],
|
||||
|
||||
"@better-auth/cli/dotenv": ["dotenv@17.4.2", "", {}, "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw=="],
|
||||
@@ -7122,6 +7142,8 @@
|
||||
|
||||
"@typespec/ts-http-runtime/https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="],
|
||||
|
||||
"@useautumn/ai-sdk/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||
|
||||
"@useautumn/sdk/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="],
|
||||
|
||||
"@vercel/sdk/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="],
|
||||
@@ -8100,6 +8122,10 @@
|
||||
|
||||
"@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
|
||||
|
||||
"@better-auth/cli/@better-auth/core/better-call": ["better-call@1.1.8", "", { "dependencies": { "@better-auth/utils": "^0.3.0", "@better-fetch/fetch": "^1.1.4", "rou3": "^0.7.10", "set-cookie-parser": "^2.7.1" }, "peerDependencies": { "zod": "^4.0.0" }, "optionalPeers": ["zod"] }, "sha512-XMQ2rs6FNXasGNfMjzbyroSwKwYbZ/T3IxruSS6U2MJRsSYh3wYtG3o6H00ZlKZ/C/UPOAD97tqgQJNsxyeTXw=="],
|
||||
|
||||
"@better-auth/cli/better-auth/better-call": ["better-call@1.1.8", "", { "dependencies": { "@better-auth/utils": "^0.3.0", "@better-fetch/fetch": "^1.1.4", "rou3": "^0.7.10", "set-cookie-parser": "^2.7.1" }, "peerDependencies": { "zod": "^4.0.0" }, "optionalPeers": ["zod"] }, "sha512-XMQ2rs6FNXasGNfMjzbyroSwKwYbZ/T3IxruSS6U2MJRsSYh3wYtG3o6H00ZlKZ/C/UPOAD97tqgQJNsxyeTXw=="],
|
||||
|
||||
"@datadog/datadog-api-client/@types/node/undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="],
|
||||
|
||||
"@dotenvx/dotenvx/execa/get-stream": ["get-stream@6.0.1", "", {}, "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="],
|
||||
@@ -9468,6 +9494,10 @@
|
||||
|
||||
"@aws-sdk/signature-v4/@smithy/signature-v4/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@1.1.0", "", { "dependencies": { "@smithy/is-array-buffer": "^1.1.0", "tslib": "^2.5.0" } }, "sha512-9m6NXE0ww+ra5HKHCHig20T+FAwxBAm7DIdwc/767uGWbRcY720ybgPacQNB96JMOI7xVr/CDa3oMzKmW4a+kw=="],
|
||||
|
||||
"@better-auth/cli/@better-auth/core/better-call/rou3": ["rou3@0.7.12", "", {}, "sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg=="],
|
||||
|
||||
"@better-auth/cli/better-auth/better-call/rou3": ["rou3@0.7.12", "", {}, "sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg=="],
|
||||
|
||||
"@dotenvx/dotenvx/execa/onetime/mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="],
|
||||
|
||||
"@eslint/config-array/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||
|
||||
@@ -25,6 +25,7 @@ COPY apps/checkout/package.json apps/checkout/
|
||||
COPY apps/docs/package.json apps/docs/
|
||||
COPY apps/sdk-test/package.json apps/sdk-test/
|
||||
COPY apps/website/package.json apps/website/
|
||||
COPY packages/ai-sdk/package.json packages/ai-sdk/
|
||||
COPY packages/atmn/package.json packages/atmn/
|
||||
COPY packages/atmn-tests/package.json packages/atmn-tests/
|
||||
COPY packages/auth/package.json packages/auth/
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"duplicates"
|
||||
],
|
||||
"ignoreWorkspaces": [
|
||||
"packages/ai-sdk",
|
||||
"packages/atmn",
|
||||
"packages/autumn-js",
|
||||
"packages/mcp",
|
||||
|
||||
@@ -122,6 +122,24 @@ actions:
|
||||
|
||||
res = autumn.track(customer_id="cus_123", feature_id="messages", value=1)
|
||||
|
||||
# Handle response
|
||||
print(res)
|
||||
- target: $["paths"]["/v1/balances.track_tokens"]["post"]
|
||||
update:
|
||||
x-codeSamples:
|
||||
- lang: python
|
||||
label: Python (SDK)
|
||||
source: |-
|
||||
from autumn_sdk import Autumn
|
||||
|
||||
|
||||
with Autumn(
|
||||
x_api_version="2.3.0",
|
||||
secret_key="<YOUR_BEARER_TOKEN_HERE>",
|
||||
) as autumn:
|
||||
|
||||
res = autumn.track_tokens(customer_id="cus_123", model_id="anthropic/claude-sonnet-4-20250514", input_tokens=1000, output_tokens=500, feature_id="ai_credits")
|
||||
|
||||
# Handle response
|
||||
print(res)
|
||||
- target: $["paths"]["/v1/balances.update"]["post"]
|
||||
|
||||
@@ -200,6 +200,9 @@ Use this to gate access before a feature action. Enable sendEvent when you want
|
||||
* [track](docs/sdks/autumn/README.md#track) - Records usage for a customer feature and returns updated balances.
|
||||
|
||||
Use this after an action happens to decrement usage, or send a negative value to credit balance back.
|
||||
* [track_tokens](docs/sdks/autumn/README.md#track_tokens) - Records AI token usage for a customer and returns the updated AI credit balance.
|
||||
|
||||
Use this after an LLM request when you have input and output token counts. Autumn converts token usage to a dollar amount using the configured model pricing and markup, then tracks that value against the customer's AI credit system.
|
||||
* [batch_track](docs/sdks/autumn/README.md#batch_track) - Enqueue up to 1000 usage events for asynchronous processing. Items are validated synchronously up front; validated items are then enqueued via SQS for background deduction by workers. The response returns 202 immediately and does not include balance information. On partial enqueue failure (some items fail to enqueue, others succeed), the endpoint still returns 202 and logs the failures server-side; clients should NOT retry, because retrying re-enqueues the already-succeeded items. A 503 is returned only when zero items were successfully enqueued (queue entirely unavailable) — that case is safe to retry.
|
||||
|
||||
### [Balances](docs/sdks/balances/README.md)
|
||||
|
||||
@@ -5,7 +5,7 @@ from autumn_sdk import errors, models, utils
|
||||
from autumn_sdk._hooks import HookContext
|
||||
from autumn_sdk.types import BaseModel, OptionalNullable, UNSET
|
||||
from autumn_sdk.utils.unmarshal_json_response import unmarshal_json_response
|
||||
from typing import List, Mapping, Optional, Union, cast
|
||||
from typing import Dict, List, Mapping, Optional, Union, cast
|
||||
|
||||
|
||||
class Features(BaseSDK):
|
||||
@@ -13,21 +13,34 @@ class Features(BaseSDK):
|
||||
self,
|
||||
*,
|
||||
name: str,
|
||||
type_: models.CreateFeatureTypeRequest,
|
||||
type_: models.CreateFeatureTypeRequestBody,
|
||||
feature_id: str,
|
||||
consumable: Optional[bool] = None,
|
||||
display: Optional[
|
||||
Union[
|
||||
models.CreateFeatureDisplayRequest,
|
||||
models.CreateFeatureDisplayRequestTypedDict,
|
||||
models.CreateFeatureDisplayRequestBody,
|
||||
models.CreateFeatureDisplayRequestBodyTypedDict,
|
||||
]
|
||||
] = None,
|
||||
credit_schema: Optional[
|
||||
Union[
|
||||
List[models.CreateFeatureCreditSchemaRequest],
|
||||
List[models.CreateFeatureCreditSchemaRequestTypedDict],
|
||||
List[models.CreateFeatureCreditSchemaRequestBody],
|
||||
List[models.CreateFeatureCreditSchemaRequestBodyTypedDict],
|
||||
]
|
||||
] = None,
|
||||
model_markups: OptionalNullable[
|
||||
Union[
|
||||
Dict[str, models.CreateFeatureModelMarkupsRequest],
|
||||
Dict[str, models.CreateFeatureModelMarkupsRequestTypedDict],
|
||||
]
|
||||
] = UNSET,
|
||||
default_markup: Optional[float] = None,
|
||||
provider_markups: OptionalNullable[
|
||||
Union[
|
||||
Dict[str, models.CreateFeatureProviderMarkupsRequest],
|
||||
Dict[str, models.CreateFeatureProviderMarkupsRequestTypedDict],
|
||||
]
|
||||
] = UNSET,
|
||||
event_names: Optional[List[str]] = None,
|
||||
retries: OptionalNullable[utils.RetryConfig] = UNSET,
|
||||
server_url: Optional[str] = None,
|
||||
@@ -43,7 +56,10 @@ class Features(BaseSDK):
|
||||
:param feature_id: The ID of the feature to create.
|
||||
:param consumable: Whether this feature is consumable. A consumable feature is one that periodically resets and is consumed rather than allocated (like credits, API requests, etc.). Applicable only for 'metered' features.
|
||||
:param display: Singular and plural display names for the feature in your user interface.
|
||||
:param credit_schema: A schema that maps 'single_use' feature IDs to credit costs. Applicable only for 'credit_system' features.
|
||||
:param credit_schema: A schema that maps 'single_use' feature IDs to credit costs. For classic credit systems only — AI credit systems use model_markups instead.
|
||||
:param model_markups: Per-model markup overrides for AI credit systems. Maps model IDs to their markup configuration.
|
||||
:param default_markup: Default percentage markup for this AI credit system. Used when no model or provider markup applies. Use -100 to make usage free.
|
||||
:param provider_markups: Per-provider default markup percentages for AI credit systems. Provider keys match the first segment of model_id.
|
||||
:param event_names:
|
||||
:param retries: Override the default retry configuration for this method
|
||||
:param server_url: Override the default server URL for this method
|
||||
@@ -65,10 +81,20 @@ class Features(BaseSDK):
|
||||
type=type_,
|
||||
consumable=consumable,
|
||||
display=utils.get_pydantic_model(
|
||||
display, Optional[models.CreateFeatureDisplayRequest]
|
||||
display, Optional[models.CreateFeatureDisplayRequestBody]
|
||||
),
|
||||
credit_schema=utils.get_pydantic_model(
|
||||
credit_schema, Optional[List[models.CreateFeatureCreditSchemaRequest]]
|
||||
credit_schema,
|
||||
Optional[List[models.CreateFeatureCreditSchemaRequestBody]],
|
||||
),
|
||||
model_markups=utils.get_pydantic_model(
|
||||
model_markups,
|
||||
OptionalNullable[Dict[str, models.CreateFeatureModelMarkupsRequest]],
|
||||
),
|
||||
default_markup=default_markup,
|
||||
provider_markups=utils.get_pydantic_model(
|
||||
provider_markups,
|
||||
OptionalNullable[Dict[str, models.CreateFeatureProviderMarkupsRequest]],
|
||||
),
|
||||
event_names=event_names,
|
||||
feature_id=feature_id,
|
||||
@@ -137,21 +163,34 @@ class Features(BaseSDK):
|
||||
self,
|
||||
*,
|
||||
name: str,
|
||||
type_: models.CreateFeatureTypeRequest,
|
||||
type_: models.CreateFeatureTypeRequestBody,
|
||||
feature_id: str,
|
||||
consumable: Optional[bool] = None,
|
||||
display: Optional[
|
||||
Union[
|
||||
models.CreateFeatureDisplayRequest,
|
||||
models.CreateFeatureDisplayRequestTypedDict,
|
||||
models.CreateFeatureDisplayRequestBody,
|
||||
models.CreateFeatureDisplayRequestBodyTypedDict,
|
||||
]
|
||||
] = None,
|
||||
credit_schema: Optional[
|
||||
Union[
|
||||
List[models.CreateFeatureCreditSchemaRequest],
|
||||
List[models.CreateFeatureCreditSchemaRequestTypedDict],
|
||||
List[models.CreateFeatureCreditSchemaRequestBody],
|
||||
List[models.CreateFeatureCreditSchemaRequestBodyTypedDict],
|
||||
]
|
||||
] = None,
|
||||
model_markups: OptionalNullable[
|
||||
Union[
|
||||
Dict[str, models.CreateFeatureModelMarkupsRequest],
|
||||
Dict[str, models.CreateFeatureModelMarkupsRequestTypedDict],
|
||||
]
|
||||
] = UNSET,
|
||||
default_markup: Optional[float] = None,
|
||||
provider_markups: OptionalNullable[
|
||||
Union[
|
||||
Dict[str, models.CreateFeatureProviderMarkupsRequest],
|
||||
Dict[str, models.CreateFeatureProviderMarkupsRequestTypedDict],
|
||||
]
|
||||
] = UNSET,
|
||||
event_names: Optional[List[str]] = None,
|
||||
retries: OptionalNullable[utils.RetryConfig] = UNSET,
|
||||
server_url: Optional[str] = None,
|
||||
@@ -167,7 +206,10 @@ class Features(BaseSDK):
|
||||
:param feature_id: The ID of the feature to create.
|
||||
:param consumable: Whether this feature is consumable. A consumable feature is one that periodically resets and is consumed rather than allocated (like credits, API requests, etc.). Applicable only for 'metered' features.
|
||||
:param display: Singular and plural display names for the feature in your user interface.
|
||||
:param credit_schema: A schema that maps 'single_use' feature IDs to credit costs. Applicable only for 'credit_system' features.
|
||||
:param credit_schema: A schema that maps 'single_use' feature IDs to credit costs. For classic credit systems only — AI credit systems use model_markups instead.
|
||||
:param model_markups: Per-model markup overrides for AI credit systems. Maps model IDs to their markup configuration.
|
||||
:param default_markup: Default percentage markup for this AI credit system. Used when no model or provider markup applies. Use -100 to make usage free.
|
||||
:param provider_markups: Per-provider default markup percentages for AI credit systems. Provider keys match the first segment of model_id.
|
||||
:param event_names:
|
||||
:param retries: Override the default retry configuration for this method
|
||||
:param server_url: Override the default server URL for this method
|
||||
@@ -189,10 +231,20 @@ class Features(BaseSDK):
|
||||
type=type_,
|
||||
consumable=consumable,
|
||||
display=utils.get_pydantic_model(
|
||||
display, Optional[models.CreateFeatureDisplayRequest]
|
||||
display, Optional[models.CreateFeatureDisplayRequestBody]
|
||||
),
|
||||
credit_schema=utils.get_pydantic_model(
|
||||
credit_schema, Optional[List[models.CreateFeatureCreditSchemaRequest]]
|
||||
credit_schema,
|
||||
Optional[List[models.CreateFeatureCreditSchemaRequestBody]],
|
||||
),
|
||||
model_markups=utils.get_pydantic_model(
|
||||
model_markups,
|
||||
OptionalNullable[Dict[str, models.CreateFeatureModelMarkupsRequest]],
|
||||
),
|
||||
default_markup=default_markup,
|
||||
provider_markups=utils.get_pydantic_model(
|
||||
provider_markups,
|
||||
OptionalNullable[Dict[str, models.CreateFeatureProviderMarkupsRequest]],
|
||||
),
|
||||
event_names=event_names,
|
||||
feature_id=feature_id,
|
||||
@@ -628,20 +680,33 @@ class Features(BaseSDK):
|
||||
*,
|
||||
feature_id: str,
|
||||
name: Optional[str] = None,
|
||||
type_: Optional[models.UpdateFeatureTypeRequest] = None,
|
||||
type_: Optional[models.UpdateFeatureTypeRequestBody] = None,
|
||||
consumable: Optional[bool] = None,
|
||||
display: Optional[
|
||||
Union[
|
||||
models.UpdateFeatureDisplayRequest,
|
||||
models.UpdateFeatureDisplayRequestTypedDict,
|
||||
models.UpdateFeatureDisplayRequestBody,
|
||||
models.UpdateFeatureDisplayRequestBodyTypedDict,
|
||||
]
|
||||
] = None,
|
||||
credit_schema: Optional[
|
||||
Union[
|
||||
List[models.UpdateFeatureCreditSchemaRequest],
|
||||
List[models.UpdateFeatureCreditSchemaRequestTypedDict],
|
||||
List[models.UpdateFeatureCreditSchemaRequestBody],
|
||||
List[models.UpdateFeatureCreditSchemaRequestBodyTypedDict],
|
||||
]
|
||||
] = None,
|
||||
model_markups: OptionalNullable[
|
||||
Union[
|
||||
Dict[str, models.UpdateFeatureModelMarkupsRequest],
|
||||
Dict[str, models.UpdateFeatureModelMarkupsRequestTypedDict],
|
||||
]
|
||||
] = UNSET,
|
||||
default_markup: Optional[float] = None,
|
||||
provider_markups: OptionalNullable[
|
||||
Union[
|
||||
Dict[str, models.UpdateFeatureProviderMarkupsRequest],
|
||||
Dict[str, models.UpdateFeatureProviderMarkupsRequestTypedDict],
|
||||
]
|
||||
] = UNSET,
|
||||
event_names: Optional[List[str]] = None,
|
||||
archived: Optional[bool] = None,
|
||||
new_feature_id: Optional[str] = None,
|
||||
@@ -659,7 +724,10 @@ class Features(BaseSDK):
|
||||
:param type: The type of the feature. 'single_use' features are consumed, like API calls, tokens, or messages. 'continuous_use' features are allocated, like seats, workspaces, or projects. 'credit_system' features are schemas that unify multiple 'single_use' features into a single credit system.
|
||||
:param consumable: Whether this feature is consumable. A consumable feature is one that periodically resets and is consumed rather than allocated (like credits, API requests, etc.). Applicable only for 'metered' features.
|
||||
:param display: Singular and plural display names for the feature in your user interface.
|
||||
:param credit_schema: A schema that maps 'single_use' feature IDs to credit costs. Applicable only for 'credit_system' features.
|
||||
:param credit_schema: A schema that maps 'single_use' feature IDs to credit costs. For classic credit systems only — AI credit systems use model_markups instead.
|
||||
:param model_markups: Per-model markup overrides for AI credit systems. Maps model IDs to their markup configuration.
|
||||
:param default_markup: Default percentage markup for this AI credit system. Used when no model or provider markup applies. Use -100 to make usage free.
|
||||
:param provider_markups: Per-provider default markup percentages for AI credit systems. Provider keys match the first segment of model_id.
|
||||
:param event_names:
|
||||
:param archived: Whether the feature is archived. Archived features are hidden from the dashboard.
|
||||
:param new_feature_id: The new ID of the feature. Feature ID can only be updated if it's not being used by any customers.
|
||||
@@ -683,10 +751,20 @@ class Features(BaseSDK):
|
||||
type=type_,
|
||||
consumable=consumable,
|
||||
display=utils.get_pydantic_model(
|
||||
display, Optional[models.UpdateFeatureDisplayRequest]
|
||||
display, Optional[models.UpdateFeatureDisplayRequestBody]
|
||||
),
|
||||
credit_schema=utils.get_pydantic_model(
|
||||
credit_schema, Optional[List[models.UpdateFeatureCreditSchemaRequest]]
|
||||
credit_schema,
|
||||
Optional[List[models.UpdateFeatureCreditSchemaRequestBody]],
|
||||
),
|
||||
model_markups=utils.get_pydantic_model(
|
||||
model_markups,
|
||||
OptionalNullable[Dict[str, models.UpdateFeatureModelMarkupsRequest]],
|
||||
),
|
||||
default_markup=default_markup,
|
||||
provider_markups=utils.get_pydantic_model(
|
||||
provider_markups,
|
||||
OptionalNullable[Dict[str, models.UpdateFeatureProviderMarkupsRequest]],
|
||||
),
|
||||
event_names=event_names,
|
||||
archived=archived,
|
||||
@@ -758,20 +836,33 @@ class Features(BaseSDK):
|
||||
*,
|
||||
feature_id: str,
|
||||
name: Optional[str] = None,
|
||||
type_: Optional[models.UpdateFeatureTypeRequest] = None,
|
||||
type_: Optional[models.UpdateFeatureTypeRequestBody] = None,
|
||||
consumable: Optional[bool] = None,
|
||||
display: Optional[
|
||||
Union[
|
||||
models.UpdateFeatureDisplayRequest,
|
||||
models.UpdateFeatureDisplayRequestTypedDict,
|
||||
models.UpdateFeatureDisplayRequestBody,
|
||||
models.UpdateFeatureDisplayRequestBodyTypedDict,
|
||||
]
|
||||
] = None,
|
||||
credit_schema: Optional[
|
||||
Union[
|
||||
List[models.UpdateFeatureCreditSchemaRequest],
|
||||
List[models.UpdateFeatureCreditSchemaRequestTypedDict],
|
||||
List[models.UpdateFeatureCreditSchemaRequestBody],
|
||||
List[models.UpdateFeatureCreditSchemaRequestBodyTypedDict],
|
||||
]
|
||||
] = None,
|
||||
model_markups: OptionalNullable[
|
||||
Union[
|
||||
Dict[str, models.UpdateFeatureModelMarkupsRequest],
|
||||
Dict[str, models.UpdateFeatureModelMarkupsRequestTypedDict],
|
||||
]
|
||||
] = UNSET,
|
||||
default_markup: Optional[float] = None,
|
||||
provider_markups: OptionalNullable[
|
||||
Union[
|
||||
Dict[str, models.UpdateFeatureProviderMarkupsRequest],
|
||||
Dict[str, models.UpdateFeatureProviderMarkupsRequestTypedDict],
|
||||
]
|
||||
] = UNSET,
|
||||
event_names: Optional[List[str]] = None,
|
||||
archived: Optional[bool] = None,
|
||||
new_feature_id: Optional[str] = None,
|
||||
@@ -789,7 +880,10 @@ class Features(BaseSDK):
|
||||
:param type: The type of the feature. 'single_use' features are consumed, like API calls, tokens, or messages. 'continuous_use' features are allocated, like seats, workspaces, or projects. 'credit_system' features are schemas that unify multiple 'single_use' features into a single credit system.
|
||||
:param consumable: Whether this feature is consumable. A consumable feature is one that periodically resets and is consumed rather than allocated (like credits, API requests, etc.). Applicable only for 'metered' features.
|
||||
:param display: Singular and plural display names for the feature in your user interface.
|
||||
:param credit_schema: A schema that maps 'single_use' feature IDs to credit costs. Applicable only for 'credit_system' features.
|
||||
:param credit_schema: A schema that maps 'single_use' feature IDs to credit costs. For classic credit systems only — AI credit systems use model_markups instead.
|
||||
:param model_markups: Per-model markup overrides for AI credit systems. Maps model IDs to their markup configuration.
|
||||
:param default_markup: Default percentage markup for this AI credit system. Used when no model or provider markup applies. Use -100 to make usage free.
|
||||
:param provider_markups: Per-provider default markup percentages for AI credit systems. Provider keys match the first segment of model_id.
|
||||
:param event_names:
|
||||
:param archived: Whether the feature is archived. Archived features are hidden from the dashboard.
|
||||
:param new_feature_id: The new ID of the feature. Feature ID can only be updated if it's not being used by any customers.
|
||||
@@ -813,10 +907,20 @@ class Features(BaseSDK):
|
||||
type=type_,
|
||||
consumable=consumable,
|
||||
display=utils.get_pydantic_model(
|
||||
display, Optional[models.UpdateFeatureDisplayRequest]
|
||||
display, Optional[models.UpdateFeatureDisplayRequestBody]
|
||||
),
|
||||
credit_schema=utils.get_pydantic_model(
|
||||
credit_schema, Optional[List[models.UpdateFeatureCreditSchemaRequest]]
|
||||
credit_schema,
|
||||
Optional[List[models.UpdateFeatureCreditSchemaRequestBody]],
|
||||
),
|
||||
model_markups=utils.get_pydantic_model(
|
||||
model_markups,
|
||||
OptionalNullable[Dict[str, models.UpdateFeatureModelMarkupsRequest]],
|
||||
),
|
||||
default_markup=default_markup,
|
||||
provider_markups=utils.get_pydantic_model(
|
||||
provider_markups,
|
||||
OptionalNullable[Dict[str, models.UpdateFeatureProviderMarkupsRequest]],
|
||||
),
|
||||
event_names=event_names,
|
||||
archived=archived,
|
||||
|
||||
@@ -261,8 +261,8 @@ class AttachItemPriceTypedDict(TypedDict):
|
||||
r"""Number of intervals per billing cycle. Defaults to 1."""
|
||||
billing_units: NotRequired[float]
|
||||
r"""Units per price increment. Usage is rounded UP when billed (e.g. billing_units=100 means 101 rounds to 200)."""
|
||||
max_purchase: NotRequired[float]
|
||||
r"""Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total."""
|
||||
max_purchase: NotRequired[Nullable[float]]
|
||||
r"""Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total. Null for no limit."""
|
||||
|
||||
|
||||
class AttachItemPrice(BaseModel):
|
||||
@@ -288,8 +288,8 @@ class AttachItemPrice(BaseModel):
|
||||
billing_units: Optional[float] = 1
|
||||
r"""Units per price increment. Usage is rounded UP when billed (e.g. billing_units=100 means 101 rounds to 200)."""
|
||||
|
||||
max_purchase: Optional[float] = None
|
||||
r"""Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total."""
|
||||
max_purchase: OptionalNullable[float] = UNSET
|
||||
r"""Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total. Null for no limit."""
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
@@ -303,15 +303,24 @@ class AttachItemPrice(BaseModel):
|
||||
"max_purchase",
|
||||
]
|
||||
)
|
||||
nullable_fields = set(["max_purchase"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
is_nullable_and_explicitly_set = (
|
||||
k in nullable_fields
|
||||
and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member
|
||||
)
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if val is not None or k not in optional_fields:
|
||||
if (
|
||||
val is not None
|
||||
or k not in optional_fields
|
||||
or is_nullable_and_explicitly_set
|
||||
):
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
@@ -594,8 +603,8 @@ class AttachAddItemPriceTypedDict(TypedDict):
|
||||
r"""Number of intervals per billing cycle. Defaults to 1."""
|
||||
billing_units: NotRequired[float]
|
||||
r"""Units per price increment. Usage is rounded UP when billed (e.g. billing_units=100 means 101 rounds to 200)."""
|
||||
max_purchase: NotRequired[float]
|
||||
r"""Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total."""
|
||||
max_purchase: NotRequired[Nullable[float]]
|
||||
r"""Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total. Null for no limit."""
|
||||
|
||||
|
||||
class AttachAddItemPrice(BaseModel):
|
||||
@@ -621,8 +630,8 @@ class AttachAddItemPrice(BaseModel):
|
||||
billing_units: Optional[float] = 1
|
||||
r"""Units per price increment. Usage is rounded UP when billed (e.g. billing_units=100 means 101 rounds to 200)."""
|
||||
|
||||
max_purchase: Optional[float] = None
|
||||
r"""Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total."""
|
||||
max_purchase: OptionalNullable[float] = UNSET
|
||||
r"""Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total. Null for no limit."""
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
@@ -636,15 +645,24 @@ class AttachAddItemPrice(BaseModel):
|
||||
"max_purchase",
|
||||
]
|
||||
)
|
||||
nullable_fields = set(["max_purchase"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
is_nullable_and_explicitly_set = (
|
||||
k in nullable_fields
|
||||
and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member
|
||||
)
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if val is not None or k not in optional_fields:
|
||||
if (
|
||||
val is not None
|
||||
or k not in optional_fields
|
||||
or is_nullable_and_explicitly_set
|
||||
):
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
@@ -809,7 +827,20 @@ AttachRemoveItemBillingMethod = Literal[
|
||||
r"""Match items with this billing method (prepaid or usage_based)."""
|
||||
|
||||
|
||||
AttachRemoveItemInterval = Literal[
|
||||
AttachIntervalRemoveItemEnum2 = Literal[
|
||||
"one_off",
|
||||
"minute",
|
||||
"hour",
|
||||
"day",
|
||||
"week",
|
||||
"month",
|
||||
"quarter",
|
||||
"semi_annual",
|
||||
"year",
|
||||
]
|
||||
|
||||
|
||||
AttachIntervalRemoveItemEnum1 = Literal[
|
||||
"one_off",
|
||||
"week",
|
||||
"month",
|
||||
@@ -817,7 +848,20 @@ AttachRemoveItemInterval = Literal[
|
||||
"semi_annual",
|
||||
"year",
|
||||
]
|
||||
r"""Match items with this interval."""
|
||||
|
||||
|
||||
AttachIntervalUnionTypedDict = TypeAliasType(
|
||||
"AttachIntervalUnionTypedDict",
|
||||
Union[AttachIntervalRemoveItemEnum1, AttachIntervalRemoveItemEnum2],
|
||||
)
|
||||
r"""Match items with this interval. Accepts either a BillingInterval (price-side) or a ResetInterval (reset-side, includes day/hour/minute) so price-less items keyed by reset.interval can be disambiguated."""
|
||||
|
||||
|
||||
AttachIntervalUnion = TypeAliasType(
|
||||
"AttachIntervalUnion",
|
||||
Union[AttachIntervalRemoveItemEnum1, AttachIntervalRemoveItemEnum2],
|
||||
)
|
||||
r"""Match items with this interval. Accepts either a BillingInterval (price-side) or a ResetInterval (reset-side, includes day/hour/minute) so price-less items keyed by reset.interval can be disambiguated."""
|
||||
|
||||
|
||||
class AttachPlanItemFilterTypedDict(TypedDict):
|
||||
@@ -827,8 +871,10 @@ class AttachPlanItemFilterTypedDict(TypedDict):
|
||||
r"""Match items linked to this feature."""
|
||||
billing_method: NotRequired[AttachRemoveItemBillingMethod]
|
||||
r"""Match items with this billing method (prepaid or usage_based)."""
|
||||
interval: NotRequired[AttachRemoveItemInterval]
|
||||
r"""Match items with this interval."""
|
||||
interval: NotRequired[AttachIntervalUnionTypedDict]
|
||||
r"""Match items with this interval. Accepts either a BillingInterval (price-side) or a ResetInterval (reset-side, includes day/hour/minute) so price-less items keyed by reset.interval can be disambiguated."""
|
||||
interval_count: NotRequired[int]
|
||||
r"""Match items with this interval_count. Disambiguates between items that share an interval but differ in count."""
|
||||
|
||||
|
||||
class AttachPlanItemFilter(BaseModel):
|
||||
@@ -840,12 +886,17 @@ class AttachPlanItemFilter(BaseModel):
|
||||
billing_method: Optional[AttachRemoveItemBillingMethod] = None
|
||||
r"""Match items with this billing method (prepaid or usage_based)."""
|
||||
|
||||
interval: Optional[AttachRemoveItemInterval] = None
|
||||
r"""Match items with this interval."""
|
||||
interval: Optional[AttachIntervalUnion] = None
|
||||
r"""Match items with this interval. Accepts either a BillingInterval (price-side) or a ResetInterval (reset-side, includes day/hour/minute) so price-less items keyed by reset.interval can be disambiguated."""
|
||||
|
||||
interval_count: Optional[int] = None
|
||||
r"""Match items with this interval_count. Disambiguates between items that share an interval but differ in count."""
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
optional_fields = set(["feature_id", "billing_method", "interval"])
|
||||
optional_fields = set(
|
||||
["feature_id", "billing_method", "interval", "interval_count"]
|
||||
)
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
@@ -926,7 +977,7 @@ class AttachCustomizeTypedDict(TypedDict):
|
||||
price: NotRequired[Nullable[AttachBasePriceTypedDict]]
|
||||
r"""Override the base price of the plan. Pass null to remove the base price."""
|
||||
items: NotRequired[List[AttachItemPlanItemTypedDict]]
|
||||
r"""Override the items in the plan (PUT-style — replaces all existing items). Mutually exclusive with add_items / remove_items / update_items."""
|
||||
r"""Override the items in the plan (PUT-style — replaces all existing items). Mutually exclusive with add_items / remove_items / deprecated update_items."""
|
||||
add_items: NotRequired[List[AttachAddItemPlanItemTypedDict]]
|
||||
r"""Items to add to the plan."""
|
||||
remove_items: NotRequired[List[AttachPlanItemFilterTypedDict]]
|
||||
@@ -942,7 +993,7 @@ class AttachCustomize(BaseModel):
|
||||
r"""Override the base price of the plan. Pass null to remove the base price."""
|
||||
|
||||
items: Optional[List[AttachItemPlanItem]] = None
|
||||
r"""Override the items in the plan (PUT-style — replaces all existing items). Mutually exclusive with add_items / remove_items / update_items."""
|
||||
r"""Override the items in the plan (PUT-style — replaces all existing items). Mutually exclusive with add_items / remove_items / deprecated update_items."""
|
||||
|
||||
add_items: Optional[List[AttachAddItemPlanItem]] = None
|
||||
r"""Items to add to the plan."""
|
||||
|
||||
@@ -10,7 +10,7 @@ from autumn_sdk.types import (
|
||||
UnrecognizedStr,
|
||||
)
|
||||
from pydantic import model_serializer
|
||||
from typing import Any, List, Literal, Optional, Union
|
||||
from typing import Dict, List, Literal, Optional, Union
|
||||
from typing_extensions import NotRequired, TypeAliasType, TypedDict
|
||||
|
||||
|
||||
@@ -19,10 +19,11 @@ BalanceType = Union[
|
||||
"boolean",
|
||||
"metered",
|
||||
"credit_system",
|
||||
"ai_credit_system",
|
||||
],
|
||||
UnrecognizedStr,
|
||||
]
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools."""
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing."""
|
||||
|
||||
|
||||
class BalanceCreditSchemaTypedDict(TypedDict):
|
||||
@@ -40,6 +41,44 @@ class BalanceCreditSchema(BaseModel):
|
||||
r"""Credits consumed per unit of the metered feature."""
|
||||
|
||||
|
||||
class BalanceModelMarkupsTypedDict(TypedDict):
|
||||
markup: NotRequired[float]
|
||||
input_cost: NotRequired[float]
|
||||
output_cost: NotRequired[float]
|
||||
|
||||
|
||||
class BalanceModelMarkups(BaseModel):
|
||||
markup: Optional[float] = None
|
||||
|
||||
input_cost: Optional[float] = None
|
||||
|
||||
output_cost: Optional[float] = None
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
optional_fields = set(["markup", "input_cost", "output_cost"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if val is not None or k not in optional_fields:
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
|
||||
|
||||
class BalanceProviderMarkupsTypedDict(TypedDict):
|
||||
markup: float
|
||||
|
||||
|
||||
class BalanceProviderMarkups(BaseModel):
|
||||
markup: float
|
||||
|
||||
|
||||
class BalanceDisplayTypedDict(TypedDict):
|
||||
r"""Display names for the feature in billing UI and customer-facing components."""
|
||||
|
||||
@@ -92,7 +131,7 @@ class BalanceFeatureTypedDict(TypedDict):
|
||||
name: str
|
||||
r"""Human-readable name displayed in the dashboard and billing UI."""
|
||||
type: BalanceType
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools."""
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing."""
|
||||
consumable: bool
|
||||
r"""For metered features: true if usage resets periodically (API calls, credits), false if allocated persistently (seats, storage)."""
|
||||
archived: bool
|
||||
@@ -101,6 +140,12 @@ class BalanceFeatureTypedDict(TypedDict):
|
||||
r"""Event names that trigger this feature's balance. Allows multiple features to respond to a single event."""
|
||||
credit_schema: NotRequired[List[BalanceCreditSchemaTypedDict]]
|
||||
r"""For credit_system features: maps metered features to their credit costs."""
|
||||
model_markups: NotRequired[Nullable[Dict[str, BalanceModelMarkupsTypedDict]]]
|
||||
r"""Per-model markup overrides for AI credit systems."""
|
||||
default_markup: NotRequired[float]
|
||||
r"""Default percentage markup for AI credit systems. Use -100 to make usage free."""
|
||||
provider_markups: NotRequired[Nullable[Dict[str, BalanceProviderMarkupsTypedDict]]]
|
||||
r"""Per-provider default markup percentages for AI credit systems."""
|
||||
display: NotRequired[BalanceDisplayTypedDict]
|
||||
r"""Display names for the feature in billing UI and customer-facing components."""
|
||||
|
||||
@@ -115,7 +160,7 @@ class BalanceFeature(BaseModel):
|
||||
r"""Human-readable name displayed in the dashboard and billing UI."""
|
||||
|
||||
type: BalanceType
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools."""
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing."""
|
||||
|
||||
consumable: bool
|
||||
r"""For metered features: true if usage resets periodically (API calls, credits), false if allocated persistently (seats, storage)."""
|
||||
@@ -129,21 +174,48 @@ class BalanceFeature(BaseModel):
|
||||
credit_schema: Optional[List[BalanceCreditSchema]] = None
|
||||
r"""For credit_system features: maps metered features to their credit costs."""
|
||||
|
||||
model_markups: OptionalNullable[Dict[str, BalanceModelMarkups]] = UNSET
|
||||
r"""Per-model markup overrides for AI credit systems."""
|
||||
|
||||
default_markup: Optional[float] = None
|
||||
r"""Default percentage markup for AI credit systems. Use -100 to make usage free."""
|
||||
|
||||
provider_markups: OptionalNullable[Dict[str, BalanceProviderMarkups]] = UNSET
|
||||
r"""Per-provider default markup percentages for AI credit systems."""
|
||||
|
||||
display: Optional[BalanceDisplay] = None
|
||||
r"""Display names for the feature in billing UI and customer-facing components."""
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
optional_fields = set(["event_names", "credit_schema", "display"])
|
||||
optional_fields = set(
|
||||
[
|
||||
"event_names",
|
||||
"credit_schema",
|
||||
"model_markups",
|
||||
"default_markup",
|
||||
"provider_markups",
|
||||
"display",
|
||||
]
|
||||
)
|
||||
nullable_fields = set(["model_markups", "provider_markups"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
is_nullable_and_explicitly_set = (
|
||||
k in nullable_fields
|
||||
and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member
|
||||
)
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if val is not None or k not in optional_fields:
|
||||
if (
|
||||
val is not None
|
||||
or k not in optional_fields
|
||||
or is_nullable_and_explicitly_set
|
||||
):
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
@@ -222,6 +294,42 @@ class BalanceReset(BaseModel):
|
||||
return m
|
||||
|
||||
|
||||
BalanceToTypedDict = TypeAliasType("BalanceToTypedDict", Union[float, str])
|
||||
|
||||
|
||||
BalanceTo = TypeAliasType("BalanceTo", Union[float, str])
|
||||
|
||||
|
||||
class BalanceTierTypedDict(TypedDict):
|
||||
to: BalanceToTypedDict
|
||||
amount: float
|
||||
flat_amount: NotRequired[float]
|
||||
|
||||
|
||||
class BalanceTier(BaseModel):
|
||||
to: BalanceTo
|
||||
|
||||
amount: float
|
||||
|
||||
flat_amount: Optional[float] = None
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
optional_fields = set(["flat_amount"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if val is not None or k not in optional_fields:
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
|
||||
|
||||
BalanceTierBehavior = Union[
|
||||
Literal[
|
||||
"graduated",
|
||||
@@ -251,7 +359,7 @@ class BalancePriceTypedDict(TypedDict):
|
||||
r"""Maximum quantity that can be purchased, or null for unlimited."""
|
||||
amount: NotRequired[float]
|
||||
r"""The per-unit price amount."""
|
||||
tiers: NotRequired[List[Nullable[Any]]]
|
||||
tiers: NotRequired[List[BalanceTierTypedDict]]
|
||||
r"""Tiered pricing configuration if applicable."""
|
||||
tier_behavior: NotRequired[BalanceTierBehavior]
|
||||
r"""How tiers are applied: graduated (split across bands) or volume (flat rate for the matched tier)."""
|
||||
@@ -270,7 +378,7 @@ class BalancePrice(BaseModel):
|
||||
amount: Optional[float] = None
|
||||
r"""The per-unit price amount."""
|
||||
|
||||
tiers: Optional[List[Nullable[Any]]] = None
|
||||
tiers: Optional[List[BalanceTier]] = None
|
||||
r"""Tiered pricing configuration if applicable."""
|
||||
|
||||
tier_behavior: Optional[BalanceTierBehavior] = None
|
||||
|
||||
@@ -263,8 +263,8 @@ class BillingUpdateItemPriceTypedDict(TypedDict):
|
||||
r"""Number of intervals per billing cycle. Defaults to 1."""
|
||||
billing_units: NotRequired[float]
|
||||
r"""Units per price increment. Usage is rounded UP when billed (e.g. billing_units=100 means 101 rounds to 200)."""
|
||||
max_purchase: NotRequired[float]
|
||||
r"""Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total."""
|
||||
max_purchase: NotRequired[Nullable[float]]
|
||||
r"""Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total. Null for no limit."""
|
||||
|
||||
|
||||
class BillingUpdateItemPrice(BaseModel):
|
||||
@@ -290,8 +290,8 @@ class BillingUpdateItemPrice(BaseModel):
|
||||
billing_units: Optional[float] = 1
|
||||
r"""Units per price increment. Usage is rounded UP when billed (e.g. billing_units=100 means 101 rounds to 200)."""
|
||||
|
||||
max_purchase: Optional[float] = None
|
||||
r"""Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total."""
|
||||
max_purchase: OptionalNullable[float] = UNSET
|
||||
r"""Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total. Null for no limit."""
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
@@ -305,15 +305,24 @@ class BillingUpdateItemPrice(BaseModel):
|
||||
"max_purchase",
|
||||
]
|
||||
)
|
||||
nullable_fields = set(["max_purchase"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
is_nullable_and_explicitly_set = (
|
||||
k in nullable_fields
|
||||
and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member
|
||||
)
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if val is not None or k not in optional_fields:
|
||||
if (
|
||||
val is not None
|
||||
or k not in optional_fields
|
||||
or is_nullable_and_explicitly_set
|
||||
):
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
@@ -598,8 +607,8 @@ class BillingUpdateAddItemPriceTypedDict(TypedDict):
|
||||
r"""Number of intervals per billing cycle. Defaults to 1."""
|
||||
billing_units: NotRequired[float]
|
||||
r"""Units per price increment. Usage is rounded UP when billed (e.g. billing_units=100 means 101 rounds to 200)."""
|
||||
max_purchase: NotRequired[float]
|
||||
r"""Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total."""
|
||||
max_purchase: NotRequired[Nullable[float]]
|
||||
r"""Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total. Null for no limit."""
|
||||
|
||||
|
||||
class BillingUpdateAddItemPrice(BaseModel):
|
||||
@@ -625,8 +634,8 @@ class BillingUpdateAddItemPrice(BaseModel):
|
||||
billing_units: Optional[float] = 1
|
||||
r"""Units per price increment. Usage is rounded UP when billed (e.g. billing_units=100 means 101 rounds to 200)."""
|
||||
|
||||
max_purchase: Optional[float] = None
|
||||
r"""Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total."""
|
||||
max_purchase: OptionalNullable[float] = UNSET
|
||||
r"""Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total. Null for no limit."""
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
@@ -640,15 +649,24 @@ class BillingUpdateAddItemPrice(BaseModel):
|
||||
"max_purchase",
|
||||
]
|
||||
)
|
||||
nullable_fields = set(["max_purchase"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
is_nullable_and_explicitly_set = (
|
||||
k in nullable_fields
|
||||
and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member
|
||||
)
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if val is not None or k not in optional_fields:
|
||||
if (
|
||||
val is not None
|
||||
or k not in optional_fields
|
||||
or is_nullable_and_explicitly_set
|
||||
):
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
@@ -813,7 +831,20 @@ BillingUpdateRemoveItemBillingMethod = Literal[
|
||||
r"""Match items with this billing method (prepaid or usage_based)."""
|
||||
|
||||
|
||||
BillingUpdateRemoveItemInterval = Literal[
|
||||
BillingUpdateIntervalRemoveItemEnum2 = Literal[
|
||||
"one_off",
|
||||
"minute",
|
||||
"hour",
|
||||
"day",
|
||||
"week",
|
||||
"month",
|
||||
"quarter",
|
||||
"semi_annual",
|
||||
"year",
|
||||
]
|
||||
|
||||
|
||||
BillingUpdateIntervalRemoveItemEnum1 = Literal[
|
||||
"one_off",
|
||||
"week",
|
||||
"month",
|
||||
@@ -821,7 +852,20 @@ BillingUpdateRemoveItemInterval = Literal[
|
||||
"semi_annual",
|
||||
"year",
|
||||
]
|
||||
r"""Match items with this interval."""
|
||||
|
||||
|
||||
BillingUpdateIntervalUnionTypedDict = TypeAliasType(
|
||||
"BillingUpdateIntervalUnionTypedDict",
|
||||
Union[BillingUpdateIntervalRemoveItemEnum1, BillingUpdateIntervalRemoveItemEnum2],
|
||||
)
|
||||
r"""Match items with this interval. Accepts either a BillingInterval (price-side) or a ResetInterval (reset-side, includes day/hour/minute) so price-less items keyed by reset.interval can be disambiguated."""
|
||||
|
||||
|
||||
BillingUpdateIntervalUnion = TypeAliasType(
|
||||
"BillingUpdateIntervalUnion",
|
||||
Union[BillingUpdateIntervalRemoveItemEnum1, BillingUpdateIntervalRemoveItemEnum2],
|
||||
)
|
||||
r"""Match items with this interval. Accepts either a BillingInterval (price-side) or a ResetInterval (reset-side, includes day/hour/minute) so price-less items keyed by reset.interval can be disambiguated."""
|
||||
|
||||
|
||||
class BillingUpdatePlanItemFilterTypedDict(TypedDict):
|
||||
@@ -831,8 +875,10 @@ class BillingUpdatePlanItemFilterTypedDict(TypedDict):
|
||||
r"""Match items linked to this feature."""
|
||||
billing_method: NotRequired[BillingUpdateRemoveItemBillingMethod]
|
||||
r"""Match items with this billing method (prepaid or usage_based)."""
|
||||
interval: NotRequired[BillingUpdateRemoveItemInterval]
|
||||
r"""Match items with this interval."""
|
||||
interval: NotRequired[BillingUpdateIntervalUnionTypedDict]
|
||||
r"""Match items with this interval. Accepts either a BillingInterval (price-side) or a ResetInterval (reset-side, includes day/hour/minute) so price-less items keyed by reset.interval can be disambiguated."""
|
||||
interval_count: NotRequired[int]
|
||||
r"""Match items with this interval_count. Disambiguates between items that share an interval but differ in count."""
|
||||
|
||||
|
||||
class BillingUpdatePlanItemFilter(BaseModel):
|
||||
@@ -844,12 +890,17 @@ class BillingUpdatePlanItemFilter(BaseModel):
|
||||
billing_method: Optional[BillingUpdateRemoveItemBillingMethod] = None
|
||||
r"""Match items with this billing method (prepaid or usage_based)."""
|
||||
|
||||
interval: Optional[BillingUpdateRemoveItemInterval] = None
|
||||
r"""Match items with this interval."""
|
||||
interval: Optional[BillingUpdateIntervalUnion] = None
|
||||
r"""Match items with this interval. Accepts either a BillingInterval (price-side) or a ResetInterval (reset-side, includes day/hour/minute) so price-less items keyed by reset.interval can be disambiguated."""
|
||||
|
||||
interval_count: Optional[int] = None
|
||||
r"""Match items with this interval_count. Disambiguates between items that share an interval but differ in count."""
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
optional_fields = set(["feature_id", "billing_method", "interval"])
|
||||
optional_fields = set(
|
||||
["feature_id", "billing_method", "interval", "interval_count"]
|
||||
)
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
@@ -930,7 +981,7 @@ class BillingUpdateCustomizeTypedDict(TypedDict):
|
||||
price: NotRequired[Nullable[BillingUpdateBasePriceTypedDict]]
|
||||
r"""Override the base price of the plan. Pass null to remove the base price."""
|
||||
items: NotRequired[List[BillingUpdateItemPlanItemTypedDict]]
|
||||
r"""Override the items in the plan (PUT-style — replaces all existing items). Mutually exclusive with add_items / remove_items / update_items."""
|
||||
r"""Override the items in the plan (PUT-style — replaces all existing items). Mutually exclusive with add_items / remove_items / deprecated update_items."""
|
||||
add_items: NotRequired[List[BillingUpdateAddItemPlanItemTypedDict]]
|
||||
r"""Items to add to the plan."""
|
||||
remove_items: NotRequired[List[BillingUpdatePlanItemFilterTypedDict]]
|
||||
@@ -946,7 +997,7 @@ class BillingUpdateCustomize(BaseModel):
|
||||
r"""Override the base price of the plan. Pass null to remove the base price."""
|
||||
|
||||
items: Optional[List[BillingUpdateItemPlanItem]] = None
|
||||
r"""Override the items in the plan (PUT-style — replaces all existing items). Mutually exclusive with add_items / remove_items / update_items."""
|
||||
r"""Override the items in the plan (PUT-style — replaces all existing items). Mutually exclusive with add_items / remove_items / deprecated update_items."""
|
||||
|
||||
add_items: Optional[List[BillingUpdateAddItemPlanItem]] = None
|
||||
r"""Items to add to the plan."""
|
||||
|
||||
@@ -164,10 +164,11 @@ FlagType2 = Union[
|
||||
"boolean",
|
||||
"metered",
|
||||
"credit_system",
|
||||
"ai_credit_system",
|
||||
],
|
||||
UnrecognizedStr,
|
||||
]
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools."""
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing."""
|
||||
|
||||
|
||||
class CheckCreditSchema2TypedDict(TypedDict):
|
||||
@@ -185,6 +186,44 @@ class CheckCreditSchema2(BaseModel):
|
||||
r"""Credits consumed per unit of the metered feature."""
|
||||
|
||||
|
||||
class CheckModelMarkups2TypedDict(TypedDict):
|
||||
markup: NotRequired[float]
|
||||
input_cost: NotRequired[float]
|
||||
output_cost: NotRequired[float]
|
||||
|
||||
|
||||
class CheckModelMarkups2(BaseModel):
|
||||
markup: Optional[float] = None
|
||||
|
||||
input_cost: Optional[float] = None
|
||||
|
||||
output_cost: Optional[float] = None
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
optional_fields = set(["markup", "input_cost", "output_cost"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if val is not None or k not in optional_fields:
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
|
||||
|
||||
class CheckProviderMarkups2TypedDict(TypedDict):
|
||||
markup: float
|
||||
|
||||
|
||||
class CheckProviderMarkups2(BaseModel):
|
||||
markup: float
|
||||
|
||||
|
||||
class FlagDisplay2TypedDict(TypedDict):
|
||||
r"""Display names for the feature in billing UI and customer-facing components."""
|
||||
|
||||
@@ -237,7 +276,7 @@ class CheckFeature2TypedDict(TypedDict):
|
||||
name: str
|
||||
r"""Human-readable name displayed in the dashboard and billing UI."""
|
||||
type: FlagType2
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools."""
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing."""
|
||||
consumable: bool
|
||||
r"""For metered features: true if usage resets periodically (API calls, credits), false if allocated persistently (seats, storage)."""
|
||||
archived: bool
|
||||
@@ -246,6 +285,12 @@ class CheckFeature2TypedDict(TypedDict):
|
||||
r"""Event names that trigger this feature's balance. Allows multiple features to respond to a single event."""
|
||||
credit_schema: NotRequired[List[CheckCreditSchema2TypedDict]]
|
||||
r"""For credit_system features: maps metered features to their credit costs."""
|
||||
model_markups: NotRequired[Nullable[Dict[str, CheckModelMarkups2TypedDict]]]
|
||||
r"""Per-model markup overrides for AI credit systems."""
|
||||
default_markup: NotRequired[float]
|
||||
r"""Default percentage markup for AI credit systems. Use -100 to make usage free."""
|
||||
provider_markups: NotRequired[Nullable[Dict[str, CheckProviderMarkups2TypedDict]]]
|
||||
r"""Per-provider default markup percentages for AI credit systems."""
|
||||
display: NotRequired[FlagDisplay2TypedDict]
|
||||
r"""Display names for the feature in billing UI and customer-facing components."""
|
||||
|
||||
@@ -260,7 +305,7 @@ class CheckFeature2(BaseModel):
|
||||
r"""Human-readable name displayed in the dashboard and billing UI."""
|
||||
|
||||
type: FlagType2
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools."""
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing."""
|
||||
|
||||
consumable: bool
|
||||
r"""For metered features: true if usage resets periodically (API calls, credits), false if allocated persistently (seats, storage)."""
|
||||
@@ -274,21 +319,48 @@ class CheckFeature2(BaseModel):
|
||||
credit_schema: Optional[List[CheckCreditSchema2]] = None
|
||||
r"""For credit_system features: maps metered features to their credit costs."""
|
||||
|
||||
model_markups: OptionalNullable[Dict[str, CheckModelMarkups2]] = UNSET
|
||||
r"""Per-model markup overrides for AI credit systems."""
|
||||
|
||||
default_markup: Optional[float] = None
|
||||
r"""Default percentage markup for AI credit systems. Use -100 to make usage free."""
|
||||
|
||||
provider_markups: OptionalNullable[Dict[str, CheckProviderMarkups2]] = UNSET
|
||||
r"""Per-provider default markup percentages for AI credit systems."""
|
||||
|
||||
display: Optional[FlagDisplay2] = None
|
||||
r"""Display names for the feature in billing UI and customer-facing components."""
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
optional_fields = set(["event_names", "credit_schema", "display"])
|
||||
optional_fields = set(
|
||||
[
|
||||
"event_names",
|
||||
"credit_schema",
|
||||
"model_markups",
|
||||
"default_markup",
|
||||
"provider_markups",
|
||||
"display",
|
||||
]
|
||||
)
|
||||
nullable_fields = set(["model_markups", "provider_markups"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
is_nullable_and_explicitly_set = (
|
||||
k in nullable_fields
|
||||
and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member
|
||||
)
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if val is not None or k not in optional_fields:
|
||||
if (
|
||||
val is not None
|
||||
or k not in optional_fields
|
||||
or is_nullable_and_explicitly_set
|
||||
):
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
@@ -1106,10 +1178,11 @@ FlagType1 = Union[
|
||||
"boolean",
|
||||
"metered",
|
||||
"credit_system",
|
||||
"ai_credit_system",
|
||||
],
|
||||
UnrecognizedStr,
|
||||
]
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools."""
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing."""
|
||||
|
||||
|
||||
class CheckCreditSchema1TypedDict(TypedDict):
|
||||
@@ -1127,6 +1200,44 @@ class CheckCreditSchema1(BaseModel):
|
||||
r"""Credits consumed per unit of the metered feature."""
|
||||
|
||||
|
||||
class CheckModelMarkups1TypedDict(TypedDict):
|
||||
markup: NotRequired[float]
|
||||
input_cost: NotRequired[float]
|
||||
output_cost: NotRequired[float]
|
||||
|
||||
|
||||
class CheckModelMarkups1(BaseModel):
|
||||
markup: Optional[float] = None
|
||||
|
||||
input_cost: Optional[float] = None
|
||||
|
||||
output_cost: Optional[float] = None
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
optional_fields = set(["markup", "input_cost", "output_cost"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if val is not None or k not in optional_fields:
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
|
||||
|
||||
class CheckProviderMarkups1TypedDict(TypedDict):
|
||||
markup: float
|
||||
|
||||
|
||||
class CheckProviderMarkups1(BaseModel):
|
||||
markup: float
|
||||
|
||||
|
||||
class FlagDisplay1TypedDict(TypedDict):
|
||||
r"""Display names for the feature in billing UI and customer-facing components."""
|
||||
|
||||
@@ -1179,7 +1290,7 @@ class CheckFeature1TypedDict(TypedDict):
|
||||
name: str
|
||||
r"""Human-readable name displayed in the dashboard and billing UI."""
|
||||
type: FlagType1
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools."""
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing."""
|
||||
consumable: bool
|
||||
r"""For metered features: true if usage resets periodically (API calls, credits), false if allocated persistently (seats, storage)."""
|
||||
archived: bool
|
||||
@@ -1188,6 +1299,12 @@ class CheckFeature1TypedDict(TypedDict):
|
||||
r"""Event names that trigger this feature's balance. Allows multiple features to respond to a single event."""
|
||||
credit_schema: NotRequired[List[CheckCreditSchema1TypedDict]]
|
||||
r"""For credit_system features: maps metered features to their credit costs."""
|
||||
model_markups: NotRequired[Nullable[Dict[str, CheckModelMarkups1TypedDict]]]
|
||||
r"""Per-model markup overrides for AI credit systems."""
|
||||
default_markup: NotRequired[float]
|
||||
r"""Default percentage markup for AI credit systems. Use -100 to make usage free."""
|
||||
provider_markups: NotRequired[Nullable[Dict[str, CheckProviderMarkups1TypedDict]]]
|
||||
r"""Per-provider default markup percentages for AI credit systems."""
|
||||
display: NotRequired[FlagDisplay1TypedDict]
|
||||
r"""Display names for the feature in billing UI and customer-facing components."""
|
||||
|
||||
@@ -1202,7 +1319,7 @@ class CheckFeature1(BaseModel):
|
||||
r"""Human-readable name displayed in the dashboard and billing UI."""
|
||||
|
||||
type: FlagType1
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools."""
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing."""
|
||||
|
||||
consumable: bool
|
||||
r"""For metered features: true if usage resets periodically (API calls, credits), false if allocated persistently (seats, storage)."""
|
||||
@@ -1216,21 +1333,48 @@ class CheckFeature1(BaseModel):
|
||||
credit_schema: Optional[List[CheckCreditSchema1]] = None
|
||||
r"""For credit_system features: maps metered features to their credit costs."""
|
||||
|
||||
model_markups: OptionalNullable[Dict[str, CheckModelMarkups1]] = UNSET
|
||||
r"""Per-model markup overrides for AI credit systems."""
|
||||
|
||||
default_markup: Optional[float] = None
|
||||
r"""Default percentage markup for AI credit systems. Use -100 to make usage free."""
|
||||
|
||||
provider_markups: OptionalNullable[Dict[str, CheckProviderMarkups1]] = UNSET
|
||||
r"""Per-provider default markup percentages for AI credit systems."""
|
||||
|
||||
display: Optional[FlagDisplay1] = None
|
||||
r"""Display names for the feature in billing UI and customer-facing components."""
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
optional_fields = set(["event_names", "credit_schema", "display"])
|
||||
optional_fields = set(
|
||||
[
|
||||
"event_names",
|
||||
"credit_schema",
|
||||
"model_markups",
|
||||
"default_markup",
|
||||
"provider_markups",
|
||||
"display",
|
||||
]
|
||||
)
|
||||
nullable_fields = set(["model_markups", "provider_markups"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
is_nullable_and_explicitly_set = (
|
||||
k in nullable_fields
|
||||
and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member
|
||||
)
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if val is not None or k not in optional_fields:
|
||||
if (
|
||||
val is not None
|
||||
or k not in optional_fields
|
||||
or is_nullable_and_explicitly_set
|
||||
):
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
|
||||
@@ -483,10 +483,11 @@ CreateEntityType = Union[
|
||||
"boolean",
|
||||
"metered",
|
||||
"credit_system",
|
||||
"ai_credit_system",
|
||||
],
|
||||
UnrecognizedStr,
|
||||
]
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools."""
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing."""
|
||||
|
||||
|
||||
class CreateEntityCreditSchemaTypedDict(TypedDict):
|
||||
@@ -504,6 +505,44 @@ class CreateEntityCreditSchema(BaseModel):
|
||||
r"""Credits consumed per unit of the metered feature."""
|
||||
|
||||
|
||||
class CreateEntityModelMarkupsTypedDict(TypedDict):
|
||||
markup: NotRequired[float]
|
||||
input_cost: NotRequired[float]
|
||||
output_cost: NotRequired[float]
|
||||
|
||||
|
||||
class CreateEntityModelMarkups(BaseModel):
|
||||
markup: Optional[float] = None
|
||||
|
||||
input_cost: Optional[float] = None
|
||||
|
||||
output_cost: Optional[float] = None
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
optional_fields = set(["markup", "input_cost", "output_cost"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if val is not None or k not in optional_fields:
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
|
||||
|
||||
class CreateEntityProviderMarkupsTypedDict(TypedDict):
|
||||
markup: float
|
||||
|
||||
|
||||
class CreateEntityProviderMarkups(BaseModel):
|
||||
markup: float
|
||||
|
||||
|
||||
class CreateEntityDisplayTypedDict(TypedDict):
|
||||
r"""Display names for the feature in billing UI and customer-facing components."""
|
||||
|
||||
@@ -556,7 +595,7 @@ class CreateEntityFeatureTypedDict(TypedDict):
|
||||
name: str
|
||||
r"""Human-readable name displayed in the dashboard and billing UI."""
|
||||
type: CreateEntityType
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools."""
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing."""
|
||||
consumable: bool
|
||||
r"""For metered features: true if usage resets periodically (API calls, credits), false if allocated persistently (seats, storage)."""
|
||||
archived: bool
|
||||
@@ -565,6 +604,14 @@ class CreateEntityFeatureTypedDict(TypedDict):
|
||||
r"""Event names that trigger this feature's balance. Allows multiple features to respond to a single event."""
|
||||
credit_schema: NotRequired[List[CreateEntityCreditSchemaTypedDict]]
|
||||
r"""For credit_system features: maps metered features to their credit costs."""
|
||||
model_markups: NotRequired[Nullable[Dict[str, CreateEntityModelMarkupsTypedDict]]]
|
||||
r"""Per-model markup overrides for AI credit systems."""
|
||||
default_markup: NotRequired[float]
|
||||
r"""Default percentage markup for AI credit systems. Use -100 to make usage free."""
|
||||
provider_markups: NotRequired[
|
||||
Nullable[Dict[str, CreateEntityProviderMarkupsTypedDict]]
|
||||
]
|
||||
r"""Per-provider default markup percentages for AI credit systems."""
|
||||
display: NotRequired[CreateEntityDisplayTypedDict]
|
||||
r"""Display names for the feature in billing UI and customer-facing components."""
|
||||
|
||||
@@ -579,7 +626,7 @@ class CreateEntityFeature(BaseModel):
|
||||
r"""Human-readable name displayed in the dashboard and billing UI."""
|
||||
|
||||
type: CreateEntityType
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools."""
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing."""
|
||||
|
||||
consumable: bool
|
||||
r"""For metered features: true if usage resets periodically (API calls, credits), false if allocated persistently (seats, storage)."""
|
||||
@@ -593,21 +640,48 @@ class CreateEntityFeature(BaseModel):
|
||||
credit_schema: Optional[List[CreateEntityCreditSchema]] = None
|
||||
r"""For credit_system features: maps metered features to their credit costs."""
|
||||
|
||||
model_markups: OptionalNullable[Dict[str, CreateEntityModelMarkups]] = UNSET
|
||||
r"""Per-model markup overrides for AI credit systems."""
|
||||
|
||||
default_markup: Optional[float] = None
|
||||
r"""Default percentage markup for AI credit systems. Use -100 to make usage free."""
|
||||
|
||||
provider_markups: OptionalNullable[Dict[str, CreateEntityProviderMarkups]] = UNSET
|
||||
r"""Per-provider default markup percentages for AI credit systems."""
|
||||
|
||||
display: Optional[CreateEntityDisplay] = None
|
||||
r"""Display names for the feature in billing UI and customer-facing components."""
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
optional_fields = set(["event_names", "credit_schema", "display"])
|
||||
optional_fields = set(
|
||||
[
|
||||
"event_names",
|
||||
"credit_schema",
|
||||
"model_markups",
|
||||
"default_markup",
|
||||
"provider_markups",
|
||||
"display",
|
||||
]
|
||||
)
|
||||
nullable_fields = set(["model_markups", "provider_markups"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
is_nullable_and_explicitly_set = (
|
||||
k in nullable_fields
|
||||
and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member
|
||||
)
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if val is not None or k not in optional_fields:
|
||||
if (
|
||||
val is not None
|
||||
or k not in optional_fields
|
||||
or is_nullable_and_explicitly_set
|
||||
):
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
|
||||
@@ -12,7 +12,7 @@ from autumn_sdk.types import (
|
||||
from autumn_sdk.utils import FieldMetadata, HeaderMetadata
|
||||
import pydantic
|
||||
from pydantic import model_serializer
|
||||
from typing import List, Literal, Optional, Union
|
||||
from typing import Dict, List, Literal, Optional, Union
|
||||
from typing_extensions import Annotated, NotRequired, TypedDict
|
||||
|
||||
|
||||
@@ -44,22 +44,23 @@ class CreateFeatureGlobals(BaseModel):
|
||||
return m
|
||||
|
||||
|
||||
CreateFeatureTypeRequest = Literal[
|
||||
CreateFeatureTypeRequestBody = Literal[
|
||||
"boolean",
|
||||
"metered",
|
||||
"credit_system",
|
||||
"ai_credit_system",
|
||||
]
|
||||
r"""The type of the feature. 'single_use' features are consumed, like API calls, tokens, or messages. 'continuous_use' features are allocated, like seats, workspaces, or projects. 'credit_system' features are schemas that unify multiple 'single_use' features into a single credit system."""
|
||||
|
||||
|
||||
class CreateFeatureDisplayRequestTypedDict(TypedDict):
|
||||
class CreateFeatureDisplayRequestBodyTypedDict(TypedDict):
|
||||
r"""Singular and plural display names for the feature in your user interface."""
|
||||
|
||||
singular: str
|
||||
plural: str
|
||||
|
||||
|
||||
class CreateFeatureDisplayRequest(BaseModel):
|
||||
class CreateFeatureDisplayRequestBody(BaseModel):
|
||||
r"""Singular and plural display names for the feature in your user interface."""
|
||||
|
||||
singular: str
|
||||
@@ -67,57 +68,33 @@ class CreateFeatureDisplayRequest(BaseModel):
|
||||
plural: str
|
||||
|
||||
|
||||
class CreateFeatureCreditSchemaRequestTypedDict(TypedDict):
|
||||
class CreateFeatureCreditSchemaRequestBodyTypedDict(TypedDict):
|
||||
metered_feature_id: str
|
||||
credit_cost: float
|
||||
|
||||
|
||||
class CreateFeatureCreditSchemaRequest(BaseModel):
|
||||
class CreateFeatureCreditSchemaRequestBody(BaseModel):
|
||||
metered_feature_id: str
|
||||
|
||||
credit_cost: float
|
||||
|
||||
|
||||
class CreateFeatureParamsTypedDict(TypedDict):
|
||||
name: str
|
||||
r"""The name of the feature."""
|
||||
type: CreateFeatureTypeRequest
|
||||
r"""The type of the feature. 'single_use' features are consumed, like API calls, tokens, or messages. 'continuous_use' features are allocated, like seats, workspaces, or projects. 'credit_system' features are schemas that unify multiple 'single_use' features into a single credit system."""
|
||||
feature_id: str
|
||||
r"""The ID of the feature to create."""
|
||||
consumable: NotRequired[bool]
|
||||
r"""Whether this feature is consumable. A consumable feature is one that periodically resets and is consumed rather than allocated (like credits, API requests, etc.). Applicable only for 'metered' features."""
|
||||
display: NotRequired[CreateFeatureDisplayRequestTypedDict]
|
||||
r"""Singular and plural display names for the feature in your user interface."""
|
||||
credit_schema: NotRequired[List[CreateFeatureCreditSchemaRequestTypedDict]]
|
||||
r"""A schema that maps 'single_use' feature IDs to credit costs. Applicable only for 'credit_system' features."""
|
||||
event_names: NotRequired[List[str]]
|
||||
class CreateFeatureModelMarkupsRequestTypedDict(TypedDict):
|
||||
markup: NotRequired[float]
|
||||
input_cost: NotRequired[float]
|
||||
output_cost: NotRequired[float]
|
||||
|
||||
|
||||
class CreateFeatureParams(BaseModel):
|
||||
name: str
|
||||
r"""The name of the feature."""
|
||||
class CreateFeatureModelMarkupsRequest(BaseModel):
|
||||
markup: Optional[float] = None
|
||||
|
||||
type: CreateFeatureTypeRequest
|
||||
r"""The type of the feature. 'single_use' features are consumed, like API calls, tokens, or messages. 'continuous_use' features are allocated, like seats, workspaces, or projects. 'credit_system' features are schemas that unify multiple 'single_use' features into a single credit system."""
|
||||
input_cost: Optional[float] = None
|
||||
|
||||
feature_id: str
|
||||
r"""The ID of the feature to create."""
|
||||
|
||||
consumable: Optional[bool] = None
|
||||
r"""Whether this feature is consumable. A consumable feature is one that periodically resets and is consumed rather than allocated (like credits, API requests, etc.). Applicable only for 'metered' features."""
|
||||
|
||||
display: Optional[CreateFeatureDisplayRequest] = None
|
||||
r"""Singular and plural display names for the feature in your user interface."""
|
||||
|
||||
credit_schema: Optional[List[CreateFeatureCreditSchemaRequest]] = None
|
||||
r"""A schema that maps 'single_use' feature IDs to credit costs. Applicable only for 'credit_system' features."""
|
||||
|
||||
event_names: Optional[List[str]] = None
|
||||
output_cost: Optional[float] = None
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
optional_fields = set(["consumable", "display", "credit_schema", "event_names"])
|
||||
optional_fields = set(["markup", "input_cost", "output_cost"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
@@ -132,15 +109,118 @@ class CreateFeatureParams(BaseModel):
|
||||
return m
|
||||
|
||||
|
||||
class CreateFeatureProviderMarkupsRequestTypedDict(TypedDict):
|
||||
markup: float
|
||||
|
||||
|
||||
class CreateFeatureProviderMarkupsRequest(BaseModel):
|
||||
markup: float
|
||||
|
||||
|
||||
class CreateFeatureParamsTypedDict(TypedDict):
|
||||
name: str
|
||||
r"""The name of the feature."""
|
||||
type: CreateFeatureTypeRequestBody
|
||||
r"""The type of the feature. 'single_use' features are consumed, like API calls, tokens, or messages. 'continuous_use' features are allocated, like seats, workspaces, or projects. 'credit_system' features are schemas that unify multiple 'single_use' features into a single credit system."""
|
||||
feature_id: str
|
||||
r"""The ID of the feature to create."""
|
||||
consumable: NotRequired[bool]
|
||||
r"""Whether this feature is consumable. A consumable feature is one that periodically resets and is consumed rather than allocated (like credits, API requests, etc.). Applicable only for 'metered' features."""
|
||||
display: NotRequired[CreateFeatureDisplayRequestBodyTypedDict]
|
||||
r"""Singular and plural display names for the feature in your user interface."""
|
||||
credit_schema: NotRequired[List[CreateFeatureCreditSchemaRequestBodyTypedDict]]
|
||||
r"""A schema that maps 'single_use' feature IDs to credit costs. For classic credit systems only — AI credit systems use model_markups instead."""
|
||||
model_markups: NotRequired[
|
||||
Nullable[Dict[str, CreateFeatureModelMarkupsRequestTypedDict]]
|
||||
]
|
||||
r"""Per-model markup overrides for AI credit systems. Maps model IDs to their markup configuration."""
|
||||
default_markup: NotRequired[float]
|
||||
r"""Default percentage markup for this AI credit system. Used when no model or provider markup applies. Use -100 to make usage free."""
|
||||
provider_markups: NotRequired[
|
||||
Nullable[Dict[str, CreateFeatureProviderMarkupsRequestTypedDict]]
|
||||
]
|
||||
r"""Per-provider default markup percentages for AI credit systems. Provider keys match the first segment of model_id."""
|
||||
event_names: NotRequired[List[str]]
|
||||
|
||||
|
||||
class CreateFeatureParams(BaseModel):
|
||||
name: str
|
||||
r"""The name of the feature."""
|
||||
|
||||
type: CreateFeatureTypeRequestBody
|
||||
r"""The type of the feature. 'single_use' features are consumed, like API calls, tokens, or messages. 'continuous_use' features are allocated, like seats, workspaces, or projects. 'credit_system' features are schemas that unify multiple 'single_use' features into a single credit system."""
|
||||
|
||||
feature_id: str
|
||||
r"""The ID of the feature to create."""
|
||||
|
||||
consumable: Optional[bool] = None
|
||||
r"""Whether this feature is consumable. A consumable feature is one that periodically resets and is consumed rather than allocated (like credits, API requests, etc.). Applicable only for 'metered' features."""
|
||||
|
||||
display: Optional[CreateFeatureDisplayRequestBody] = None
|
||||
r"""Singular and plural display names for the feature in your user interface."""
|
||||
|
||||
credit_schema: Optional[List[CreateFeatureCreditSchemaRequestBody]] = None
|
||||
r"""A schema that maps 'single_use' feature IDs to credit costs. For classic credit systems only — AI credit systems use model_markups instead."""
|
||||
|
||||
model_markups: OptionalNullable[Dict[str, CreateFeatureModelMarkupsRequest]] = UNSET
|
||||
r"""Per-model markup overrides for AI credit systems. Maps model IDs to their markup configuration."""
|
||||
|
||||
default_markup: Optional[float] = None
|
||||
r"""Default percentage markup for this AI credit system. Used when no model or provider markup applies. Use -100 to make usage free."""
|
||||
|
||||
provider_markups: OptionalNullable[
|
||||
Dict[str, CreateFeatureProviderMarkupsRequest]
|
||||
] = UNSET
|
||||
r"""Per-provider default markup percentages for AI credit systems. Provider keys match the first segment of model_id."""
|
||||
|
||||
event_names: Optional[List[str]] = None
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
optional_fields = set(
|
||||
[
|
||||
"consumable",
|
||||
"display",
|
||||
"credit_schema",
|
||||
"model_markups",
|
||||
"default_markup",
|
||||
"provider_markups",
|
||||
"event_names",
|
||||
]
|
||||
)
|
||||
nullable_fields = set(["model_markups", "provider_markups"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
is_nullable_and_explicitly_set = (
|
||||
k in nullable_fields
|
||||
and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member
|
||||
)
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if (
|
||||
val is not None
|
||||
or k not in optional_fields
|
||||
or is_nullable_and_explicitly_set
|
||||
):
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
|
||||
|
||||
CreateFeatureTypeResponse = Union[
|
||||
Literal[
|
||||
"boolean",
|
||||
"metered",
|
||||
"credit_system",
|
||||
"ai_credit_system",
|
||||
],
|
||||
UnrecognizedStr,
|
||||
]
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools."""
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing."""
|
||||
|
||||
|
||||
class CreateFeatureCreditSchemaResponseTypedDict(TypedDict):
|
||||
@@ -158,6 +238,44 @@ class CreateFeatureCreditSchemaResponse(BaseModel):
|
||||
r"""Credits consumed per unit of the metered feature."""
|
||||
|
||||
|
||||
class CreateFeatureModelMarkupsResponseTypedDict(TypedDict):
|
||||
markup: NotRequired[float]
|
||||
input_cost: NotRequired[float]
|
||||
output_cost: NotRequired[float]
|
||||
|
||||
|
||||
class CreateFeatureModelMarkupsResponse(BaseModel):
|
||||
markup: Optional[float] = None
|
||||
|
||||
input_cost: Optional[float] = None
|
||||
|
||||
output_cost: Optional[float] = None
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
optional_fields = set(["markup", "input_cost", "output_cost"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if val is not None or k not in optional_fields:
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
|
||||
|
||||
class CreateFeatureProviderMarkupsResponseTypedDict(TypedDict):
|
||||
markup: float
|
||||
|
||||
|
||||
class CreateFeatureProviderMarkupsResponse(BaseModel):
|
||||
markup: float
|
||||
|
||||
|
||||
class CreateFeatureDisplayResponseTypedDict(TypedDict):
|
||||
r"""Display names for the feature in billing UI and customer-facing components."""
|
||||
|
||||
@@ -210,7 +328,7 @@ class CreateFeatureResponseTypedDict(TypedDict):
|
||||
name: str
|
||||
r"""Human-readable name displayed in the dashboard and billing UI."""
|
||||
type: CreateFeatureTypeResponse
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools."""
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing."""
|
||||
consumable: bool
|
||||
r"""For metered features: true if usage resets periodically (API calls, credits), false if allocated persistently (seats, storage)."""
|
||||
archived: bool
|
||||
@@ -219,6 +337,16 @@ class CreateFeatureResponseTypedDict(TypedDict):
|
||||
r"""Event names that trigger this feature's balance. Allows multiple features to respond to a single event."""
|
||||
credit_schema: NotRequired[List[CreateFeatureCreditSchemaResponseTypedDict]]
|
||||
r"""For credit_system features: maps metered features to their credit costs."""
|
||||
model_markups: NotRequired[
|
||||
Nullable[Dict[str, CreateFeatureModelMarkupsResponseTypedDict]]
|
||||
]
|
||||
r"""Per-model markup overrides for AI credit systems."""
|
||||
default_markup: NotRequired[float]
|
||||
r"""Default percentage markup for AI credit systems. Use -100 to make usage free."""
|
||||
provider_markups: NotRequired[
|
||||
Nullable[Dict[str, CreateFeatureProviderMarkupsResponseTypedDict]]
|
||||
]
|
||||
r"""Per-provider default markup percentages for AI credit systems."""
|
||||
display: NotRequired[CreateFeatureDisplayResponseTypedDict]
|
||||
r"""Display names for the feature in billing UI and customer-facing components."""
|
||||
|
||||
@@ -233,7 +361,7 @@ class CreateFeatureResponse(BaseModel):
|
||||
r"""Human-readable name displayed in the dashboard and billing UI."""
|
||||
|
||||
type: CreateFeatureTypeResponse
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools."""
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing."""
|
||||
|
||||
consumable: bool
|
||||
r"""For metered features: true if usage resets periodically (API calls, credits), false if allocated persistently (seats, storage)."""
|
||||
@@ -247,21 +375,52 @@ class CreateFeatureResponse(BaseModel):
|
||||
credit_schema: Optional[List[CreateFeatureCreditSchemaResponse]] = None
|
||||
r"""For credit_system features: maps metered features to their credit costs."""
|
||||
|
||||
model_markups: OptionalNullable[Dict[str, CreateFeatureModelMarkupsResponse]] = (
|
||||
UNSET
|
||||
)
|
||||
r"""Per-model markup overrides for AI credit systems."""
|
||||
|
||||
default_markup: Optional[float] = None
|
||||
r"""Default percentage markup for AI credit systems. Use -100 to make usage free."""
|
||||
|
||||
provider_markups: OptionalNullable[
|
||||
Dict[str, CreateFeatureProviderMarkupsResponse]
|
||||
] = UNSET
|
||||
r"""Per-provider default markup percentages for AI credit systems."""
|
||||
|
||||
display: Optional[CreateFeatureDisplayResponse] = None
|
||||
r"""Display names for the feature in billing UI and customer-facing components."""
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
optional_fields = set(["event_names", "credit_schema", "display"])
|
||||
optional_fields = set(
|
||||
[
|
||||
"event_names",
|
||||
"credit_schema",
|
||||
"model_markups",
|
||||
"default_markup",
|
||||
"provider_markups",
|
||||
"display",
|
||||
]
|
||||
)
|
||||
nullable_fields = set(["model_markups", "provider_markups"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
is_nullable_and_explicitly_set = (
|
||||
k in nullable_fields
|
||||
and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member
|
||||
)
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if val is not None or k not in optional_fields:
|
||||
if (
|
||||
val is not None
|
||||
or k not in optional_fields
|
||||
or is_nullable_and_explicitly_set
|
||||
):
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
|
||||
@@ -12,7 +12,7 @@ from autumn_sdk.types import (
|
||||
from autumn_sdk.utils import FieldMetadata, HeaderMetadata
|
||||
import pydantic
|
||||
from pydantic import model_serializer
|
||||
from typing import Any, List, Literal, Optional, Union
|
||||
from typing import List, Literal, Optional, Union
|
||||
from typing_extensions import Annotated, NotRequired, TypeAliasType, TypedDict
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ class CreatePlanGlobals(BaseModel):
|
||||
return m
|
||||
|
||||
|
||||
CreatePlanPriceIntervalRequest = Literal[
|
||||
CreatePlanPriceIntervalRequestBody = Literal[
|
||||
"one_off",
|
||||
"week",
|
||||
"month",
|
||||
@@ -55,24 +55,24 @@ CreatePlanPriceIntervalRequest = Literal[
|
||||
r"""Billing interval (e.g. 'month', 'year')."""
|
||||
|
||||
|
||||
class CreatePlanPriceRequestTypedDict(TypedDict):
|
||||
class CreatePlanPriceRequestBodyTypedDict(TypedDict):
|
||||
r"""Base recurring price for the plan. Omit for free or usage-only plans."""
|
||||
|
||||
amount: float
|
||||
r"""Base price amount for the plan."""
|
||||
interval: CreatePlanPriceIntervalRequest
|
||||
interval: CreatePlanPriceIntervalRequestBody
|
||||
r"""Billing interval (e.g. 'month', 'year')."""
|
||||
interval_count: NotRequired[float]
|
||||
r"""Number of intervals per billing cycle. Defaults to 1."""
|
||||
|
||||
|
||||
class CreatePlanPriceRequest(BaseModel):
|
||||
class CreatePlanPriceRequestBody(BaseModel):
|
||||
r"""Base recurring price for the plan. Omit for free or usage-only plans."""
|
||||
|
||||
amount: float
|
||||
r"""Base price amount for the plan."""
|
||||
|
||||
interval: CreatePlanPriceIntervalRequest
|
||||
interval: CreatePlanPriceIntervalRequestBody
|
||||
r"""Billing interval (e.g. 'month', 'year')."""
|
||||
|
||||
interval_count: Optional[float] = None
|
||||
@@ -95,7 +95,7 @@ class CreatePlanPriceRequest(BaseModel):
|
||||
return m
|
||||
|
||||
|
||||
CreatePlanResetIntervalRequest = Literal[
|
||||
CreatePlanResetIntervalRequestBody = Literal[
|
||||
"one_off",
|
||||
"minute",
|
||||
"hour",
|
||||
@@ -109,19 +109,19 @@ CreatePlanResetIntervalRequest = Literal[
|
||||
r"""Interval at which balance resets (e.g. 'month', 'year'). For consumable features only."""
|
||||
|
||||
|
||||
class CreatePlanResetRequestTypedDict(TypedDict):
|
||||
class CreatePlanResetRequestBodyTypedDict(TypedDict):
|
||||
r"""Reset configuration for consumable features. Omit for non-consumable features like seats."""
|
||||
|
||||
interval: CreatePlanResetIntervalRequest
|
||||
interval: CreatePlanResetIntervalRequestBody
|
||||
r"""Interval at which balance resets (e.g. 'month', 'year'). For consumable features only."""
|
||||
interval_count: NotRequired[float]
|
||||
r"""Number of intervals between resets. Defaults to 1."""
|
||||
|
||||
|
||||
class CreatePlanResetRequest(BaseModel):
|
||||
class CreatePlanResetRequestBody(BaseModel):
|
||||
r"""Reset configuration for consumable features. Omit for non-consumable features like seats."""
|
||||
|
||||
interval: CreatePlanResetIntervalRequest
|
||||
interval: CreatePlanResetIntervalRequestBody
|
||||
r"""Interval at which balance resets (e.g. 'month', 'year'). For consumable features only."""
|
||||
|
||||
interval_count: Optional[float] = None
|
||||
@@ -144,20 +144,22 @@ class CreatePlanResetRequest(BaseModel):
|
||||
return m
|
||||
|
||||
|
||||
CreatePlanToTypedDict = TypeAliasType("CreatePlanToTypedDict", Union[float, str])
|
||||
CreatePlanToRequestBodyTypedDict = TypeAliasType(
|
||||
"CreatePlanToRequestBodyTypedDict", Union[float, str]
|
||||
)
|
||||
|
||||
|
||||
CreatePlanTo = TypeAliasType("CreatePlanTo", Union[float, str])
|
||||
CreatePlanToRequestBody = TypeAliasType("CreatePlanToRequestBody", Union[float, str])
|
||||
|
||||
|
||||
class CreatePlanTierTypedDict(TypedDict):
|
||||
to: CreatePlanToTypedDict
|
||||
class CreatePlanTierRequestBodyTypedDict(TypedDict):
|
||||
to: CreatePlanToRequestBodyTypedDict
|
||||
amount: NotRequired[float]
|
||||
flat_amount: NotRequired[float]
|
||||
|
||||
|
||||
class CreatePlanTier(BaseModel):
|
||||
to: CreatePlanTo
|
||||
class CreatePlanTierRequestBody(BaseModel):
|
||||
to: CreatePlanToRequestBody
|
||||
|
||||
amount: Optional[float] = None
|
||||
|
||||
@@ -180,13 +182,13 @@ class CreatePlanTier(BaseModel):
|
||||
return m
|
||||
|
||||
|
||||
CreatePlanTierBehaviorRequest = Literal[
|
||||
CreatePlanTierBehaviorRequestBody = Literal[
|
||||
"graduated",
|
||||
"volume",
|
||||
]
|
||||
|
||||
|
||||
CreatePlanItemPriceIntervalRequest = Literal[
|
||||
CreatePlanItemPriceIntervalRequestBody = Literal[
|
||||
"one_off",
|
||||
"week",
|
||||
"month",
|
||||
@@ -197,49 +199,49 @@ CreatePlanItemPriceIntervalRequest = Literal[
|
||||
r"""Billing interval. For consumable features, should match reset.interval."""
|
||||
|
||||
|
||||
CreatePlanBillingMethodRequest = Literal[
|
||||
CreatePlanBillingMethodRequestBody = Literal[
|
||||
"prepaid",
|
||||
"usage_based",
|
||||
]
|
||||
r"""'prepaid' for upfront payment (seats), 'usage_based' for pay-as-you-go."""
|
||||
|
||||
|
||||
class CreatePlanItemPriceRequestTypedDict(TypedDict):
|
||||
class CreatePlanItemPriceRequestBodyTypedDict(TypedDict):
|
||||
r"""Pricing for usage beyond included units. Omit for free features."""
|
||||
|
||||
interval: CreatePlanItemPriceIntervalRequest
|
||||
interval: CreatePlanItemPriceIntervalRequestBody
|
||||
r"""Billing interval. For consumable features, should match reset.interval."""
|
||||
billing_method: CreatePlanBillingMethodRequest
|
||||
billing_method: CreatePlanBillingMethodRequestBody
|
||||
r"""'prepaid' for upfront payment (seats), 'usage_based' for pay-as-you-go."""
|
||||
amount: NotRequired[float]
|
||||
r"""Price per billing_units after included usage. Either 'amount' or 'tiers' is required."""
|
||||
tiers: NotRequired[List[CreatePlanTierTypedDict]]
|
||||
tiers: NotRequired[List[CreatePlanTierRequestBodyTypedDict]]
|
||||
r"""Tiered pricing. Either 'amount' or 'tiers' is required."""
|
||||
tier_behavior: NotRequired[CreatePlanTierBehaviorRequest]
|
||||
tier_behavior: NotRequired[CreatePlanTierBehaviorRequestBody]
|
||||
interval_count: NotRequired[float]
|
||||
r"""Number of intervals per billing cycle. Defaults to 1."""
|
||||
billing_units: NotRequired[float]
|
||||
r"""Units per price increment. Usage is rounded UP when billed (e.g. billing_units=100 means 101 rounds to 200)."""
|
||||
max_purchase: NotRequired[float]
|
||||
r"""Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total."""
|
||||
max_purchase: NotRequired[Nullable[float]]
|
||||
r"""Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total. Null for no limit."""
|
||||
|
||||
|
||||
class CreatePlanItemPriceRequest(BaseModel):
|
||||
class CreatePlanItemPriceRequestBody(BaseModel):
|
||||
r"""Pricing for usage beyond included units. Omit for free features."""
|
||||
|
||||
interval: CreatePlanItemPriceIntervalRequest
|
||||
interval: CreatePlanItemPriceIntervalRequestBody
|
||||
r"""Billing interval. For consumable features, should match reset.interval."""
|
||||
|
||||
billing_method: CreatePlanBillingMethodRequest
|
||||
billing_method: CreatePlanBillingMethodRequestBody
|
||||
r"""'prepaid' for upfront payment (seats), 'usage_based' for pay-as-you-go."""
|
||||
|
||||
amount: Optional[float] = None
|
||||
r"""Price per billing_units after included usage. Either 'amount' or 'tiers' is required."""
|
||||
|
||||
tiers: Optional[List[CreatePlanTier]] = None
|
||||
tiers: Optional[List[CreatePlanTierRequestBody]] = None
|
||||
r"""Tiered pricing. Either 'amount' or 'tiers' is required."""
|
||||
|
||||
tier_behavior: Optional[CreatePlanTierBehaviorRequest] = None
|
||||
tier_behavior: Optional[CreatePlanTierBehaviorRequestBody] = None
|
||||
|
||||
interval_count: Optional[float] = 1
|
||||
r"""Number of intervals per billing cycle. Defaults to 1."""
|
||||
@@ -247,8 +249,8 @@ class CreatePlanItemPriceRequest(BaseModel):
|
||||
billing_units: Optional[float] = 1
|
||||
r"""Units per price increment. Usage is rounded UP when billed (e.g. billing_units=100 means 101 rounds to 200)."""
|
||||
|
||||
max_purchase: Optional[float] = None
|
||||
r"""Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total."""
|
||||
max_purchase: OptionalNullable[float] = UNSET
|
||||
r"""Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total. Null for no limit."""
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
@@ -262,15 +264,24 @@ class CreatePlanItemPriceRequest(BaseModel):
|
||||
"max_purchase",
|
||||
]
|
||||
)
|
||||
nullable_fields = set(["max_purchase"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
is_nullable_and_explicitly_set = (
|
||||
k in nullable_fields
|
||||
and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member
|
||||
)
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if val is not None or k not in optional_fields:
|
||||
if (
|
||||
val is not None
|
||||
or k not in optional_fields
|
||||
or is_nullable_and_explicitly_set
|
||||
):
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
@@ -314,17 +325,17 @@ class CreatePlanProration(BaseModel):
|
||||
r"""Credit behavior when quantity decreases mid-cycle."""
|
||||
|
||||
|
||||
CreatePlanExpiryDurationTypeRequest = Literal[
|
||||
CreatePlanExpiryDurationTypeRequestBody = Literal[
|
||||
"month",
|
||||
"forever",
|
||||
]
|
||||
r"""When rolled over units expire."""
|
||||
|
||||
|
||||
class CreatePlanRolloverRequestTypedDict(TypedDict):
|
||||
class CreatePlanRolloverRequestBodyTypedDict(TypedDict):
|
||||
r"""Rollover config for unused units. If set, unused included units carry over."""
|
||||
|
||||
expiry_duration_type: CreatePlanExpiryDurationTypeRequest
|
||||
expiry_duration_type: CreatePlanExpiryDurationTypeRequestBody
|
||||
r"""When rolled over units expire."""
|
||||
max: NotRequired[float]
|
||||
r"""Max rollover units. Omit for unlimited rollover."""
|
||||
@@ -334,10 +345,10 @@ class CreatePlanRolloverRequestTypedDict(TypedDict):
|
||||
r"""Number of periods before expiry."""
|
||||
|
||||
|
||||
class CreatePlanRolloverRequest(BaseModel):
|
||||
class CreatePlanRolloverRequestBody(BaseModel):
|
||||
r"""Rollover config for unused units. If set, unused included units carry over."""
|
||||
|
||||
expiry_duration_type: CreatePlanExpiryDurationTypeRequest
|
||||
expiry_duration_type: CreatePlanExpiryDurationTypeRequestBody
|
||||
r"""When rolled over units expire."""
|
||||
|
||||
max: Optional[float] = None
|
||||
@@ -375,13 +386,13 @@ class CreatePlanPlanItemTypedDict(TypedDict):
|
||||
r"""Number of free units included. Balance resets to this each interval for consumable features."""
|
||||
unlimited: NotRequired[bool]
|
||||
r"""If true, customer has unlimited access to this feature."""
|
||||
reset: NotRequired[CreatePlanResetRequestTypedDict]
|
||||
reset: NotRequired[CreatePlanResetRequestBodyTypedDict]
|
||||
r"""Reset configuration for consumable features. Omit for non-consumable features like seats."""
|
||||
price: NotRequired[CreatePlanItemPriceRequestTypedDict]
|
||||
price: NotRequired[CreatePlanItemPriceRequestBodyTypedDict]
|
||||
r"""Pricing for usage beyond included units. Omit for free features."""
|
||||
proration: NotRequired[CreatePlanProrationTypedDict]
|
||||
r"""Proration settings for prepaid features. Controls mid-cycle quantity change billing."""
|
||||
rollover: NotRequired[CreatePlanRolloverRequestTypedDict]
|
||||
rollover: NotRequired[CreatePlanRolloverRequestBodyTypedDict]
|
||||
r"""Rollover config for unused units. If set, unused included units carry over."""
|
||||
|
||||
|
||||
@@ -397,16 +408,16 @@ class CreatePlanPlanItem(BaseModel):
|
||||
unlimited: Optional[bool] = None
|
||||
r"""If true, customer has unlimited access to this feature."""
|
||||
|
||||
reset: Optional[CreatePlanResetRequest] = None
|
||||
reset: Optional[CreatePlanResetRequestBody] = None
|
||||
r"""Reset configuration for consumable features. Omit for non-consumable features like seats."""
|
||||
|
||||
price: Optional[CreatePlanItemPriceRequest] = None
|
||||
price: Optional[CreatePlanItemPriceRequestBody] = None
|
||||
r"""Pricing for usage beyond included units. Omit for free features."""
|
||||
|
||||
proration: Optional[CreatePlanProration] = None
|
||||
r"""Proration settings for prepaid features. Controls mid-cycle quantity change billing."""
|
||||
|
||||
rollover: Optional[CreatePlanRolloverRequest] = None
|
||||
rollover: Optional[CreatePlanRolloverRequestBody] = None
|
||||
r"""Rollover config for unused units. If set, unused included units carry over."""
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
@@ -531,7 +542,7 @@ class CreatePlanParamsTypedDict(TypedDict):
|
||||
r"""If true, this plan can be attached alongside other plans. Otherwise, attaching replaces existing plans in the same group."""
|
||||
auto_enable: NotRequired[bool]
|
||||
r"""If true, plan is automatically attached when a customer is created. Use for free tiers."""
|
||||
price: NotRequired[CreatePlanPriceRequestTypedDict]
|
||||
price: NotRequired[CreatePlanPriceRequestBodyTypedDict]
|
||||
r"""Base recurring price for the plan. Omit for free or usage-only plans."""
|
||||
items: NotRequired[List[CreatePlanPlanItemTypedDict]]
|
||||
r"""Feature configurations for this plan. Each item defines included units, pricing, and reset behavior."""
|
||||
@@ -561,7 +572,7 @@ class CreatePlanParams(BaseModel):
|
||||
auto_enable: Optional[bool] = False
|
||||
r"""If true, plan is automatically attached when a customer is created. Use for free tiers."""
|
||||
|
||||
price: Optional[CreatePlanPriceRequest] = None
|
||||
price: Optional[CreatePlanPriceRequestBody] = None
|
||||
r"""Base recurring price for the plan. Omit for free or usage-only plans."""
|
||||
|
||||
items: Optional[List[CreatePlanPlanItem]] = None
|
||||
@@ -710,6 +721,7 @@ CreatePlanType = Union[
|
||||
"single_use",
|
||||
"continuous_use",
|
||||
"credit_system",
|
||||
"ai_credit_system",
|
||||
],
|
||||
UnrecognizedStr,
|
||||
]
|
||||
@@ -858,6 +870,44 @@ class CreatePlanResetResponse(BaseModel):
|
||||
return m
|
||||
|
||||
|
||||
CreatePlanToResponseTypedDict = TypeAliasType(
|
||||
"CreatePlanToResponseTypedDict", Union[float, str]
|
||||
)
|
||||
|
||||
|
||||
CreatePlanToResponse = TypeAliasType("CreatePlanToResponse", Union[float, str])
|
||||
|
||||
|
||||
class CreatePlanTierResponseTypedDict(TypedDict):
|
||||
to: CreatePlanToResponseTypedDict
|
||||
amount: float
|
||||
flat_amount: NotRequired[float]
|
||||
|
||||
|
||||
class CreatePlanTierResponse(BaseModel):
|
||||
to: CreatePlanToResponse
|
||||
|
||||
amount: float
|
||||
|
||||
flat_amount: Optional[float] = None
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
optional_fields = set(["flat_amount"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if val is not None or k not in optional_fields:
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
|
||||
|
||||
CreatePlanTierBehaviorResponse = Union[
|
||||
Literal[
|
||||
"graduated",
|
||||
@@ -902,7 +952,7 @@ class CreatePlanItemPriceResponseTypedDict(TypedDict):
|
||||
r"""Maximum units a customer can purchase beyond included. E.g. if included=100 and max_purchase=300, customer can use up to 400 total before usage is capped. Null for no limit."""
|
||||
amount: NotRequired[float]
|
||||
r"""Price per billing_units after included usage is consumed. Mutually exclusive with tiers."""
|
||||
tiers: NotRequired[List[Nullable[Any]]]
|
||||
tiers: NotRequired[List[CreatePlanTierResponseTypedDict]]
|
||||
r"""Tiered pricing configuration. Each tier's 'to' INCLUDES the included amount. Either 'tiers' or 'amount' is required."""
|
||||
tier_behavior: NotRequired[CreatePlanTierBehaviorResponse]
|
||||
interval_count: NotRequired[float]
|
||||
@@ -925,7 +975,7 @@ class CreatePlanItemPriceResponse(BaseModel):
|
||||
amount: Optional[float] = None
|
||||
r"""Price per billing_units after included usage is consumed. Mutually exclusive with tiers."""
|
||||
|
||||
tiers: Optional[List[Nullable[Any]]] = None
|
||||
tiers: Optional[List[CreatePlanTierResponse]] = None
|
||||
r"""Tiered pricing configuration. Each tier's 'to' INCLUDES the included amount. Either 'tiers' or 'amount' is required."""
|
||||
|
||||
tier_behavior: Optional[CreatePlanTierBehaviorResponse] = None
|
||||
|
||||
@@ -14,7 +14,7 @@ import pydantic
|
||||
from pydantic import model_serializer
|
||||
from pydantic.functional_validators import AfterValidator
|
||||
from typing import Any, Dict, List, Literal, Optional, Union
|
||||
from typing_extensions import Annotated, NotRequired, TypedDict
|
||||
from typing_extensions import Annotated, NotRequired, TypeAliasType, TypedDict
|
||||
|
||||
|
||||
class CreateScheduleGlobalsTypedDict(TypedDict):
|
||||
@@ -243,7 +243,7 @@ class CreateScheduleBasePrice2(BaseModel):
|
||||
return m
|
||||
|
||||
|
||||
CreateScheduleResetInterval2 = Literal[
|
||||
CreateScheduleItemResetInterval2 = Literal[
|
||||
"one_off",
|
||||
"minute",
|
||||
"hour",
|
||||
@@ -257,19 +257,19 @@ CreateScheduleResetInterval2 = Literal[
|
||||
r"""Interval at which balance resets (e.g. 'month', 'year'). For consumable features only."""
|
||||
|
||||
|
||||
class CreateScheduleReset2TypedDict(TypedDict):
|
||||
class CreateScheduleItemReset2TypedDict(TypedDict):
|
||||
r"""Reset configuration for consumable features. Omit for non-consumable features like seats."""
|
||||
|
||||
interval: CreateScheduleResetInterval2
|
||||
interval: CreateScheduleItemResetInterval2
|
||||
r"""Interval at which balance resets (e.g. 'month', 'year'). For consumable features only."""
|
||||
interval_count: NotRequired[float]
|
||||
r"""Number of intervals between resets. Defaults to 1."""
|
||||
|
||||
|
||||
class CreateScheduleReset2(BaseModel):
|
||||
class CreateScheduleItemReset2(BaseModel):
|
||||
r"""Reset configuration for consumable features. Omit for non-consumable features like seats."""
|
||||
|
||||
interval: CreateScheduleResetInterval2
|
||||
interval: CreateScheduleItemResetInterval2
|
||||
r"""Interval at which balance resets (e.g. 'month', 'year'). For consumable features only."""
|
||||
|
||||
interval_count: Optional[float] = None
|
||||
@@ -292,13 +292,13 @@ class CreateScheduleReset2(BaseModel):
|
||||
return m
|
||||
|
||||
|
||||
class CreateScheduleTier2TypedDict(TypedDict):
|
||||
class CreateScheduleItemTier2TypedDict(TypedDict):
|
||||
to: NotRequired[Any]
|
||||
amount: NotRequired[Any]
|
||||
flat_amount: NotRequired[Any]
|
||||
|
||||
|
||||
class CreateScheduleTier2(BaseModel):
|
||||
class CreateScheduleItemTier2(BaseModel):
|
||||
to: Optional[Any] = None
|
||||
|
||||
amount: Optional[Any] = None
|
||||
@@ -322,7 +322,7 @@ class CreateScheduleTier2(BaseModel):
|
||||
return m
|
||||
|
||||
|
||||
CreateScheduleTierBehavior2 = Literal[
|
||||
CreateScheduleItemTierBehavior2 = Literal[
|
||||
"graduated",
|
||||
"volume",
|
||||
]
|
||||
@@ -339,49 +339,49 @@ CreateScheduleItemPriceInterval2 = Literal[
|
||||
r"""Billing interval. For consumable features, should match reset.interval."""
|
||||
|
||||
|
||||
CreateScheduleBillingMethod2 = Literal[
|
||||
CreateScheduleItemBillingMethod2 = Literal[
|
||||
"prepaid",
|
||||
"usage_based",
|
||||
]
|
||||
r"""'prepaid' for upfront payment (seats), 'usage_based' for pay-as-you-go."""
|
||||
|
||||
|
||||
class CreateSchedulePrice2TypedDict(TypedDict):
|
||||
class CreateScheduleItemPrice2TypedDict(TypedDict):
|
||||
r"""Pricing for usage beyond included units. Omit for free features."""
|
||||
|
||||
interval: CreateScheduleItemPriceInterval2
|
||||
r"""Billing interval. For consumable features, should match reset.interval."""
|
||||
billing_method: CreateScheduleBillingMethod2
|
||||
billing_method: CreateScheduleItemBillingMethod2
|
||||
r"""'prepaid' for upfront payment (seats), 'usage_based' for pay-as-you-go."""
|
||||
amount: NotRequired[float]
|
||||
r"""Price per billing_units after included usage. Either 'amount' or 'tiers' is required."""
|
||||
tiers: NotRequired[List[CreateScheduleTier2TypedDict]]
|
||||
tiers: NotRequired[List[CreateScheduleItemTier2TypedDict]]
|
||||
r"""Tiered pricing. Either 'amount' or 'tiers' is required."""
|
||||
tier_behavior: NotRequired[CreateScheduleTierBehavior2]
|
||||
tier_behavior: NotRequired[CreateScheduleItemTierBehavior2]
|
||||
interval_count: NotRequired[float]
|
||||
r"""Number of intervals per billing cycle. Defaults to 1."""
|
||||
billing_units: NotRequired[float]
|
||||
r"""Units per price increment. Usage is rounded UP when billed (e.g. billing_units=100 means 101 rounds to 200)."""
|
||||
max_purchase: NotRequired[float]
|
||||
r"""Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total."""
|
||||
max_purchase: NotRequired[Nullable[float]]
|
||||
r"""Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total. Null for no limit."""
|
||||
|
||||
|
||||
class CreateSchedulePrice2(BaseModel):
|
||||
class CreateScheduleItemPrice2(BaseModel):
|
||||
r"""Pricing for usage beyond included units. Omit for free features."""
|
||||
|
||||
interval: CreateScheduleItemPriceInterval2
|
||||
r"""Billing interval. For consumable features, should match reset.interval."""
|
||||
|
||||
billing_method: CreateScheduleBillingMethod2
|
||||
billing_method: CreateScheduleItemBillingMethod2
|
||||
r"""'prepaid' for upfront payment (seats), 'usage_based' for pay-as-you-go."""
|
||||
|
||||
amount: Optional[float] = None
|
||||
r"""Price per billing_units after included usage. Either 'amount' or 'tiers' is required."""
|
||||
|
||||
tiers: Optional[List[CreateScheduleTier2]] = None
|
||||
tiers: Optional[List[CreateScheduleItemTier2]] = None
|
||||
r"""Tiered pricing. Either 'amount' or 'tiers' is required."""
|
||||
|
||||
tier_behavior: Optional[CreateScheduleTierBehavior2] = None
|
||||
tier_behavior: Optional[CreateScheduleItemTierBehavior2] = None
|
||||
|
||||
interval_count: Optional[float] = 1
|
||||
r"""Number of intervals per billing cycle. Defaults to 1."""
|
||||
@@ -389,8 +389,8 @@ class CreateSchedulePrice2(BaseModel):
|
||||
billing_units: Optional[float] = 1
|
||||
r"""Units per price increment. Usage is rounded UP when billed (e.g. billing_units=100 means 101 rounds to 200)."""
|
||||
|
||||
max_purchase: Optional[float] = None
|
||||
r"""Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total."""
|
||||
max_purchase: OptionalNullable[float] = UNSET
|
||||
r"""Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total. Null for no limit."""
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
@@ -404,21 +404,30 @@ class CreateSchedulePrice2(BaseModel):
|
||||
"max_purchase",
|
||||
]
|
||||
)
|
||||
nullable_fields = set(["max_purchase"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
is_nullable_and_explicitly_set = (
|
||||
k in nullable_fields
|
||||
and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member
|
||||
)
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if val is not None or k not in optional_fields:
|
||||
if (
|
||||
val is not None
|
||||
or k not in optional_fields
|
||||
or is_nullable_and_explicitly_set
|
||||
):
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
|
||||
|
||||
CreateScheduleOnIncrease2 = Literal[
|
||||
CreateScheduleItemOnIncrease2 = Literal[
|
||||
"bill_immediately",
|
||||
"prorate_immediately",
|
||||
"prorate_next_cycle",
|
||||
@@ -427,7 +436,7 @@ CreateScheduleOnIncrease2 = Literal[
|
||||
r"""Billing behavior when quantity increases mid-cycle."""
|
||||
|
||||
|
||||
CreateScheduleOnDecrease2 = Literal[
|
||||
CreateScheduleItemOnDecrease2 = Literal[
|
||||
"prorate",
|
||||
"prorate_immediately",
|
||||
"prorate_next_cycle",
|
||||
@@ -437,36 +446,36 @@ CreateScheduleOnDecrease2 = Literal[
|
||||
r"""Credit behavior when quantity decreases mid-cycle."""
|
||||
|
||||
|
||||
class CreateScheduleProration2TypedDict(TypedDict):
|
||||
class CreateScheduleItemProration2TypedDict(TypedDict):
|
||||
r"""Proration settings for prepaid features. Controls mid-cycle quantity change billing."""
|
||||
|
||||
on_increase: CreateScheduleOnIncrease2
|
||||
on_increase: CreateScheduleItemOnIncrease2
|
||||
r"""Billing behavior when quantity increases mid-cycle."""
|
||||
on_decrease: CreateScheduleOnDecrease2
|
||||
on_decrease: CreateScheduleItemOnDecrease2
|
||||
r"""Credit behavior when quantity decreases mid-cycle."""
|
||||
|
||||
|
||||
class CreateScheduleProration2(BaseModel):
|
||||
class CreateScheduleItemProration2(BaseModel):
|
||||
r"""Proration settings for prepaid features. Controls mid-cycle quantity change billing."""
|
||||
|
||||
on_increase: CreateScheduleOnIncrease2
|
||||
on_increase: CreateScheduleItemOnIncrease2
|
||||
r"""Billing behavior when quantity increases mid-cycle."""
|
||||
|
||||
on_decrease: CreateScheduleOnDecrease2
|
||||
on_decrease: CreateScheduleItemOnDecrease2
|
||||
r"""Credit behavior when quantity decreases mid-cycle."""
|
||||
|
||||
|
||||
CreateScheduleExpiryDurationType2 = Literal[
|
||||
CreateScheduleItemExpiryDurationType2 = Literal[
|
||||
"month",
|
||||
"forever",
|
||||
]
|
||||
r"""When rolled over units expire."""
|
||||
|
||||
|
||||
class CreateScheduleRollover2TypedDict(TypedDict):
|
||||
class CreateScheduleItemRollover2TypedDict(TypedDict):
|
||||
r"""Rollover config for unused units. If set, unused included units carry over."""
|
||||
|
||||
expiry_duration_type: CreateScheduleExpiryDurationType2
|
||||
expiry_duration_type: CreateScheduleItemExpiryDurationType2
|
||||
r"""When rolled over units expire."""
|
||||
max: NotRequired[float]
|
||||
r"""Max rollover units. Omit for unlimited rollover."""
|
||||
@@ -476,10 +485,10 @@ class CreateScheduleRollover2TypedDict(TypedDict):
|
||||
r"""Number of periods before expiry."""
|
||||
|
||||
|
||||
class CreateScheduleRollover2(BaseModel):
|
||||
class CreateScheduleItemRollover2(BaseModel):
|
||||
r"""Rollover config for unused units. If set, unused included units carry over."""
|
||||
|
||||
expiry_duration_type: CreateScheduleExpiryDurationType2
|
||||
expiry_duration_type: CreateScheduleItemExpiryDurationType2
|
||||
r"""When rolled over units expire."""
|
||||
|
||||
max: Optional[float] = None
|
||||
@@ -508,7 +517,7 @@ class CreateScheduleRollover2(BaseModel):
|
||||
return m
|
||||
|
||||
|
||||
class CreateSchedulePlanItem2TypedDict(TypedDict):
|
||||
class CreateScheduleItemPlanItem2TypedDict(TypedDict):
|
||||
r"""Configuration for a feature item in a plan, including usage limits, pricing, and rollover settings."""
|
||||
|
||||
feature_id: str
|
||||
@@ -517,17 +526,17 @@ class CreateSchedulePlanItem2TypedDict(TypedDict):
|
||||
r"""Number of free units included. Balance resets to this each interval for consumable features."""
|
||||
unlimited: NotRequired[bool]
|
||||
r"""If true, customer has unlimited access to this feature."""
|
||||
reset: NotRequired[CreateScheduleReset2TypedDict]
|
||||
reset: NotRequired[CreateScheduleItemReset2TypedDict]
|
||||
r"""Reset configuration for consumable features. Omit for non-consumable features like seats."""
|
||||
price: NotRequired[CreateSchedulePrice2TypedDict]
|
||||
price: NotRequired[CreateScheduleItemPrice2TypedDict]
|
||||
r"""Pricing for usage beyond included units. Omit for free features."""
|
||||
proration: NotRequired[CreateScheduleProration2TypedDict]
|
||||
proration: NotRequired[CreateScheduleItemProration2TypedDict]
|
||||
r"""Proration settings for prepaid features. Controls mid-cycle quantity change billing."""
|
||||
rollover: NotRequired[CreateScheduleRollover2TypedDict]
|
||||
rollover: NotRequired[CreateScheduleItemRollover2TypedDict]
|
||||
r"""Rollover config for unused units. If set, unused included units carry over."""
|
||||
|
||||
|
||||
class CreateSchedulePlanItem2(BaseModel):
|
||||
class CreateScheduleItemPlanItem2(BaseModel):
|
||||
r"""Configuration for a feature item in a plan, including usage limits, pricing, and rollover settings."""
|
||||
|
||||
feature_id: str
|
||||
@@ -539,16 +548,16 @@ class CreateSchedulePlanItem2(BaseModel):
|
||||
unlimited: Optional[bool] = None
|
||||
r"""If true, customer has unlimited access to this feature."""
|
||||
|
||||
reset: Optional[CreateScheduleReset2] = None
|
||||
reset: Optional[CreateScheduleItemReset2] = None
|
||||
r"""Reset configuration for consumable features. Omit for non-consumable features like seats."""
|
||||
|
||||
price: Optional[CreateSchedulePrice2] = None
|
||||
price: Optional[CreateScheduleItemPrice2] = None
|
||||
r"""Pricing for usage beyond included units. Omit for free features."""
|
||||
|
||||
proration: Optional[CreateScheduleProration2] = None
|
||||
proration: Optional[CreateScheduleItemProration2] = None
|
||||
r"""Proration settings for prepaid features. Controls mid-cycle quantity change billing."""
|
||||
|
||||
rollover: Optional[CreateScheduleRollover2] = None
|
||||
rollover: Optional[CreateScheduleItemRollover2] = None
|
||||
r"""Rollover config for unused units. If set, unused included units carry over."""
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
@@ -570,27 +579,464 @@ class CreateSchedulePlanItem2(BaseModel):
|
||||
return m
|
||||
|
||||
|
||||
CreateScheduleAddItemResetInterval2 = Literal[
|
||||
"one_off",
|
||||
"minute",
|
||||
"hour",
|
||||
"day",
|
||||
"week",
|
||||
"month",
|
||||
"quarter",
|
||||
"semi_annual",
|
||||
"year",
|
||||
]
|
||||
r"""Interval at which balance resets (e.g. 'month', 'year'). For consumable features only."""
|
||||
|
||||
|
||||
class CreateScheduleAddItemReset2TypedDict(TypedDict):
|
||||
r"""Reset configuration for consumable features. Omit for non-consumable features like seats."""
|
||||
|
||||
interval: CreateScheduleAddItemResetInterval2
|
||||
r"""Interval at which balance resets (e.g. 'month', 'year'). For consumable features only."""
|
||||
interval_count: NotRequired[float]
|
||||
r"""Number of intervals between resets. Defaults to 1."""
|
||||
|
||||
|
||||
class CreateScheduleAddItemReset2(BaseModel):
|
||||
r"""Reset configuration for consumable features. Omit for non-consumable features like seats."""
|
||||
|
||||
interval: CreateScheduleAddItemResetInterval2
|
||||
r"""Interval at which balance resets (e.g. 'month', 'year'). For consumable features only."""
|
||||
|
||||
interval_count: Optional[float] = None
|
||||
r"""Number of intervals between resets. Defaults to 1."""
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
optional_fields = set(["interval_count"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if val is not None or k not in optional_fields:
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
|
||||
|
||||
class CreateScheduleAddItemTier2TypedDict(TypedDict):
|
||||
to: NotRequired[Any]
|
||||
amount: NotRequired[Any]
|
||||
flat_amount: NotRequired[Any]
|
||||
|
||||
|
||||
class CreateScheduleAddItemTier2(BaseModel):
|
||||
to: Optional[Any] = None
|
||||
|
||||
amount: Optional[Any] = None
|
||||
|
||||
flat_amount: Optional[Any] = None
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
optional_fields = set(["to", "amount", "flat_amount"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if val is not None or k not in optional_fields:
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
|
||||
|
||||
CreateScheduleAddItemTierBehavior2 = Literal[
|
||||
"graduated",
|
||||
"volume",
|
||||
]
|
||||
|
||||
|
||||
CreateScheduleAddItemPriceInterval2 = Literal[
|
||||
"one_off",
|
||||
"week",
|
||||
"month",
|
||||
"quarter",
|
||||
"semi_annual",
|
||||
"year",
|
||||
]
|
||||
r"""Billing interval. For consumable features, should match reset.interval."""
|
||||
|
||||
|
||||
CreateScheduleAddItemBillingMethod2 = Literal[
|
||||
"prepaid",
|
||||
"usage_based",
|
||||
]
|
||||
r"""'prepaid' for upfront payment (seats), 'usage_based' for pay-as-you-go."""
|
||||
|
||||
|
||||
class CreateScheduleAddItemPrice2TypedDict(TypedDict):
|
||||
r"""Pricing for usage beyond included units. Omit for free features."""
|
||||
|
||||
interval: CreateScheduleAddItemPriceInterval2
|
||||
r"""Billing interval. For consumable features, should match reset.interval."""
|
||||
billing_method: CreateScheduleAddItemBillingMethod2
|
||||
r"""'prepaid' for upfront payment (seats), 'usage_based' for pay-as-you-go."""
|
||||
amount: NotRequired[float]
|
||||
r"""Price per billing_units after included usage. Either 'amount' or 'tiers' is required."""
|
||||
tiers: NotRequired[List[CreateScheduleAddItemTier2TypedDict]]
|
||||
r"""Tiered pricing. Either 'amount' or 'tiers' is required."""
|
||||
tier_behavior: NotRequired[CreateScheduleAddItemTierBehavior2]
|
||||
interval_count: NotRequired[float]
|
||||
r"""Number of intervals per billing cycle. Defaults to 1."""
|
||||
billing_units: NotRequired[float]
|
||||
r"""Units per price increment. Usage is rounded UP when billed (e.g. billing_units=100 means 101 rounds to 200)."""
|
||||
max_purchase: NotRequired[Nullable[float]]
|
||||
r"""Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total. Null for no limit."""
|
||||
|
||||
|
||||
class CreateScheduleAddItemPrice2(BaseModel):
|
||||
r"""Pricing for usage beyond included units. Omit for free features."""
|
||||
|
||||
interval: CreateScheduleAddItemPriceInterval2
|
||||
r"""Billing interval. For consumable features, should match reset.interval."""
|
||||
|
||||
billing_method: CreateScheduleAddItemBillingMethod2
|
||||
r"""'prepaid' for upfront payment (seats), 'usage_based' for pay-as-you-go."""
|
||||
|
||||
amount: Optional[float] = None
|
||||
r"""Price per billing_units after included usage. Either 'amount' or 'tiers' is required."""
|
||||
|
||||
tiers: Optional[List[CreateScheduleAddItemTier2]] = None
|
||||
r"""Tiered pricing. Either 'amount' or 'tiers' is required."""
|
||||
|
||||
tier_behavior: Optional[CreateScheduleAddItemTierBehavior2] = None
|
||||
|
||||
interval_count: Optional[float] = 1
|
||||
r"""Number of intervals per billing cycle. Defaults to 1."""
|
||||
|
||||
billing_units: Optional[float] = 1
|
||||
r"""Units per price increment. Usage is rounded UP when billed (e.g. billing_units=100 means 101 rounds to 200)."""
|
||||
|
||||
max_purchase: OptionalNullable[float] = UNSET
|
||||
r"""Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total. Null for no limit."""
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
optional_fields = set(
|
||||
[
|
||||
"amount",
|
||||
"tiers",
|
||||
"tier_behavior",
|
||||
"interval_count",
|
||||
"billing_units",
|
||||
"max_purchase",
|
||||
]
|
||||
)
|
||||
nullable_fields = set(["max_purchase"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
is_nullable_and_explicitly_set = (
|
||||
k in nullable_fields
|
||||
and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member
|
||||
)
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if (
|
||||
val is not None
|
||||
or k not in optional_fields
|
||||
or is_nullable_and_explicitly_set
|
||||
):
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
|
||||
|
||||
CreateScheduleAddItemOnIncrease2 = Literal[
|
||||
"bill_immediately",
|
||||
"prorate_immediately",
|
||||
"prorate_next_cycle",
|
||||
"bill_next_cycle",
|
||||
]
|
||||
r"""Billing behavior when quantity increases mid-cycle."""
|
||||
|
||||
|
||||
CreateScheduleAddItemOnDecrease2 = Literal[
|
||||
"prorate",
|
||||
"prorate_immediately",
|
||||
"prorate_next_cycle",
|
||||
"none",
|
||||
"no_prorations",
|
||||
]
|
||||
r"""Credit behavior when quantity decreases mid-cycle."""
|
||||
|
||||
|
||||
class CreateScheduleAddItemProration2TypedDict(TypedDict):
|
||||
r"""Proration settings for prepaid features. Controls mid-cycle quantity change billing."""
|
||||
|
||||
on_increase: CreateScheduleAddItemOnIncrease2
|
||||
r"""Billing behavior when quantity increases mid-cycle."""
|
||||
on_decrease: CreateScheduleAddItemOnDecrease2
|
||||
r"""Credit behavior when quantity decreases mid-cycle."""
|
||||
|
||||
|
||||
class CreateScheduleAddItemProration2(BaseModel):
|
||||
r"""Proration settings for prepaid features. Controls mid-cycle quantity change billing."""
|
||||
|
||||
on_increase: CreateScheduleAddItemOnIncrease2
|
||||
r"""Billing behavior when quantity increases mid-cycle."""
|
||||
|
||||
on_decrease: CreateScheduleAddItemOnDecrease2
|
||||
r"""Credit behavior when quantity decreases mid-cycle."""
|
||||
|
||||
|
||||
CreateScheduleAddItemExpiryDurationType2 = Literal[
|
||||
"month",
|
||||
"forever",
|
||||
]
|
||||
r"""When rolled over units expire."""
|
||||
|
||||
|
||||
class CreateScheduleAddItemRollover2TypedDict(TypedDict):
|
||||
r"""Rollover config for unused units. If set, unused included units carry over."""
|
||||
|
||||
expiry_duration_type: CreateScheduleAddItemExpiryDurationType2
|
||||
r"""When rolled over units expire."""
|
||||
max: NotRequired[float]
|
||||
r"""Max rollover units. Omit for unlimited rollover."""
|
||||
max_percentage: NotRequired[float]
|
||||
r"""Maximum rollover as a percentage (0-100) of included + prepaid grant. Mutually exclusive with max."""
|
||||
expiry_duration_length: NotRequired[float]
|
||||
r"""Number of periods before expiry."""
|
||||
|
||||
|
||||
class CreateScheduleAddItemRollover2(BaseModel):
|
||||
r"""Rollover config for unused units. If set, unused included units carry over."""
|
||||
|
||||
expiry_duration_type: CreateScheduleAddItemExpiryDurationType2
|
||||
r"""When rolled over units expire."""
|
||||
|
||||
max: Optional[float] = None
|
||||
r"""Max rollover units. Omit for unlimited rollover."""
|
||||
|
||||
max_percentage: Optional[float] = None
|
||||
r"""Maximum rollover as a percentage (0-100) of included + prepaid grant. Mutually exclusive with max."""
|
||||
|
||||
expiry_duration_length: Optional[float] = None
|
||||
r"""Number of periods before expiry."""
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
optional_fields = set(["max", "max_percentage", "expiry_duration_length"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if val is not None or k not in optional_fields:
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
|
||||
|
||||
class CreateScheduleAddItemPlanItem2TypedDict(TypedDict):
|
||||
r"""Configuration for a feature item in a plan, including usage limits, pricing, and rollover settings."""
|
||||
|
||||
feature_id: str
|
||||
r"""The ID of the feature to configure."""
|
||||
included: NotRequired[float]
|
||||
r"""Number of free units included. Balance resets to this each interval for consumable features."""
|
||||
unlimited: NotRequired[bool]
|
||||
r"""If true, customer has unlimited access to this feature."""
|
||||
reset: NotRequired[CreateScheduleAddItemReset2TypedDict]
|
||||
r"""Reset configuration for consumable features. Omit for non-consumable features like seats."""
|
||||
price: NotRequired[CreateScheduleAddItemPrice2TypedDict]
|
||||
r"""Pricing for usage beyond included units. Omit for free features."""
|
||||
proration: NotRequired[CreateScheduleAddItemProration2TypedDict]
|
||||
r"""Proration settings for prepaid features. Controls mid-cycle quantity change billing."""
|
||||
rollover: NotRequired[CreateScheduleAddItemRollover2TypedDict]
|
||||
r"""Rollover config for unused units. If set, unused included units carry over."""
|
||||
|
||||
|
||||
class CreateScheduleAddItemPlanItem2(BaseModel):
|
||||
r"""Configuration for a feature item in a plan, including usage limits, pricing, and rollover settings."""
|
||||
|
||||
feature_id: str
|
||||
r"""The ID of the feature to configure."""
|
||||
|
||||
included: Optional[float] = None
|
||||
r"""Number of free units included. Balance resets to this each interval for consumable features."""
|
||||
|
||||
unlimited: Optional[bool] = None
|
||||
r"""If true, customer has unlimited access to this feature."""
|
||||
|
||||
reset: Optional[CreateScheduleAddItemReset2] = None
|
||||
r"""Reset configuration for consumable features. Omit for non-consumable features like seats."""
|
||||
|
||||
price: Optional[CreateScheduleAddItemPrice2] = None
|
||||
r"""Pricing for usage beyond included units. Omit for free features."""
|
||||
|
||||
proration: Optional[CreateScheduleAddItemProration2] = None
|
||||
r"""Proration settings for prepaid features. Controls mid-cycle quantity change billing."""
|
||||
|
||||
rollover: Optional[CreateScheduleAddItemRollover2] = None
|
||||
r"""Rollover config for unused units. If set, unused included units carry over."""
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
optional_fields = set(
|
||||
["included", "unlimited", "reset", "price", "proration", "rollover"]
|
||||
)
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if val is not None or k not in optional_fields:
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
|
||||
|
||||
CreateScheduleRemoveItemBillingMethod2 = Literal[
|
||||
"prepaid",
|
||||
"usage_based",
|
||||
]
|
||||
r"""Match items with this billing method (prepaid or usage_based)."""
|
||||
|
||||
|
||||
CreateScheduleIntervalRemoveItemEnum4 = Literal[
|
||||
"one_off",
|
||||
"minute",
|
||||
"hour",
|
||||
"day",
|
||||
"week",
|
||||
"month",
|
||||
"quarter",
|
||||
"semi_annual",
|
||||
"year",
|
||||
]
|
||||
|
||||
|
||||
CreateScheduleIntervalRemoveItemEnum3 = Literal[
|
||||
"one_off",
|
||||
"week",
|
||||
"month",
|
||||
"quarter",
|
||||
"semi_annual",
|
||||
"year",
|
||||
]
|
||||
|
||||
|
||||
CreateScheduleIntervalUnion2TypedDict = TypeAliasType(
|
||||
"CreateScheduleIntervalUnion2TypedDict",
|
||||
Union[CreateScheduleIntervalRemoveItemEnum3, CreateScheduleIntervalRemoveItemEnum4],
|
||||
)
|
||||
r"""Match items with this interval. Accepts either a BillingInterval (price-side) or a ResetInterval (reset-side, includes day/hour/minute) so price-less items keyed by reset.interval can be disambiguated."""
|
||||
|
||||
|
||||
CreateScheduleIntervalUnion2 = TypeAliasType(
|
||||
"CreateScheduleIntervalUnion2",
|
||||
Union[CreateScheduleIntervalRemoveItemEnum3, CreateScheduleIntervalRemoveItemEnum4],
|
||||
)
|
||||
r"""Match items with this interval. Accepts either a BillingInterval (price-side) or a ResetInterval (reset-side, includes day/hour/minute) so price-less items keyed by reset.interval can be disambiguated."""
|
||||
|
||||
|
||||
class CreateSchedulePlanItemFilter2TypedDict(TypedDict):
|
||||
r"""Filter for matching plan items. All provided fields must match (AND)."""
|
||||
|
||||
feature_id: NotRequired[str]
|
||||
r"""Match items linked to this feature."""
|
||||
billing_method: NotRequired[CreateScheduleRemoveItemBillingMethod2]
|
||||
r"""Match items with this billing method (prepaid or usage_based)."""
|
||||
interval: NotRequired[CreateScheduleIntervalUnion2TypedDict]
|
||||
r"""Match items with this interval. Accepts either a BillingInterval (price-side) or a ResetInterval (reset-side, includes day/hour/minute) so price-less items keyed by reset.interval can be disambiguated."""
|
||||
interval_count: NotRequired[int]
|
||||
r"""Match items with this interval_count. Disambiguates between items that share an interval but differ in count."""
|
||||
|
||||
|
||||
class CreateSchedulePlanItemFilter2(BaseModel):
|
||||
r"""Filter for matching plan items. All provided fields must match (AND)."""
|
||||
|
||||
feature_id: Optional[str] = None
|
||||
r"""Match items linked to this feature."""
|
||||
|
||||
billing_method: Optional[CreateScheduleRemoveItemBillingMethod2] = None
|
||||
r"""Match items with this billing method (prepaid or usage_based)."""
|
||||
|
||||
interval: Optional[CreateScheduleIntervalUnion2] = None
|
||||
r"""Match items with this interval. Accepts either a BillingInterval (price-side) or a ResetInterval (reset-side, includes day/hour/minute) so price-less items keyed by reset.interval can be disambiguated."""
|
||||
|
||||
interval_count: Optional[int] = None
|
||||
r"""Match items with this interval_count. Disambiguates between items that share an interval but differ in count."""
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
optional_fields = set(
|
||||
["feature_id", "billing_method", "interval", "interval_count"]
|
||||
)
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if val is not None or k not in optional_fields:
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
|
||||
|
||||
class CreateScheduleCustomize2TypedDict(TypedDict):
|
||||
r"""Customize the plan to schedule. Can override the price, items, or both."""
|
||||
r"""Customize the plan to schedule. Can override price, replace items, or patch items with add_items, remove_items, and update_items."""
|
||||
|
||||
price: NotRequired[Nullable[CreateScheduleBasePrice2TypedDict]]
|
||||
r"""Override the base price of the plan. Pass null to remove the base price."""
|
||||
items: NotRequired[List[CreateSchedulePlanItem2TypedDict]]
|
||||
r"""Override the items in the plan."""
|
||||
items: NotRequired[List[CreateScheduleItemPlanItem2TypedDict]]
|
||||
r"""Override the items in the plan (PUT-style — replaces all existing items). Mutually exclusive with add_items / remove_items / deprecated update_items."""
|
||||
add_items: NotRequired[List[CreateScheduleAddItemPlanItem2TypedDict]]
|
||||
r"""Items to add to the plan."""
|
||||
remove_items: NotRequired[List[CreateSchedulePlanItemFilter2TypedDict]]
|
||||
r"""Filters selecting items to remove from the plan."""
|
||||
|
||||
|
||||
class CreateScheduleCustomize2(BaseModel):
|
||||
r"""Customize the plan to schedule. Can override the price, items, or both."""
|
||||
r"""Customize the plan to schedule. Can override price, replace items, or patch items with add_items, remove_items, and update_items."""
|
||||
|
||||
price: OptionalNullable[CreateScheduleBasePrice2] = UNSET
|
||||
r"""Override the base price of the plan. Pass null to remove the base price."""
|
||||
|
||||
items: Optional[List[CreateSchedulePlanItem2]] = None
|
||||
r"""Override the items in the plan."""
|
||||
items: Optional[List[CreateScheduleItemPlanItem2]] = None
|
||||
r"""Override the items in the plan (PUT-style — replaces all existing items). Mutually exclusive with add_items / remove_items / deprecated update_items."""
|
||||
|
||||
add_items: Optional[List[CreateScheduleAddItemPlanItem2]] = None
|
||||
r"""Items to add to the plan."""
|
||||
|
||||
remove_items: Optional[List[CreateSchedulePlanItemFilter2]] = None
|
||||
r"""Filters selecting items to remove from the plan."""
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
optional_fields = set(["price", "items"])
|
||||
optional_fields = set(["price", "items", "add_items", "remove_items"])
|
||||
nullable_fields = set(["price"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
@@ -622,7 +1068,7 @@ class CreateSchedulePlan2TypedDict(TypedDict):
|
||||
version: NotRequired[float]
|
||||
r"""Optional explicit plan version to schedule."""
|
||||
customize: NotRequired[CreateScheduleCustomize2TypedDict]
|
||||
r"""Customize the plan to schedule. Can override the price, items, or both."""
|
||||
r"""Customize the plan to schedule. Can override price, replace items, or patch items with add_items, remove_items, and update_items."""
|
||||
subscription_id: NotRequired[str]
|
||||
r"""A unique ID to identify this subscription. Useful when scheduling the same plan multiple times."""
|
||||
|
||||
@@ -638,7 +1084,7 @@ class CreateSchedulePlan2(BaseModel):
|
||||
r"""Optional explicit plan version to schedule."""
|
||||
|
||||
customize: Optional[CreateScheduleCustomize2] = None
|
||||
r"""Customize the plan to schedule. Can override the price, items, or both."""
|
||||
r"""Customize the plan to schedule. Can override price, replace items, or patch items with add_items, remove_items, and update_items."""
|
||||
|
||||
subscription_id: Optional[str] = None
|
||||
r"""A unique ID to identify this subscription. Useful when scheduling the same plan multiple times."""
|
||||
|
||||
@@ -569,10 +569,11 @@ CustomerFlagsType = Union[
|
||||
"boolean",
|
||||
"metered",
|
||||
"credit_system",
|
||||
"ai_credit_system",
|
||||
],
|
||||
UnrecognizedStr,
|
||||
]
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools."""
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing."""
|
||||
|
||||
|
||||
class CustomerCreditSchemaTypedDict(TypedDict):
|
||||
@@ -590,6 +591,44 @@ class CustomerCreditSchema(BaseModel):
|
||||
r"""Credits consumed per unit of the metered feature."""
|
||||
|
||||
|
||||
class CustomerModelMarkupsTypedDict(TypedDict):
|
||||
markup: NotRequired[float]
|
||||
input_cost: NotRequired[float]
|
||||
output_cost: NotRequired[float]
|
||||
|
||||
|
||||
class CustomerModelMarkups(BaseModel):
|
||||
markup: Optional[float] = None
|
||||
|
||||
input_cost: Optional[float] = None
|
||||
|
||||
output_cost: Optional[float] = None
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
optional_fields = set(["markup", "input_cost", "output_cost"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if val is not None or k not in optional_fields:
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
|
||||
|
||||
class CustomerProviderMarkupsTypedDict(TypedDict):
|
||||
markup: float
|
||||
|
||||
|
||||
class CustomerProviderMarkups(BaseModel):
|
||||
markup: float
|
||||
|
||||
|
||||
class CustomerDisplayTypedDict(TypedDict):
|
||||
r"""Display names for the feature in billing UI and customer-facing components."""
|
||||
|
||||
@@ -642,7 +681,7 @@ class CustomerFeatureTypedDict(TypedDict):
|
||||
name: str
|
||||
r"""Human-readable name displayed in the dashboard and billing UI."""
|
||||
type: CustomerFlagsType
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools."""
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing."""
|
||||
consumable: bool
|
||||
r"""For metered features: true if usage resets periodically (API calls, credits), false if allocated persistently (seats, storage)."""
|
||||
archived: bool
|
||||
@@ -651,6 +690,12 @@ class CustomerFeatureTypedDict(TypedDict):
|
||||
r"""Event names that trigger this feature's balance. Allows multiple features to respond to a single event."""
|
||||
credit_schema: NotRequired[List[CustomerCreditSchemaTypedDict]]
|
||||
r"""For credit_system features: maps metered features to their credit costs."""
|
||||
model_markups: NotRequired[Nullable[Dict[str, CustomerModelMarkupsTypedDict]]]
|
||||
r"""Per-model markup overrides for AI credit systems."""
|
||||
default_markup: NotRequired[float]
|
||||
r"""Default percentage markup for AI credit systems. Use -100 to make usage free."""
|
||||
provider_markups: NotRequired[Nullable[Dict[str, CustomerProviderMarkupsTypedDict]]]
|
||||
r"""Per-provider default markup percentages for AI credit systems."""
|
||||
display: NotRequired[CustomerDisplayTypedDict]
|
||||
r"""Display names for the feature in billing UI and customer-facing components."""
|
||||
|
||||
@@ -665,7 +710,7 @@ class CustomerFeature(BaseModel):
|
||||
r"""Human-readable name displayed in the dashboard and billing UI."""
|
||||
|
||||
type: CustomerFlagsType
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools."""
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing."""
|
||||
|
||||
consumable: bool
|
||||
r"""For metered features: true if usage resets periodically (API calls, credits), false if allocated persistently (seats, storage)."""
|
||||
@@ -679,21 +724,48 @@ class CustomerFeature(BaseModel):
|
||||
credit_schema: Optional[List[CustomerCreditSchema]] = None
|
||||
r"""For credit_system features: maps metered features to their credit costs."""
|
||||
|
||||
model_markups: OptionalNullable[Dict[str, CustomerModelMarkups]] = UNSET
|
||||
r"""Per-model markup overrides for AI credit systems."""
|
||||
|
||||
default_markup: Optional[float] = None
|
||||
r"""Default percentage markup for AI credit systems. Use -100 to make usage free."""
|
||||
|
||||
provider_markups: OptionalNullable[Dict[str, CustomerProviderMarkups]] = UNSET
|
||||
r"""Per-provider default markup percentages for AI credit systems."""
|
||||
|
||||
display: Optional[CustomerDisplay] = None
|
||||
r"""Display names for the feature in billing UI and customer-facing components."""
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
optional_fields = set(["event_names", "credit_schema", "display"])
|
||||
optional_fields = set(
|
||||
[
|
||||
"event_names",
|
||||
"credit_schema",
|
||||
"model_markups",
|
||||
"default_markup",
|
||||
"provider_markups",
|
||||
"display",
|
||||
]
|
||||
)
|
||||
nullable_fields = set(["model_markups", "provider_markups"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
is_nullable_and_explicitly_set = (
|
||||
k in nullable_fields
|
||||
and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member
|
||||
)
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if val is not None or k not in optional_fields:
|
||||
if (
|
||||
val is not None
|
||||
or k not in optional_fields
|
||||
or is_nullable_and_explicitly_set
|
||||
):
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
|
||||
@@ -632,10 +632,11 @@ GetCustomerFlagsType = Union[
|
||||
"boolean",
|
||||
"metered",
|
||||
"credit_system",
|
||||
"ai_credit_system",
|
||||
],
|
||||
UnrecognizedStr,
|
||||
]
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools."""
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing."""
|
||||
|
||||
|
||||
class GetCustomerCreditSchemaTypedDict(TypedDict):
|
||||
@@ -653,6 +654,44 @@ class GetCustomerCreditSchema(BaseModel):
|
||||
r"""Credits consumed per unit of the metered feature."""
|
||||
|
||||
|
||||
class GetCustomerModelMarkupsTypedDict(TypedDict):
|
||||
markup: NotRequired[float]
|
||||
input_cost: NotRequired[float]
|
||||
output_cost: NotRequired[float]
|
||||
|
||||
|
||||
class GetCustomerModelMarkups(BaseModel):
|
||||
markup: Optional[float] = None
|
||||
|
||||
input_cost: Optional[float] = None
|
||||
|
||||
output_cost: Optional[float] = None
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
optional_fields = set(["markup", "input_cost", "output_cost"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if val is not None or k not in optional_fields:
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
|
||||
|
||||
class GetCustomerProviderMarkupsTypedDict(TypedDict):
|
||||
markup: float
|
||||
|
||||
|
||||
class GetCustomerProviderMarkups(BaseModel):
|
||||
markup: float
|
||||
|
||||
|
||||
class GetCustomerDisplayTypedDict(TypedDict):
|
||||
r"""Display names for the feature in billing UI and customer-facing components."""
|
||||
|
||||
@@ -705,7 +744,7 @@ class GetCustomerFeatureTypedDict(TypedDict):
|
||||
name: str
|
||||
r"""Human-readable name displayed in the dashboard and billing UI."""
|
||||
type: GetCustomerFlagsType
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools."""
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing."""
|
||||
consumable: bool
|
||||
r"""For metered features: true if usage resets periodically (API calls, credits), false if allocated persistently (seats, storage)."""
|
||||
archived: bool
|
||||
@@ -714,6 +753,14 @@ class GetCustomerFeatureTypedDict(TypedDict):
|
||||
r"""Event names that trigger this feature's balance. Allows multiple features to respond to a single event."""
|
||||
credit_schema: NotRequired[List[GetCustomerCreditSchemaTypedDict]]
|
||||
r"""For credit_system features: maps metered features to their credit costs."""
|
||||
model_markups: NotRequired[Nullable[Dict[str, GetCustomerModelMarkupsTypedDict]]]
|
||||
r"""Per-model markup overrides for AI credit systems."""
|
||||
default_markup: NotRequired[float]
|
||||
r"""Default percentage markup for AI credit systems. Use -100 to make usage free."""
|
||||
provider_markups: NotRequired[
|
||||
Nullable[Dict[str, GetCustomerProviderMarkupsTypedDict]]
|
||||
]
|
||||
r"""Per-provider default markup percentages for AI credit systems."""
|
||||
display: NotRequired[GetCustomerDisplayTypedDict]
|
||||
r"""Display names for the feature in billing UI and customer-facing components."""
|
||||
|
||||
@@ -728,7 +775,7 @@ class GetCustomerFeature(BaseModel):
|
||||
r"""Human-readable name displayed in the dashboard and billing UI."""
|
||||
|
||||
type: GetCustomerFlagsType
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools."""
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing."""
|
||||
|
||||
consumable: bool
|
||||
r"""For metered features: true if usage resets periodically (API calls, credits), false if allocated persistently (seats, storage)."""
|
||||
@@ -742,21 +789,48 @@ class GetCustomerFeature(BaseModel):
|
||||
credit_schema: Optional[List[GetCustomerCreditSchema]] = None
|
||||
r"""For credit_system features: maps metered features to their credit costs."""
|
||||
|
||||
model_markups: OptionalNullable[Dict[str, GetCustomerModelMarkups]] = UNSET
|
||||
r"""Per-model markup overrides for AI credit systems."""
|
||||
|
||||
default_markup: Optional[float] = None
|
||||
r"""Default percentage markup for AI credit systems. Use -100 to make usage free."""
|
||||
|
||||
provider_markups: OptionalNullable[Dict[str, GetCustomerProviderMarkups]] = UNSET
|
||||
r"""Per-provider default markup percentages for AI credit systems."""
|
||||
|
||||
display: Optional[GetCustomerDisplay] = None
|
||||
r"""Display names for the feature in billing UI and customer-facing components."""
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
optional_fields = set(["event_names", "credit_schema", "display"])
|
||||
optional_fields = set(
|
||||
[
|
||||
"event_names",
|
||||
"credit_schema",
|
||||
"model_markups",
|
||||
"default_markup",
|
||||
"provider_markups",
|
||||
"display",
|
||||
]
|
||||
)
|
||||
nullable_fields = set(["model_markups", "provider_markups"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
is_nullable_and_explicitly_set = (
|
||||
k in nullable_fields
|
||||
and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member
|
||||
)
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if val is not None or k not in optional_fields:
|
||||
if (
|
||||
val is not None
|
||||
or k not in optional_fields
|
||||
or is_nullable_and_explicitly_set
|
||||
):
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
|
||||
@@ -291,10 +291,11 @@ GetEntityType = Union[
|
||||
"boolean",
|
||||
"metered",
|
||||
"credit_system",
|
||||
"ai_credit_system",
|
||||
],
|
||||
UnrecognizedStr,
|
||||
]
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools."""
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing."""
|
||||
|
||||
|
||||
class GetEntityCreditSchemaTypedDict(TypedDict):
|
||||
@@ -312,6 +313,44 @@ class GetEntityCreditSchema(BaseModel):
|
||||
r"""Credits consumed per unit of the metered feature."""
|
||||
|
||||
|
||||
class GetEntityModelMarkupsTypedDict(TypedDict):
|
||||
markup: NotRequired[float]
|
||||
input_cost: NotRequired[float]
|
||||
output_cost: NotRequired[float]
|
||||
|
||||
|
||||
class GetEntityModelMarkups(BaseModel):
|
||||
markup: Optional[float] = None
|
||||
|
||||
input_cost: Optional[float] = None
|
||||
|
||||
output_cost: Optional[float] = None
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
optional_fields = set(["markup", "input_cost", "output_cost"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if val is not None or k not in optional_fields:
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
|
||||
|
||||
class GetEntityProviderMarkupsTypedDict(TypedDict):
|
||||
markup: float
|
||||
|
||||
|
||||
class GetEntityProviderMarkups(BaseModel):
|
||||
markup: float
|
||||
|
||||
|
||||
class GetEntityDisplayTypedDict(TypedDict):
|
||||
r"""Display names for the feature in billing UI and customer-facing components."""
|
||||
|
||||
@@ -364,7 +403,7 @@ class GetEntityFeatureTypedDict(TypedDict):
|
||||
name: str
|
||||
r"""Human-readable name displayed in the dashboard and billing UI."""
|
||||
type: GetEntityType
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools."""
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing."""
|
||||
consumable: bool
|
||||
r"""For metered features: true if usage resets periodically (API calls, credits), false if allocated persistently (seats, storage)."""
|
||||
archived: bool
|
||||
@@ -373,6 +412,14 @@ class GetEntityFeatureTypedDict(TypedDict):
|
||||
r"""Event names that trigger this feature's balance. Allows multiple features to respond to a single event."""
|
||||
credit_schema: NotRequired[List[GetEntityCreditSchemaTypedDict]]
|
||||
r"""For credit_system features: maps metered features to their credit costs."""
|
||||
model_markups: NotRequired[Nullable[Dict[str, GetEntityModelMarkupsTypedDict]]]
|
||||
r"""Per-model markup overrides for AI credit systems."""
|
||||
default_markup: NotRequired[float]
|
||||
r"""Default percentage markup for AI credit systems. Use -100 to make usage free."""
|
||||
provider_markups: NotRequired[
|
||||
Nullable[Dict[str, GetEntityProviderMarkupsTypedDict]]
|
||||
]
|
||||
r"""Per-provider default markup percentages for AI credit systems."""
|
||||
display: NotRequired[GetEntityDisplayTypedDict]
|
||||
r"""Display names for the feature in billing UI and customer-facing components."""
|
||||
|
||||
@@ -387,7 +434,7 @@ class GetEntityFeature(BaseModel):
|
||||
r"""Human-readable name displayed in the dashboard and billing UI."""
|
||||
|
||||
type: GetEntityType
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools."""
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing."""
|
||||
|
||||
consumable: bool
|
||||
r"""For metered features: true if usage resets periodically (API calls, credits), false if allocated persistently (seats, storage)."""
|
||||
@@ -401,21 +448,48 @@ class GetEntityFeature(BaseModel):
|
||||
credit_schema: Optional[List[GetEntityCreditSchema]] = None
|
||||
r"""For credit_system features: maps metered features to their credit costs."""
|
||||
|
||||
model_markups: OptionalNullable[Dict[str, GetEntityModelMarkups]] = UNSET
|
||||
r"""Per-model markup overrides for AI credit systems."""
|
||||
|
||||
default_markup: Optional[float] = None
|
||||
r"""Default percentage markup for AI credit systems. Use -100 to make usage free."""
|
||||
|
||||
provider_markups: OptionalNullable[Dict[str, GetEntityProviderMarkups]] = UNSET
|
||||
r"""Per-provider default markup percentages for AI credit systems."""
|
||||
|
||||
display: Optional[GetEntityDisplay] = None
|
||||
r"""Display names for the feature in billing UI and customer-facing components."""
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
optional_fields = set(["event_names", "credit_schema", "display"])
|
||||
optional_fields = set(
|
||||
[
|
||||
"event_names",
|
||||
"credit_schema",
|
||||
"model_markups",
|
||||
"default_markup",
|
||||
"provider_markups",
|
||||
"display",
|
||||
]
|
||||
)
|
||||
nullable_fields = set(["model_markups", "provider_markups"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
is_nullable_and_explicitly_set = (
|
||||
k in nullable_fields
|
||||
and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member
|
||||
)
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if val is not None or k not in optional_fields:
|
||||
if (
|
||||
val is not None
|
||||
or k not in optional_fields
|
||||
or is_nullable_and_explicitly_set
|
||||
):
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
|
||||
@@ -12,7 +12,7 @@ from autumn_sdk.types import (
|
||||
from autumn_sdk.utils import FieldMetadata, HeaderMetadata
|
||||
import pydantic
|
||||
from pydantic import model_serializer
|
||||
from typing import List, Literal, Optional, Union
|
||||
from typing import Dict, List, Literal, Optional, Union
|
||||
from typing_extensions import Annotated, NotRequired, TypedDict
|
||||
|
||||
|
||||
@@ -59,10 +59,11 @@ GetFeatureType = Union[
|
||||
"boolean",
|
||||
"metered",
|
||||
"credit_system",
|
||||
"ai_credit_system",
|
||||
],
|
||||
UnrecognizedStr,
|
||||
]
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools."""
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing."""
|
||||
|
||||
|
||||
class GetFeatureCreditSchemaTypedDict(TypedDict):
|
||||
@@ -80,6 +81,44 @@ class GetFeatureCreditSchema(BaseModel):
|
||||
r"""Credits consumed per unit of the metered feature."""
|
||||
|
||||
|
||||
class GetFeatureModelMarkupsTypedDict(TypedDict):
|
||||
markup: NotRequired[float]
|
||||
input_cost: NotRequired[float]
|
||||
output_cost: NotRequired[float]
|
||||
|
||||
|
||||
class GetFeatureModelMarkups(BaseModel):
|
||||
markup: Optional[float] = None
|
||||
|
||||
input_cost: Optional[float] = None
|
||||
|
||||
output_cost: Optional[float] = None
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
optional_fields = set(["markup", "input_cost", "output_cost"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if val is not None or k not in optional_fields:
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
|
||||
|
||||
class GetFeatureProviderMarkupsTypedDict(TypedDict):
|
||||
markup: float
|
||||
|
||||
|
||||
class GetFeatureProviderMarkups(BaseModel):
|
||||
markup: float
|
||||
|
||||
|
||||
class GetFeatureDisplayTypedDict(TypedDict):
|
||||
r"""Display names for the feature in billing UI and customer-facing components."""
|
||||
|
||||
@@ -132,7 +171,7 @@ class GetFeatureResponseTypedDict(TypedDict):
|
||||
name: str
|
||||
r"""Human-readable name displayed in the dashboard and billing UI."""
|
||||
type: GetFeatureType
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools."""
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing."""
|
||||
consumable: bool
|
||||
r"""For metered features: true if usage resets periodically (API calls, credits), false if allocated persistently (seats, storage)."""
|
||||
archived: bool
|
||||
@@ -141,6 +180,14 @@ class GetFeatureResponseTypedDict(TypedDict):
|
||||
r"""Event names that trigger this feature's balance. Allows multiple features to respond to a single event."""
|
||||
credit_schema: NotRequired[List[GetFeatureCreditSchemaTypedDict]]
|
||||
r"""For credit_system features: maps metered features to their credit costs."""
|
||||
model_markups: NotRequired[Nullable[Dict[str, GetFeatureModelMarkupsTypedDict]]]
|
||||
r"""Per-model markup overrides for AI credit systems."""
|
||||
default_markup: NotRequired[float]
|
||||
r"""Default percentage markup for AI credit systems. Use -100 to make usage free."""
|
||||
provider_markups: NotRequired[
|
||||
Nullable[Dict[str, GetFeatureProviderMarkupsTypedDict]]
|
||||
]
|
||||
r"""Per-provider default markup percentages for AI credit systems."""
|
||||
display: NotRequired[GetFeatureDisplayTypedDict]
|
||||
r"""Display names for the feature in billing UI and customer-facing components."""
|
||||
|
||||
@@ -155,7 +202,7 @@ class GetFeatureResponse(BaseModel):
|
||||
r"""Human-readable name displayed in the dashboard and billing UI."""
|
||||
|
||||
type: GetFeatureType
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools."""
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing."""
|
||||
|
||||
consumable: bool
|
||||
r"""For metered features: true if usage resets periodically (API calls, credits), false if allocated persistently (seats, storage)."""
|
||||
@@ -169,21 +216,48 @@ class GetFeatureResponse(BaseModel):
|
||||
credit_schema: Optional[List[GetFeatureCreditSchema]] = None
|
||||
r"""For credit_system features: maps metered features to their credit costs."""
|
||||
|
||||
model_markups: OptionalNullable[Dict[str, GetFeatureModelMarkups]] = UNSET
|
||||
r"""Per-model markup overrides for AI credit systems."""
|
||||
|
||||
default_markup: Optional[float] = None
|
||||
r"""Default percentage markup for AI credit systems. Use -100 to make usage free."""
|
||||
|
||||
provider_markups: OptionalNullable[Dict[str, GetFeatureProviderMarkups]] = UNSET
|
||||
r"""Per-provider default markup percentages for AI credit systems."""
|
||||
|
||||
display: Optional[GetFeatureDisplay] = None
|
||||
r"""Display names for the feature in billing UI and customer-facing components."""
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
optional_fields = set(["event_names", "credit_schema", "display"])
|
||||
optional_fields = set(
|
||||
[
|
||||
"event_names",
|
||||
"credit_schema",
|
||||
"model_markups",
|
||||
"default_markup",
|
||||
"provider_markups",
|
||||
"display",
|
||||
]
|
||||
)
|
||||
nullable_fields = set(["model_markups", "provider_markups"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
is_nullable_and_explicitly_set = (
|
||||
k in nullable_fields
|
||||
and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member
|
||||
)
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if val is not None or k not in optional_fields:
|
||||
if (
|
||||
val is not None
|
||||
or k not in optional_fields
|
||||
or is_nullable_and_explicitly_set
|
||||
):
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
|
||||
@@ -12,8 +12,8 @@ from autumn_sdk.types import (
|
||||
from autumn_sdk.utils import FieldMetadata, HeaderMetadata
|
||||
import pydantic
|
||||
from pydantic import model_serializer
|
||||
from typing import Any, List, Literal, Optional, Union
|
||||
from typing_extensions import Annotated, NotRequired, TypedDict
|
||||
from typing import List, Literal, Optional, Union
|
||||
from typing_extensions import Annotated, NotRequired, TypeAliasType, TypedDict
|
||||
|
||||
|
||||
class GetPlanGlobalsTypedDict(TypedDict):
|
||||
@@ -172,6 +172,7 @@ GetPlanType = Union[
|
||||
"single_use",
|
||||
"continuous_use",
|
||||
"credit_system",
|
||||
"ai_credit_system",
|
||||
],
|
||||
UnrecognizedStr,
|
||||
]
|
||||
@@ -320,6 +321,42 @@ class GetPlanReset(BaseModel):
|
||||
return m
|
||||
|
||||
|
||||
GetPlanToTypedDict = TypeAliasType("GetPlanToTypedDict", Union[float, str])
|
||||
|
||||
|
||||
GetPlanTo = TypeAliasType("GetPlanTo", Union[float, str])
|
||||
|
||||
|
||||
class GetPlanTierTypedDict(TypedDict):
|
||||
to: GetPlanToTypedDict
|
||||
amount: float
|
||||
flat_amount: NotRequired[float]
|
||||
|
||||
|
||||
class GetPlanTier(BaseModel):
|
||||
to: GetPlanTo
|
||||
|
||||
amount: float
|
||||
|
||||
flat_amount: Optional[float] = None
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
optional_fields = set(["flat_amount"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if val is not None or k not in optional_fields:
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
|
||||
|
||||
GetPlanTierBehavior = Union[
|
||||
Literal[
|
||||
"graduated",
|
||||
@@ -364,7 +401,7 @@ class GetPlanItemPriceTypedDict(TypedDict):
|
||||
r"""Maximum units a customer can purchase beyond included. E.g. if included=100 and max_purchase=300, customer can use up to 400 total before usage is capped. Null for no limit."""
|
||||
amount: NotRequired[float]
|
||||
r"""Price per billing_units after included usage is consumed. Mutually exclusive with tiers."""
|
||||
tiers: NotRequired[List[Nullable[Any]]]
|
||||
tiers: NotRequired[List[GetPlanTierTypedDict]]
|
||||
r"""Tiered pricing configuration. Each tier's 'to' INCLUDES the included amount. Either 'tiers' or 'amount' is required."""
|
||||
tier_behavior: NotRequired[GetPlanTierBehavior]
|
||||
interval_count: NotRequired[float]
|
||||
@@ -387,7 +424,7 @@ class GetPlanItemPrice(BaseModel):
|
||||
amount: Optional[float] = None
|
||||
r"""Price per billing_units after included usage is consumed. Mutually exclusive with tiers."""
|
||||
|
||||
tiers: Optional[List[Nullable[Any]]] = None
|
||||
tiers: Optional[List[GetPlanTier]] = None
|
||||
r"""Tiered pricing configuration. Each tier's 'to' INCLUDES the included amount. Either 'tiers' or 'amount' is required."""
|
||||
|
||||
tier_behavior: Optional[GetPlanTierBehavior] = None
|
||||
|
||||
@@ -701,10 +701,11 @@ ListCustomersType = Union[
|
||||
"boolean",
|
||||
"metered",
|
||||
"credit_system",
|
||||
"ai_credit_system",
|
||||
],
|
||||
UnrecognizedStr,
|
||||
]
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools."""
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing."""
|
||||
|
||||
|
||||
class ListCustomersCreditSchemaTypedDict(TypedDict):
|
||||
@@ -722,6 +723,44 @@ class ListCustomersCreditSchema(BaseModel):
|
||||
r"""Credits consumed per unit of the metered feature."""
|
||||
|
||||
|
||||
class ListCustomersModelMarkupsTypedDict(TypedDict):
|
||||
markup: NotRequired[float]
|
||||
input_cost: NotRequired[float]
|
||||
output_cost: NotRequired[float]
|
||||
|
||||
|
||||
class ListCustomersModelMarkups(BaseModel):
|
||||
markup: Optional[float] = None
|
||||
|
||||
input_cost: Optional[float] = None
|
||||
|
||||
output_cost: Optional[float] = None
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
optional_fields = set(["markup", "input_cost", "output_cost"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if val is not None or k not in optional_fields:
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
|
||||
|
||||
class ListCustomersProviderMarkupsTypedDict(TypedDict):
|
||||
markup: float
|
||||
|
||||
|
||||
class ListCustomersProviderMarkups(BaseModel):
|
||||
markup: float
|
||||
|
||||
|
||||
class ListCustomersDisplayTypedDict(TypedDict):
|
||||
r"""Display names for the feature in billing UI and customer-facing components."""
|
||||
|
||||
@@ -774,7 +813,7 @@ class ListCustomersFeatureTypedDict(TypedDict):
|
||||
name: str
|
||||
r"""Human-readable name displayed in the dashboard and billing UI."""
|
||||
type: ListCustomersType
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools."""
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing."""
|
||||
consumable: bool
|
||||
r"""For metered features: true if usage resets periodically (API calls, credits), false if allocated persistently (seats, storage)."""
|
||||
archived: bool
|
||||
@@ -783,6 +822,14 @@ class ListCustomersFeatureTypedDict(TypedDict):
|
||||
r"""Event names that trigger this feature's balance. Allows multiple features to respond to a single event."""
|
||||
credit_schema: NotRequired[List[ListCustomersCreditSchemaTypedDict]]
|
||||
r"""For credit_system features: maps metered features to their credit costs."""
|
||||
model_markups: NotRequired[Nullable[Dict[str, ListCustomersModelMarkupsTypedDict]]]
|
||||
r"""Per-model markup overrides for AI credit systems."""
|
||||
default_markup: NotRequired[float]
|
||||
r"""Default percentage markup for AI credit systems. Use -100 to make usage free."""
|
||||
provider_markups: NotRequired[
|
||||
Nullable[Dict[str, ListCustomersProviderMarkupsTypedDict]]
|
||||
]
|
||||
r"""Per-provider default markup percentages for AI credit systems."""
|
||||
display: NotRequired[ListCustomersDisplayTypedDict]
|
||||
r"""Display names for the feature in billing UI and customer-facing components."""
|
||||
|
||||
@@ -797,7 +844,7 @@ class ListCustomersFeature(BaseModel):
|
||||
r"""Human-readable name displayed in the dashboard and billing UI."""
|
||||
|
||||
type: ListCustomersType
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools."""
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing."""
|
||||
|
||||
consumable: bool
|
||||
r"""For metered features: true if usage resets periodically (API calls, credits), false if allocated persistently (seats, storage)."""
|
||||
@@ -811,21 +858,48 @@ class ListCustomersFeature(BaseModel):
|
||||
credit_schema: Optional[List[ListCustomersCreditSchema]] = None
|
||||
r"""For credit_system features: maps metered features to their credit costs."""
|
||||
|
||||
model_markups: OptionalNullable[Dict[str, ListCustomersModelMarkups]] = UNSET
|
||||
r"""Per-model markup overrides for AI credit systems."""
|
||||
|
||||
default_markup: Optional[float] = None
|
||||
r"""Default percentage markup for AI credit systems. Use -100 to make usage free."""
|
||||
|
||||
provider_markups: OptionalNullable[Dict[str, ListCustomersProviderMarkups]] = UNSET
|
||||
r"""Per-provider default markup percentages for AI credit systems."""
|
||||
|
||||
display: Optional[ListCustomersDisplay] = None
|
||||
r"""Display names for the feature in billing UI and customer-facing components."""
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
optional_fields = set(["event_names", "credit_schema", "display"])
|
||||
optional_fields = set(
|
||||
[
|
||||
"event_names",
|
||||
"credit_schema",
|
||||
"model_markups",
|
||||
"default_markup",
|
||||
"provider_markups",
|
||||
"display",
|
||||
]
|
||||
)
|
||||
nullable_fields = set(["model_markups", "provider_markups"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
is_nullable_and_explicitly_set = (
|
||||
k in nullable_fields
|
||||
and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member
|
||||
)
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if val is not None or k not in optional_fields:
|
||||
if (
|
||||
val is not None
|
||||
or k not in optional_fields
|
||||
or is_nullable_and_explicitly_set
|
||||
):
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
|
||||
@@ -367,10 +367,11 @@ ListEntitiesType = Union[
|
||||
"boolean",
|
||||
"metered",
|
||||
"credit_system",
|
||||
"ai_credit_system",
|
||||
],
|
||||
UnrecognizedStr,
|
||||
]
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools."""
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing."""
|
||||
|
||||
|
||||
class ListEntitiesCreditSchemaTypedDict(TypedDict):
|
||||
@@ -388,6 +389,44 @@ class ListEntitiesCreditSchema(BaseModel):
|
||||
r"""Credits consumed per unit of the metered feature."""
|
||||
|
||||
|
||||
class ListEntitiesModelMarkupsTypedDict(TypedDict):
|
||||
markup: NotRequired[float]
|
||||
input_cost: NotRequired[float]
|
||||
output_cost: NotRequired[float]
|
||||
|
||||
|
||||
class ListEntitiesModelMarkups(BaseModel):
|
||||
markup: Optional[float] = None
|
||||
|
||||
input_cost: Optional[float] = None
|
||||
|
||||
output_cost: Optional[float] = None
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
optional_fields = set(["markup", "input_cost", "output_cost"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if val is not None or k not in optional_fields:
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
|
||||
|
||||
class ListEntitiesProviderMarkupsTypedDict(TypedDict):
|
||||
markup: float
|
||||
|
||||
|
||||
class ListEntitiesProviderMarkups(BaseModel):
|
||||
markup: float
|
||||
|
||||
|
||||
class ListEntitiesDisplayTypedDict(TypedDict):
|
||||
r"""Display names for the feature in billing UI and customer-facing components."""
|
||||
|
||||
@@ -440,7 +479,7 @@ class ListEntitiesFeatureTypedDict(TypedDict):
|
||||
name: str
|
||||
r"""Human-readable name displayed in the dashboard and billing UI."""
|
||||
type: ListEntitiesType
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools."""
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing."""
|
||||
consumable: bool
|
||||
r"""For metered features: true if usage resets periodically (API calls, credits), false if allocated persistently (seats, storage)."""
|
||||
archived: bool
|
||||
@@ -449,6 +488,14 @@ class ListEntitiesFeatureTypedDict(TypedDict):
|
||||
r"""Event names that trigger this feature's balance. Allows multiple features to respond to a single event."""
|
||||
credit_schema: NotRequired[List[ListEntitiesCreditSchemaTypedDict]]
|
||||
r"""For credit_system features: maps metered features to their credit costs."""
|
||||
model_markups: NotRequired[Nullable[Dict[str, ListEntitiesModelMarkupsTypedDict]]]
|
||||
r"""Per-model markup overrides for AI credit systems."""
|
||||
default_markup: NotRequired[float]
|
||||
r"""Default percentage markup for AI credit systems. Use -100 to make usage free."""
|
||||
provider_markups: NotRequired[
|
||||
Nullable[Dict[str, ListEntitiesProviderMarkupsTypedDict]]
|
||||
]
|
||||
r"""Per-provider default markup percentages for AI credit systems."""
|
||||
display: NotRequired[ListEntitiesDisplayTypedDict]
|
||||
r"""Display names for the feature in billing UI and customer-facing components."""
|
||||
|
||||
@@ -463,7 +510,7 @@ class ListEntitiesFeature(BaseModel):
|
||||
r"""Human-readable name displayed in the dashboard and billing UI."""
|
||||
|
||||
type: ListEntitiesType
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools."""
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing."""
|
||||
|
||||
consumable: bool
|
||||
r"""For metered features: true if usage resets periodically (API calls, credits), false if allocated persistently (seats, storage)."""
|
||||
@@ -477,21 +524,48 @@ class ListEntitiesFeature(BaseModel):
|
||||
credit_schema: Optional[List[ListEntitiesCreditSchema]] = None
|
||||
r"""For credit_system features: maps metered features to their credit costs."""
|
||||
|
||||
model_markups: OptionalNullable[Dict[str, ListEntitiesModelMarkups]] = UNSET
|
||||
r"""Per-model markup overrides for AI credit systems."""
|
||||
|
||||
default_markup: Optional[float] = None
|
||||
r"""Default percentage markup for AI credit systems. Use -100 to make usage free."""
|
||||
|
||||
provider_markups: OptionalNullable[Dict[str, ListEntitiesProviderMarkups]] = UNSET
|
||||
r"""Per-provider default markup percentages for AI credit systems."""
|
||||
|
||||
display: Optional[ListEntitiesDisplay] = None
|
||||
r"""Display names for the feature in billing UI and customer-facing components."""
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
optional_fields = set(["event_names", "credit_schema", "display"])
|
||||
optional_fields = set(
|
||||
[
|
||||
"event_names",
|
||||
"credit_schema",
|
||||
"model_markups",
|
||||
"default_markup",
|
||||
"provider_markups",
|
||||
"display",
|
||||
]
|
||||
)
|
||||
nullable_fields = set(["model_markups", "provider_markups"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
is_nullable_and_explicitly_set = (
|
||||
k in nullable_fields
|
||||
and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member
|
||||
)
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if val is not None or k not in optional_fields:
|
||||
if (
|
||||
val is not None
|
||||
or k not in optional_fields
|
||||
or is_nullable_and_explicitly_set
|
||||
):
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
|
||||
@@ -12,7 +12,7 @@ from autumn_sdk.types import (
|
||||
from autumn_sdk.utils import FieldMetadata, HeaderMetadata
|
||||
import pydantic
|
||||
from pydantic import model_serializer
|
||||
from typing import List, Literal, Optional, Union
|
||||
from typing import Dict, List, Literal, Optional, Union
|
||||
from typing_extensions import Annotated, NotRequired, TypedDict
|
||||
|
||||
|
||||
@@ -57,10 +57,11 @@ ListFeaturesType = Union[
|
||||
"boolean",
|
||||
"metered",
|
||||
"credit_system",
|
||||
"ai_credit_system",
|
||||
],
|
||||
UnrecognizedStr,
|
||||
]
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools."""
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing."""
|
||||
|
||||
|
||||
class ListFeaturesCreditSchemaTypedDict(TypedDict):
|
||||
@@ -78,6 +79,44 @@ class ListFeaturesCreditSchema(BaseModel):
|
||||
r"""Credits consumed per unit of the metered feature."""
|
||||
|
||||
|
||||
class ListFeaturesModelMarkupsTypedDict(TypedDict):
|
||||
markup: NotRequired[float]
|
||||
input_cost: NotRequired[float]
|
||||
output_cost: NotRequired[float]
|
||||
|
||||
|
||||
class ListFeaturesModelMarkups(BaseModel):
|
||||
markup: Optional[float] = None
|
||||
|
||||
input_cost: Optional[float] = None
|
||||
|
||||
output_cost: Optional[float] = None
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
optional_fields = set(["markup", "input_cost", "output_cost"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if val is not None or k not in optional_fields:
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
|
||||
|
||||
class ListFeaturesProviderMarkupsTypedDict(TypedDict):
|
||||
markup: float
|
||||
|
||||
|
||||
class ListFeaturesProviderMarkups(BaseModel):
|
||||
markup: float
|
||||
|
||||
|
||||
class ListFeaturesDisplayTypedDict(TypedDict):
|
||||
r"""Display names for the feature in billing UI and customer-facing components."""
|
||||
|
||||
@@ -128,7 +167,7 @@ class ListFeaturesListTypedDict(TypedDict):
|
||||
name: str
|
||||
r"""Human-readable name displayed in the dashboard and billing UI."""
|
||||
type: ListFeaturesType
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools."""
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing."""
|
||||
consumable: bool
|
||||
r"""For metered features: true if usage resets periodically (API calls, credits), false if allocated persistently (seats, storage)."""
|
||||
archived: bool
|
||||
@@ -137,6 +176,14 @@ class ListFeaturesListTypedDict(TypedDict):
|
||||
r"""Event names that trigger this feature's balance. Allows multiple features to respond to a single event."""
|
||||
credit_schema: NotRequired[List[ListFeaturesCreditSchemaTypedDict]]
|
||||
r"""For credit_system features: maps metered features to their credit costs."""
|
||||
model_markups: NotRequired[Nullable[Dict[str, ListFeaturesModelMarkupsTypedDict]]]
|
||||
r"""Per-model markup overrides for AI credit systems."""
|
||||
default_markup: NotRequired[float]
|
||||
r"""Default percentage markup for AI credit systems. Use -100 to make usage free."""
|
||||
provider_markups: NotRequired[
|
||||
Nullable[Dict[str, ListFeaturesProviderMarkupsTypedDict]]
|
||||
]
|
||||
r"""Per-provider default markup percentages for AI credit systems."""
|
||||
display: NotRequired[ListFeaturesDisplayTypedDict]
|
||||
r"""Display names for the feature in billing UI and customer-facing components."""
|
||||
|
||||
@@ -149,7 +196,7 @@ class ListFeaturesList(BaseModel):
|
||||
r"""Human-readable name displayed in the dashboard and billing UI."""
|
||||
|
||||
type: ListFeaturesType
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools."""
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing."""
|
||||
|
||||
consumable: bool
|
||||
r"""For metered features: true if usage resets periodically (API calls, credits), false if allocated persistently (seats, storage)."""
|
||||
@@ -163,21 +210,48 @@ class ListFeaturesList(BaseModel):
|
||||
credit_schema: Optional[List[ListFeaturesCreditSchema]] = None
|
||||
r"""For credit_system features: maps metered features to their credit costs."""
|
||||
|
||||
model_markups: OptionalNullable[Dict[str, ListFeaturesModelMarkups]] = UNSET
|
||||
r"""Per-model markup overrides for AI credit systems."""
|
||||
|
||||
default_markup: Optional[float] = None
|
||||
r"""Default percentage markup for AI credit systems. Use -100 to make usage free."""
|
||||
|
||||
provider_markups: OptionalNullable[Dict[str, ListFeaturesProviderMarkups]] = UNSET
|
||||
r"""Per-provider default markup percentages for AI credit systems."""
|
||||
|
||||
display: Optional[ListFeaturesDisplay] = None
|
||||
r"""Display names for the feature in billing UI and customer-facing components."""
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
optional_fields = set(["event_names", "credit_schema", "display"])
|
||||
optional_fields = set(
|
||||
[
|
||||
"event_names",
|
||||
"credit_schema",
|
||||
"model_markups",
|
||||
"default_markup",
|
||||
"provider_markups",
|
||||
"display",
|
||||
]
|
||||
)
|
||||
nullable_fields = set(["model_markups", "provider_markups"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
is_nullable_and_explicitly_set = (
|
||||
k in nullable_fields
|
||||
and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member
|
||||
)
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if val is not None or k not in optional_fields:
|
||||
if (
|
||||
val is not None
|
||||
or k not in optional_fields
|
||||
or is_nullable_and_explicitly_set
|
||||
):
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
|
||||
@@ -12,8 +12,8 @@ from autumn_sdk.types import (
|
||||
from autumn_sdk.utils import FieldMetadata, HeaderMetadata
|
||||
import pydantic
|
||||
from pydantic import model_serializer
|
||||
from typing import Any, List, Literal, Optional, Union
|
||||
from typing_extensions import Annotated, NotRequired, TypedDict
|
||||
from typing import List, Literal, Optional, Union
|
||||
from typing_extensions import Annotated, NotRequired, TypeAliasType, TypedDict
|
||||
|
||||
|
||||
class ListPlansGlobalsTypedDict(TypedDict):
|
||||
@@ -177,6 +177,7 @@ ListPlansType = Union[
|
||||
"single_use",
|
||||
"continuous_use",
|
||||
"credit_system",
|
||||
"ai_credit_system",
|
||||
],
|
||||
UnrecognizedStr,
|
||||
]
|
||||
@@ -325,6 +326,42 @@ class ListPlansReset(BaseModel):
|
||||
return m
|
||||
|
||||
|
||||
ListPlansToTypedDict = TypeAliasType("ListPlansToTypedDict", Union[float, str])
|
||||
|
||||
|
||||
ListPlansTo = TypeAliasType("ListPlansTo", Union[float, str])
|
||||
|
||||
|
||||
class ListPlansTierTypedDict(TypedDict):
|
||||
to: ListPlansToTypedDict
|
||||
amount: float
|
||||
flat_amount: NotRequired[float]
|
||||
|
||||
|
||||
class ListPlansTier(BaseModel):
|
||||
to: ListPlansTo
|
||||
|
||||
amount: float
|
||||
|
||||
flat_amount: Optional[float] = None
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
optional_fields = set(["flat_amount"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if val is not None or k not in optional_fields:
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
|
||||
|
||||
ListPlansTierBehavior = Union[
|
||||
Literal[
|
||||
"graduated",
|
||||
@@ -369,7 +406,7 @@ class ListPlansItemPriceTypedDict(TypedDict):
|
||||
r"""Maximum units a customer can purchase beyond included. E.g. if included=100 and max_purchase=300, customer can use up to 400 total before usage is capped. Null for no limit."""
|
||||
amount: NotRequired[float]
|
||||
r"""Price per billing_units after included usage is consumed. Mutually exclusive with tiers."""
|
||||
tiers: NotRequired[List[Nullable[Any]]]
|
||||
tiers: NotRequired[List[ListPlansTierTypedDict]]
|
||||
r"""Tiered pricing configuration. Each tier's 'to' INCLUDES the included amount. Either 'tiers' or 'amount' is required."""
|
||||
tier_behavior: NotRequired[ListPlansTierBehavior]
|
||||
interval_count: NotRequired[float]
|
||||
@@ -392,7 +429,7 @@ class ListPlansItemPrice(BaseModel):
|
||||
amount: Optional[float] = None
|
||||
r"""Price per billing_units after included usage is consumed. Mutually exclusive with tiers."""
|
||||
|
||||
tiers: Optional[List[Nullable[Any]]] = None
|
||||
tiers: Optional[List[ListPlansTier]] = None
|
||||
r"""Tiered pricing configuration. Each tier's 'to' INCLUDES the included amount. Either 'tiers' or 'amount' is required."""
|
||||
|
||||
tier_behavior: Optional[ListPlansTierBehavior] = None
|
||||
|
||||
@@ -221,8 +221,8 @@ class MultiAttachPriceTypedDict(TypedDict):
|
||||
r"""Number of intervals per billing cycle. Defaults to 1."""
|
||||
billing_units: NotRequired[float]
|
||||
r"""Units per price increment. Usage is rounded UP when billed (e.g. billing_units=100 means 101 rounds to 200)."""
|
||||
max_purchase: NotRequired[float]
|
||||
r"""Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total."""
|
||||
max_purchase: NotRequired[Nullable[float]]
|
||||
r"""Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total. Null for no limit."""
|
||||
|
||||
|
||||
class MultiAttachPrice(BaseModel):
|
||||
@@ -248,8 +248,8 @@ class MultiAttachPrice(BaseModel):
|
||||
billing_units: Optional[float] = 1
|
||||
r"""Units per price increment. Usage is rounded UP when billed (e.g. billing_units=100 means 101 rounds to 200)."""
|
||||
|
||||
max_purchase: Optional[float] = None
|
||||
r"""Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total."""
|
||||
max_purchase: OptionalNullable[float] = UNSET
|
||||
r"""Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total. Null for no limit."""
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
@@ -263,15 +263,24 @@ class MultiAttachPrice(BaseModel):
|
||||
"max_purchase",
|
||||
]
|
||||
)
|
||||
nullable_fields = set(["max_purchase"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
is_nullable_and_explicitly_set = (
|
||||
k in nullable_fields
|
||||
and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member
|
||||
)
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if val is not None or k not in optional_fields:
|
||||
if (
|
||||
val is not None
|
||||
or k not in optional_fields
|
||||
or is_nullable_and_explicitly_set
|
||||
):
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
|
||||
@@ -10,8 +10,8 @@ from autumn_sdk.types import (
|
||||
UnrecognizedStr,
|
||||
)
|
||||
from pydantic import model_serializer
|
||||
from typing import Any, List, Literal, Optional, Union
|
||||
from typing_extensions import NotRequired, TypedDict
|
||||
from typing import List, Literal, Optional, Union
|
||||
from typing_extensions import NotRequired, TypeAliasType, TypedDict
|
||||
|
||||
|
||||
PlanPriceInterval = Union[
|
||||
@@ -111,6 +111,7 @@ PlanType = Union[
|
||||
"single_use",
|
||||
"continuous_use",
|
||||
"credit_system",
|
||||
"ai_credit_system",
|
||||
],
|
||||
UnrecognizedStr,
|
||||
]
|
||||
@@ -259,6 +260,42 @@ class PlanReset(BaseModel):
|
||||
return m
|
||||
|
||||
|
||||
PlanToTypedDict = TypeAliasType("PlanToTypedDict", Union[float, str])
|
||||
|
||||
|
||||
PlanTo = TypeAliasType("PlanTo", Union[float, str])
|
||||
|
||||
|
||||
class PlanTierTypedDict(TypedDict):
|
||||
to: PlanToTypedDict
|
||||
amount: float
|
||||
flat_amount: NotRequired[float]
|
||||
|
||||
|
||||
class PlanTier(BaseModel):
|
||||
to: PlanTo
|
||||
|
||||
amount: float
|
||||
|
||||
flat_amount: Optional[float] = None
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
optional_fields = set(["flat_amount"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if val is not None or k not in optional_fields:
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
|
||||
|
||||
PlanTierBehavior = Union[
|
||||
Literal[
|
||||
"graduated",
|
||||
@@ -303,7 +340,7 @@ class PlanItemPriceTypedDict(TypedDict):
|
||||
r"""Maximum units a customer can purchase beyond included. E.g. if included=100 and max_purchase=300, customer can use up to 400 total before usage is capped. Null for no limit."""
|
||||
amount: NotRequired[float]
|
||||
r"""Price per billing_units after included usage is consumed. Mutually exclusive with tiers."""
|
||||
tiers: NotRequired[List[Nullable[Any]]]
|
||||
tiers: NotRequired[List[PlanTierTypedDict]]
|
||||
r"""Tiered pricing configuration. Each tier's 'to' INCLUDES the included amount. Either 'tiers' or 'amount' is required."""
|
||||
tier_behavior: NotRequired[PlanTierBehavior]
|
||||
interval_count: NotRequired[float]
|
||||
@@ -326,7 +363,7 @@ class PlanItemPrice(BaseModel):
|
||||
amount: Optional[float] = None
|
||||
r"""Price per billing_units after included usage is consumed. Mutually exclusive with tiers."""
|
||||
|
||||
tiers: Optional[List[Nullable[Any]]] = None
|
||||
tiers: Optional[List[PlanTier]] = None
|
||||
r"""Tiered pricing configuration. Each tier's 'to' INCLUDES the included amount. Either 'tiers' or 'amount' is required."""
|
||||
|
||||
tier_behavior: Optional[PlanTierBehavior] = None
|
||||
|
||||
@@ -264,8 +264,8 @@ class PreviewAttachItemPriceTypedDict(TypedDict):
|
||||
r"""Number of intervals per billing cycle. Defaults to 1."""
|
||||
billing_units: NotRequired[float]
|
||||
r"""Units per price increment. Usage is rounded UP when billed (e.g. billing_units=100 means 101 rounds to 200)."""
|
||||
max_purchase: NotRequired[float]
|
||||
r"""Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total."""
|
||||
max_purchase: NotRequired[Nullable[float]]
|
||||
r"""Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total. Null for no limit."""
|
||||
|
||||
|
||||
class PreviewAttachItemPrice(BaseModel):
|
||||
@@ -291,8 +291,8 @@ class PreviewAttachItemPrice(BaseModel):
|
||||
billing_units: Optional[float] = 1
|
||||
r"""Units per price increment. Usage is rounded UP when billed (e.g. billing_units=100 means 101 rounds to 200)."""
|
||||
|
||||
max_purchase: Optional[float] = None
|
||||
r"""Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total."""
|
||||
max_purchase: OptionalNullable[float] = UNSET
|
||||
r"""Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total. Null for no limit."""
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
@@ -306,15 +306,24 @@ class PreviewAttachItemPrice(BaseModel):
|
||||
"max_purchase",
|
||||
]
|
||||
)
|
||||
nullable_fields = set(["max_purchase"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
is_nullable_and_explicitly_set = (
|
||||
k in nullable_fields
|
||||
and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member
|
||||
)
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if val is not None or k not in optional_fields:
|
||||
if (
|
||||
val is not None
|
||||
or k not in optional_fields
|
||||
or is_nullable_and_explicitly_set
|
||||
):
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
@@ -599,8 +608,8 @@ class PreviewAttachAddItemPriceTypedDict(TypedDict):
|
||||
r"""Number of intervals per billing cycle. Defaults to 1."""
|
||||
billing_units: NotRequired[float]
|
||||
r"""Units per price increment. Usage is rounded UP when billed (e.g. billing_units=100 means 101 rounds to 200)."""
|
||||
max_purchase: NotRequired[float]
|
||||
r"""Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total."""
|
||||
max_purchase: NotRequired[Nullable[float]]
|
||||
r"""Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total. Null for no limit."""
|
||||
|
||||
|
||||
class PreviewAttachAddItemPrice(BaseModel):
|
||||
@@ -626,8 +635,8 @@ class PreviewAttachAddItemPrice(BaseModel):
|
||||
billing_units: Optional[float] = 1
|
||||
r"""Units per price increment. Usage is rounded UP when billed (e.g. billing_units=100 means 101 rounds to 200)."""
|
||||
|
||||
max_purchase: Optional[float] = None
|
||||
r"""Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total."""
|
||||
max_purchase: OptionalNullable[float] = UNSET
|
||||
r"""Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total. Null for no limit."""
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
@@ -641,15 +650,24 @@ class PreviewAttachAddItemPrice(BaseModel):
|
||||
"max_purchase",
|
||||
]
|
||||
)
|
||||
nullable_fields = set(["max_purchase"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
is_nullable_and_explicitly_set = (
|
||||
k in nullable_fields
|
||||
and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member
|
||||
)
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if val is not None or k not in optional_fields:
|
||||
if (
|
||||
val is not None
|
||||
or k not in optional_fields
|
||||
or is_nullable_and_explicitly_set
|
||||
):
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
@@ -814,7 +832,20 @@ PreviewAttachRemoveItemBillingMethod = Literal[
|
||||
r"""Match items with this billing method (prepaid or usage_based)."""
|
||||
|
||||
|
||||
PreviewAttachRemoveItemInterval = Literal[
|
||||
PreviewAttachIntervalRemoveItemEnum2 = Literal[
|
||||
"one_off",
|
||||
"minute",
|
||||
"hour",
|
||||
"day",
|
||||
"week",
|
||||
"month",
|
||||
"quarter",
|
||||
"semi_annual",
|
||||
"year",
|
||||
]
|
||||
|
||||
|
||||
PreviewAttachIntervalRemoveItemEnum1 = Literal[
|
||||
"one_off",
|
||||
"week",
|
||||
"month",
|
||||
@@ -822,7 +853,20 @@ PreviewAttachRemoveItemInterval = Literal[
|
||||
"semi_annual",
|
||||
"year",
|
||||
]
|
||||
r"""Match items with this interval."""
|
||||
|
||||
|
||||
PreviewAttachIntervalUnionTypedDict = TypeAliasType(
|
||||
"PreviewAttachIntervalUnionTypedDict",
|
||||
Union[PreviewAttachIntervalRemoveItemEnum1, PreviewAttachIntervalRemoveItemEnum2],
|
||||
)
|
||||
r"""Match items with this interval. Accepts either a BillingInterval (price-side) or a ResetInterval (reset-side, includes day/hour/minute) so price-less items keyed by reset.interval can be disambiguated."""
|
||||
|
||||
|
||||
PreviewAttachIntervalUnion = TypeAliasType(
|
||||
"PreviewAttachIntervalUnion",
|
||||
Union[PreviewAttachIntervalRemoveItemEnum1, PreviewAttachIntervalRemoveItemEnum2],
|
||||
)
|
||||
r"""Match items with this interval. Accepts either a BillingInterval (price-side) or a ResetInterval (reset-side, includes day/hour/minute) so price-less items keyed by reset.interval can be disambiguated."""
|
||||
|
||||
|
||||
class PreviewAttachPlanItemFilterTypedDict(TypedDict):
|
||||
@@ -832,8 +876,10 @@ class PreviewAttachPlanItemFilterTypedDict(TypedDict):
|
||||
r"""Match items linked to this feature."""
|
||||
billing_method: NotRequired[PreviewAttachRemoveItemBillingMethod]
|
||||
r"""Match items with this billing method (prepaid or usage_based)."""
|
||||
interval: NotRequired[PreviewAttachRemoveItemInterval]
|
||||
r"""Match items with this interval."""
|
||||
interval: NotRequired[PreviewAttachIntervalUnionTypedDict]
|
||||
r"""Match items with this interval. Accepts either a BillingInterval (price-side) or a ResetInterval (reset-side, includes day/hour/minute) so price-less items keyed by reset.interval can be disambiguated."""
|
||||
interval_count: NotRequired[int]
|
||||
r"""Match items with this interval_count. Disambiguates between items that share an interval but differ in count."""
|
||||
|
||||
|
||||
class PreviewAttachPlanItemFilter(BaseModel):
|
||||
@@ -845,12 +891,17 @@ class PreviewAttachPlanItemFilter(BaseModel):
|
||||
billing_method: Optional[PreviewAttachRemoveItemBillingMethod] = None
|
||||
r"""Match items with this billing method (prepaid or usage_based)."""
|
||||
|
||||
interval: Optional[PreviewAttachRemoveItemInterval] = None
|
||||
r"""Match items with this interval."""
|
||||
interval: Optional[PreviewAttachIntervalUnion] = None
|
||||
r"""Match items with this interval. Accepts either a BillingInterval (price-side) or a ResetInterval (reset-side, includes day/hour/minute) so price-less items keyed by reset.interval can be disambiguated."""
|
||||
|
||||
interval_count: Optional[int] = None
|
||||
r"""Match items with this interval_count. Disambiguates between items that share an interval but differ in count."""
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
optional_fields = set(["feature_id", "billing_method", "interval"])
|
||||
optional_fields = set(
|
||||
["feature_id", "billing_method", "interval", "interval_count"]
|
||||
)
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
@@ -931,7 +982,7 @@ class PreviewAttachCustomizeTypedDict(TypedDict):
|
||||
price: NotRequired[Nullable[PreviewAttachBasePriceTypedDict]]
|
||||
r"""Override the base price of the plan. Pass null to remove the base price."""
|
||||
items: NotRequired[List[PreviewAttachItemPlanItemTypedDict]]
|
||||
r"""Override the items in the plan (PUT-style — replaces all existing items). Mutually exclusive with add_items / remove_items / update_items."""
|
||||
r"""Override the items in the plan (PUT-style — replaces all existing items). Mutually exclusive with add_items / remove_items / deprecated update_items."""
|
||||
add_items: NotRequired[List[PreviewAttachAddItemPlanItemTypedDict]]
|
||||
r"""Items to add to the plan."""
|
||||
remove_items: NotRequired[List[PreviewAttachPlanItemFilterTypedDict]]
|
||||
@@ -947,7 +998,7 @@ class PreviewAttachCustomize(BaseModel):
|
||||
r"""Override the base price of the plan. Pass null to remove the base price."""
|
||||
|
||||
items: Optional[List[PreviewAttachItemPlanItem]] = None
|
||||
r"""Override the items in the plan (PUT-style — replaces all existing items). Mutually exclusive with add_items / remove_items / update_items."""
|
||||
r"""Override the items in the plan (PUT-style — replaces all existing items). Mutually exclusive with add_items / remove_items / deprecated update_items."""
|
||||
|
||||
add_items: Optional[List[PreviewAttachAddItemPlanItem]] = None
|
||||
r"""Items to add to the plan."""
|
||||
|
||||
@@ -224,8 +224,8 @@ class PreviewMultiAttachPriceTypedDict(TypedDict):
|
||||
r"""Number of intervals per billing cycle. Defaults to 1."""
|
||||
billing_units: NotRequired[float]
|
||||
r"""Units per price increment. Usage is rounded UP when billed (e.g. billing_units=100 means 101 rounds to 200)."""
|
||||
max_purchase: NotRequired[float]
|
||||
r"""Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total."""
|
||||
max_purchase: NotRequired[Nullable[float]]
|
||||
r"""Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total. Null for no limit."""
|
||||
|
||||
|
||||
class PreviewMultiAttachPrice(BaseModel):
|
||||
@@ -251,8 +251,8 @@ class PreviewMultiAttachPrice(BaseModel):
|
||||
billing_units: Optional[float] = 1
|
||||
r"""Units per price increment. Usage is rounded UP when billed (e.g. billing_units=100 means 101 rounds to 200)."""
|
||||
|
||||
max_purchase: Optional[float] = None
|
||||
r"""Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total."""
|
||||
max_purchase: OptionalNullable[float] = UNSET
|
||||
r"""Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total. Null for no limit."""
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
@@ -266,15 +266,24 @@ class PreviewMultiAttachPrice(BaseModel):
|
||||
"max_purchase",
|
||||
]
|
||||
)
|
||||
nullable_fields = set(["max_purchase"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
is_nullable_and_explicitly_set = (
|
||||
k in nullable_fields
|
||||
and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member
|
||||
)
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if val is not None or k not in optional_fields:
|
||||
if (
|
||||
val is not None
|
||||
or k not in optional_fields
|
||||
or is_nullable_and_explicitly_set
|
||||
):
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
|
||||
@@ -264,8 +264,8 @@ class PreviewUpdateItemPriceTypedDict(TypedDict):
|
||||
r"""Number of intervals per billing cycle. Defaults to 1."""
|
||||
billing_units: NotRequired[float]
|
||||
r"""Units per price increment. Usage is rounded UP when billed (e.g. billing_units=100 means 101 rounds to 200)."""
|
||||
max_purchase: NotRequired[float]
|
||||
r"""Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total."""
|
||||
max_purchase: NotRequired[Nullable[float]]
|
||||
r"""Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total. Null for no limit."""
|
||||
|
||||
|
||||
class PreviewUpdateItemPrice(BaseModel):
|
||||
@@ -291,8 +291,8 @@ class PreviewUpdateItemPrice(BaseModel):
|
||||
billing_units: Optional[float] = 1
|
||||
r"""Units per price increment. Usage is rounded UP when billed (e.g. billing_units=100 means 101 rounds to 200)."""
|
||||
|
||||
max_purchase: Optional[float] = None
|
||||
r"""Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total."""
|
||||
max_purchase: OptionalNullable[float] = UNSET
|
||||
r"""Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total. Null for no limit."""
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
@@ -306,15 +306,24 @@ class PreviewUpdateItemPrice(BaseModel):
|
||||
"max_purchase",
|
||||
]
|
||||
)
|
||||
nullable_fields = set(["max_purchase"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
is_nullable_and_explicitly_set = (
|
||||
k in nullable_fields
|
||||
and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member
|
||||
)
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if val is not None or k not in optional_fields:
|
||||
if (
|
||||
val is not None
|
||||
or k not in optional_fields
|
||||
or is_nullable_and_explicitly_set
|
||||
):
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
@@ -599,8 +608,8 @@ class PreviewUpdateAddItemPriceTypedDict(TypedDict):
|
||||
r"""Number of intervals per billing cycle. Defaults to 1."""
|
||||
billing_units: NotRequired[float]
|
||||
r"""Units per price increment. Usage is rounded UP when billed (e.g. billing_units=100 means 101 rounds to 200)."""
|
||||
max_purchase: NotRequired[float]
|
||||
r"""Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total."""
|
||||
max_purchase: NotRequired[Nullable[float]]
|
||||
r"""Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total. Null for no limit."""
|
||||
|
||||
|
||||
class PreviewUpdateAddItemPrice(BaseModel):
|
||||
@@ -626,8 +635,8 @@ class PreviewUpdateAddItemPrice(BaseModel):
|
||||
billing_units: Optional[float] = 1
|
||||
r"""Units per price increment. Usage is rounded UP when billed (e.g. billing_units=100 means 101 rounds to 200)."""
|
||||
|
||||
max_purchase: Optional[float] = None
|
||||
r"""Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total."""
|
||||
max_purchase: OptionalNullable[float] = UNSET
|
||||
r"""Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total. Null for no limit."""
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
@@ -641,15 +650,24 @@ class PreviewUpdateAddItemPrice(BaseModel):
|
||||
"max_purchase",
|
||||
]
|
||||
)
|
||||
nullable_fields = set(["max_purchase"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
is_nullable_and_explicitly_set = (
|
||||
k in nullable_fields
|
||||
and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member
|
||||
)
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if val is not None or k not in optional_fields:
|
||||
if (
|
||||
val is not None
|
||||
or k not in optional_fields
|
||||
or is_nullable_and_explicitly_set
|
||||
):
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
@@ -814,7 +832,20 @@ PreviewUpdateRemoveItemBillingMethod = Literal[
|
||||
r"""Match items with this billing method (prepaid or usage_based)."""
|
||||
|
||||
|
||||
PreviewUpdateRemoveItemInterval = Literal[
|
||||
PreviewUpdateIntervalRemoveItemEnum2 = Literal[
|
||||
"one_off",
|
||||
"minute",
|
||||
"hour",
|
||||
"day",
|
||||
"week",
|
||||
"month",
|
||||
"quarter",
|
||||
"semi_annual",
|
||||
"year",
|
||||
]
|
||||
|
||||
|
||||
PreviewUpdateIntervalRemoveItemEnum1 = Literal[
|
||||
"one_off",
|
||||
"week",
|
||||
"month",
|
||||
@@ -822,7 +853,20 @@ PreviewUpdateRemoveItemInterval = Literal[
|
||||
"semi_annual",
|
||||
"year",
|
||||
]
|
||||
r"""Match items with this interval."""
|
||||
|
||||
|
||||
PreviewUpdateIntervalUnionTypedDict = TypeAliasType(
|
||||
"PreviewUpdateIntervalUnionTypedDict",
|
||||
Union[PreviewUpdateIntervalRemoveItemEnum1, PreviewUpdateIntervalRemoveItemEnum2],
|
||||
)
|
||||
r"""Match items with this interval. Accepts either a BillingInterval (price-side) or a ResetInterval (reset-side, includes day/hour/minute) so price-less items keyed by reset.interval can be disambiguated."""
|
||||
|
||||
|
||||
PreviewUpdateIntervalUnion = TypeAliasType(
|
||||
"PreviewUpdateIntervalUnion",
|
||||
Union[PreviewUpdateIntervalRemoveItemEnum1, PreviewUpdateIntervalRemoveItemEnum2],
|
||||
)
|
||||
r"""Match items with this interval. Accepts either a BillingInterval (price-side) or a ResetInterval (reset-side, includes day/hour/minute) so price-less items keyed by reset.interval can be disambiguated."""
|
||||
|
||||
|
||||
class PreviewUpdatePlanItemFilterTypedDict(TypedDict):
|
||||
@@ -832,8 +876,10 @@ class PreviewUpdatePlanItemFilterTypedDict(TypedDict):
|
||||
r"""Match items linked to this feature."""
|
||||
billing_method: NotRequired[PreviewUpdateRemoveItemBillingMethod]
|
||||
r"""Match items with this billing method (prepaid or usage_based)."""
|
||||
interval: NotRequired[PreviewUpdateRemoveItemInterval]
|
||||
r"""Match items with this interval."""
|
||||
interval: NotRequired[PreviewUpdateIntervalUnionTypedDict]
|
||||
r"""Match items with this interval. Accepts either a BillingInterval (price-side) or a ResetInterval (reset-side, includes day/hour/minute) so price-less items keyed by reset.interval can be disambiguated."""
|
||||
interval_count: NotRequired[int]
|
||||
r"""Match items with this interval_count. Disambiguates between items that share an interval but differ in count."""
|
||||
|
||||
|
||||
class PreviewUpdatePlanItemFilter(BaseModel):
|
||||
@@ -845,12 +891,17 @@ class PreviewUpdatePlanItemFilter(BaseModel):
|
||||
billing_method: Optional[PreviewUpdateRemoveItemBillingMethod] = None
|
||||
r"""Match items with this billing method (prepaid or usage_based)."""
|
||||
|
||||
interval: Optional[PreviewUpdateRemoveItemInterval] = None
|
||||
r"""Match items with this interval."""
|
||||
interval: Optional[PreviewUpdateIntervalUnion] = None
|
||||
r"""Match items with this interval. Accepts either a BillingInterval (price-side) or a ResetInterval (reset-side, includes day/hour/minute) so price-less items keyed by reset.interval can be disambiguated."""
|
||||
|
||||
interval_count: Optional[int] = None
|
||||
r"""Match items with this interval_count. Disambiguates between items that share an interval but differ in count."""
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
optional_fields = set(["feature_id", "billing_method", "interval"])
|
||||
optional_fields = set(
|
||||
["feature_id", "billing_method", "interval", "interval_count"]
|
||||
)
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
@@ -931,7 +982,7 @@ class PreviewUpdateCustomizeTypedDict(TypedDict):
|
||||
price: NotRequired[Nullable[PreviewUpdateBasePriceTypedDict]]
|
||||
r"""Override the base price of the plan. Pass null to remove the base price."""
|
||||
items: NotRequired[List[PreviewUpdateItemPlanItemTypedDict]]
|
||||
r"""Override the items in the plan (PUT-style — replaces all existing items). Mutually exclusive with add_items / remove_items / update_items."""
|
||||
r"""Override the items in the plan (PUT-style — replaces all existing items). Mutually exclusive with add_items / remove_items / deprecated update_items."""
|
||||
add_items: NotRequired[List[PreviewUpdateAddItemPlanItemTypedDict]]
|
||||
r"""Items to add to the plan."""
|
||||
remove_items: NotRequired[List[PreviewUpdatePlanItemFilterTypedDict]]
|
||||
@@ -947,7 +998,7 @@ class PreviewUpdateCustomize(BaseModel):
|
||||
r"""Override the base price of the plan. Pass null to remove the base price."""
|
||||
|
||||
items: Optional[List[PreviewUpdateItemPlanItem]] = None
|
||||
r"""Override the items in the plan (PUT-style — replaces all existing items). Mutually exclusive with add_items / remove_items / update_items."""
|
||||
r"""Override the items in the plan (PUT-style — replaces all existing items). Mutually exclusive with add_items / remove_items / deprecated update_items."""
|
||||
|
||||
add_items: Optional[List[PreviewUpdateAddItemPlanItem]] = None
|
||||
r"""Items to add to the plan."""
|
||||
|
||||
@@ -262,8 +262,8 @@ class SetupPaymentItemPriceTypedDict(TypedDict):
|
||||
r"""Number of intervals per billing cycle. Defaults to 1."""
|
||||
billing_units: NotRequired[float]
|
||||
r"""Units per price increment. Usage is rounded UP when billed (e.g. billing_units=100 means 101 rounds to 200)."""
|
||||
max_purchase: NotRequired[float]
|
||||
r"""Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total."""
|
||||
max_purchase: NotRequired[Nullable[float]]
|
||||
r"""Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total. Null for no limit."""
|
||||
|
||||
|
||||
class SetupPaymentItemPrice(BaseModel):
|
||||
@@ -289,8 +289,8 @@ class SetupPaymentItemPrice(BaseModel):
|
||||
billing_units: Optional[float] = 1
|
||||
r"""Units per price increment. Usage is rounded UP when billed (e.g. billing_units=100 means 101 rounds to 200)."""
|
||||
|
||||
max_purchase: Optional[float] = None
|
||||
r"""Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total."""
|
||||
max_purchase: OptionalNullable[float] = UNSET
|
||||
r"""Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total. Null for no limit."""
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
@@ -304,15 +304,24 @@ class SetupPaymentItemPrice(BaseModel):
|
||||
"max_purchase",
|
||||
]
|
||||
)
|
||||
nullable_fields = set(["max_purchase"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
is_nullable_and_explicitly_set = (
|
||||
k in nullable_fields
|
||||
and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member
|
||||
)
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if val is not None or k not in optional_fields:
|
||||
if (
|
||||
val is not None
|
||||
or k not in optional_fields
|
||||
or is_nullable_and_explicitly_set
|
||||
):
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
@@ -597,8 +606,8 @@ class SetupPaymentAddItemPriceTypedDict(TypedDict):
|
||||
r"""Number of intervals per billing cycle. Defaults to 1."""
|
||||
billing_units: NotRequired[float]
|
||||
r"""Units per price increment. Usage is rounded UP when billed (e.g. billing_units=100 means 101 rounds to 200)."""
|
||||
max_purchase: NotRequired[float]
|
||||
r"""Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total."""
|
||||
max_purchase: NotRequired[Nullable[float]]
|
||||
r"""Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total. Null for no limit."""
|
||||
|
||||
|
||||
class SetupPaymentAddItemPrice(BaseModel):
|
||||
@@ -624,8 +633,8 @@ class SetupPaymentAddItemPrice(BaseModel):
|
||||
billing_units: Optional[float] = 1
|
||||
r"""Units per price increment. Usage is rounded UP when billed (e.g. billing_units=100 means 101 rounds to 200)."""
|
||||
|
||||
max_purchase: Optional[float] = None
|
||||
r"""Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total."""
|
||||
max_purchase: OptionalNullable[float] = UNSET
|
||||
r"""Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total. Null for no limit."""
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
@@ -639,15 +648,24 @@ class SetupPaymentAddItemPrice(BaseModel):
|
||||
"max_purchase",
|
||||
]
|
||||
)
|
||||
nullable_fields = set(["max_purchase"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
is_nullable_and_explicitly_set = (
|
||||
k in nullable_fields
|
||||
and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member
|
||||
)
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if val is not None or k not in optional_fields:
|
||||
if (
|
||||
val is not None
|
||||
or k not in optional_fields
|
||||
or is_nullable_and_explicitly_set
|
||||
):
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
@@ -812,7 +830,20 @@ SetupPaymentRemoveItemBillingMethod = Literal[
|
||||
r"""Match items with this billing method (prepaid or usage_based)."""
|
||||
|
||||
|
||||
SetupPaymentRemoveItemInterval = Literal[
|
||||
SetupPaymentIntervalRemoveItemEnum2 = Literal[
|
||||
"one_off",
|
||||
"minute",
|
||||
"hour",
|
||||
"day",
|
||||
"week",
|
||||
"month",
|
||||
"quarter",
|
||||
"semi_annual",
|
||||
"year",
|
||||
]
|
||||
|
||||
|
||||
SetupPaymentIntervalRemoveItemEnum1 = Literal[
|
||||
"one_off",
|
||||
"week",
|
||||
"month",
|
||||
@@ -820,7 +851,20 @@ SetupPaymentRemoveItemInterval = Literal[
|
||||
"semi_annual",
|
||||
"year",
|
||||
]
|
||||
r"""Match items with this interval."""
|
||||
|
||||
|
||||
SetupPaymentIntervalUnionTypedDict = TypeAliasType(
|
||||
"SetupPaymentIntervalUnionTypedDict",
|
||||
Union[SetupPaymentIntervalRemoveItemEnum1, SetupPaymentIntervalRemoveItemEnum2],
|
||||
)
|
||||
r"""Match items with this interval. Accepts either a BillingInterval (price-side) or a ResetInterval (reset-side, includes day/hour/minute) so price-less items keyed by reset.interval can be disambiguated."""
|
||||
|
||||
|
||||
SetupPaymentIntervalUnion = TypeAliasType(
|
||||
"SetupPaymentIntervalUnion",
|
||||
Union[SetupPaymentIntervalRemoveItemEnum1, SetupPaymentIntervalRemoveItemEnum2],
|
||||
)
|
||||
r"""Match items with this interval. Accepts either a BillingInterval (price-side) or a ResetInterval (reset-side, includes day/hour/minute) so price-less items keyed by reset.interval can be disambiguated."""
|
||||
|
||||
|
||||
class SetupPaymentPlanItemFilterTypedDict(TypedDict):
|
||||
@@ -830,8 +874,10 @@ class SetupPaymentPlanItemFilterTypedDict(TypedDict):
|
||||
r"""Match items linked to this feature."""
|
||||
billing_method: NotRequired[SetupPaymentRemoveItemBillingMethod]
|
||||
r"""Match items with this billing method (prepaid or usage_based)."""
|
||||
interval: NotRequired[SetupPaymentRemoveItemInterval]
|
||||
r"""Match items with this interval."""
|
||||
interval: NotRequired[SetupPaymentIntervalUnionTypedDict]
|
||||
r"""Match items with this interval. Accepts either a BillingInterval (price-side) or a ResetInterval (reset-side, includes day/hour/minute) so price-less items keyed by reset.interval can be disambiguated."""
|
||||
interval_count: NotRequired[int]
|
||||
r"""Match items with this interval_count. Disambiguates between items that share an interval but differ in count."""
|
||||
|
||||
|
||||
class SetupPaymentPlanItemFilter(BaseModel):
|
||||
@@ -843,12 +889,17 @@ class SetupPaymentPlanItemFilter(BaseModel):
|
||||
billing_method: Optional[SetupPaymentRemoveItemBillingMethod] = None
|
||||
r"""Match items with this billing method (prepaid or usage_based)."""
|
||||
|
||||
interval: Optional[SetupPaymentRemoveItemInterval] = None
|
||||
r"""Match items with this interval."""
|
||||
interval: Optional[SetupPaymentIntervalUnion] = None
|
||||
r"""Match items with this interval. Accepts either a BillingInterval (price-side) or a ResetInterval (reset-side, includes day/hour/minute) so price-less items keyed by reset.interval can be disambiguated."""
|
||||
|
||||
interval_count: Optional[int] = None
|
||||
r"""Match items with this interval_count. Disambiguates between items that share an interval but differ in count."""
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
optional_fields = set(["feature_id", "billing_method", "interval"])
|
||||
optional_fields = set(
|
||||
["feature_id", "billing_method", "interval", "interval_count"]
|
||||
)
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
@@ -929,7 +980,7 @@ class SetupPaymentCustomizeTypedDict(TypedDict):
|
||||
price: NotRequired[Nullable[SetupPaymentBasePriceTypedDict]]
|
||||
r"""Override the base price of the plan. Pass null to remove the base price."""
|
||||
items: NotRequired[List[SetupPaymentItemPlanItemTypedDict]]
|
||||
r"""Override the items in the plan (PUT-style — replaces all existing items). Mutually exclusive with add_items / remove_items / update_items."""
|
||||
r"""Override the items in the plan (PUT-style — replaces all existing items). Mutually exclusive with add_items / remove_items / deprecated update_items."""
|
||||
add_items: NotRequired[List[SetupPaymentAddItemPlanItemTypedDict]]
|
||||
r"""Items to add to the plan."""
|
||||
remove_items: NotRequired[List[SetupPaymentPlanItemFilterTypedDict]]
|
||||
@@ -945,7 +996,7 @@ class SetupPaymentCustomize(BaseModel):
|
||||
r"""Override the base price of the plan. Pass null to remove the base price."""
|
||||
|
||||
items: Optional[List[SetupPaymentItemPlanItem]] = None
|
||||
r"""Override the items in the plan (PUT-style — replaces all existing items). Mutually exclusive with add_items / remove_items / update_items."""
|
||||
r"""Override the items in the plan (PUT-style — replaces all existing items). Mutually exclusive with add_items / remove_items / deprecated update_items."""
|
||||
|
||||
add_items: Optional[List[SetupPaymentAddItemPlanItem]] = None
|
||||
r"""Items to add to the plan."""
|
||||
|
||||
@@ -220,7 +220,7 @@ class TrackReset2(BaseModel):
|
||||
return m
|
||||
|
||||
|
||||
class Deduction2TypedDict(TypedDict):
|
||||
class TrackDeduction2TypedDict(TypedDict):
|
||||
balance_id: str
|
||||
r"""ID of the underlying balance row that was deducted from (customer_entitlement or rollover)."""
|
||||
feature_id: str
|
||||
@@ -233,7 +233,7 @@ class Deduction2TypedDict(TypedDict):
|
||||
r"""Amount deducted from this balance. Positive when usage was consumed, negative when credit was restored (e.g. a refund via negative track value)."""
|
||||
|
||||
|
||||
class Deduction2(BaseModel):
|
||||
class TrackDeduction2(BaseModel):
|
||||
balance_id: str
|
||||
r"""ID of the underlying balance row that was deducted from (customer_entitlement or rollover)."""
|
||||
|
||||
@@ -279,7 +279,7 @@ class TrackResponseBody2TypedDict(TypedDict):
|
||||
r"""The event name that was tracked, if event_name was used instead of feature_id."""
|
||||
balances: NotRequired[Dict[str, Nullable[BalanceTypedDict]]]
|
||||
r"""Map of feature_id to updated balance for the tracked feature and any related features (e.g. linked credit systems). Value is null when the customer has no balance for that feature."""
|
||||
deductions: NotRequired[List[Deduction2TypedDict]]
|
||||
deductions: NotRequired[List[TrackDeduction2TypedDict]]
|
||||
r"""Per-balance breakdown of what this event deducted. A single event can consume from multiple balance rows when credit systems or rollovers are involved; this surfaces each one so callers can build per-feature usage views without polling."""
|
||||
|
||||
|
||||
@@ -304,7 +304,7 @@ class TrackResponseBody2(BaseModel):
|
||||
balances: Optional[Dict[str, Nullable[Balance]]] = None
|
||||
r"""Map of feature_id to updated balance for the tracked feature and any related features (e.g. linked credit systems). Value is null when the customer has no balance for that feature."""
|
||||
|
||||
deductions: Optional[List[Deduction2]] = None
|
||||
deductions: Optional[List[TrackDeduction2]] = None
|
||||
r"""Per-balance breakdown of what this event deducted. A single event can consume from multiple balance rows when credit systems or rollovers are involved; this surfaces each one so callers can build per-feature usage views without polling."""
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
@@ -406,7 +406,7 @@ class TrackReset1(BaseModel):
|
||||
return m
|
||||
|
||||
|
||||
class Deduction1TypedDict(TypedDict):
|
||||
class TrackDeduction1TypedDict(TypedDict):
|
||||
balance_id: str
|
||||
r"""ID of the underlying balance row that was deducted from (customer_entitlement or rollover)."""
|
||||
feature_id: str
|
||||
@@ -419,7 +419,7 @@ class Deduction1TypedDict(TypedDict):
|
||||
r"""Amount deducted from this balance. Positive when usage was consumed, negative when credit was restored (e.g. a refund via negative track value)."""
|
||||
|
||||
|
||||
class Deduction1(BaseModel):
|
||||
class TrackDeduction1(BaseModel):
|
||||
balance_id: str
|
||||
r"""ID of the underlying balance row that was deducted from (customer_entitlement or rollover)."""
|
||||
|
||||
@@ -465,7 +465,7 @@ class TrackResponseBody1TypedDict(TypedDict):
|
||||
r"""The event name that was tracked, if event_name was used instead of feature_id."""
|
||||
balances: NotRequired[Dict[str, Nullable[BalanceTypedDict]]]
|
||||
r"""Map of feature_id to updated balance for the tracked feature and any related features (e.g. linked credit systems). Value is null when the customer has no balance for that feature."""
|
||||
deductions: NotRequired[List[Deduction1TypedDict]]
|
||||
deductions: NotRequired[List[TrackDeduction1TypedDict]]
|
||||
r"""Per-balance breakdown of what this event deducted. A single event can consume from multiple balance rows when credit systems or rollovers are involved; this surfaces each one so callers can build per-feature usage views without polling."""
|
||||
|
||||
|
||||
@@ -490,7 +490,7 @@ class TrackResponseBody1(BaseModel):
|
||||
balances: Optional[Dict[str, Nullable[Balance]]] = None
|
||||
r"""Map of feature_id to updated balance for the tracked feature and any related features (e.g. linked credit systems). Value is null when the customer has no balance for that feature."""
|
||||
|
||||
deductions: Optional[List[Deduction1]] = None
|
||||
deductions: Optional[List[TrackDeduction1]] = None
|
||||
r"""Per-balance breakdown of what this event deducted. A single event can consume from multiple balance rows when credit systems or rollovers are involved; this surfaces each one so callers can build per-feature usage views without polling."""
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
|
||||
513
others/python-sdk/src/autumn_sdk/models/tracktokensop.py
Normal file
@@ -0,0 +1,513 @@
|
||||
"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
|
||||
|
||||
from __future__ import annotations
|
||||
from .balance import Balance, BalanceTypedDict
|
||||
from autumn_sdk.types import BaseModel, Nullable, UNSET_SENTINEL, UnrecognizedStr
|
||||
from autumn_sdk.utils import FieldMetadata, HeaderMetadata
|
||||
import pydantic
|
||||
from pydantic import model_serializer
|
||||
from typing import Any, Dict, List, Literal, Optional, Union
|
||||
from typing_extensions import Annotated, NotRequired, TypeAliasType, TypedDict
|
||||
|
||||
|
||||
class TrackTokensGlobalsTypedDict(TypedDict):
|
||||
x_api_version: NotRequired[str]
|
||||
|
||||
|
||||
class TrackTokensGlobals(BaseModel):
|
||||
x_api_version: Annotated[
|
||||
Optional[str],
|
||||
pydantic.Field(alias="x-api-version"),
|
||||
FieldMetadata(header=HeaderMetadata(style="simple", explode=False)),
|
||||
] = "2.3.0"
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
optional_fields = set(["x-api-version"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if val is not None or k not in optional_fields:
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
|
||||
|
||||
class TrackTokensParamsTypedDict(TypedDict):
|
||||
customer_id: str
|
||||
r"""The ID of the customer."""
|
||||
model_id: str
|
||||
r"""The AI model as '<provider>/<model>' (e.g. 'anthropic/claude-opus-4-8', 'openrouter/openai/gpt-4o'). The provider is the first path segment and must match a provider + model key in models.dev."""
|
||||
input_tokens: int
|
||||
r"""Number of non-cached text input tokens consumed. Exclusive of cache and audio token pools."""
|
||||
output_tokens: int
|
||||
r"""Number of text output tokens consumed. Exclusive of the reasoning and audio output pools."""
|
||||
entity_id: NotRequired[str]
|
||||
r"""The ID of the entity for entity-scoped balances."""
|
||||
feature_id: NotRequired[str]
|
||||
r"""The ID of the AI credit system feature. Auto-detected from the customer's entitlements if omitted — only required when a customer has multiple AI credit systems."""
|
||||
cache_read_tokens: NotRequired[int]
|
||||
r"""Number of cached input tokens read."""
|
||||
cache_write_tokens: NotRequired[int]
|
||||
r"""Number of input tokens written to the cache."""
|
||||
audio_input_tokens: NotRequired[int]
|
||||
r"""Number of audio input tokens consumed."""
|
||||
audio_output_tokens: NotRequired[int]
|
||||
r"""Number of audio output tokens generated."""
|
||||
reasoning_tokens: NotRequired[int]
|
||||
r"""Number of reasoning tokens generated."""
|
||||
properties: NotRequired[Dict[str, Any]]
|
||||
r"""Additional properties to attach to this usage event."""
|
||||
|
||||
|
||||
class TrackTokensParams(BaseModel):
|
||||
customer_id: str
|
||||
r"""The ID of the customer."""
|
||||
|
||||
model_id: str
|
||||
r"""The AI model as '<provider>/<model>' (e.g. 'anthropic/claude-opus-4-8', 'openrouter/openai/gpt-4o'). The provider is the first path segment and must match a provider + model key in models.dev."""
|
||||
|
||||
input_tokens: int
|
||||
r"""Number of non-cached text input tokens consumed. Exclusive of cache and audio token pools."""
|
||||
|
||||
output_tokens: int
|
||||
r"""Number of text output tokens consumed. Exclusive of the reasoning and audio output pools."""
|
||||
|
||||
entity_id: Optional[str] = None
|
||||
r"""The ID of the entity for entity-scoped balances."""
|
||||
|
||||
feature_id: Optional[str] = None
|
||||
r"""The ID of the AI credit system feature. Auto-detected from the customer's entitlements if omitted — only required when a customer has multiple AI credit systems."""
|
||||
|
||||
cache_read_tokens: Optional[int] = None
|
||||
r"""Number of cached input tokens read."""
|
||||
|
||||
cache_write_tokens: Optional[int] = None
|
||||
r"""Number of input tokens written to the cache."""
|
||||
|
||||
audio_input_tokens: Optional[int] = None
|
||||
r"""Number of audio input tokens consumed."""
|
||||
|
||||
audio_output_tokens: Optional[int] = None
|
||||
r"""Number of audio output tokens generated."""
|
||||
|
||||
reasoning_tokens: Optional[int] = None
|
||||
r"""Number of reasoning tokens generated."""
|
||||
|
||||
properties: Optional[Dict[str, Any]] = None
|
||||
r"""Additional properties to attach to this usage event."""
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
optional_fields = set(
|
||||
[
|
||||
"entity_id",
|
||||
"feature_id",
|
||||
"cache_read_tokens",
|
||||
"cache_write_tokens",
|
||||
"audio_input_tokens",
|
||||
"audio_output_tokens",
|
||||
"reasoning_tokens",
|
||||
"properties",
|
||||
]
|
||||
)
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if val is not None or k not in optional_fields:
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
|
||||
|
||||
TrackTokensIntervalEnum2 = Union[
|
||||
Literal[
|
||||
"one_off",
|
||||
"minute",
|
||||
"hour",
|
||||
"day",
|
||||
"week",
|
||||
"month",
|
||||
"quarter",
|
||||
"semi_annual",
|
||||
"year",
|
||||
],
|
||||
UnrecognizedStr,
|
||||
]
|
||||
|
||||
|
||||
TrackTokensIntervalUnion2TypedDict = TypeAliasType(
|
||||
"TrackTokensIntervalUnion2TypedDict", Union[TrackTokensIntervalEnum2, str]
|
||||
)
|
||||
r"""The reset interval (hour, day, week, month, etc.) or 'multiple' if combined from different intervals."""
|
||||
|
||||
|
||||
TrackTokensIntervalUnion2 = TypeAliasType(
|
||||
"TrackTokensIntervalUnion2", Union[TrackTokensIntervalEnum2, str]
|
||||
)
|
||||
r"""The reset interval (hour, day, week, month, etc.) or 'multiple' if combined from different intervals."""
|
||||
|
||||
|
||||
class TrackTokensReset2TypedDict(TypedDict):
|
||||
interval: TrackTokensIntervalUnion2TypedDict
|
||||
r"""The reset interval (hour, day, week, month, etc.) or 'multiple' if combined from different intervals."""
|
||||
resets_at: Nullable[float]
|
||||
r"""Timestamp when the balance will next reset."""
|
||||
interval_count: NotRequired[float]
|
||||
r"""Number of intervals between resets (eg. 2 for bi-monthly)."""
|
||||
|
||||
|
||||
class TrackTokensReset2(BaseModel):
|
||||
interval: TrackTokensIntervalUnion2
|
||||
r"""The reset interval (hour, day, week, month, etc.) or 'multiple' if combined from different intervals."""
|
||||
|
||||
resets_at: Nullable[float]
|
||||
r"""Timestamp when the balance will next reset."""
|
||||
|
||||
interval_count: Optional[float] = None
|
||||
r"""Number of intervals between resets (eg. 2 for bi-monthly)."""
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
optional_fields = set(["interval_count"])
|
||||
nullable_fields = set(["resets_at"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
is_nullable_and_explicitly_set = (
|
||||
k in nullable_fields
|
||||
and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member
|
||||
)
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if (
|
||||
val is not None
|
||||
or k not in optional_fields
|
||||
or is_nullable_and_explicitly_set
|
||||
):
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
|
||||
|
||||
class TrackTokensDeduction2TypedDict(TypedDict):
|
||||
balance_id: str
|
||||
r"""ID of the underlying balance row that was deducted from (customer_entitlement or rollover)."""
|
||||
feature_id: str
|
||||
r"""The feature this balance belongs to."""
|
||||
plan_id: Nullable[str]
|
||||
r"""ID of the plan/product this balance belongs to. Null when the balance can't be attributed to a single plan (e.g. it spans multiple)."""
|
||||
reset: Nullable[TrackTokensReset2TypedDict]
|
||||
r"""Reset configuration for the balance this deduction came from, or null if the balance doesn't reset."""
|
||||
value: float
|
||||
r"""Amount deducted from this balance. Positive when usage was consumed, negative when credit was restored (e.g. a refund via negative track value)."""
|
||||
|
||||
|
||||
class TrackTokensDeduction2(BaseModel):
|
||||
balance_id: str
|
||||
r"""ID of the underlying balance row that was deducted from (customer_entitlement or rollover)."""
|
||||
|
||||
feature_id: str
|
||||
r"""The feature this balance belongs to."""
|
||||
|
||||
plan_id: Nullable[str]
|
||||
r"""ID of the plan/product this balance belongs to. Null when the balance can't be attributed to a single plan (e.g. it spans multiple)."""
|
||||
|
||||
reset: Nullable[TrackTokensReset2]
|
||||
r"""Reset configuration for the balance this deduction came from, or null if the balance doesn't reset."""
|
||||
|
||||
value: float
|
||||
r"""Amount deducted from this balance. Positive when usage was consumed, negative when credit was restored (e.g. a refund via negative track value)."""
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
|
||||
|
||||
class TrackTokensResponseBody2TypedDict(TypedDict):
|
||||
r"""Accepted. Autumn is experiencing degraded service from a downstream provider, so the token usage event was accepted for replay and will be tracked as soon as the service is restored."""
|
||||
|
||||
customer_id: str
|
||||
r"""The ID of the customer whose usage was tracked."""
|
||||
value: float
|
||||
r"""The amount of usage that was recorded."""
|
||||
balance: Nullable[BalanceTypedDict]
|
||||
r"""The updated balance for the tracked feature. Null if tracking by event_name that affects multiple features."""
|
||||
entity_id: NotRequired[str]
|
||||
r"""The ID of the entity, if entity-scoped tracking was performed."""
|
||||
event_name: NotRequired[str]
|
||||
r"""The event name that was tracked, if event_name was used instead of feature_id."""
|
||||
balances: NotRequired[Dict[str, Nullable[BalanceTypedDict]]]
|
||||
r"""Map of feature_id to updated balance for the tracked feature and any related features (e.g. linked credit systems). Value is null when the customer has no balance for that feature."""
|
||||
deductions: NotRequired[List[TrackTokensDeduction2TypedDict]]
|
||||
r"""Per-balance breakdown of what this event deducted. A single event can consume from multiple balance rows when credit systems or rollovers are involved; this surfaces each one so callers can build per-feature usage views without polling."""
|
||||
|
||||
|
||||
class TrackTokensResponseBody2(BaseModel):
|
||||
r"""Accepted. Autumn is experiencing degraded service from a downstream provider, so the token usage event was accepted for replay and will be tracked as soon as the service is restored."""
|
||||
|
||||
customer_id: str
|
||||
r"""The ID of the customer whose usage was tracked."""
|
||||
|
||||
value: float
|
||||
r"""The amount of usage that was recorded."""
|
||||
|
||||
balance: Nullable[Balance]
|
||||
r"""The updated balance for the tracked feature. Null if tracking by event_name that affects multiple features."""
|
||||
|
||||
entity_id: Optional[str] = None
|
||||
r"""The ID of the entity, if entity-scoped tracking was performed."""
|
||||
|
||||
event_name: Optional[str] = None
|
||||
r"""The event name that was tracked, if event_name was used instead of feature_id."""
|
||||
|
||||
balances: Optional[Dict[str, Nullable[Balance]]] = None
|
||||
r"""Map of feature_id to updated balance for the tracked feature and any related features (e.g. linked credit systems). Value is null when the customer has no balance for that feature."""
|
||||
|
||||
deductions: Optional[List[TrackTokensDeduction2]] = None
|
||||
r"""Per-balance breakdown of what this event deducted. A single event can consume from multiple balance rows when credit systems or rollovers are involved; this surfaces each one so callers can build per-feature usage views without polling."""
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
optional_fields = set(["entity_id", "event_name", "balances", "deductions"])
|
||||
nullable_fields = set(["balance"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
is_nullable_and_explicitly_set = (
|
||||
k in nullable_fields
|
||||
and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member
|
||||
)
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if (
|
||||
val is not None
|
||||
or k not in optional_fields
|
||||
or is_nullable_and_explicitly_set
|
||||
):
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
|
||||
|
||||
TrackTokensIntervalEnum1 = Union[
|
||||
Literal[
|
||||
"one_off",
|
||||
"minute",
|
||||
"hour",
|
||||
"day",
|
||||
"week",
|
||||
"month",
|
||||
"quarter",
|
||||
"semi_annual",
|
||||
"year",
|
||||
],
|
||||
UnrecognizedStr,
|
||||
]
|
||||
|
||||
|
||||
TrackTokensIntervalUnion1TypedDict = TypeAliasType(
|
||||
"TrackTokensIntervalUnion1TypedDict", Union[TrackTokensIntervalEnum1, str]
|
||||
)
|
||||
r"""The reset interval (hour, day, week, month, etc.) or 'multiple' if combined from different intervals."""
|
||||
|
||||
|
||||
TrackTokensIntervalUnion1 = TypeAliasType(
|
||||
"TrackTokensIntervalUnion1", Union[TrackTokensIntervalEnum1, str]
|
||||
)
|
||||
r"""The reset interval (hour, day, week, month, etc.) or 'multiple' if combined from different intervals."""
|
||||
|
||||
|
||||
class TrackTokensReset1TypedDict(TypedDict):
|
||||
interval: TrackTokensIntervalUnion1TypedDict
|
||||
r"""The reset interval (hour, day, week, month, etc.) or 'multiple' if combined from different intervals."""
|
||||
resets_at: Nullable[float]
|
||||
r"""Timestamp when the balance will next reset."""
|
||||
interval_count: NotRequired[float]
|
||||
r"""Number of intervals between resets (eg. 2 for bi-monthly)."""
|
||||
|
||||
|
||||
class TrackTokensReset1(BaseModel):
|
||||
interval: TrackTokensIntervalUnion1
|
||||
r"""The reset interval (hour, day, week, month, etc.) or 'multiple' if combined from different intervals."""
|
||||
|
||||
resets_at: Nullable[float]
|
||||
r"""Timestamp when the balance will next reset."""
|
||||
|
||||
interval_count: Optional[float] = None
|
||||
r"""Number of intervals between resets (eg. 2 for bi-monthly)."""
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
optional_fields = set(["interval_count"])
|
||||
nullable_fields = set(["resets_at"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
is_nullable_and_explicitly_set = (
|
||||
k in nullable_fields
|
||||
and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member
|
||||
)
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if (
|
||||
val is not None
|
||||
or k not in optional_fields
|
||||
or is_nullable_and_explicitly_set
|
||||
):
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
|
||||
|
||||
class TrackTokensDeduction1TypedDict(TypedDict):
|
||||
balance_id: str
|
||||
r"""ID of the underlying balance row that was deducted from (customer_entitlement or rollover)."""
|
||||
feature_id: str
|
||||
r"""The feature this balance belongs to."""
|
||||
plan_id: Nullable[str]
|
||||
r"""ID of the plan/product this balance belongs to. Null when the balance can't be attributed to a single plan (e.g. it spans multiple)."""
|
||||
reset: Nullable[TrackTokensReset1TypedDict]
|
||||
r"""Reset configuration for the balance this deduction came from, or null if the balance doesn't reset."""
|
||||
value: float
|
||||
r"""Amount deducted from this balance. Positive when usage was consumed, negative when credit was restored (e.g. a refund via negative track value)."""
|
||||
|
||||
|
||||
class TrackTokensDeduction1(BaseModel):
|
||||
balance_id: str
|
||||
r"""ID of the underlying balance row that was deducted from (customer_entitlement or rollover)."""
|
||||
|
||||
feature_id: str
|
||||
r"""The feature this balance belongs to."""
|
||||
|
||||
plan_id: Nullable[str]
|
||||
r"""ID of the plan/product this balance belongs to. Null when the balance can't be attributed to a single plan (e.g. it spans multiple)."""
|
||||
|
||||
reset: Nullable[TrackTokensReset1]
|
||||
r"""Reset configuration for the balance this deduction came from, or null if the balance doesn't reset."""
|
||||
|
||||
value: float
|
||||
r"""Amount deducted from this balance. Positive when usage was consumed, negative when credit was restored (e.g. a refund via negative track value)."""
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
|
||||
|
||||
class TrackTokensResponseBody1TypedDict(TypedDict):
|
||||
r"""OK"""
|
||||
|
||||
customer_id: str
|
||||
r"""The ID of the customer whose usage was tracked."""
|
||||
value: float
|
||||
r"""The amount of usage that was recorded."""
|
||||
balance: Nullable[BalanceTypedDict]
|
||||
r"""The updated balance for the tracked feature. Null if tracking by event_name that affects multiple features."""
|
||||
entity_id: NotRequired[str]
|
||||
r"""The ID of the entity, if entity-scoped tracking was performed."""
|
||||
event_name: NotRequired[str]
|
||||
r"""The event name that was tracked, if event_name was used instead of feature_id."""
|
||||
balances: NotRequired[Dict[str, Nullable[BalanceTypedDict]]]
|
||||
r"""Map of feature_id to updated balance for the tracked feature and any related features (e.g. linked credit systems). Value is null when the customer has no balance for that feature."""
|
||||
deductions: NotRequired[List[TrackTokensDeduction1TypedDict]]
|
||||
r"""Per-balance breakdown of what this event deducted. A single event can consume from multiple balance rows when credit systems or rollovers are involved; this surfaces each one so callers can build per-feature usage views without polling."""
|
||||
|
||||
|
||||
class TrackTokensResponseBody1(BaseModel):
|
||||
r"""OK"""
|
||||
|
||||
customer_id: str
|
||||
r"""The ID of the customer whose usage was tracked."""
|
||||
|
||||
value: float
|
||||
r"""The amount of usage that was recorded."""
|
||||
|
||||
balance: Nullable[Balance]
|
||||
r"""The updated balance for the tracked feature. Null if tracking by event_name that affects multiple features."""
|
||||
|
||||
entity_id: Optional[str] = None
|
||||
r"""The ID of the entity, if entity-scoped tracking was performed."""
|
||||
|
||||
event_name: Optional[str] = None
|
||||
r"""The event name that was tracked, if event_name was used instead of feature_id."""
|
||||
|
||||
balances: Optional[Dict[str, Nullable[Balance]]] = None
|
||||
r"""Map of feature_id to updated balance for the tracked feature and any related features (e.g. linked credit systems). Value is null when the customer has no balance for that feature."""
|
||||
|
||||
deductions: Optional[List[TrackTokensDeduction1]] = None
|
||||
r"""Per-balance breakdown of what this event deducted. A single event can consume from multiple balance rows when credit systems or rollovers are involved; this surfaces each one so callers can build per-feature usage views without polling."""
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
optional_fields = set(["entity_id", "event_name", "balances", "deductions"])
|
||||
nullable_fields = set(["balance"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
is_nullable_and_explicitly_set = (
|
||||
k in nullable_fields
|
||||
and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member
|
||||
)
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if (
|
||||
val is not None
|
||||
or k not in optional_fields
|
||||
or is_nullable_and_explicitly_set
|
||||
):
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
|
||||
|
||||
TrackTokensResponseTypedDict = TypeAliasType(
|
||||
"TrackTokensResponseTypedDict",
|
||||
Union[TrackTokensResponseBody1TypedDict, TrackTokensResponseBody2TypedDict],
|
||||
)
|
||||
|
||||
|
||||
TrackTokensResponse = TypeAliasType(
|
||||
"TrackTokensResponse", Union[TrackTokensResponseBody1, TrackTokensResponseBody2]
|
||||
)
|
||||
@@ -46,7 +46,7 @@ class UpdateCustomerGlobals(BaseModel):
|
||||
return m
|
||||
|
||||
|
||||
UpdateCustomerIntervalRequest = Literal[
|
||||
UpdateCustomerIntervalRequestBody = Literal[
|
||||
"hour",
|
||||
"day",
|
||||
"week",
|
||||
@@ -58,7 +58,7 @@ r"""The time interval for the purchase limit window."""
|
||||
class UpdateCustomerPurchaseLimitRequestTypedDict(TypedDict):
|
||||
r"""Optional rate limit to cap how often auto top-ups occur."""
|
||||
|
||||
interval: UpdateCustomerIntervalRequest
|
||||
interval: UpdateCustomerIntervalRequestBody
|
||||
r"""The time interval for the purchase limit window."""
|
||||
limit: float
|
||||
r"""Maximum number of auto top-ups allowed within the interval."""
|
||||
@@ -69,7 +69,7 @@ class UpdateCustomerPurchaseLimitRequestTypedDict(TypedDict):
|
||||
class UpdateCustomerPurchaseLimitRequest(BaseModel):
|
||||
r"""Optional rate limit to cap how often auto top-ups occur."""
|
||||
|
||||
interval: UpdateCustomerIntervalRequest
|
||||
interval: UpdateCustomerIntervalRequestBody
|
||||
r"""The time interval for the purchase limit window."""
|
||||
|
||||
limit: float
|
||||
@@ -994,10 +994,11 @@ UpdateCustomerType = Union[
|
||||
"boolean",
|
||||
"metered",
|
||||
"credit_system",
|
||||
"ai_credit_system",
|
||||
],
|
||||
UnrecognizedStr,
|
||||
]
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools."""
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing."""
|
||||
|
||||
|
||||
class UpdateCustomerCreditSchemaTypedDict(TypedDict):
|
||||
@@ -1015,6 +1016,44 @@ class UpdateCustomerCreditSchema(BaseModel):
|
||||
r"""Credits consumed per unit of the metered feature."""
|
||||
|
||||
|
||||
class UpdateCustomerModelMarkupsTypedDict(TypedDict):
|
||||
markup: NotRequired[float]
|
||||
input_cost: NotRequired[float]
|
||||
output_cost: NotRequired[float]
|
||||
|
||||
|
||||
class UpdateCustomerModelMarkups(BaseModel):
|
||||
markup: Optional[float] = None
|
||||
|
||||
input_cost: Optional[float] = None
|
||||
|
||||
output_cost: Optional[float] = None
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
optional_fields = set(["markup", "input_cost", "output_cost"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if val is not None or k not in optional_fields:
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
|
||||
|
||||
class UpdateCustomerProviderMarkupsTypedDict(TypedDict):
|
||||
markup: float
|
||||
|
||||
|
||||
class UpdateCustomerProviderMarkups(BaseModel):
|
||||
markup: float
|
||||
|
||||
|
||||
class UpdateCustomerDisplayTypedDict(TypedDict):
|
||||
r"""Display names for the feature in billing UI and customer-facing components."""
|
||||
|
||||
@@ -1067,7 +1106,7 @@ class UpdateCustomerFeatureTypedDict(TypedDict):
|
||||
name: str
|
||||
r"""Human-readable name displayed in the dashboard and billing UI."""
|
||||
type: UpdateCustomerType
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools."""
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing."""
|
||||
consumable: bool
|
||||
r"""For metered features: true if usage resets periodically (API calls, credits), false if allocated persistently (seats, storage)."""
|
||||
archived: bool
|
||||
@@ -1076,6 +1115,14 @@ class UpdateCustomerFeatureTypedDict(TypedDict):
|
||||
r"""Event names that trigger this feature's balance. Allows multiple features to respond to a single event."""
|
||||
credit_schema: NotRequired[List[UpdateCustomerCreditSchemaTypedDict]]
|
||||
r"""For credit_system features: maps metered features to their credit costs."""
|
||||
model_markups: NotRequired[Nullable[Dict[str, UpdateCustomerModelMarkupsTypedDict]]]
|
||||
r"""Per-model markup overrides for AI credit systems."""
|
||||
default_markup: NotRequired[float]
|
||||
r"""Default percentage markup for AI credit systems. Use -100 to make usage free."""
|
||||
provider_markups: NotRequired[
|
||||
Nullable[Dict[str, UpdateCustomerProviderMarkupsTypedDict]]
|
||||
]
|
||||
r"""Per-provider default markup percentages for AI credit systems."""
|
||||
display: NotRequired[UpdateCustomerDisplayTypedDict]
|
||||
r"""Display names for the feature in billing UI and customer-facing components."""
|
||||
|
||||
@@ -1090,7 +1137,7 @@ class UpdateCustomerFeature(BaseModel):
|
||||
r"""Human-readable name displayed in the dashboard and billing UI."""
|
||||
|
||||
type: UpdateCustomerType
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools."""
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing."""
|
||||
|
||||
consumable: bool
|
||||
r"""For metered features: true if usage resets periodically (API calls, credits), false if allocated persistently (seats, storage)."""
|
||||
@@ -1104,21 +1151,48 @@ class UpdateCustomerFeature(BaseModel):
|
||||
credit_schema: Optional[List[UpdateCustomerCreditSchema]] = None
|
||||
r"""For credit_system features: maps metered features to their credit costs."""
|
||||
|
||||
model_markups: OptionalNullable[Dict[str, UpdateCustomerModelMarkups]] = UNSET
|
||||
r"""Per-model markup overrides for AI credit systems."""
|
||||
|
||||
default_markup: Optional[float] = None
|
||||
r"""Default percentage markup for AI credit systems. Use -100 to make usage free."""
|
||||
|
||||
provider_markups: OptionalNullable[Dict[str, UpdateCustomerProviderMarkups]] = UNSET
|
||||
r"""Per-provider default markup percentages for AI credit systems."""
|
||||
|
||||
display: Optional[UpdateCustomerDisplay] = None
|
||||
r"""Display names for the feature in billing UI and customer-facing components."""
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
optional_fields = set(["event_names", "credit_schema", "display"])
|
||||
optional_fields = set(
|
||||
[
|
||||
"event_names",
|
||||
"credit_schema",
|
||||
"model_markups",
|
||||
"default_markup",
|
||||
"provider_markups",
|
||||
"display",
|
||||
]
|
||||
)
|
||||
nullable_fields = set(["model_markups", "provider_markups"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
is_nullable_and_explicitly_set = (
|
||||
k in nullable_fields
|
||||
and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member
|
||||
)
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if val is not None or k not in optional_fields:
|
||||
if (
|
||||
val is not None
|
||||
or k not in optional_fields
|
||||
or is_nullable_and_explicitly_set
|
||||
):
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
|
||||
@@ -458,10 +458,11 @@ UpdateEntityType = Union[
|
||||
"boolean",
|
||||
"metered",
|
||||
"credit_system",
|
||||
"ai_credit_system",
|
||||
],
|
||||
UnrecognizedStr,
|
||||
]
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools."""
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing."""
|
||||
|
||||
|
||||
class UpdateEntityCreditSchemaTypedDict(TypedDict):
|
||||
@@ -479,6 +480,44 @@ class UpdateEntityCreditSchema(BaseModel):
|
||||
r"""Credits consumed per unit of the metered feature."""
|
||||
|
||||
|
||||
class UpdateEntityModelMarkupsTypedDict(TypedDict):
|
||||
markup: NotRequired[float]
|
||||
input_cost: NotRequired[float]
|
||||
output_cost: NotRequired[float]
|
||||
|
||||
|
||||
class UpdateEntityModelMarkups(BaseModel):
|
||||
markup: Optional[float] = None
|
||||
|
||||
input_cost: Optional[float] = None
|
||||
|
||||
output_cost: Optional[float] = None
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
optional_fields = set(["markup", "input_cost", "output_cost"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if val is not None or k not in optional_fields:
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
|
||||
|
||||
class UpdateEntityProviderMarkupsTypedDict(TypedDict):
|
||||
markup: float
|
||||
|
||||
|
||||
class UpdateEntityProviderMarkups(BaseModel):
|
||||
markup: float
|
||||
|
||||
|
||||
class UpdateEntityDisplayTypedDict(TypedDict):
|
||||
r"""Display names for the feature in billing UI and customer-facing components."""
|
||||
|
||||
@@ -531,7 +570,7 @@ class UpdateEntityFeatureTypedDict(TypedDict):
|
||||
name: str
|
||||
r"""Human-readable name displayed in the dashboard and billing UI."""
|
||||
type: UpdateEntityType
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools."""
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing."""
|
||||
consumable: bool
|
||||
r"""For metered features: true if usage resets periodically (API calls, credits), false if allocated persistently (seats, storage)."""
|
||||
archived: bool
|
||||
@@ -540,6 +579,14 @@ class UpdateEntityFeatureTypedDict(TypedDict):
|
||||
r"""Event names that trigger this feature's balance. Allows multiple features to respond to a single event."""
|
||||
credit_schema: NotRequired[List[UpdateEntityCreditSchemaTypedDict]]
|
||||
r"""For credit_system features: maps metered features to their credit costs."""
|
||||
model_markups: NotRequired[Nullable[Dict[str, UpdateEntityModelMarkupsTypedDict]]]
|
||||
r"""Per-model markup overrides for AI credit systems."""
|
||||
default_markup: NotRequired[float]
|
||||
r"""Default percentage markup for AI credit systems. Use -100 to make usage free."""
|
||||
provider_markups: NotRequired[
|
||||
Nullable[Dict[str, UpdateEntityProviderMarkupsTypedDict]]
|
||||
]
|
||||
r"""Per-provider default markup percentages for AI credit systems."""
|
||||
display: NotRequired[UpdateEntityDisplayTypedDict]
|
||||
r"""Display names for the feature in billing UI and customer-facing components."""
|
||||
|
||||
@@ -554,7 +601,7 @@ class UpdateEntityFeature(BaseModel):
|
||||
r"""Human-readable name displayed in the dashboard and billing UI."""
|
||||
|
||||
type: UpdateEntityType
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools."""
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing."""
|
||||
|
||||
consumable: bool
|
||||
r"""For metered features: true if usage resets periodically (API calls, credits), false if allocated persistently (seats, storage)."""
|
||||
@@ -568,21 +615,48 @@ class UpdateEntityFeature(BaseModel):
|
||||
credit_schema: Optional[List[UpdateEntityCreditSchema]] = None
|
||||
r"""For credit_system features: maps metered features to their credit costs."""
|
||||
|
||||
model_markups: OptionalNullable[Dict[str, UpdateEntityModelMarkups]] = UNSET
|
||||
r"""Per-model markup overrides for AI credit systems."""
|
||||
|
||||
default_markup: Optional[float] = None
|
||||
r"""Default percentage markup for AI credit systems. Use -100 to make usage free."""
|
||||
|
||||
provider_markups: OptionalNullable[Dict[str, UpdateEntityProviderMarkups]] = UNSET
|
||||
r"""Per-provider default markup percentages for AI credit systems."""
|
||||
|
||||
display: Optional[UpdateEntityDisplay] = None
|
||||
r"""Display names for the feature in billing UI and customer-facing components."""
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
optional_fields = set(["event_names", "credit_schema", "display"])
|
||||
optional_fields = set(
|
||||
[
|
||||
"event_names",
|
||||
"credit_schema",
|
||||
"model_markups",
|
||||
"default_markup",
|
||||
"provider_markups",
|
||||
"display",
|
||||
]
|
||||
)
|
||||
nullable_fields = set(["model_markups", "provider_markups"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
is_nullable_and_explicitly_set = (
|
||||
k in nullable_fields
|
||||
and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member
|
||||
)
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if val is not None or k not in optional_fields:
|
||||
if (
|
||||
val is not None
|
||||
or k not in optional_fields
|
||||
or is_nullable_and_explicitly_set
|
||||
):
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
|
||||
@@ -12,7 +12,7 @@ from autumn_sdk.types import (
|
||||
from autumn_sdk.utils import FieldMetadata, HeaderMetadata
|
||||
import pydantic
|
||||
from pydantic import model_serializer
|
||||
from typing import List, Literal, Optional, Union
|
||||
from typing import Dict, List, Literal, Optional, Union
|
||||
from typing_extensions import Annotated, NotRequired, TypedDict
|
||||
|
||||
|
||||
@@ -44,22 +44,23 @@ class UpdateFeatureGlobals(BaseModel):
|
||||
return m
|
||||
|
||||
|
||||
UpdateFeatureTypeRequest = Literal[
|
||||
UpdateFeatureTypeRequestBody = Literal[
|
||||
"boolean",
|
||||
"metered",
|
||||
"credit_system",
|
||||
"ai_credit_system",
|
||||
]
|
||||
r"""The type of the feature. 'single_use' features are consumed, like API calls, tokens, or messages. 'continuous_use' features are allocated, like seats, workspaces, or projects. 'credit_system' features are schemas that unify multiple 'single_use' features into a single credit system."""
|
||||
|
||||
|
||||
class UpdateFeatureDisplayRequestTypedDict(TypedDict):
|
||||
class UpdateFeatureDisplayRequestBodyTypedDict(TypedDict):
|
||||
r"""Singular and plural display names for the feature in your user interface."""
|
||||
|
||||
singular: str
|
||||
plural: str
|
||||
|
||||
|
||||
class UpdateFeatureDisplayRequest(BaseModel):
|
||||
class UpdateFeatureDisplayRequestBody(BaseModel):
|
||||
r"""Singular and plural display names for the feature in your user interface."""
|
||||
|
||||
singular: str
|
||||
@@ -67,30 +68,78 @@ class UpdateFeatureDisplayRequest(BaseModel):
|
||||
plural: str
|
||||
|
||||
|
||||
class UpdateFeatureCreditSchemaRequestTypedDict(TypedDict):
|
||||
class UpdateFeatureCreditSchemaRequestBodyTypedDict(TypedDict):
|
||||
metered_feature_id: str
|
||||
credit_cost: float
|
||||
|
||||
|
||||
class UpdateFeatureCreditSchemaRequest(BaseModel):
|
||||
class UpdateFeatureCreditSchemaRequestBody(BaseModel):
|
||||
metered_feature_id: str
|
||||
|
||||
credit_cost: float
|
||||
|
||||
|
||||
class UpdateFeatureModelMarkupsRequestTypedDict(TypedDict):
|
||||
markup: NotRequired[float]
|
||||
input_cost: NotRequired[float]
|
||||
output_cost: NotRequired[float]
|
||||
|
||||
|
||||
class UpdateFeatureModelMarkupsRequest(BaseModel):
|
||||
markup: Optional[float] = None
|
||||
|
||||
input_cost: Optional[float] = None
|
||||
|
||||
output_cost: Optional[float] = None
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
optional_fields = set(["markup", "input_cost", "output_cost"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if val is not None or k not in optional_fields:
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
|
||||
|
||||
class UpdateFeatureProviderMarkupsRequestTypedDict(TypedDict):
|
||||
markup: float
|
||||
|
||||
|
||||
class UpdateFeatureProviderMarkupsRequest(BaseModel):
|
||||
markup: float
|
||||
|
||||
|
||||
class UpdateFeatureParamsTypedDict(TypedDict):
|
||||
feature_id: str
|
||||
r"""The ID of the feature to update."""
|
||||
name: NotRequired[str]
|
||||
r"""The name of the feature."""
|
||||
type: NotRequired[UpdateFeatureTypeRequest]
|
||||
type: NotRequired[UpdateFeatureTypeRequestBody]
|
||||
r"""The type of the feature. 'single_use' features are consumed, like API calls, tokens, or messages. 'continuous_use' features are allocated, like seats, workspaces, or projects. 'credit_system' features are schemas that unify multiple 'single_use' features into a single credit system."""
|
||||
consumable: NotRequired[bool]
|
||||
r"""Whether this feature is consumable. A consumable feature is one that periodically resets and is consumed rather than allocated (like credits, API requests, etc.). Applicable only for 'metered' features."""
|
||||
display: NotRequired[UpdateFeatureDisplayRequestTypedDict]
|
||||
display: NotRequired[UpdateFeatureDisplayRequestBodyTypedDict]
|
||||
r"""Singular and plural display names for the feature in your user interface."""
|
||||
credit_schema: NotRequired[List[UpdateFeatureCreditSchemaRequestTypedDict]]
|
||||
r"""A schema that maps 'single_use' feature IDs to credit costs. Applicable only for 'credit_system' features."""
|
||||
credit_schema: NotRequired[List[UpdateFeatureCreditSchemaRequestBodyTypedDict]]
|
||||
r"""A schema that maps 'single_use' feature IDs to credit costs. For classic credit systems only — AI credit systems use model_markups instead."""
|
||||
model_markups: NotRequired[
|
||||
Nullable[Dict[str, UpdateFeatureModelMarkupsRequestTypedDict]]
|
||||
]
|
||||
r"""Per-model markup overrides for AI credit systems. Maps model IDs to their markup configuration."""
|
||||
default_markup: NotRequired[float]
|
||||
r"""Default percentage markup for this AI credit system. Used when no model or provider markup applies. Use -100 to make usage free."""
|
||||
provider_markups: NotRequired[
|
||||
Nullable[Dict[str, UpdateFeatureProviderMarkupsRequestTypedDict]]
|
||||
]
|
||||
r"""Per-provider default markup percentages for AI credit systems. Provider keys match the first segment of model_id."""
|
||||
event_names: NotRequired[List[str]]
|
||||
archived: NotRequired[bool]
|
||||
r"""Whether the feature is archived. Archived features are hidden from the dashboard."""
|
||||
@@ -105,17 +154,28 @@ class UpdateFeatureParams(BaseModel):
|
||||
name: Optional[str] = None
|
||||
r"""The name of the feature."""
|
||||
|
||||
type: Optional[UpdateFeatureTypeRequest] = None
|
||||
type: Optional[UpdateFeatureTypeRequestBody] = None
|
||||
r"""The type of the feature. 'single_use' features are consumed, like API calls, tokens, or messages. 'continuous_use' features are allocated, like seats, workspaces, or projects. 'credit_system' features are schemas that unify multiple 'single_use' features into a single credit system."""
|
||||
|
||||
consumable: Optional[bool] = None
|
||||
r"""Whether this feature is consumable. A consumable feature is one that periodically resets and is consumed rather than allocated (like credits, API requests, etc.). Applicable only for 'metered' features."""
|
||||
|
||||
display: Optional[UpdateFeatureDisplayRequest] = None
|
||||
display: Optional[UpdateFeatureDisplayRequestBody] = None
|
||||
r"""Singular and plural display names for the feature in your user interface."""
|
||||
|
||||
credit_schema: Optional[List[UpdateFeatureCreditSchemaRequest]] = None
|
||||
r"""A schema that maps 'single_use' feature IDs to credit costs. Applicable only for 'credit_system' features."""
|
||||
credit_schema: Optional[List[UpdateFeatureCreditSchemaRequestBody]] = None
|
||||
r"""A schema that maps 'single_use' feature IDs to credit costs. For classic credit systems only — AI credit systems use model_markups instead."""
|
||||
|
||||
model_markups: OptionalNullable[Dict[str, UpdateFeatureModelMarkupsRequest]] = UNSET
|
||||
r"""Per-model markup overrides for AI credit systems. Maps model IDs to their markup configuration."""
|
||||
|
||||
default_markup: Optional[float] = None
|
||||
r"""Default percentage markup for this AI credit system. Used when no model or provider markup applies. Use -100 to make usage free."""
|
||||
|
||||
provider_markups: OptionalNullable[
|
||||
Dict[str, UpdateFeatureProviderMarkupsRequest]
|
||||
] = UNSET
|
||||
r"""Per-provider default markup percentages for AI credit systems. Provider keys match the first segment of model_id."""
|
||||
|
||||
event_names: Optional[List[str]] = None
|
||||
|
||||
@@ -134,20 +194,32 @@ class UpdateFeatureParams(BaseModel):
|
||||
"consumable",
|
||||
"display",
|
||||
"credit_schema",
|
||||
"model_markups",
|
||||
"default_markup",
|
||||
"provider_markups",
|
||||
"event_names",
|
||||
"archived",
|
||||
"new_feature_id",
|
||||
]
|
||||
)
|
||||
nullable_fields = set(["model_markups", "provider_markups"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
is_nullable_and_explicitly_set = (
|
||||
k in nullable_fields
|
||||
and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member
|
||||
)
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if val is not None or k not in optional_fields:
|
||||
if (
|
||||
val is not None
|
||||
or k not in optional_fields
|
||||
or is_nullable_and_explicitly_set
|
||||
):
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
@@ -158,10 +230,11 @@ UpdateFeatureTypeResponse = Union[
|
||||
"boolean",
|
||||
"metered",
|
||||
"credit_system",
|
||||
"ai_credit_system",
|
||||
],
|
||||
UnrecognizedStr,
|
||||
]
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools."""
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing."""
|
||||
|
||||
|
||||
class UpdateFeatureCreditSchemaResponseTypedDict(TypedDict):
|
||||
@@ -179,6 +252,44 @@ class UpdateFeatureCreditSchemaResponse(BaseModel):
|
||||
r"""Credits consumed per unit of the metered feature."""
|
||||
|
||||
|
||||
class UpdateFeatureModelMarkupsResponseTypedDict(TypedDict):
|
||||
markup: NotRequired[float]
|
||||
input_cost: NotRequired[float]
|
||||
output_cost: NotRequired[float]
|
||||
|
||||
|
||||
class UpdateFeatureModelMarkupsResponse(BaseModel):
|
||||
markup: Optional[float] = None
|
||||
|
||||
input_cost: Optional[float] = None
|
||||
|
||||
output_cost: Optional[float] = None
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
optional_fields = set(["markup", "input_cost", "output_cost"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if val is not None or k not in optional_fields:
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
|
||||
|
||||
class UpdateFeatureProviderMarkupsResponseTypedDict(TypedDict):
|
||||
markup: float
|
||||
|
||||
|
||||
class UpdateFeatureProviderMarkupsResponse(BaseModel):
|
||||
markup: float
|
||||
|
||||
|
||||
class UpdateFeatureDisplayResponseTypedDict(TypedDict):
|
||||
r"""Display names for the feature in billing UI and customer-facing components."""
|
||||
|
||||
@@ -231,7 +342,7 @@ class UpdateFeatureResponseTypedDict(TypedDict):
|
||||
name: str
|
||||
r"""Human-readable name displayed in the dashboard and billing UI."""
|
||||
type: UpdateFeatureTypeResponse
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools."""
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing."""
|
||||
consumable: bool
|
||||
r"""For metered features: true if usage resets periodically (API calls, credits), false if allocated persistently (seats, storage)."""
|
||||
archived: bool
|
||||
@@ -240,6 +351,16 @@ class UpdateFeatureResponseTypedDict(TypedDict):
|
||||
r"""Event names that trigger this feature's balance. Allows multiple features to respond to a single event."""
|
||||
credit_schema: NotRequired[List[UpdateFeatureCreditSchemaResponseTypedDict]]
|
||||
r"""For credit_system features: maps metered features to their credit costs."""
|
||||
model_markups: NotRequired[
|
||||
Nullable[Dict[str, UpdateFeatureModelMarkupsResponseTypedDict]]
|
||||
]
|
||||
r"""Per-model markup overrides for AI credit systems."""
|
||||
default_markup: NotRequired[float]
|
||||
r"""Default percentage markup for AI credit systems. Use -100 to make usage free."""
|
||||
provider_markups: NotRequired[
|
||||
Nullable[Dict[str, UpdateFeatureProviderMarkupsResponseTypedDict]]
|
||||
]
|
||||
r"""Per-provider default markup percentages for AI credit systems."""
|
||||
display: NotRequired[UpdateFeatureDisplayResponseTypedDict]
|
||||
r"""Display names for the feature in billing UI and customer-facing components."""
|
||||
|
||||
@@ -254,7 +375,7 @@ class UpdateFeatureResponse(BaseModel):
|
||||
r"""Human-readable name displayed in the dashboard and billing UI."""
|
||||
|
||||
type: UpdateFeatureTypeResponse
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools."""
|
||||
r"""Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools, 'ai_credit_system' for model-based token pricing."""
|
||||
|
||||
consumable: bool
|
||||
r"""For metered features: true if usage resets periodically (API calls, credits), false if allocated persistently (seats, storage)."""
|
||||
@@ -268,21 +389,52 @@ class UpdateFeatureResponse(BaseModel):
|
||||
credit_schema: Optional[List[UpdateFeatureCreditSchemaResponse]] = None
|
||||
r"""For credit_system features: maps metered features to their credit costs."""
|
||||
|
||||
model_markups: OptionalNullable[Dict[str, UpdateFeatureModelMarkupsResponse]] = (
|
||||
UNSET
|
||||
)
|
||||
r"""Per-model markup overrides for AI credit systems."""
|
||||
|
||||
default_markup: Optional[float] = None
|
||||
r"""Default percentage markup for AI credit systems. Use -100 to make usage free."""
|
||||
|
||||
provider_markups: OptionalNullable[
|
||||
Dict[str, UpdateFeatureProviderMarkupsResponse]
|
||||
] = UNSET
|
||||
r"""Per-provider default markup percentages for AI credit systems."""
|
||||
|
||||
display: Optional[UpdateFeatureDisplayResponse] = None
|
||||
r"""Display names for the feature in billing UI and customer-facing components."""
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
optional_fields = set(["event_names", "credit_schema", "display"])
|
||||
optional_fields = set(
|
||||
[
|
||||
"event_names",
|
||||
"credit_schema",
|
||||
"model_markups",
|
||||
"default_markup",
|
||||
"provider_markups",
|
||||
"display",
|
||||
]
|
||||
)
|
||||
nullable_fields = set(["model_markups", "provider_markups"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
is_nullable_and_explicitly_set = (
|
||||
k in nullable_fields
|
||||
and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member
|
||||
)
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if val is not None or k not in optional_fields:
|
||||
if (
|
||||
val is not None
|
||||
or k not in optional_fields
|
||||
or is_nullable_and_explicitly_set
|
||||
):
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
|
||||
@@ -12,7 +12,7 @@ from autumn_sdk.types import (
|
||||
from autumn_sdk.utils import FieldMetadata, HeaderMetadata
|
||||
import pydantic
|
||||
from pydantic import model_serializer
|
||||
from typing import Any, List, Literal, Optional, Union
|
||||
from typing import List, Literal, Optional, Union
|
||||
from typing_extensions import Annotated, NotRequired, TypeAliasType, TypedDict
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ class UpdatePlanGlobals(BaseModel):
|
||||
return m
|
||||
|
||||
|
||||
UpdatePlanPriceIntervalRequest = Literal[
|
||||
UpdatePlanPriceIntervalRequestBody = Literal[
|
||||
"one_off",
|
||||
"week",
|
||||
"month",
|
||||
@@ -60,7 +60,7 @@ class UpdatePlanBasePriceTypedDict(TypedDict):
|
||||
|
||||
amount: float
|
||||
r"""Base price amount for the plan."""
|
||||
interval: UpdatePlanPriceIntervalRequest
|
||||
interval: UpdatePlanPriceIntervalRequestBody
|
||||
r"""Billing interval (e.g. 'month', 'year')."""
|
||||
interval_count: NotRequired[float]
|
||||
r"""Number of intervals per billing cycle. Defaults to 1."""
|
||||
@@ -72,7 +72,7 @@ class UpdatePlanBasePrice(BaseModel):
|
||||
amount: float
|
||||
r"""Base price amount for the plan."""
|
||||
|
||||
interval: UpdatePlanPriceIntervalRequest
|
||||
interval: UpdatePlanPriceIntervalRequestBody
|
||||
r"""Billing interval (e.g. 'month', 'year')."""
|
||||
|
||||
interval_count: Optional[float] = None
|
||||
@@ -95,7 +95,7 @@ class UpdatePlanBasePrice(BaseModel):
|
||||
return m
|
||||
|
||||
|
||||
UpdatePlanResetIntervalRequest = Literal[
|
||||
UpdatePlanResetIntervalRequestBody = Literal[
|
||||
"one_off",
|
||||
"minute",
|
||||
"hour",
|
||||
@@ -109,19 +109,19 @@ UpdatePlanResetIntervalRequest = Literal[
|
||||
r"""Interval at which balance resets (e.g. 'month', 'year'). For consumable features only."""
|
||||
|
||||
|
||||
class UpdatePlanResetRequestTypedDict(TypedDict):
|
||||
class UpdatePlanResetRequestBodyTypedDict(TypedDict):
|
||||
r"""Reset configuration for consumable features. Omit for non-consumable features like seats."""
|
||||
|
||||
interval: UpdatePlanResetIntervalRequest
|
||||
interval: UpdatePlanResetIntervalRequestBody
|
||||
r"""Interval at which balance resets (e.g. 'month', 'year'). For consumable features only."""
|
||||
interval_count: NotRequired[float]
|
||||
r"""Number of intervals between resets. Defaults to 1."""
|
||||
|
||||
|
||||
class UpdatePlanResetRequest(BaseModel):
|
||||
class UpdatePlanResetRequestBody(BaseModel):
|
||||
r"""Reset configuration for consumable features. Omit for non-consumable features like seats."""
|
||||
|
||||
interval: UpdatePlanResetIntervalRequest
|
||||
interval: UpdatePlanResetIntervalRequestBody
|
||||
r"""Interval at which balance resets (e.g. 'month', 'year'). For consumable features only."""
|
||||
|
||||
interval_count: Optional[float] = None
|
||||
@@ -144,20 +144,22 @@ class UpdatePlanResetRequest(BaseModel):
|
||||
return m
|
||||
|
||||
|
||||
UpdatePlanToTypedDict = TypeAliasType("UpdatePlanToTypedDict", Union[float, str])
|
||||
UpdatePlanToRequestBodyTypedDict = TypeAliasType(
|
||||
"UpdatePlanToRequestBodyTypedDict", Union[float, str]
|
||||
)
|
||||
|
||||
|
||||
UpdatePlanTo = TypeAliasType("UpdatePlanTo", Union[float, str])
|
||||
UpdatePlanToRequestBody = TypeAliasType("UpdatePlanToRequestBody", Union[float, str])
|
||||
|
||||
|
||||
class UpdatePlanTierTypedDict(TypedDict):
|
||||
to: UpdatePlanToTypedDict
|
||||
class UpdatePlanTierRequestBodyTypedDict(TypedDict):
|
||||
to: UpdatePlanToRequestBodyTypedDict
|
||||
amount: NotRequired[float]
|
||||
flat_amount: NotRequired[float]
|
||||
|
||||
|
||||
class UpdatePlanTier(BaseModel):
|
||||
to: UpdatePlanTo
|
||||
class UpdatePlanTierRequestBody(BaseModel):
|
||||
to: UpdatePlanToRequestBody
|
||||
|
||||
amount: Optional[float] = None
|
||||
|
||||
@@ -180,13 +182,13 @@ class UpdatePlanTier(BaseModel):
|
||||
return m
|
||||
|
||||
|
||||
UpdatePlanTierBehaviorRequest = Literal[
|
||||
UpdatePlanTierBehaviorRequestBody = Literal[
|
||||
"graduated",
|
||||
"volume",
|
||||
]
|
||||
|
||||
|
||||
UpdatePlanItemPriceIntervalRequest = Literal[
|
||||
UpdatePlanItemPriceIntervalRequestBody = Literal[
|
||||
"one_off",
|
||||
"week",
|
||||
"month",
|
||||
@@ -197,49 +199,49 @@ UpdatePlanItemPriceIntervalRequest = Literal[
|
||||
r"""Billing interval. For consumable features, should match reset.interval."""
|
||||
|
||||
|
||||
UpdatePlanBillingMethodRequest = Literal[
|
||||
UpdatePlanBillingMethodRequestBody = Literal[
|
||||
"prepaid",
|
||||
"usage_based",
|
||||
]
|
||||
r"""'prepaid' for upfront payment (seats), 'usage_based' for pay-as-you-go."""
|
||||
|
||||
|
||||
class UpdatePlanPriceRequestTypedDict(TypedDict):
|
||||
class UpdatePlanPriceRequestBodyTypedDict(TypedDict):
|
||||
r"""Pricing for usage beyond included units. Omit for free features."""
|
||||
|
||||
interval: UpdatePlanItemPriceIntervalRequest
|
||||
interval: UpdatePlanItemPriceIntervalRequestBody
|
||||
r"""Billing interval. For consumable features, should match reset.interval."""
|
||||
billing_method: UpdatePlanBillingMethodRequest
|
||||
billing_method: UpdatePlanBillingMethodRequestBody
|
||||
r"""'prepaid' for upfront payment (seats), 'usage_based' for pay-as-you-go."""
|
||||
amount: NotRequired[float]
|
||||
r"""Price per billing_units after included usage. Either 'amount' or 'tiers' is required."""
|
||||
tiers: NotRequired[List[UpdatePlanTierTypedDict]]
|
||||
tiers: NotRequired[List[UpdatePlanTierRequestBodyTypedDict]]
|
||||
r"""Tiered pricing. Either 'amount' or 'tiers' is required."""
|
||||
tier_behavior: NotRequired[UpdatePlanTierBehaviorRequest]
|
||||
tier_behavior: NotRequired[UpdatePlanTierBehaviorRequestBody]
|
||||
interval_count: NotRequired[float]
|
||||
r"""Number of intervals per billing cycle. Defaults to 1."""
|
||||
billing_units: NotRequired[float]
|
||||
r"""Units per price increment. Usage is rounded UP when billed (e.g. billing_units=100 means 101 rounds to 200)."""
|
||||
max_purchase: NotRequired[float]
|
||||
r"""Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total."""
|
||||
max_purchase: NotRequired[Nullable[float]]
|
||||
r"""Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total. Null for no limit."""
|
||||
|
||||
|
||||
class UpdatePlanPriceRequest(BaseModel):
|
||||
class UpdatePlanPriceRequestBody(BaseModel):
|
||||
r"""Pricing for usage beyond included units. Omit for free features."""
|
||||
|
||||
interval: UpdatePlanItemPriceIntervalRequest
|
||||
interval: UpdatePlanItemPriceIntervalRequestBody
|
||||
r"""Billing interval. For consumable features, should match reset.interval."""
|
||||
|
||||
billing_method: UpdatePlanBillingMethodRequest
|
||||
billing_method: UpdatePlanBillingMethodRequestBody
|
||||
r"""'prepaid' for upfront payment (seats), 'usage_based' for pay-as-you-go."""
|
||||
|
||||
amount: Optional[float] = None
|
||||
r"""Price per billing_units after included usage. Either 'amount' or 'tiers' is required."""
|
||||
|
||||
tiers: Optional[List[UpdatePlanTier]] = None
|
||||
tiers: Optional[List[UpdatePlanTierRequestBody]] = None
|
||||
r"""Tiered pricing. Either 'amount' or 'tiers' is required."""
|
||||
|
||||
tier_behavior: Optional[UpdatePlanTierBehaviorRequest] = None
|
||||
tier_behavior: Optional[UpdatePlanTierBehaviorRequestBody] = None
|
||||
|
||||
interval_count: Optional[float] = 1
|
||||
r"""Number of intervals per billing cycle. Defaults to 1."""
|
||||
@@ -247,8 +249,8 @@ class UpdatePlanPriceRequest(BaseModel):
|
||||
billing_units: Optional[float] = 1
|
||||
r"""Units per price increment. Usage is rounded UP when billed (e.g. billing_units=100 means 101 rounds to 200)."""
|
||||
|
||||
max_purchase: Optional[float] = None
|
||||
r"""Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total."""
|
||||
max_purchase: OptionalNullable[float] = UNSET
|
||||
r"""Max units purchasable beyond included. E.g. included=100, max_purchase=300 allows 400 total. Null for no limit."""
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
@@ -262,15 +264,24 @@ class UpdatePlanPriceRequest(BaseModel):
|
||||
"max_purchase",
|
||||
]
|
||||
)
|
||||
nullable_fields = set(["max_purchase"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
is_nullable_and_explicitly_set = (
|
||||
k in nullable_fields
|
||||
and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member
|
||||
)
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if val is not None or k not in optional_fields:
|
||||
if (
|
||||
val is not None
|
||||
or k not in optional_fields
|
||||
or is_nullable_and_explicitly_set
|
||||
):
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
@@ -314,17 +325,17 @@ class UpdatePlanProration(BaseModel):
|
||||
r"""Credit behavior when quantity decreases mid-cycle."""
|
||||
|
||||
|
||||
UpdatePlanExpiryDurationTypeRequest = Literal[
|
||||
UpdatePlanExpiryDurationTypeRequestBody = Literal[
|
||||
"month",
|
||||
"forever",
|
||||
]
|
||||
r"""When rolled over units expire."""
|
||||
|
||||
|
||||
class UpdatePlanRolloverRequestTypedDict(TypedDict):
|
||||
class UpdatePlanRolloverRequestBodyTypedDict(TypedDict):
|
||||
r"""Rollover config for unused units. If set, unused included units carry over."""
|
||||
|
||||
expiry_duration_type: UpdatePlanExpiryDurationTypeRequest
|
||||
expiry_duration_type: UpdatePlanExpiryDurationTypeRequestBody
|
||||
r"""When rolled over units expire."""
|
||||
max: NotRequired[float]
|
||||
r"""Max rollover units. Omit for unlimited rollover."""
|
||||
@@ -334,10 +345,10 @@ class UpdatePlanRolloverRequestTypedDict(TypedDict):
|
||||
r"""Number of periods before expiry."""
|
||||
|
||||
|
||||
class UpdatePlanRolloverRequest(BaseModel):
|
||||
class UpdatePlanRolloverRequestBody(BaseModel):
|
||||
r"""Rollover config for unused units. If set, unused included units carry over."""
|
||||
|
||||
expiry_duration_type: UpdatePlanExpiryDurationTypeRequest
|
||||
expiry_duration_type: UpdatePlanExpiryDurationTypeRequestBody
|
||||
r"""When rolled over units expire."""
|
||||
|
||||
max: Optional[float] = None
|
||||
@@ -375,13 +386,13 @@ class UpdatePlanPlanItemTypedDict(TypedDict):
|
||||
r"""Number of free units included. Balance resets to this each interval for consumable features."""
|
||||
unlimited: NotRequired[bool]
|
||||
r"""If true, customer has unlimited access to this feature."""
|
||||
reset: NotRequired[UpdatePlanResetRequestTypedDict]
|
||||
reset: NotRequired[UpdatePlanResetRequestBodyTypedDict]
|
||||
r"""Reset configuration for consumable features. Omit for non-consumable features like seats."""
|
||||
price: NotRequired[UpdatePlanPriceRequestTypedDict]
|
||||
price: NotRequired[UpdatePlanPriceRequestBodyTypedDict]
|
||||
r"""Pricing for usage beyond included units. Omit for free features."""
|
||||
proration: NotRequired[UpdatePlanProrationTypedDict]
|
||||
r"""Proration settings for prepaid features. Controls mid-cycle quantity change billing."""
|
||||
rollover: NotRequired[UpdatePlanRolloverRequestTypedDict]
|
||||
rollover: NotRequired[UpdatePlanRolloverRequestBodyTypedDict]
|
||||
r"""Rollover config for unused units. If set, unused included units carry over."""
|
||||
|
||||
|
||||
@@ -397,16 +408,16 @@ class UpdatePlanPlanItem(BaseModel):
|
||||
unlimited: Optional[bool] = None
|
||||
r"""If true, customer has unlimited access to this feature."""
|
||||
|
||||
reset: Optional[UpdatePlanResetRequest] = None
|
||||
reset: Optional[UpdatePlanResetRequestBody] = None
|
||||
r"""Reset configuration for consumable features. Omit for non-consumable features like seats."""
|
||||
|
||||
price: Optional[UpdatePlanPriceRequest] = None
|
||||
price: Optional[UpdatePlanPriceRequestBody] = None
|
||||
r"""Pricing for usage beyond included units. Omit for free features."""
|
||||
|
||||
proration: Optional[UpdatePlanProration] = None
|
||||
r"""Proration settings for prepaid features. Controls mid-cycle quantity change billing."""
|
||||
|
||||
rollover: Optional[UpdatePlanRolloverRequest] = None
|
||||
rollover: Optional[UpdatePlanRolloverRequestBody] = None
|
||||
r"""Rollover config for unused units. If set, unused included units carry over."""
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
@@ -543,6 +554,7 @@ class UpdatePlanParamsTypedDict(TypedDict):
|
||||
archived: NotRequired[bool]
|
||||
new_plan_id: NotRequired[str]
|
||||
r"""The new ID to use for the plan. Can only be updated if the plan has not been used by any customers."""
|
||||
disable_version: NotRequired[bool]
|
||||
|
||||
|
||||
class UpdatePlanParams(BaseModel):
|
||||
@@ -584,6 +596,8 @@ class UpdatePlanParams(BaseModel):
|
||||
new_plan_id: Optional[str] = None
|
||||
r"""The new ID to use for the plan. Can only be updated if the plan has not been used by any customers."""
|
||||
|
||||
disable_version: Optional[bool] = None
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
optional_fields = set(
|
||||
@@ -601,6 +615,7 @@ class UpdatePlanParams(BaseModel):
|
||||
"version",
|
||||
"archived",
|
||||
"new_plan_id",
|
||||
"disable_version",
|
||||
]
|
||||
)
|
||||
nullable_fields = set(["price", "free_trial"])
|
||||
@@ -723,6 +738,7 @@ UpdatePlanType = Union[
|
||||
"single_use",
|
||||
"continuous_use",
|
||||
"credit_system",
|
||||
"ai_credit_system",
|
||||
],
|
||||
UnrecognizedStr,
|
||||
]
|
||||
@@ -871,6 +887,44 @@ class UpdatePlanResetResponse(BaseModel):
|
||||
return m
|
||||
|
||||
|
||||
UpdatePlanToResponseTypedDict = TypeAliasType(
|
||||
"UpdatePlanToResponseTypedDict", Union[float, str]
|
||||
)
|
||||
|
||||
|
||||
UpdatePlanToResponse = TypeAliasType("UpdatePlanToResponse", Union[float, str])
|
||||
|
||||
|
||||
class UpdatePlanTierResponseTypedDict(TypedDict):
|
||||
to: UpdatePlanToResponseTypedDict
|
||||
amount: float
|
||||
flat_amount: NotRequired[float]
|
||||
|
||||
|
||||
class UpdatePlanTierResponse(BaseModel):
|
||||
to: UpdatePlanToResponse
|
||||
|
||||
amount: float
|
||||
|
||||
flat_amount: Optional[float] = None
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize_model(self, handler):
|
||||
optional_fields = set(["flat_amount"])
|
||||
serialized = handler(self)
|
||||
m = {}
|
||||
|
||||
for n, f in type(self).model_fields.items():
|
||||
k = f.alias or n
|
||||
val = serialized.get(k, serialized.get(n))
|
||||
|
||||
if val != UNSET_SENTINEL:
|
||||
if val is not None or k not in optional_fields:
|
||||
m[k] = val
|
||||
|
||||
return m
|
||||
|
||||
|
||||
UpdatePlanTierBehaviorResponse = Union[
|
||||
Literal[
|
||||
"graduated",
|
||||
@@ -915,7 +969,7 @@ class UpdatePlanItemPriceResponseTypedDict(TypedDict):
|
||||
r"""Maximum units a customer can purchase beyond included. E.g. if included=100 and max_purchase=300, customer can use up to 400 total before usage is capped. Null for no limit."""
|
||||
amount: NotRequired[float]
|
||||
r"""Price per billing_units after included usage is consumed. Mutually exclusive with tiers."""
|
||||
tiers: NotRequired[List[Nullable[Any]]]
|
||||
tiers: NotRequired[List[UpdatePlanTierResponseTypedDict]]
|
||||
r"""Tiered pricing configuration. Each tier's 'to' INCLUDES the included amount. Either 'tiers' or 'amount' is required."""
|
||||
tier_behavior: NotRequired[UpdatePlanTierBehaviorResponse]
|
||||
interval_count: NotRequired[float]
|
||||
@@ -938,7 +992,7 @@ class UpdatePlanItemPriceResponse(BaseModel):
|
||||
amount: Optional[float] = None
|
||||
r"""Price per billing_units after included usage is consumed. Mutually exclusive with tiers."""
|
||||
|
||||
tiers: Optional[List[Nullable[Any]]] = None
|
||||
tiers: Optional[List[UpdatePlanTierResponse]] = None
|
||||
r"""Tiered pricing configuration. Each tier's 'to' INCLUDES the included amount. Either 'tiers' or 'amount' is required."""
|
||||
|
||||
tier_behavior: Optional[UpdatePlanTierBehaviorResponse] = None
|
||||
|
||||
@@ -19,7 +19,10 @@ class Plans(BaseSDK):
|
||||
add_on: Optional[bool] = False,
|
||||
auto_enable: Optional[bool] = False,
|
||||
price: Optional[
|
||||
Union[models.CreatePlanPriceRequest, models.CreatePlanPriceRequestTypedDict]
|
||||
Union[
|
||||
models.CreatePlanPriceRequestBody,
|
||||
models.CreatePlanPriceRequestBodyTypedDict,
|
||||
]
|
||||
] = None,
|
||||
items: Optional[
|
||||
Union[
|
||||
@@ -81,7 +84,7 @@ class Plans(BaseSDK):
|
||||
add_on=add_on,
|
||||
auto_enable=auto_enable,
|
||||
price=utils.get_pydantic_model(
|
||||
price, Optional[models.CreatePlanPriceRequest]
|
||||
price, Optional[models.CreatePlanPriceRequestBody]
|
||||
),
|
||||
items=utils.get_pydantic_model(
|
||||
items, Optional[List[models.CreatePlanPlanItem]]
|
||||
@@ -164,7 +167,10 @@ class Plans(BaseSDK):
|
||||
add_on: Optional[bool] = False,
|
||||
auto_enable: Optional[bool] = False,
|
||||
price: Optional[
|
||||
Union[models.CreatePlanPriceRequest, models.CreatePlanPriceRequestTypedDict]
|
||||
Union[
|
||||
models.CreatePlanPriceRequestBody,
|
||||
models.CreatePlanPriceRequestBodyTypedDict,
|
||||
]
|
||||
] = None,
|
||||
items: Optional[
|
||||
Union[
|
||||
@@ -226,7 +232,7 @@ class Plans(BaseSDK):
|
||||
add_on=add_on,
|
||||
auto_enable=auto_enable,
|
||||
price=utils.get_pydantic_model(
|
||||
price, Optional[models.CreatePlanPriceRequest]
|
||||
price, Optional[models.CreatePlanPriceRequestBody]
|
||||
),
|
||||
items=utils.get_pydantic_model(
|
||||
items, Optional[List[models.CreatePlanPlanItem]]
|
||||
@@ -718,6 +724,7 @@ class Plans(BaseSDK):
|
||||
version: Optional[float] = None,
|
||||
archived: Optional[bool] = False,
|
||||
new_plan_id: Optional[str] = None,
|
||||
disable_version: Optional[bool] = None,
|
||||
retries: OptionalNullable[utils.RetryConfig] = UNSET,
|
||||
server_url: Optional[str] = None,
|
||||
timeout_ms: Optional[int] = None,
|
||||
@@ -743,6 +750,7 @@ class Plans(BaseSDK):
|
||||
:param version:
|
||||
:param archived:
|
||||
:param new_plan_id: The new ID to use for the plan. Can only be updated if the plan has not been used by any customers.
|
||||
:param disable_version:
|
||||
:param retries: Override the default retry configuration for this method
|
||||
:param server_url: Override the default server URL for this method
|
||||
:param timeout_ms: Override the default request timeout configuration for this method in milliseconds
|
||||
@@ -781,6 +789,7 @@ class Plans(BaseSDK):
|
||||
version=version,
|
||||
archived=archived,
|
||||
new_plan_id=new_plan_id,
|
||||
disable_version=disable_version,
|
||||
)
|
||||
|
||||
req = self._build_request(
|
||||
@@ -875,6 +884,7 @@ class Plans(BaseSDK):
|
||||
version: Optional[float] = None,
|
||||
archived: Optional[bool] = False,
|
||||
new_plan_id: Optional[str] = None,
|
||||
disable_version: Optional[bool] = None,
|
||||
retries: OptionalNullable[utils.RetryConfig] = UNSET,
|
||||
server_url: Optional[str] = None,
|
||||
timeout_ms: Optional[int] = None,
|
||||
@@ -900,6 +910,7 @@ class Plans(BaseSDK):
|
||||
:param version:
|
||||
:param archived:
|
||||
:param new_plan_id: The new ID to use for the plan. Can only be updated if the plan has not been used by any customers.
|
||||
:param disable_version:
|
||||
:param retries: Override the default retry configuration for this method
|
||||
:param server_url: Override the default server URL for this method
|
||||
:param timeout_ms: Override the default request timeout configuration for this method in milliseconds
|
||||
@@ -938,6 +949,7 @@ class Plans(BaseSDK):
|
||||
version=version,
|
||||
archived=archived,
|
||||
new_plan_id=new_plan_id,
|
||||
disable_version=disable_version,
|
||||
)
|
||||
|
||||
req = self._build_request_async(
|
||||
|
||||
@@ -684,6 +684,260 @@ class Autumn(BaseSDK):
|
||||
|
||||
raise errors.AutumnDefaultError("Unexpected response received", http_res)
|
||||
|
||||
def track_tokens(
|
||||
self,
|
||||
*,
|
||||
customer_id: str,
|
||||
model_id: str,
|
||||
input_tokens: int,
|
||||
output_tokens: int,
|
||||
entity_id: Optional[str] = None,
|
||||
feature_id: Optional[str] = None,
|
||||
cache_read_tokens: Optional[int] = None,
|
||||
cache_write_tokens: Optional[int] = None,
|
||||
audio_input_tokens: Optional[int] = None,
|
||||
audio_output_tokens: Optional[int] = None,
|
||||
reasoning_tokens: Optional[int] = None,
|
||||
properties: Optional[Dict[str, Any]] = None,
|
||||
retries: OptionalNullable[utils.RetryConfig] = UNSET,
|
||||
server_url: Optional[str] = None,
|
||||
timeout_ms: Optional[int] = None,
|
||||
http_headers: Optional[Mapping[str, str]] = None,
|
||||
) -> models.TrackTokensResponse:
|
||||
r"""Records AI token usage for a customer and returns the updated AI credit balance.
|
||||
|
||||
Use this after an LLM request when you have input and output token counts. Autumn converts token usage to a dollar amount using the configured model pricing and markup, then tracks that value against the customer's AI credit system.
|
||||
|
||||
:param customer_id: The ID of the customer.
|
||||
:param model_id: The AI model as '<provider>/<model>' (e.g. 'anthropic/claude-opus-4-8', 'openrouter/openai/gpt-4o'). The provider is the first path segment and must match a provider + model key in models.dev.
|
||||
:param input_tokens: Number of non-cached text input tokens consumed. Exclusive of cache and audio token pools.
|
||||
:param output_tokens: Number of text output tokens consumed. Exclusive of the reasoning and audio output pools.
|
||||
:param entity_id: The ID of the entity for entity-scoped balances.
|
||||
:param feature_id: The ID of the AI credit system feature. Auto-detected from the customer's entitlements if omitted — only required when a customer has multiple AI credit systems.
|
||||
:param cache_read_tokens: Number of cached input tokens read.
|
||||
:param cache_write_tokens: Number of input tokens written to the cache.
|
||||
:param audio_input_tokens: Number of audio input tokens consumed.
|
||||
:param audio_output_tokens: Number of audio output tokens generated.
|
||||
:param reasoning_tokens: Number of reasoning tokens generated.
|
||||
:param properties: Additional properties to attach to this usage event.
|
||||
:param retries: Override the default retry configuration for this method
|
||||
:param server_url: Override the default server URL for this method
|
||||
:param timeout_ms: Override the default request timeout configuration for this method in milliseconds
|
||||
:param http_headers: Additional headers to set or replace on requests.
|
||||
"""
|
||||
base_url = None
|
||||
url_variables = None
|
||||
if timeout_ms is None:
|
||||
timeout_ms = self.sdk_configuration.timeout_ms
|
||||
|
||||
if server_url is not None:
|
||||
base_url = server_url
|
||||
else:
|
||||
base_url = self._get_url(base_url, url_variables)
|
||||
|
||||
request = models.TrackTokensParams(
|
||||
customer_id=customer_id,
|
||||
entity_id=entity_id,
|
||||
feature_id=feature_id,
|
||||
model_id=model_id,
|
||||
input_tokens=input_tokens,
|
||||
output_tokens=output_tokens,
|
||||
cache_read_tokens=cache_read_tokens,
|
||||
cache_write_tokens=cache_write_tokens,
|
||||
audio_input_tokens=audio_input_tokens,
|
||||
audio_output_tokens=audio_output_tokens,
|
||||
reasoning_tokens=reasoning_tokens,
|
||||
properties=properties,
|
||||
)
|
||||
|
||||
req = self._build_request(
|
||||
method="POST",
|
||||
path="/v1/balances.track_tokens",
|
||||
base_url=base_url,
|
||||
url_variables=url_variables,
|
||||
request=request,
|
||||
request_body_required=True,
|
||||
request_has_path_params=False,
|
||||
request_has_query_params=True,
|
||||
user_agent_header="user-agent",
|
||||
accept_header_value="application/json",
|
||||
http_headers=http_headers,
|
||||
_globals=models.TrackTokensGlobals(
|
||||
x_api_version=self.sdk_configuration.globals.x_api_version,
|
||||
),
|
||||
security=self.sdk_configuration.security,
|
||||
get_serialized_body=lambda: utils.serialize_request_body(
|
||||
request, False, False, "json", models.TrackTokensParams
|
||||
),
|
||||
allow_empty_value=None,
|
||||
timeout_ms=timeout_ms,
|
||||
)
|
||||
|
||||
if retries == UNSET:
|
||||
if self.sdk_configuration.retry_config is not UNSET:
|
||||
retries = self.sdk_configuration.retry_config
|
||||
|
||||
retry_config = None
|
||||
if isinstance(retries, utils.RetryConfig):
|
||||
retry_config = (retries, ["429", "500", "502", "503", "504"])
|
||||
|
||||
http_res = self.do_request(
|
||||
hook_ctx=HookContext(
|
||||
config=self.sdk_configuration,
|
||||
base_url=base_url or "",
|
||||
operation_id="trackTokens",
|
||||
oauth2_scopes=None,
|
||||
security_source=self.sdk_configuration.security,
|
||||
),
|
||||
request=req,
|
||||
is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c),
|
||||
retry_config=retry_config,
|
||||
)
|
||||
|
||||
if utils.match_response(http_res, "200", "application/json"):
|
||||
return unmarshal_json_response(models.TrackTokensResponseBody1, http_res)
|
||||
if utils.match_response(http_res, "202", "application/json"):
|
||||
return unmarshal_json_response(models.TrackTokensResponseBody2, http_res)
|
||||
if utils.match_response(http_res, "4XX", "*"):
|
||||
http_res_text = utils.stream_to_text(http_res)
|
||||
raise errors.AutumnDefaultError(
|
||||
"API error occurred", http_res, http_res_text
|
||||
)
|
||||
if utils.match_response(http_res, "5XX", "*"):
|
||||
http_res_text = utils.stream_to_text(http_res)
|
||||
raise errors.AutumnDefaultError(
|
||||
"API error occurred", http_res, http_res_text
|
||||
)
|
||||
|
||||
raise errors.AutumnDefaultError("Unexpected response received", http_res)
|
||||
|
||||
async def track_tokens_async(
|
||||
self,
|
||||
*,
|
||||
customer_id: str,
|
||||
model_id: str,
|
||||
input_tokens: int,
|
||||
output_tokens: int,
|
||||
entity_id: Optional[str] = None,
|
||||
feature_id: Optional[str] = None,
|
||||
cache_read_tokens: Optional[int] = None,
|
||||
cache_write_tokens: Optional[int] = None,
|
||||
audio_input_tokens: Optional[int] = None,
|
||||
audio_output_tokens: Optional[int] = None,
|
||||
reasoning_tokens: Optional[int] = None,
|
||||
properties: Optional[Dict[str, Any]] = None,
|
||||
retries: OptionalNullable[utils.RetryConfig] = UNSET,
|
||||
server_url: Optional[str] = None,
|
||||
timeout_ms: Optional[int] = None,
|
||||
http_headers: Optional[Mapping[str, str]] = None,
|
||||
) -> models.TrackTokensResponse:
|
||||
r"""Records AI token usage for a customer and returns the updated AI credit balance.
|
||||
|
||||
Use this after an LLM request when you have input and output token counts. Autumn converts token usage to a dollar amount using the configured model pricing and markup, then tracks that value against the customer's AI credit system.
|
||||
|
||||
:param customer_id: The ID of the customer.
|
||||
:param model_id: The AI model as '<provider>/<model>' (e.g. 'anthropic/claude-opus-4-8', 'openrouter/openai/gpt-4o'). The provider is the first path segment and must match a provider + model key in models.dev.
|
||||
:param input_tokens: Number of non-cached text input tokens consumed. Exclusive of cache and audio token pools.
|
||||
:param output_tokens: Number of text output tokens consumed. Exclusive of the reasoning and audio output pools.
|
||||
:param entity_id: The ID of the entity for entity-scoped balances.
|
||||
:param feature_id: The ID of the AI credit system feature. Auto-detected from the customer's entitlements if omitted — only required when a customer has multiple AI credit systems.
|
||||
:param cache_read_tokens: Number of cached input tokens read.
|
||||
:param cache_write_tokens: Number of input tokens written to the cache.
|
||||
:param audio_input_tokens: Number of audio input tokens consumed.
|
||||
:param audio_output_tokens: Number of audio output tokens generated.
|
||||
:param reasoning_tokens: Number of reasoning tokens generated.
|
||||
:param properties: Additional properties to attach to this usage event.
|
||||
:param retries: Override the default retry configuration for this method
|
||||
:param server_url: Override the default server URL for this method
|
||||
:param timeout_ms: Override the default request timeout configuration for this method in milliseconds
|
||||
:param http_headers: Additional headers to set or replace on requests.
|
||||
"""
|
||||
base_url = None
|
||||
url_variables = None
|
||||
if timeout_ms is None:
|
||||
timeout_ms = self.sdk_configuration.timeout_ms
|
||||
|
||||
if server_url is not None:
|
||||
base_url = server_url
|
||||
else:
|
||||
base_url = self._get_url(base_url, url_variables)
|
||||
|
||||
request = models.TrackTokensParams(
|
||||
customer_id=customer_id,
|
||||
entity_id=entity_id,
|
||||
feature_id=feature_id,
|
||||
model_id=model_id,
|
||||
input_tokens=input_tokens,
|
||||
output_tokens=output_tokens,
|
||||
cache_read_tokens=cache_read_tokens,
|
||||
cache_write_tokens=cache_write_tokens,
|
||||
audio_input_tokens=audio_input_tokens,
|
||||
audio_output_tokens=audio_output_tokens,
|
||||
reasoning_tokens=reasoning_tokens,
|
||||
properties=properties,
|
||||
)
|
||||
|
||||
req = self._build_request_async(
|
||||
method="POST",
|
||||
path="/v1/balances.track_tokens",
|
||||
base_url=base_url,
|
||||
url_variables=url_variables,
|
||||
request=request,
|
||||
request_body_required=True,
|
||||
request_has_path_params=False,
|
||||
request_has_query_params=True,
|
||||
user_agent_header="user-agent",
|
||||
accept_header_value="application/json",
|
||||
http_headers=http_headers,
|
||||
_globals=models.TrackTokensGlobals(
|
||||
x_api_version=self.sdk_configuration.globals.x_api_version,
|
||||
),
|
||||
security=self.sdk_configuration.security,
|
||||
get_serialized_body=lambda: utils.serialize_request_body(
|
||||
request, False, False, "json", models.TrackTokensParams
|
||||
),
|
||||
allow_empty_value=None,
|
||||
timeout_ms=timeout_ms,
|
||||
)
|
||||
|
||||
if retries == UNSET:
|
||||
if self.sdk_configuration.retry_config is not UNSET:
|
||||
retries = self.sdk_configuration.retry_config
|
||||
|
||||
retry_config = None
|
||||
if isinstance(retries, utils.RetryConfig):
|
||||
retry_config = (retries, ["429", "500", "502", "503", "504"])
|
||||
|
||||
http_res = await self.do_request_async(
|
||||
hook_ctx=HookContext(
|
||||
config=self.sdk_configuration,
|
||||
base_url=base_url or "",
|
||||
operation_id="trackTokens",
|
||||
oauth2_scopes=None,
|
||||
security_source=self.sdk_configuration.security,
|
||||
),
|
||||
request=req,
|
||||
is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c),
|
||||
retry_config=retry_config,
|
||||
)
|
||||
|
||||
if utils.match_response(http_res, "200", "application/json"):
|
||||
return unmarshal_json_response(models.TrackTokensResponseBody1, http_res)
|
||||
if utils.match_response(http_res, "202", "application/json"):
|
||||
return unmarshal_json_response(models.TrackTokensResponseBody2, http_res)
|
||||
if utils.match_response(http_res, "4XX", "*"):
|
||||
http_res_text = await utils.stream_to_text_async(http_res)
|
||||
raise errors.AutumnDefaultError(
|
||||
"API error occurred", http_res, http_res_text
|
||||
)
|
||||
if utils.match_response(http_res, "5XX", "*"):
|
||||
http_res_text = await utils.stream_to_text_async(http_res)
|
||||
raise errors.AutumnDefaultError(
|
||||
"API error occurred", http_res, http_res_text
|
||||
)
|
||||
|
||||
raise errors.AutumnDefaultError("Unexpected response received", http_res)
|
||||
|
||||
def batch_track(
|
||||
self,
|
||||
*,
|
||||
|
||||
@@ -22,7 +22,8 @@
|
||||
"packages/autumn-js",
|
||||
"packages/openapi",
|
||||
"packages/ksuid",
|
||||
"packages/stripe-sync"
|
||||
"packages/stripe-sync",
|
||||
"packages/ai-sdk"
|
||||
],
|
||||
"catalog": {
|
||||
"stripe": "19.3.0-beta.1",
|
||||
@@ -50,9 +51,7 @@
|
||||
}
|
||||
},
|
||||
"overrides": {
|
||||
"@better-auth/core": "1.6.5",
|
||||
"@better-auth/passkey": "1.6.5",
|
||||
"better-auth": "1.6.5",
|
||||
"@modelcontextprotocol/sdk": "1.29.0",
|
||||
"@isaacs/brace-expansion": "5.0.1",
|
||||
"fast-xml-parser": "5.3.4",
|
||||
@@ -140,7 +139,7 @@
|
||||
"site": "cd apps/website && bun dev && cd ../..",
|
||||
"docs": "bun -F @autumn/docs dev",
|
||||
"docs:build": "bun -F @autumn/docs build",
|
||||
"ts": "turbo run ts --filter=@autumn/server --filter=autumn-js --filter=@autumn/openapi --filter=atmn --filter=checkout --filter=@autumn/auth --filter=@autumn/mcp --filter=@autumn/leaf",
|
||||
"ts": "turbo run ts --filter=@autumn/server --filter=autumn-js --filter=@autumn/openapi --filter=atmn --filter=checkout --filter=@useautumn/ai-sdk --filter=@autumn/auth --filter=@autumn/mcp --filter=@autumn/leaf",
|
||||
"kill:ts": "while pgrep -f tsgo > /dev/null; do pkill -9 -f tsgo; sleep 0.1; done",
|
||||
"atmn:build": "bun -F atmn build",
|
||||
"openapi:ts": "bun -F @autumn/openapi ts",
|
||||
|
||||
253
packages/ai-sdk/bun.lock
Normal file
@@ -0,0 +1,253 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"configVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "@useautumn/ai-sdk",
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.9.1",
|
||||
"tsup": "^8.4.0",
|
||||
"typescript": "^5.8.3",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"ai": "^6.0.116",
|
||||
"autumn-js": "*",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@ai-sdk/gateway": ["@ai-sdk/gateway@3.0.66", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.19", "@vercel/oidc": "3.1.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-SIQ0YY0iMuv+07HLsZ+bB990zUJ6S4ujORAh+Jv1V2KGNn73qQKnGO0JBk+w+Res8YqOFSycwDoWcFlQrVxS4A=="],
|
||||
|
||||
"@ai-sdk/provider": ["@ai-sdk/provider@3.0.8", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ=="],
|
||||
|
||||
"@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.19", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-3eG55CrSWCu2SXlqq2QCsFjo3+E7+Gmg7i/oRVoSZzIodTuDSfLb3MRje67xE9RFea73Zao7Lm4mADIfUETKGg=="],
|
||||
|
||||
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.4", "", { "os": "aix", "cpu": "ppc64" }, "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q=="],
|
||||
|
||||
"@esbuild/android-arm": ["@esbuild/android-arm@0.27.4", "", { "os": "android", "cpu": "arm" }, "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ=="],
|
||||
|
||||
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.4", "", { "os": "android", "cpu": "arm64" }, "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw=="],
|
||||
|
||||
"@esbuild/android-x64": ["@esbuild/android-x64@0.27.4", "", { "os": "android", "cpu": "x64" }, "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw=="],
|
||||
|
||||
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ=="],
|
||||
|
||||
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw=="],
|
||||
|
||||
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.4", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw=="],
|
||||
|
||||
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ=="],
|
||||
|
||||
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.4", "", { "os": "linux", "cpu": "arm" }, "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg=="],
|
||||
|
||||
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA=="],
|
||||
|
||||
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.4", "", { "os": "linux", "cpu": "ia32" }, "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA=="],
|
||||
|
||||
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA=="],
|
||||
|
||||
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw=="],
|
||||
|
||||
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA=="],
|
||||
|
||||
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw=="],
|
||||
|
||||
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA=="],
|
||||
|
||||
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.4", "", { "os": "linux", "cpu": "x64" }, "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA=="],
|
||||
|
||||
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.4", "", { "os": "none", "cpu": "arm64" }, "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q=="],
|
||||
|
||||
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.4", "", { "os": "none", "cpu": "x64" }, "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg=="],
|
||||
|
||||
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.4", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow=="],
|
||||
|
||||
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.4", "", { "os": "openbsd", "cpu": "x64" }, "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ=="],
|
||||
|
||||
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.4", "", { "os": "none", "cpu": "arm64" }, "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg=="],
|
||||
|
||||
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.4", "", { "os": "sunos", "cpu": "x64" }, "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g=="],
|
||||
|
||||
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg=="],
|
||||
|
||||
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw=="],
|
||||
|
||||
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.4", "", { "os": "win32", "cpu": "x64" }, "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg=="],
|
||||
|
||||
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
|
||||
|
||||
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
|
||||
|
||||
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
|
||||
|
||||
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
|
||||
|
||||
"@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
|
||||
|
||||
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.59.0", "", { "os": "android", "cpu": "arm" }, "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg=="],
|
||||
|
||||
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.59.0", "", { "os": "android", "cpu": "arm64" }, "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q=="],
|
||||
|
||||
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.59.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg=="],
|
||||
|
||||
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.59.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w=="],
|
||||
|
||||
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.59.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA=="],
|
||||
|
||||
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.59.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg=="],
|
||||
|
||||
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.59.0", "", { "os": "linux", "cpu": "arm" }, "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw=="],
|
||||
|
||||
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.59.0", "", { "os": "linux", "cpu": "arm" }, "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA=="],
|
||||
|
||||
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.59.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA=="],
|
||||
|
||||
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.59.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA=="],
|
||||
|
||||
"@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg=="],
|
||||
|
||||
"@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q=="],
|
||||
|
||||
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.59.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA=="],
|
||||
|
||||
"@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.59.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA=="],
|
||||
|
||||
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg=="],
|
||||
|
||||
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg=="],
|
||||
|
||||
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.59.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w=="],
|
||||
|
||||
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.59.0", "", { "os": "linux", "cpu": "x64" }, "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg=="],
|
||||
|
||||
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.59.0", "", { "os": "linux", "cpu": "x64" }, "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg=="],
|
||||
|
||||
"@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.59.0", "", { "os": "openbsd", "cpu": "x64" }, "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ=="],
|
||||
|
||||
"@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.59.0", "", { "os": "none", "cpu": "arm64" }, "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA=="],
|
||||
|
||||
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.59.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A=="],
|
||||
|
||||
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.59.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA=="],
|
||||
|
||||
"@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.59.0", "", { "os": "win32", "cpu": "x64" }, "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA=="],
|
||||
|
||||
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.59.0", "", { "os": "win32", "cpu": "x64" }, "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA=="],
|
||||
|
||||
"@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||
|
||||
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
|
||||
|
||||
"@types/node": ["@types/node@24.12.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ=="],
|
||||
|
||||
"@vercel/oidc": ["@vercel/oidc@3.1.0", "", {}, "sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w=="],
|
||||
|
||||
"acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="],
|
||||
|
||||
"ai": ["ai@6.0.116", "", { "dependencies": { "@ai-sdk/gateway": "3.0.66", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.19", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-7yM+cTmyRLeNIXwt4Vj+mrrJgVQ9RMIW5WO0ydoLoYkewIvsMcvUmqS4j2RJTUXaF1HphwmSKUMQ/HypNRGOmA=="],
|
||||
|
||||
"any-promise": ["any-promise@1.3.0", "", {}, "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="],
|
||||
|
||||
"autumn-js": ["autumn-js@1.0.5", "", { "dependencies": { "query-string": "^9.2.2", "rou3": "^0.6.1", "zod": "^4.0.0" }, "peerDependencies": { "better-auth": "^1.3.17", "better-call": "^1.0.12", "express": "^5.2.1", "hono": "^4.0.0", "next": "^14.0.0 || ^15.0.0", "react": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["better-auth", "better-call", "express", "hono", "next", "react"] }, "sha512-Xh4hqx7EO+hilVjCuilLjUt5iw6RP8vxKmkKmwwHM8WhrHH4StAol17Qis9FtZ/ixeHfe4TOFojzgLhP74kETw=="],
|
||||
|
||||
"bundle-require": ["bundle-require@5.1.0", "", { "dependencies": { "load-tsconfig": "^0.2.3" }, "peerDependencies": { "esbuild": ">=0.18" } }, "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA=="],
|
||||
|
||||
"cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="],
|
||||
|
||||
"chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
|
||||
|
||||
"commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="],
|
||||
|
||||
"confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="],
|
||||
|
||||
"consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="],
|
||||
|
||||
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"decode-uri-component": ["decode-uri-component@0.4.1", "", {}, "sha512-+8VxcR21HhTy8nOt6jf20w0c9CADrw1O8d+VZ/YzzCt4bJ3uBjw+D1q2osAB8RnpwwaeYBxy0HyKQxD5JBMuuQ=="],
|
||||
|
||||
"esbuild": ["esbuild@0.27.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.4", "@esbuild/android-arm": "0.27.4", "@esbuild/android-arm64": "0.27.4", "@esbuild/android-x64": "0.27.4", "@esbuild/darwin-arm64": "0.27.4", "@esbuild/darwin-x64": "0.27.4", "@esbuild/freebsd-arm64": "0.27.4", "@esbuild/freebsd-x64": "0.27.4", "@esbuild/linux-arm": "0.27.4", "@esbuild/linux-arm64": "0.27.4", "@esbuild/linux-ia32": "0.27.4", "@esbuild/linux-loong64": "0.27.4", "@esbuild/linux-mips64el": "0.27.4", "@esbuild/linux-ppc64": "0.27.4", "@esbuild/linux-riscv64": "0.27.4", "@esbuild/linux-s390x": "0.27.4", "@esbuild/linux-x64": "0.27.4", "@esbuild/netbsd-arm64": "0.27.4", "@esbuild/netbsd-x64": "0.27.4", "@esbuild/openbsd-arm64": "0.27.4", "@esbuild/openbsd-x64": "0.27.4", "@esbuild/openharmony-arm64": "0.27.4", "@esbuild/sunos-x64": "0.27.4", "@esbuild/win32-arm64": "0.27.4", "@esbuild/win32-ia32": "0.27.4", "@esbuild/win32-x64": "0.27.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ=="],
|
||||
|
||||
"eventsource-parser": ["eventsource-parser@3.0.6", "", {}, "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg=="],
|
||||
|
||||
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
|
||||
|
||||
"filter-obj": ["filter-obj@5.1.0", "", {}, "sha512-qWeTREPoT7I0bifpPUXtxkZJ1XJzxWtfoWWkdVGqa+eCr3SHW/Ocp89o8vLvbUuQnadybJpjOKu4V+RwO6sGng=="],
|
||||
|
||||
"fix-dts-default-cjs-exports": ["fix-dts-default-cjs-exports@1.0.1", "", { "dependencies": { "magic-string": "^0.30.17", "mlly": "^1.7.4", "rollup": "^4.34.8" } }, "sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg=="],
|
||||
|
||||
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
||||
|
||||
"joycon": ["joycon@3.1.1", "", {}, "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw=="],
|
||||
|
||||
"json-schema": ["json-schema@0.4.0", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="],
|
||||
|
||||
"lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="],
|
||||
|
||||
"lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="],
|
||||
|
||||
"load-tsconfig": ["load-tsconfig@0.2.5", "", {}, "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg=="],
|
||||
|
||||
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
|
||||
|
||||
"mlly": ["mlly@1.8.1", "", { "dependencies": { "acorn": "^8.16.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.3" } }, "sha512-SnL6sNutTwRWWR/vcmCYHSADjiEesp5TGQQ0pXyLhW5IoeibRlF/CbSLailbB3CNqJUk9cVJ9dUDnbD7GrcHBQ=="],
|
||||
|
||||
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||
|
||||
"mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="],
|
||||
|
||||
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
|
||||
|
||||
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
||||
|
||||
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
||||
|
||||
"picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
|
||||
|
||||
"pirates": ["pirates@4.0.7", "", {}, "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA=="],
|
||||
|
||||
"pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="],
|
||||
|
||||
"postcss-load-config": ["postcss-load-config@6.0.1", "", { "dependencies": { "lilconfig": "^3.1.1" }, "peerDependencies": { "jiti": ">=1.21.0", "postcss": ">=8.0.9", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["jiti", "postcss", "tsx", "yaml"] }, "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g=="],
|
||||
|
||||
"query-string": ["query-string@9.3.1", "", { "dependencies": { "decode-uri-component": "^0.4.1", "filter-obj": "^5.1.0", "split-on-first": "^3.0.0" } }, "sha512-5fBfMOcDi5SA9qj5jZhWAcTtDfKF5WFdd2uD9nVNlbxVv1baq65aALy6qofpNEGELHvisjjasxQp7BlM9gvMzw=="],
|
||||
|
||||
"react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="],
|
||||
|
||||
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
|
||||
|
||||
"resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="],
|
||||
|
||||
"rollup": ["rollup@4.59.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.59.0", "@rollup/rollup-android-arm64": "4.59.0", "@rollup/rollup-darwin-arm64": "4.59.0", "@rollup/rollup-darwin-x64": "4.59.0", "@rollup/rollup-freebsd-arm64": "4.59.0", "@rollup/rollup-freebsd-x64": "4.59.0", "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", "@rollup/rollup-linux-arm-musleabihf": "4.59.0", "@rollup/rollup-linux-arm64-gnu": "4.59.0", "@rollup/rollup-linux-arm64-musl": "4.59.0", "@rollup/rollup-linux-loong64-gnu": "4.59.0", "@rollup/rollup-linux-loong64-musl": "4.59.0", "@rollup/rollup-linux-ppc64-gnu": "4.59.0", "@rollup/rollup-linux-ppc64-musl": "4.59.0", "@rollup/rollup-linux-riscv64-gnu": "4.59.0", "@rollup/rollup-linux-riscv64-musl": "4.59.0", "@rollup/rollup-linux-s390x-gnu": "4.59.0", "@rollup/rollup-linux-x64-gnu": "4.59.0", "@rollup/rollup-linux-x64-musl": "4.59.0", "@rollup/rollup-openbsd-x64": "4.59.0", "@rollup/rollup-openharmony-arm64": "4.59.0", "@rollup/rollup-win32-arm64-msvc": "4.59.0", "@rollup/rollup-win32-ia32-msvc": "4.59.0", "@rollup/rollup-win32-x64-gnu": "4.59.0", "@rollup/rollup-win32-x64-msvc": "4.59.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg=="],
|
||||
|
||||
"rou3": ["rou3@0.6.3", "", {}, "sha512-1HSG1ENTj7Kkm5muMnXuzzfdDOf7CFnbSYFA+H3Fp/rB9lOCxCPgy1jlZxTKyFoC5jJay8Mmc+VbPLYRjzYLrA=="],
|
||||
|
||||
"source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="],
|
||||
|
||||
"split-on-first": ["split-on-first@3.0.0", "", {}, "sha512-qxQJTx2ryR0Dw0ITYyekNQWpz6f8dGd7vffGNflQQ3Iqj9NJ6qiZ7ELpZsJ/QBhIVAiDfXdag3+Gp8RvWa62AA=="],
|
||||
|
||||
"sucrase": ["sucrase@3.35.1", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "tinyglobby": "^0.2.11", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw=="],
|
||||
|
||||
"thenify": ["thenify@3.3.1", "", { "dependencies": { "any-promise": "^1.0.0" } }, "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw=="],
|
||||
|
||||
"thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="],
|
||||
|
||||
"tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="],
|
||||
|
||||
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
|
||||
|
||||
"tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="],
|
||||
|
||||
"ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="],
|
||||
|
||||
"tsup": ["tsup@8.5.1", "", { "dependencies": { "bundle-require": "^5.1.0", "cac": "^6.7.14", "chokidar": "^4.0.3", "consola": "^3.4.0", "debug": "^4.4.0", "esbuild": "^0.27.0", "fix-dts-default-cjs-exports": "^1.0.0", "joycon": "^3.1.1", "picocolors": "^1.1.1", "postcss-load-config": "^6.0.1", "resolve-from": "^5.0.0", "rollup": "^4.34.8", "source-map": "^0.7.6", "sucrase": "^3.35.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.11", "tree-kill": "^1.2.2" }, "peerDependencies": { "@microsoft/api-extractor": "^7.36.0", "@swc/core": "^1", "postcss": "^8.4.12", "typescript": ">=4.5.0" }, "optionalPeers": ["@microsoft/api-extractor", "@swc/core", "postcss", "typescript"], "bin": { "tsup": "dist/cli-default.js", "tsup-node": "dist/cli-node.js" } }, "sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing=="],
|
||||
|
||||
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||
|
||||
"ufo": ["ufo@1.6.3", "", {}, "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q=="],
|
||||
|
||||
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
|
||||
|
||||
"zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
|
||||
}
|
||||
}
|
||||
39
packages/ai-sdk/package.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "@useautumn/ai-sdk",
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"description": "AI SDK for Autumn",
|
||||
"main": "./dist/sdk/index.cjs",
|
||||
"module": "./dist/sdk/index.js",
|
||||
"types": "./dist/sdk/index.d.ts",
|
||||
"files": [
|
||||
"dist",
|
||||
"README.md",
|
||||
"LICENSE.md"
|
||||
],
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/sdk/index.d.ts",
|
||||
"require": "./dist/sdk/index.cjs",
|
||||
"import": "./dist/sdk/index.js"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"ts": "tsgo --noEmit --skipLibCheck",
|
||||
"test": "bun test tests/unit",
|
||||
"build": "rm -rf dist && tsup",
|
||||
"prepublishOnly": "bun run build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ai-sdk/provider": "^3.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"ai": "^6.0.116",
|
||||
"autumn-js": "*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.9.1",
|
||||
"tsup": "^8.4.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
107
packages/ai-sdk/src/index.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
import type { LanguageModelV3 } from "@ai-sdk/provider";
|
||||
import { type LanguageModelMiddleware, wrapLanguageModel } from "ai";
|
||||
import { normalizeUsage, type TokenPools, type UsageLike } from "./usage.js";
|
||||
|
||||
export type { TokenPools, UsageLike } from "./usage.js";
|
||||
|
||||
type TrackTokensParams = TokenPools & {
|
||||
customerId: string;
|
||||
modelId: string;
|
||||
featureId?: string;
|
||||
entityId?: string;
|
||||
properties?: Record<string, unknown>;
|
||||
};
|
||||
|
||||
/** Structural view of the autumn-js client; older versions may not ship balances.trackTokens. */
|
||||
export type AutumnClient = {
|
||||
balances?: {
|
||||
trackTokens?: (params: TrackTokensParams) => Promise<unknown>;
|
||||
};
|
||||
};
|
||||
|
||||
export type WithAutumnOptions = {
|
||||
/** Autumn SDK client instance. */
|
||||
autumn: AutumnClient;
|
||||
/** The AI SDK language model to wrap. */
|
||||
model: LanguageModelV3;
|
||||
/** The Autumn customer ID to attribute usage to. */
|
||||
customerId: string;
|
||||
/** Override the provider prefix used in the model name (e.g. "openrouter", "custom"). Falls back to `model.provider`. */
|
||||
providerId?: string;
|
||||
/** Target a specific AI credit system feature. Auto-detected if omitted. */
|
||||
featureId?: string;
|
||||
/** Entity ID for entity-scoped balance tracking. */
|
||||
entityId?: string;
|
||||
/** Additional properties to attach to each usage event. */
|
||||
properties?: Record<string, unknown>;
|
||||
};
|
||||
|
||||
export const withAutumn = ({
|
||||
autumn,
|
||||
model,
|
||||
customerId,
|
||||
providerId,
|
||||
featureId,
|
||||
entityId,
|
||||
properties,
|
||||
}: WithAutumnOptions): LanguageModelV3 => {
|
||||
const modelName = `${providerId ?? model.provider}/${model.modelId}`;
|
||||
|
||||
const trackUsage = async (usage: UsageLike) => {
|
||||
try {
|
||||
const trackTokens = autumn.balances?.trackTokens;
|
||||
if (!trackTokens) {
|
||||
throw new Error(
|
||||
"autumn-js client does not support balances.trackTokens — upgrade autumn-js.",
|
||||
);
|
||||
}
|
||||
await trackTokens({
|
||||
...normalizeUsage(usage, modelName),
|
||||
customerId,
|
||||
modelId: modelName,
|
||||
featureId,
|
||||
entityId,
|
||||
properties,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("[Autumn Tracking] Failed to track usage:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const middleware: LanguageModelMiddleware = {
|
||||
specificationVersion: "v3",
|
||||
wrapGenerate: async ({ doGenerate }) => {
|
||||
const result = await doGenerate();
|
||||
await trackUsage(result.usage as UsageLike);
|
||||
return result;
|
||||
},
|
||||
wrapStream: async ({ doStream }) => {
|
||||
const { stream, ...rest } = await doStream();
|
||||
|
||||
let trackingPromise: Promise<void> | undefined;
|
||||
|
||||
type StreamChunk = typeof stream extends ReadableStream<infer T>
|
||||
? T
|
||||
: never;
|
||||
|
||||
const transformStream = new TransformStream<StreamChunk, StreamChunk>({
|
||||
transform(chunk, controller) {
|
||||
if (chunk.type === "finish" && chunk.usage) {
|
||||
trackingPromise = trackUsage(chunk.usage as UsageLike);
|
||||
}
|
||||
controller.enqueue(chunk);
|
||||
},
|
||||
async flush() {
|
||||
await trackingPromise;
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
stream: stream.pipeThrough(transformStream),
|
||||
...rest,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
return wrapLanguageModel({ model, middleware });
|
||||
};
|
||||
117
packages/ai-sdk/src/usage.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
type NestedTokens = {
|
||||
total?: number | null;
|
||||
noCache?: number | null;
|
||||
cacheRead?: number | null;
|
||||
cacheWrite?: number | null;
|
||||
text?: number | null;
|
||||
reasoning?: number | null;
|
||||
};
|
||||
|
||||
type LegacyCount = number | { total?: number | null } | null;
|
||||
|
||||
/** Lenient view over AI SDK usage shapes: nested V3 counts, flat counts with token details, and legacy prompt/completion counts. */
|
||||
export type UsageLike = {
|
||||
inputTokens?: number | NestedTokens | null;
|
||||
outputTokens?: number | NestedTokens | null;
|
||||
promptTokens?: LegacyCount;
|
||||
completionTokens?: LegacyCount;
|
||||
inputTokenDetails?: {
|
||||
noCacheTokens?: number | null;
|
||||
cacheReadTokens?: number | null;
|
||||
cacheWriteTokens?: number | null;
|
||||
} | null;
|
||||
outputTokenDetails?: {
|
||||
textTokens?: number | null;
|
||||
reasoningTokens?: number | null;
|
||||
} | null;
|
||||
cachedInputTokens?: number | null;
|
||||
reasoningTokens?: number | null;
|
||||
};
|
||||
|
||||
export type TokenPools = {
|
||||
inputTokens: number;
|
||||
outputTokens: number;
|
||||
cacheReadTokens: number;
|
||||
cacheWriteTokens: number;
|
||||
reasoningTokens: number;
|
||||
};
|
||||
|
||||
const flatCount = (value: LegacyCount | undefined): number | undefined =>
|
||||
typeof value === "number" ? value : (value?.total ?? undefined);
|
||||
|
||||
const isNested = (
|
||||
value: number | NestedTokens | null | undefined,
|
||||
): value is NestedTokens => value != null && typeof value === "object";
|
||||
|
||||
const toParts = (usage: UsageLike) => {
|
||||
const input = usage.inputTokens;
|
||||
const output = usage.outputTokens;
|
||||
|
||||
if (isNested(input)) {
|
||||
const out = isNested(output) ? output : undefined;
|
||||
return {
|
||||
cacheRead: input.cacheRead ?? 0,
|
||||
cacheWrite: input.cacheWrite ?? 0,
|
||||
reasoning: out?.reasoning ?? 0,
|
||||
textInput: input.noCache,
|
||||
totalInput: input.total,
|
||||
textOutput: out?.text,
|
||||
totalOutput: out?.total,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
cacheRead:
|
||||
usage.inputTokenDetails?.cacheReadTokens ?? usage.cachedInputTokens ?? 0,
|
||||
cacheWrite: usage.inputTokenDetails?.cacheWriteTokens ?? 0,
|
||||
reasoning:
|
||||
usage.outputTokenDetails?.reasoningTokens ?? usage.reasoningTokens ?? 0,
|
||||
textInput: usage.inputTokenDetails?.noCacheTokens,
|
||||
totalInput:
|
||||
typeof input === "number" ? input : flatCount(usage.promptTokens),
|
||||
textOutput: usage.outputTokenDetails?.textTokens,
|
||||
totalOutput:
|
||||
typeof output === "number" ? output : flatCount(usage.completionTokens),
|
||||
};
|
||||
};
|
||||
|
||||
const clamp = (value: number) => Math.max(0, value);
|
||||
|
||||
/** Splits provider usage into exclusive token pools; throws if the provider returned no usable counts. */
|
||||
export const normalizeUsage = (
|
||||
usage: UsageLike,
|
||||
modelName: string,
|
||||
): TokenPools => {
|
||||
const parts = toParts(usage);
|
||||
|
||||
const required = (
|
||||
value: number | null | undefined,
|
||||
label: string,
|
||||
): number => {
|
||||
if (value == null) {
|
||||
throw new Error(
|
||||
`[Autumn] ${label} token usage was not returned by the model provider (${modelName}). This provider may not support usage tracking.`,
|
||||
);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
const textInput =
|
||||
parts.textInput ??
|
||||
(parts.totalInput != null
|
||||
? parts.totalInput - parts.cacheRead - parts.cacheWrite
|
||||
: undefined);
|
||||
const textOutput =
|
||||
parts.textOutput ??
|
||||
(parts.totalOutput != null
|
||||
? parts.totalOutput - parts.reasoning
|
||||
: undefined);
|
||||
|
||||
return {
|
||||
inputTokens: clamp(required(textInput, "Input")),
|
||||
outputTokens: clamp(required(textOutput, "Output")),
|
||||
cacheReadTokens: clamp(parts.cacheRead),
|
||||
cacheWriteTokens: clamp(parts.cacheWrite),
|
||||
reasoningTokens: clamp(parts.reasoning),
|
||||
};
|
||||
};
|
||||
144
packages/ai-sdk/tests/unit/index.test.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import type { LanguageModelV3, LanguageModelV3Usage } from "@ai-sdk/provider";
|
||||
import { generateText, streamText } from "ai";
|
||||
import { withAutumn } from "../../src/index.js";
|
||||
|
||||
type TrackTokensParams = {
|
||||
customerId: string;
|
||||
modelId: string;
|
||||
inputTokens: number;
|
||||
outputTokens: number;
|
||||
cacheReadTokens?: number;
|
||||
cacheWriteTokens?: number;
|
||||
reasoningTokens?: number;
|
||||
featureId?: string;
|
||||
entityId?: string;
|
||||
properties?: Record<string, unknown>;
|
||||
};
|
||||
|
||||
const usage: LanguageModelV3Usage = {
|
||||
inputTokens: {
|
||||
total: 13,
|
||||
noCache: 10,
|
||||
cacheRead: 2,
|
||||
cacheWrite: 1,
|
||||
},
|
||||
outputTokens: {
|
||||
total: 7,
|
||||
text: 5,
|
||||
reasoning: 2,
|
||||
},
|
||||
};
|
||||
|
||||
const finishReason = { unified: "stop" as const, raw: "stop" };
|
||||
|
||||
const createAutumn = () => {
|
||||
const calls: TrackTokensParams[] = [];
|
||||
|
||||
return {
|
||||
calls,
|
||||
autumn: {
|
||||
balances: {
|
||||
trackTokens: async (params: TrackTokensParams) => {
|
||||
calls.push(params);
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const createModel = (): LanguageModelV3 => ({
|
||||
specificationVersion: "v3",
|
||||
provider: "openai",
|
||||
modelId: "gpt-test",
|
||||
supportedUrls: {},
|
||||
async doGenerate() {
|
||||
return {
|
||||
content: [{ type: "text", text: "hello" }],
|
||||
finishReason,
|
||||
usage,
|
||||
warnings: [],
|
||||
};
|
||||
},
|
||||
async doStream() {
|
||||
return {
|
||||
stream: new ReadableStream({
|
||||
start(controller) {
|
||||
controller.enqueue({ type: "text-start", id: "text-1" });
|
||||
controller.enqueue({
|
||||
type: "text-delta",
|
||||
id: "text-1",
|
||||
delta: "hello",
|
||||
});
|
||||
controller.enqueue({ type: "text-end", id: "text-1" });
|
||||
controller.enqueue({ type: "finish", finishReason, usage });
|
||||
controller.close();
|
||||
},
|
||||
}),
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
describe("withAutumn", () => {
|
||||
test("tracks token usage from generateText", async () => {
|
||||
const { autumn, calls } = createAutumn();
|
||||
|
||||
const model = withAutumn({
|
||||
autumn,
|
||||
model: createModel(),
|
||||
customerId: "cus_test",
|
||||
featureId: "ai_credits",
|
||||
entityId: "entity_test",
|
||||
properties: { source: "test" },
|
||||
});
|
||||
|
||||
const result = await generateText({ model, prompt: "Say hello" });
|
||||
|
||||
expect(result.text).toBe("hello");
|
||||
expect(calls).toEqual([
|
||||
{
|
||||
customerId: "cus_test",
|
||||
modelId: "openai/gpt-test",
|
||||
inputTokens: 10,
|
||||
outputTokens: 5,
|
||||
cacheReadTokens: 2,
|
||||
cacheWriteTokens: 1,
|
||||
reasoningTokens: 2,
|
||||
featureId: "ai_credits",
|
||||
entityId: "entity_test",
|
||||
properties: { source: "test" },
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test("tracks token usage from streamText when the stream finishes", async () => {
|
||||
const { autumn, calls } = createAutumn();
|
||||
|
||||
const model = withAutumn({
|
||||
autumn,
|
||||
model: createModel(),
|
||||
customerId: "cus_stream",
|
||||
providerId: "custom-openai",
|
||||
});
|
||||
|
||||
const result = streamText({ model, prompt: "Say hello" });
|
||||
const chunks: string[] = [];
|
||||
|
||||
for await (const chunk of result.textStream) {
|
||||
chunks.push(chunk);
|
||||
}
|
||||
|
||||
expect(chunks.join("")).toBe("hello");
|
||||
expect(calls).toEqual([
|
||||
{
|
||||
customerId: "cus_stream",
|
||||
modelId: "custom-openai/gpt-test",
|
||||
inputTokens: 10,
|
||||
outputTokens: 5,
|
||||
cacheReadTokens: 2,
|
||||
cacheWriteTokens: 1,
|
||||
reasoningTokens: 2,
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
93
packages/ai-sdk/tests/unit/usage.test.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { normalizeUsage } from "../../src/usage.js";
|
||||
|
||||
const MODEL = "openai/gpt-test";
|
||||
|
||||
describe("normalizeUsage", () => {
|
||||
test("nested V3 counts split into exclusive pools", () => {
|
||||
expect(
|
||||
normalizeUsage(
|
||||
{
|
||||
inputTokens: { total: 13, noCache: 10, cacheRead: 2, cacheWrite: 1 },
|
||||
outputTokens: { total: 7, text: 5, reasoning: 2 },
|
||||
},
|
||||
MODEL,
|
||||
),
|
||||
).toEqual({
|
||||
inputTokens: 10,
|
||||
outputTokens: 5,
|
||||
cacheReadTokens: 2,
|
||||
cacheWriteTokens: 1,
|
||||
reasoningTokens: 2,
|
||||
});
|
||||
});
|
||||
|
||||
test("nested totals without breakdowns derive text pools", () => {
|
||||
expect(
|
||||
normalizeUsage(
|
||||
{
|
||||
inputTokens: { total: 13, cacheRead: 2, cacheWrite: 1 },
|
||||
outputTokens: { total: 7, reasoning: 2 },
|
||||
},
|
||||
MODEL,
|
||||
),
|
||||
).toEqual({
|
||||
inputTokens: 10,
|
||||
outputTokens: 5,
|
||||
cacheReadTokens: 2,
|
||||
cacheWriteTokens: 1,
|
||||
reasoningTokens: 2,
|
||||
});
|
||||
});
|
||||
|
||||
test("flat counts with token details", () => {
|
||||
expect(
|
||||
normalizeUsage(
|
||||
{
|
||||
inputTokens: 13,
|
||||
outputTokens: 7,
|
||||
inputTokenDetails: { cacheReadTokens: 2, cacheWriteTokens: 1 },
|
||||
outputTokenDetails: { reasoningTokens: 2 },
|
||||
},
|
||||
MODEL,
|
||||
),
|
||||
).toEqual({
|
||||
inputTokens: 10,
|
||||
outputTokens: 5,
|
||||
cacheReadTokens: 2,
|
||||
cacheWriteTokens: 1,
|
||||
reasoningTokens: 2,
|
||||
});
|
||||
});
|
||||
|
||||
test("legacy prompt/completion counts", () => {
|
||||
expect(
|
||||
normalizeUsage(
|
||||
{ promptTokens: 100, completionTokens: { total: 50 } },
|
||||
MODEL,
|
||||
),
|
||||
).toEqual({
|
||||
inputTokens: 100,
|
||||
outputTokens: 50,
|
||||
cacheReadTokens: 0,
|
||||
cacheWriteTokens: 0,
|
||||
reasoningTokens: 0,
|
||||
});
|
||||
});
|
||||
|
||||
test("inconsistent totals clamp to zero instead of going negative", () => {
|
||||
const pools = normalizeUsage(
|
||||
{
|
||||
inputTokens: { total: 1, cacheRead: 5, cacheWrite: 0 },
|
||||
outputTokens: { total: 1, reasoning: 5 },
|
||||
},
|
||||
MODEL,
|
||||
);
|
||||
expect(pools.inputTokens).toBe(0);
|
||||
expect(pools.outputTokens).toBe(0);
|
||||
});
|
||||
|
||||
test("missing usage throws with the model name", () => {
|
||||
expect(() => normalizeUsage({}, MODEL)).toThrow(/gpt-test/);
|
||||
});
|
||||
});
|
||||
19
packages/ai-sdk/tsconfig.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"noImplicitAny": true,
|
||||
"esModuleInterop": true,
|
||||
"strictNullChecks": true,
|
||||
"target": "ES2022",
|
||||
"moduleResolution": "NodeNext",
|
||||
"module": "NodeNext",
|
||||
"declaration": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"outDir": "dist",
|
||||
"lib": ["ES2022"],
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["dist", "node_modules"]
|
||||
}
|
||||
12
packages/ai-sdk/tsup.config.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { defineConfig } from "tsup";
|
||||
|
||||
export default defineConfig({
|
||||
entry: {
|
||||
"sdk/index": "src/index.ts",
|
||||
},
|
||||
format: ["cjs", "esm"],
|
||||
dts: true,
|
||||
splitting: false,
|
||||
sourcemap: false,
|
||||
clean: true,
|
||||
});
|
||||
@@ -349,6 +349,32 @@ function normalizeFeatureForCompare(f: Feature): Record<string, unknown> {
|
||||
}));
|
||||
}
|
||||
|
||||
if (f.type === "ai_credit_system") {
|
||||
const ai = f as Extract<Feature, { type: "ai_credit_system" }>;
|
||||
if (ai.modelMarkups && Object.keys(ai.modelMarkups).length > 0) {
|
||||
result.modelMarkups = Object.fromEntries(
|
||||
Object.entries(ai.modelMarkups)
|
||||
.sort(([a], [b]) => a.localeCompare(b))
|
||||
.map(([modelId, entry]) => [
|
||||
modelId,
|
||||
{
|
||||
markup: entry.markup,
|
||||
inputCost: entry.inputCost,
|
||||
outputCost: entry.outputCost,
|
||||
},
|
||||
]),
|
||||
);
|
||||
}
|
||||
if (ai.defaultMarkup != null) result.defaultMarkup = ai.defaultMarkup;
|
||||
if (ai.providerMarkups && Object.keys(ai.providerMarkups).length > 0) {
|
||||
result.providerMarkups = Object.fromEntries(
|
||||
Object.entries(ai.providerMarkups).sort(([a], [b]) =>
|
||||
a.localeCompare(b),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,92 +4,108 @@
|
||||
|
||||
import { z } from "zod/v4";
|
||||
|
||||
|
||||
export const FeatureSchema = z.object({
|
||||
id: z.string().meta({
|
||||
description:
|
||||
"The unique identifier for this feature, used in /check and /track calls.",
|
||||
}),
|
||||
name: z.string().meta({
|
||||
description:
|
||||
"Human-readable name displayed in the dashboard and billing UI.",
|
||||
}),
|
||||
eventNames: z.array(z.string()).optional().meta({
|
||||
description:
|
||||
"Event names that trigger this feature's balance. Allows multiple features to respond to a single event.",
|
||||
}),
|
||||
creditSchema: z
|
||||
.array(
|
||||
z.object({
|
||||
metered_feature_id: z.string().meta({
|
||||
description:
|
||||
"ID of the metered feature that draws from this credit system.",
|
||||
}),
|
||||
credit_cost: z.number().meta({
|
||||
description: "Credits consumed per unit of the metered feature.",
|
||||
}),
|
||||
}),
|
||||
)
|
||||
.optional()
|
||||
.meta({
|
||||
description:
|
||||
"For credit_system features: maps metered features to their credit costs.",
|
||||
}),
|
||||
archived: z.boolean().meta({
|
||||
description:
|
||||
"Whether the feature is archived and hidden from the dashboard.",
|
||||
})
|
||||
id: z.string().meta({
|
||||
description:
|
||||
"The unique identifier for this feature, used in /check and /track calls.",
|
||||
}),
|
||||
name: z.string().meta({
|
||||
description:
|
||||
"Human-readable name displayed in the dashboard and billing UI.",
|
||||
}),
|
||||
eventNames: z.array(z.string()).optional().meta({
|
||||
description:
|
||||
"Event names that trigger this feature's balance. Allows multiple features to respond to a single event.",
|
||||
}),
|
||||
creditSchema: z
|
||||
.array(
|
||||
z.object({
|
||||
metered_feature_id: z.string().meta({
|
||||
description:
|
||||
"ID of the metered feature that draws from this credit system.",
|
||||
}),
|
||||
credit_cost: z.number().meta({
|
||||
description: "Credits consumed per unit of the metered feature.",
|
||||
}),
|
||||
}),
|
||||
)
|
||||
.optional()
|
||||
.meta({
|
||||
description:
|
||||
"For credit_system features: maps metered features to their credit costs.",
|
||||
}),
|
||||
archived: z.boolean().meta({
|
||||
description:
|
||||
"Whether the feature is archived and hidden from the dashboard.",
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
|
||||
// Base fields shared by all feature types
|
||||
type FeatureBase = {
|
||||
/** Unique identifier for the feature */
|
||||
id: string;
|
||||
/** Display name for the feature */
|
||||
name: string;
|
||||
/** Whether the feature is archived */
|
||||
archived?: boolean;
|
||||
/** Event names that trigger this feature */
|
||||
eventNames?: string[];
|
||||
/** Credit schema for credit_system features */
|
||||
creditSchema?: Array<{
|
||||
meteredFeatureId: string;
|
||||
creditCost: number;
|
||||
}>;
|
||||
/** Unique identifier for the feature */
|
||||
id: string;
|
||||
/** Display name for the feature */
|
||||
name: string;
|
||||
/** Whether the feature is archived */
|
||||
archived?: boolean;
|
||||
/** Event names that trigger this feature */
|
||||
eventNames?: string[];
|
||||
/** Credit schema for credit_system features */
|
||||
creditSchema?: Array<{
|
||||
meteredFeatureId: string;
|
||||
creditCost: number;
|
||||
}>;
|
||||
};
|
||||
|
||||
/** Boolean feature - no consumable field allowed */
|
||||
export type BooleanFeature = FeatureBase & {
|
||||
type: "boolean";
|
||||
consumable?: never;
|
||||
type: "boolean";
|
||||
consumable?: never;
|
||||
};
|
||||
|
||||
/** Metered feature - requires consumable field */
|
||||
export type MeteredFeature = FeatureBase & {
|
||||
type: "metered";
|
||||
/** Whether usage is consumed (true) or accumulated (false) */
|
||||
consumable: boolean;
|
||||
type: "metered";
|
||||
/** Whether usage is consumed (true) or accumulated (false) */
|
||||
consumable: boolean;
|
||||
};
|
||||
|
||||
/** Credit system feature - always consumable */
|
||||
export type CreditSystemFeature = FeatureBase & {
|
||||
type: "credit_system";
|
||||
/** Credit systems are always consumable */
|
||||
consumable?: true;
|
||||
/** Required: defines how credits map to metered features */
|
||||
creditSchema: Array<{
|
||||
meteredFeatureId: string;
|
||||
creditCost: number;
|
||||
}>;
|
||||
type: "credit_system";
|
||||
/** Credit systems are always consumable */
|
||||
consumable?: true;
|
||||
/** Required: defines how credits map to metered features */
|
||||
creditSchema: Array<{
|
||||
meteredFeatureId: string;
|
||||
creditCost: number;
|
||||
}>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Feature definition with type-safe constraints:
|
||||
* - Boolean features cannot have consumable
|
||||
* - Metered features require consumable (true = single_use style, false = continuous_use style)
|
||||
* - Credit system features are always consumable and require creditSchema
|
||||
*/
|
||||
export type Feature = BooleanFeature | MeteredFeature | CreditSystemFeature;
|
||||
export type ModelMarkupEntry = {
|
||||
/** Per-model markup override. Omit to inherit provider/global markup. */
|
||||
markup?: number;
|
||||
inputCost?: number;
|
||||
outputCost?: number;
|
||||
};
|
||||
|
||||
export type ProviderMarkupEntry = {
|
||||
markup: number;
|
||||
};
|
||||
|
||||
/** AI credit system feature - uses model-based pricing */
|
||||
export type AiCreditSystemFeature = FeatureBase & {
|
||||
type: "ai_credit_system";
|
||||
/** Per-model markup overrides (highest priority). */
|
||||
modelMarkups?: Record<string, ModelMarkupEntry>;
|
||||
/** Default markup applied when no model or provider markup matches. */
|
||||
defaultMarkup?: number;
|
||||
/** Per-provider default markups, keyed by the first segment of the model id. */
|
||||
providerMarkups?: Record<string, ProviderMarkupEntry>;
|
||||
};
|
||||
|
||||
export type Feature =
|
||||
| BooleanFeature
|
||||
| MeteredFeature
|
||||
| CreditSystemFeature
|
||||
| AiCreditSystemFeature;
|
||||
|
||||
@@ -6,14 +6,14 @@ import { createTransformer } from "./Transformer.js";
|
||||
describe("Transformer", () => {
|
||||
describe("Feature transforms", () => {
|
||||
test("boolean feature", () => {
|
||||
const apiFeature = {
|
||||
const result = transformApiFeature({
|
||||
id: "enabled",
|
||||
name: "Feature Enabled",
|
||||
type: "boolean",
|
||||
consumable: false,
|
||||
archived: false,
|
||||
event_names: [],
|
||||
};
|
||||
|
||||
const result = transformApiFeature(apiFeature);
|
||||
});
|
||||
|
||||
expect(result.type).toBe("boolean");
|
||||
expect(result.id).toBe("enabled");
|
||||
@@ -21,47 +21,72 @@ describe("Transformer", () => {
|
||||
});
|
||||
|
||||
test("single_use → metered with consumable=true", () => {
|
||||
const apiFeature = {
|
||||
const result = transformApiFeature({
|
||||
id: "api_calls",
|
||||
name: "API Calls",
|
||||
type: "single_use",
|
||||
consumable: true,
|
||||
archived: false,
|
||||
event_names: ["api.call"],
|
||||
};
|
||||
|
||||
const result = transformApiFeature(apiFeature);
|
||||
});
|
||||
|
||||
expect(result.type).toBe("metered");
|
||||
expect(result.consumable).toBe(true);
|
||||
if (result.type === "metered") {
|
||||
expect(result.consumable).toBe(true);
|
||||
}
|
||||
expect(result.id).toBe("api_calls");
|
||||
});
|
||||
|
||||
test("continuous_use → metered with consumable=false", () => {
|
||||
const apiFeature = {
|
||||
const result = transformApiFeature({
|
||||
id: "seats",
|
||||
name: "Seats",
|
||||
type: "continuous_use",
|
||||
consumable: false,
|
||||
archived: false,
|
||||
event_names: [],
|
||||
};
|
||||
|
||||
const result = transformApiFeature(apiFeature);
|
||||
});
|
||||
|
||||
expect(result.type).toBe("metered");
|
||||
expect(result.consumable).toBe(false);
|
||||
if (result.type === "metered") {
|
||||
expect(result.consumable).toBe(false);
|
||||
}
|
||||
});
|
||||
|
||||
test("credit_system", () => {
|
||||
const apiFeature = {
|
||||
const result = transformApiFeature({
|
||||
id: "credits",
|
||||
name: "Credits",
|
||||
type: "credit_system",
|
||||
consumable: true,
|
||||
archived: false,
|
||||
credit_schema: [{ metered_feature_id: "api_calls", credit_cost: 10 }],
|
||||
};
|
||||
|
||||
const result = transformApiFeature(apiFeature);
|
||||
});
|
||||
|
||||
expect(result.type).toBe("credit_system");
|
||||
expect(result.consumable).toBe(true);
|
||||
expect(result.creditSchema).toHaveLength(1);
|
||||
if (result.type === "credit_system") {
|
||||
expect(result.consumable).toBe(true);
|
||||
expect(result.creditSchema).toHaveLength(1);
|
||||
}
|
||||
});
|
||||
|
||||
test("ai_credit_system", () => {
|
||||
const result = transformApiFeature({
|
||||
id: "ai_credits",
|
||||
name: "AI Credits",
|
||||
type: "ai_credit_system",
|
||||
consumable: true,
|
||||
archived: false,
|
||||
model_markups: {
|
||||
"anthropic/claude-opus-4-5": { markup: 20 },
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.type).toBe("ai_credit_system");
|
||||
if (result.type === "ai_credit_system") {
|
||||
expect(result.modelMarkups).toBeDefined();
|
||||
expect(result.modelMarkups!["anthropic/claude-opus-4-5"].markup).toBe(20);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,26 +1,38 @@
|
||||
import type { Feature } from "../../../compose/models/featureModels.js";
|
||||
import type { Feature, ModelMarkupEntry } from "../../../compose/models/featureModels.js";
|
||||
import type { ApiFeature } from "../../api/types/feature.js";
|
||||
import { createTransformer } from "./Transformer.js";
|
||||
|
||||
type RawApiFeature = Omit<ApiFeature, "type"> & { type: string };
|
||||
|
||||
function mapCreditSchema(
|
||||
api: any,
|
||||
api: RawApiFeature,
|
||||
): Array<{ meteredFeatureId: string; creditCost: number }> {
|
||||
return (api.credit_schema ?? []).map(
|
||||
(cs: { metered_feature_id: string; credit_cost: number }) => ({
|
||||
meteredFeatureId: cs.metered_feature_id,
|
||||
creditCost: cs.credit_cost,
|
||||
}),
|
||||
return (api.credit_schema ?? []).map((cs) => ({
|
||||
meteredFeatureId: cs.metered_feature_id,
|
||||
creditCost: cs.credit_cost,
|
||||
}));
|
||||
}
|
||||
|
||||
function mapModelMarkups(api: RawApiFeature): Record<string, ModelMarkupEntry> | undefined {
|
||||
if (!api.model_markups) return undefined;
|
||||
return Object.fromEntries(
|
||||
Object.entries(api.model_markups).map(([modelId, entry]) => [
|
||||
modelId,
|
||||
{
|
||||
markup: entry.markup,
|
||||
inputCost: entry.input_cost,
|
||||
outputCost: entry.output_cost,
|
||||
},
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
const BASE_COMPUTE = {
|
||||
eventNames: (api: any) =>
|
||||
eventNames: (api: RawApiFeature) =>
|
||||
api.event_names && api.event_names.length > 0 ? api.event_names : undefined,
|
||||
};
|
||||
|
||||
/**
|
||||
* Declarative feature transformer - replaces 79 lines with 40 lines of config
|
||||
*/
|
||||
export const featureTransformer = createTransformer<any, Feature>({
|
||||
export const featureTransformer = createTransformer<RawApiFeature, Feature>({
|
||||
discriminator: "type",
|
||||
cases: {
|
||||
// Boolean features: just copy base fields, no consumable
|
||||
@@ -32,14 +44,24 @@ export const featureTransformer = createTransformer<any, Feature>({
|
||||
},
|
||||
},
|
||||
|
||||
// Credit system features: always consumable
|
||||
credit_system: {
|
||||
copy: ["id", "name", "archived"],
|
||||
compute: {
|
||||
...BASE_COMPUTE,
|
||||
type: () => "credit_system" as const,
|
||||
consumable: () => true,
|
||||
creditSchema: mapCreditSchema,
|
||||
creditSchema: (api) => mapCreditSchema(api),
|
||||
},
|
||||
},
|
||||
|
||||
ai_credit_system: {
|
||||
copy: ["id", "name", "archived"],
|
||||
compute: {
|
||||
...BASE_COMPUTE,
|
||||
type: () => "ai_credit_system" as const,
|
||||
modelMarkups: (api) => mapModelMarkups(api),
|
||||
defaultMarkup: (api) => api.default_markup ?? undefined,
|
||||
providerMarkups: (api) => api.provider_markups ?? undefined,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -85,6 +107,6 @@ export const featureTransformer = createTransformer<any, Feature>({
|
||||
},
|
||||
});
|
||||
|
||||
export function transformApiFeature(apiFeature: any): Feature {
|
||||
export function transformApiFeature(apiFeature: RawApiFeature): Feature {
|
||||
return featureTransformer.transform(apiFeature);
|
||||
}
|
||||
|
||||