Billing
The billing endpoints expose usage metering (minutes, analyses, speaker profiles, text credits) and subscription state for the authenticated workspace. You can read the current-month usage summary, pull a per-day usage breakdown as JSON or CSV, list the available plans, and confirm a Stripe checkout after a redirect. Usage aggregates are scoped to the workspace resolved from your credentials, while plan limits come from the account's subscription.
All endpoints are served under https://api.perceive8.com/v1/billing. Unless noted otherwise, authenticate with X-API-Key: pk_live_... or Authorization: Bearer <jwt>.
Endpoints
GET /v1/billing/usage
Returns the current calendar month's usage summary and limits for the authenticated workspace.
curl https://api.perceive8.com/v1/billing/usage \
-H "X-API-Key: pk_live_xxxxxxxxxxxx"
Response
| Field | Type | Description |
|---|---|---|
minutes_used |
number | Minutes consumed this month (tier multipliers applied) |
minutes_limit |
number | Total available minutes: plan limit + loyalty bonus + remaining pack minutes |
analyses_used |
integer | Analyses created this month |
analyses_limit |
integer | Plan max_analyses plus any per-user override |
speakers_used |
integer | Enrolled speaker profiles |
speakers_limit |
integer | Plan max_speaker_ids plus any per-user override |
text_credits_used |
number | Text credits consumed this month |
text_credits_limit |
number | Plan monthly text credits + remaining pack credits |
text_credits_remaining |
number | Text credits left this month |
period_start |
string | ISO 8601 timestamp, first day of the current month |
period_end |
string | ISO 8601 timestamp, last day of the current month at 23:59:59 |
tier_usage |
object | Per-tier breakdown: lite, pro, and max, each with minutes_used, multiplier (1.0 / 1.5 / 3.0), and adjusted_minutes |
plan |
string | Plan name (e.g. "Free") |
monthly_limit |
number | Plan monthly minutes only (legacy field) |
minutes_remaining |
number | Minutes left this month (legacy field) |
pack_minutes |
number | Remaining prepaid pack minutes (legacy field) |
bonus_minutes |
number | Loyalty bonus minutes: 10% of plan minutes once the subscription is past its first billing cycle |
subscription |
object | null | Active subscription summary (id, status, current_period_start, current_period_end, cancel_at_period_end), or null |
{
"minutes_used": 142.5,
"minutes_limit": 630.0,
"analyses_used": 18,
"analyses_limit": 100,
"speakers_used": 4,
"speakers_limit": 10,
"text_credits_used": 220.0,
"text_credits_limit": 1000.0,
"text_credits_remaining": 780.0,
"period_start": "2026-08-01T00:00:00",
"period_end": "2026-08-31T23:59:59",
"tier_usage": {
"lite": { "minutes_used": 40.0, "multiplier": 1.0, "adjusted_minutes": 40.0 },
"pro": { "minutes_used": 60.0, "multiplier": 1.5, "adjusted_minutes": 90.0 },
"max": { "minutes_used": 4.17, "multiplier": 3.0, "adjusted_minutes": 12.5 }
},
"plan": "Pro",
"monthly_limit": 600.0,
"minutes_remaining": 487.5,
"pack_minutes": 0.0,
"bonus_minutes": 30.0,
"subscription": {
"id": "5f0e6b1c-2f3a-4c9d-8e7b-1a2b3c4d5e6f",
"status": "active",
"current_period_start": "2026-08-01T00:00:00",
"current_period_end": "2026-09-01T00:00:00",
"cancel_at_period_end": false
}
}
Accounts with no usage records yet receive a reduced shape: zeroed counters and limits, the legacy fields, and no tier_usage or bonus_minutes keys.
GET /v1/billing/subscription
Returns the account's active subscription and full plan details. If there is no active subscription, the response is {"subscription": null, "plan": "Free"}.
Response
subscription fields:
| Field | Type | Description |
|---|---|---|
id |
string (UUID) | Internal subscription ID |
status |
string | Mirrors the Stripe subscription status; observed values include active, past_due, incomplete, canceled |
stripe_customer_id |
string | null | Stripe customer ID |
stripe_subscription_id |
string | null | Stripe subscription ID |
current_period_start |
string | null | ISO 8601 timestamp |
current_period_end |
string | null | ISO 8601 timestamp |
cancel_at_period_end |
boolean | Whether the subscription cancels at period end |
plan fields: see the plan object table under GET /v1/billing/plans — the same fields are returned here.
{
"subscription": {
"id": "5f0e6b1c-2f3a-4c9d-8e7b-1a2b3c4d5e6f",
"status": "active",
"stripe_customer_id": "cus_A1b2C3d4E5f6G7",
"stripe_subscription_id": "sub_1MowGbLkdIwHu7ix",
"current_period_start": "2026-08-01T00:00:00",
"current_period_end": "2026-09-01T00:00:00",
"cancel_at_period_end": false
},
"plan": {
"id": "9c8b7a6d-5e4f-3a2b-1c0d-9e8f7a6b5c4d",
"name": "Pro",
"monthly_minutes": 600,
"price_cents": 4900,
"max_file_size_mb": 500,
"max_concurrent": 5,
"storage_gb": 100.0,
"stripe_price_id": "price_1MowGcLkdIwHu7jx",
"max_analyses": 100,
"max_speaker_ids": 10,
"rate_limit_rpm": 120,
"rate_limit_daily": 5000,
"monthly_text_credits": 1000,
"has_webhooks": true,
"has_custom_playbooks": true,
"has_sentiment_analysis": true,
"has_emotion_analysis": false,
"has_sso": false,
"has_audit_logs": false,
"max_concurrent_streams": 3,
"max_stream_duration_hours": 4,
"data_retention_days": 90,
"is_contact_sales": false
}
}
GET /v1/billing/plans
Lists all active plans, ordered by price_cents ascending. No authentication is required for this endpoint.
Response
| Field | Type | Description |
|---|---|---|
plans |
array | Active plan objects (see fields below) |
Each plan object:
| Field | Type | Description |
|---|---|---|
id |
string (UUID) | Plan ID |
name |
string | Plan name |
monthly_minutes |
integer | Included audio minutes per month |
price_cents |
integer | Monthly price in cents |
max_file_size_mb |
integer | Maximum upload size in MB |
max_concurrent |
integer | Maximum concurrent processing jobs |
storage_gb |
number | Included storage in GB |
stripe_price_id |
string | null | Stripe price ID for checkout |
max_analyses |
integer | null | Maximum analyses per month |
max_speaker_ids |
integer | null | Maximum enrolled speaker profiles |
rate_limit_rpm |
integer | Requests per minute |
rate_limit_daily |
integer | Requests per day |
monthly_text_credits |
integer | null | Included text credits per month |
has_webhooks |
boolean | Webhook subscriptions enabled |
has_custom_playbooks |
boolean | Custom playbooks enabled |
has_sentiment_analysis |
boolean | Sentiment analysis enabled |
has_emotion_analysis |
boolean | Emotion analysis enabled |
has_sso |
boolean | SSO enabled |
has_audit_logs |
boolean | Audit logs enabled |
max_concurrent_streams |
integer | Maximum concurrent live streams |
max_stream_duration_hours |
integer | Maximum stream duration in hours |
data_retention_days |
integer | Data retention period in days |
is_contact_sales |
boolean | Enterprise plan — contact sales instead of self-serve checkout |
{
"plans": [
{
"id": "00000000-0000-4000-a000-000000000001",
"name": "Free",
"monthly_minutes": 30,
"price_cents": 0,
"max_file_size_mb": 50,
"max_concurrent": 1,
"storage_gb": 1.0,
"stripe_price_id": null,
"max_analyses": 5,
"max_speaker_ids": 2,
"rate_limit_rpm": 30,
"rate_limit_daily": 500,
"monthly_text_credits": 100,
"has_webhooks": false,
"has_custom_playbooks": false,
"has_sentiment_analysis": false,
"has_emotion_analysis": false,
"has_sso": false,
"has_audit_logs": false,
"max_concurrent_streams": 1,
"max_stream_duration_hours": 1,
"data_retention_days": 7,
"is_contact_sales": false
}
]
}
GET /v1/billing/usage/daily
Returns a per-day usage breakdown grouped by job type for a given month.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
month |
query | string | Month in YYYY-MM format. Defaults to the current month. Returns 400 if the format is invalid or the month is not 01–12. |
curl "https://api.perceive8.com/v1/billing/usage/daily?month=2026-08" \
-H "X-API-Key: pk_live_xxxxxxxxxxxx"
Response
| Field | Type | Description |
|---|---|---|
month |
string | The resolved month, YYYY-MM |
days_in_month |
integer | Number of days in that month |
job_types |
array of strings | Sorted, de-duplicated job type labels present in the breakdown |
daily_usage |
array | One entry per (date, job type) pair with usage |
Each daily_usage entry:
| Field | Type | Description |
|---|---|---|
date |
string | Day, YYYY-MM-DD |
job_type |
string | "<run_type> - <provider_name>", or "unknown" when no processing run is linked |
total_minutes |
number | Minutes consumed that day for that job type |
{
"month": "2026-08",
"days_in_month": 31,
"job_types": ["transcription - assemblyai", "unknown"],
"daily_usage": [
{ "date": "2026-08-03", "job_type": "transcription - assemblyai", "total_minutes": 42.5 },
{ "date": "2026-08-04", "job_type": "transcription - assemblyai", "total_minutes": 18.0 },
{ "date": "2026-08-04", "job_type": "unknown", "total_minutes": 3.25 }
]
}
GET /v1/billing/usage/daily/csv
Downloads the same daily usage breakdown as a CSV file. Accepts the same month query parameter as GET /v1/billing/usage/daily. The response is streamed with Content-Type: text/csv and Content-Disposition: attachment; filename="usage-YYYY-MM.csv". Columns: date, job_type, total_minutes.
curl "https://api.perceive8.com/v1/billing/usage/daily/csv?month=2026-08" \
-H "X-API-Key: pk_live_xxxxxxxxxxxx" \
-o usage-2026-08.csv
Response
date,job_type,total_minutes
2026-08-03,transcription - assemblyai,42.5
2026-08-04,transcription - assemblyai,18.0
2026-08-04,unknown,3.25
POST /v1/billing/confirm-checkout
Confirms a Stripe checkout session and syncs the subscription. This is a fallback for when Stripe webhooks fail or are delayed; call it after a successful checkout redirect. Requires the admin workspace role (workspace owners always qualify).
Request body
{
"session_id": "cs_live_a1b2c3d4e5f6g7h8"
}
| Field | Type | Description |
|---|---|---|
session_id |
string | Stripe checkout session ID (cs_...). Required. |
The endpoint verifies with Stripe that the session is paid and that the session's metadata.user_id matches the authenticated user, validates the price against the known plan catalog, then creates or updates the subscription.
Response
{
"status": "ok",
"subscription": {
"id": "5f0e6b1c-2f3a-4c9d-8e7b-1a2b3c4d5e6f",
"plan_id": "9c8b7a6d-5e4f-3a2b-1c0d-9e8f7a6b5c4d",
"plan_name": "Pro",
"status": "active",
"stripe_customer_id": "cus_A1b2C3d4E5f6G7",
"stripe_subscription_id": "sub_1MowGbLkdIwHu7ix",
"current_period_start": "2026-08-19T06:20:05",
"current_period_end": "2026-09-19T06:20:05"
}
}
Errors (returned as {"detail": "..."})
| Status | Detail |
|---|---|
400 |
session_id required |
400 |
Invalid session_id — Stripe could not retrieve the session |
400 |
Session not yet paid |
400 |
No subscription in session |
400 |
Unknown price_id: ... — the session's price does not match any active plan |
403 |
Session does not belong to this user |
403 |
Admin access required for this workspace — caller lacks the admin role |
500 |
Failed to retrieve subscription from Stripe |
Stripe webhook
POST /v1/billing/webhook is the Stripe callback endpoint. It exists for Stripe's event delivery (signature-verified) and for the site's pre-verified internal forwarding — not for API consumers. Do not call it from your integration; its payload is Stripe's event format, which is outside the scope of this reference.