Skip to main content
Home›Docs›API Reference›Billing
API Reference

Billing

Read usage metering, daily usage breakdowns, the plan catalog, and subscription state for your workspace.

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.