Partner API base path is /api/v1 on https://api.dcast.pro. This site documents the live surface; use the Quickstart QA curls to validate keys and routes.

Monetization API

Sell access through tiers and subscription checkouts. Read your revenue, payment history, and purchases. All calls below are scoped to the API key owner.

Payout / withdrawal flows are admin-only. Configuring a payout account and opening the payout-account management dashboard are not available via the Partner API. A pk_* key can read payout history and whether the account is connected (GET /monetization/connect-status) — it cannot initiate or configure withdrawals. If you need a payout account configured, contact [email protected].
Financial endpoints require an Owner-class key. Revenue, payments, payouts, the ledger summary, purchases, and your own subscriptions need FULL_ACCESS scope or a finance:read permission; tier price mutations (POST / PATCH / DELETE /tiers) need finance:write. READ_ONLY and STANDARD keys receive 403 FINANCIAL_SCOPE_REQUIRED. The tier catalogue (GET /tiers) and content analytics stay open. Full matrix: Authentication → Financial scope.

Read endpoints (Owner-class key required)

MethodEndpointDescription
GET/monetization/paymentsCompleted video sales on your content (canonical pagination); not payouts
GET/monetization/revenueAggregate revenue summary for the caller
curl -s -H "Authorization: Bearer pk_YOUR_KEY_HERE" \
  "https://api.dcast.pro/api/v1/monetization/payments?page=1&limit=20"

# Response — each payment is a VideoPurchase row (status=COMPLETED only),
# with the associated video nested for convenience. Pagination caps at limit=100.
{
  "success": true,
  "data": {
    "payments": [
      {
        "id": "...",
        "videoId": "...",
        "sellerId": "...",
        "buyerId": "...",
        "buyerEmail": "[email protected]",
        "amount": "4.99",
        "platformFee": "0.25",
        "currency": "USD",
        "status": "COMPLETED",
        "accessCode": "...",
        "videoTitle": "Episode 12",
        "stripeCheckoutSessionId": "cs_...",
        "stripePaymentIntentId": "pi_...",
        "stripeConnectAccountId": "acct_...",
        "createdAt": "2026-05-20T12:00:00.000Z",
        "completedAt": "2026-05-20T12:00:12.000Z",
        "purchasedAt": "2026-05-20T12:00:12.000Z",
        "video": { "id": "...", "title": "Episode 12", "thumbnailUrl": "..." }
      }
    ],
    "pagination": { "page": 1, "limit": 20, "total": 7, "pages": 1 }
  }
}

GET /monetization/revenue

Aggregate revenue summary. Optional ?period=7d|30d|90d filters the video-sales bucket (subscription MRR is always current). Defaults to 30d.

curl -s -H "Authorization: Bearer pk_YOUR_KEY_HERE" \
  "https://api.dcast.pro/api/v1/monetization/revenue?period=30d"

# Response
{
  "success": true,
  "data": {
    "period": "30d",
    "subscriptionRevenue": 49.95,
    "videoSalesRevenue": 14.97,
    "total": 64.92,
    "currency": "USD"
  }
}

Connect-status (creator-readable)

GET /monetization/connect-status is a read-only check that any pk_* key can call to ask "is this creator wired up for paid checkouts yet?". Branch your UI on it before showing PPV / subscription controls.

curl -s -H "Authorization: Bearer pk_YOUR_KEY_HERE" \
  "https://api.dcast.pro/api/v1/monetization/connect-status"

# No Connect account yet → 200
{ "success": true, "data": { "connected": false, "accountId": null } }

# Connect account live → 200
{ "success": true, "data": {
    "connected": true,
    "accountId": "acct_...",
    "detailsSubmitted": true,
    "chargesEnabled": true,
    "payoutsEnabled": true,
    "requirements": { ... }
} }

Admin-only payout endpoints

For reference: the two payout-account lifecycle endpoints below return 403 ADMIN_ONLY when called with a pk_* key. Onboarding and dashboard links are minted by DCAST support on the creator's behalf — creators read status (above) but don't self-serve the lifecycle.

MethodEndpointScope
POST/monetization/connect-onboardADMIN-ONLY
GET/monetization/connect-dashboardADMIN-ONLY

Tiers (read)

curl -s -H "Authorization: Bearer pk_YOUR_KEY_HERE" \
  "https://api.dcast.pro/api/v1/tiers?page=1&limit=50"

curl -s -H "Authorization: Bearer pk_YOUR_KEY_HERE" \
  "https://api.dcast.pro/api/v1/tiers/{id}"

List response shape:

{
  "success": true,
  "data": {
    "tiers": [
      { "id": "...", "name": "Pro", "price": 9.99, "subscribersCount": 12 }
    ],
    "pagination": { "page": 1, "limit": 50, "total": 3, "pages": 1 }
  }
}

POST /tiers — create a new subscription tier

Required: name, priceMonthly (≥ 1.00), benefits (non-empty array of strings). Optional: description, priceAnnual, trialDays, isMostPopular. Plan-based tier-count limit enforced (403 PLAN_LIMIT_EXCEEDED with current/limit/plan in error.details). If your Stripe Connect account is connected, the tier's Stripe Product + Prices are created automatically; otherwise the tier is created DB-only and the Stripe sync runs on first checkout.

curl -s -X POST -H "Authorization: Bearer pk_YOUR_KEY_HERE" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "name": "Pro",
    "description": "Early access + VIP chat",
    "priceMonthly": 19.00,
    "priceAnnual": 190.00,
    "benefits": ["1080p downloads", "Discord access"],
    "trialDays": 7,
    "isMostPopular": true
  }' \
  https://api.dcast.pro/api/v1/tiers
# → 201 { "success": true, "data": { id, name, priceMonthly, ..., stripePriceId } }

PATCH /tiers/:id — update a tier

Partial update. 409 TIER_PRICE_LOCKED if you try to change priceMonthly / priceAnnual while the tier has active subscribers (create a new tier or wait for current subscriptions to end). Setting isMostPopular: true automatically clears that flag on any other tier (exclusivity).

curl -s -X PATCH -H "Authorization: Bearer pk_YOUR_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{ "description": "Now with VIP DMs", "trialDays": 14 }' \
  https://api.dcast.pro/api/v1/tiers/{tierId}
# → 200 { "success": true, "data": { ...updated tier... } }

DELETE /tiers/:id — remove a tier

Only when no subscribers are attached. 409 TIER_HAS_SUBSCRIBERS with error.details.subscribersCount if any subscription is still bound to this tier; use PATCH /tiers/:id {"isActive": false} to deactivate instead.

curl -s -X DELETE -H "Authorization: Bearer pk_YOUR_KEY_HERE" \
  https://api.dcast.pro/api/v1/tiers/{tierId}
# → 200 { "success": true, "data": { "deleted": true, "id": "tier_..." } }

GET /tiers/:id — single tier shape

curl -s -H "Authorization: Bearer pk_YOUR_KEY_HERE" \
  https://api.dcast.pro/api/v1/tiers/{tierId}

# Response
{
  "success": true,
  "data": {
    "id": "tier_...",
    "name": "Pro",
    "description": "Includes early access + VIP chat",
    "position": 1,
    "isMostPopular": true,
    "isActive": true,
    "trialDays": 7,
    "benefits": ["1080p downloads", "Discord access"],
    "priceMonthly": 19.00,
    "priceAnnual": 190.00,
    "subscribersCount": 142
  }
}

Owner-scoped — a pk_* key only sees its own creator's tiers (foreign id → 404 NOT_FOUND). Public tier roster for any creator is exposed via GET /channel/:username instead.

GET /me/subscriptions — what I'm subscribed to

Lists every subscription where the API key owner is the buyer, not the creator, of any status, newest first — filter on status yourself. Not paginated. Needs a FULL_ACCESS key or finance:read.

curl -s -H "Authorization: Bearer pk_YOUR_KEY_HERE" \
  "https://api.dcast.pro/api/v1/me/subscriptions"

# Response
{
  "success": true,
  "data": {
    "subscriptions": [
      {
        "subscriptionId": "sub_...",
        "creatorUserId": "…",
        "creatorUsername": "pamela",
        "creatorDisplayName": "Pamela Reif",
        "creatorAvatarUrl": "https://…",
        "tierId": "tier_pamela_pro",
        "tierName": "Pro",
        "status": "ACTIVE",
        "interval": "month",
        "paymentMethod": "…",
        "subscribedAt": "2026-05-25T10:00:00.000Z",
        "currentPeriodEnd": "2026-06-25T18:00:00.000Z",
        "canceledAt": null
      }
    ]
  }
}

Free-tier subscribe / unsubscribe

Free-tier follows are the «Follow» / «Subscribe» primitive on creator storefronts. The buyer's identity comes from the API key owner; there is no body — the call creates a free follow with no tier. Paid tiers go through POST /checkout/subscription.

# Subscribe (free follow, no body).
curl -s -X POST https://api.dcast.pro/api/v1/users/{username}/subscribe \
  -H "Authorization: Bearer pk_YOUR_KEY_HERE"

# Unsubscribe (cancel a free-tier follow).
curl -s -X DELETE https://api.dcast.pro/api/v1/users/{username}/subscribe \
  -H "Authorization: Bearer pk_YOUR_KEY_HERE"

Rate limit: 60/hour/key. Foreign / banned username → 404 NOT_FOUND. Subscribing to your own creator account → 400 BAD_REQUEST. Already following → 200 with top-level idempotent: true. DELETE on a paid subscription → 400 PAID_SUBSCRIPTION.

Checkout sessions (sell access)

Initiate a paid subscription. The buyer is redirected to the hosted checkout page; on success they are returned to successUrl.

Required body fields for POST /checkout/subscription: tierId and creatorUsername (must match the tier owner — guards against tier-id swaps). Optional: interval (monthly default, or annual if the tier has priceAnnual set), successUrl, cancelUrl (default to the creator page on success/cancel). The buyer identity is taken from the API key owner — there is no customerEmail field.

Who buys. The checkout endpoint buys for the account that owns the API key — the subscription is granted to that account. An X-User-Token for any other user is refused 401 INVALID_USER_TOKEN (multi-user flow over a shared pk_ key is disabled). A site that sells to many viewers therefore cannot use one key for these calls; let the embeddable player sell instead. Walkthrough: Recipe: paid catalog.
curl -s -X POST https://api.dcast.pro/api/v1/checkout/subscription \
  -H "Authorization: Bearer pk_YOUR_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{
    "tierId": "...",
    "creatorUsername": "creator-handle",
    "interval": "monthly",
    "successUrl": "https://yoursite.com/success",
    "cancelUrl": "https://yoursite.com/cancel"
  }'

# Response
{
  "success": true,
  "data": {
    "sessionId": "cs_...",
    "checkoutUrl": "https://checkout..."
  }
}

Errors: 400 BAD_REQUEST if tierId / creatorUsername missing, or if creatorUsername does not match the tier owner, or if the tier is inactive, or if you try to subscribe to your own tier, or if you are already actively subscribed. 404 NOT_FOUND if the tier does not exist. 400 BAD_REQUEST with message "Creator has not set up payments" if the creator's Stripe Connect account is missing. 403 integration_domain_required / origin_not_allowed from the origin lock — see Domain verification.

POST /checkout/video is retired (410 ENDPOINT_RETIRED); paid videos are bought inside the embedded player.

Domain verification for payments

POST /checkout/subscription passes an origin lock before a payment is created. It works like this:

  • A request whose Origin (or Referer) is a DCAST host passes.
  • Otherwise the account must have at least one verified integration domain. With none, the call is refused 403 integration_domain_required — also when the request carries no Origin at all.
  • With at least one, a request from a verified domain or any of its subdomains passes; a request from any other origin is refused 403 origin_not_allowed. A server-to-server call without Origin/Referer passes.
  • If the domain lookup itself fails, the call is refused 403 origin_lock_unavailable; retry.

These refusals are a bare string, not the usual error object:

{ "success": false, "error": "integration_domain_required" }

Declare and verify a domain

  1. In the dashboard open Site customization → Domain → Integration domains and add your domain (for example shop.example.com). Up to 50 domains per account.
  2. The dashboard shows a DNS record to create: type TXT, host _dcast-verify.<your-domain>, value dcast-verify=<32 hex characters>.
  3. Create that TXT record at your DNS provider, wait for it to resolve, and press Verify. Until the record resolves the check answers verified: false with TXT_RECORD_NOT_FOUND.
  4. Only verified domains count; a declared but unverified domain does not open the lock. If a later Verify fails, the domain loses its verified status.

Declaring and verifying domains is done in the dashboard (signed-in session); there is no Partner API endpoint for it. The embed allowlist on Account controls where the player may be framed and is not part of this check. Purchases made inside the embeddable player come from the DCAST player origin and do not need your domain to be verified.

Purchases

curl -s -H "Authorization: Bearer pk_YOUR_KEY_HERE" \
  "https://api.dcast.pro/api/v1/purchases"

curl -s -H "Authorization: Bearer pk_YOUR_KEY_HERE" \
  "https://api.dcast.pro/api/v1/videos/{videoId}/purchases"

Returns purchases against the caller's content. Note: GET /purchases returns an aggregated summary (totalPurchases, totalRevenue, purchasesByVideo) with an _note field rather than canonical pagination. Both need a FULL_ACCESS key or finance:read.

Subscribers

Read, add, and remove subscribers on your own channel. All endpoints are owner-scoped to the API key holder. Free-tier subscribers (tier with priceMonthly = 0, or no tier at all) can be added directly via API. Paid tiers must go through POST /checkout/subscription — the API will not bypass payment.

MethodEndpointDescription
GET/subscribers?tierId=&status=&page=&limit= (alias: /me/subscribers)Paginated list of subscribers on your channel (canonical pagination). Optional tierId and status (ACTIVE | CANCELLED | EXPIRED | PAST_DUE) filters.
GET/subscribers/count?tierId=Aggregate counts: total, active, canceled, expired, pastDue, and a byTier map.
POST/subscribersAdd a free subscriber. Body: { email, tierId?, displayName?, source? }. Idempotent on (email, tier). 100/hour/key rate cap.
DELETE/subscribers/:subscriberUserId?tierId=Cancel a subscription. Paid subs are canceled upstream first; free follows are soft-deleted (status=CANCELLED).
GET/me/subscriptionsSubscriptions the API key owner holds on OTHER creators' channels.

Errors: PAID_TIER_USE_CHECKOUT (400) when posting to a paid tier — redirect to POST /checkout/subscription. ALREADY_SUBSCRIBED (409) when the subscriber is already active on a different tier — DELETE first to switch. TIER_INACTIVE (400) when the tier exists but isActive=false. NOT_FOUND (404) for unknown tierId (never reveals other creators' tiers). RATE_LIMITED (429) above 100 adds/hour/key.

Idempotency: posting the same (email, tierId) twice while the subscription is ACTIVE returns 200 with an extra idempotent: true flag in the envelope (no row created). Posting against a previously-cancelled subscription reactivates it. The unique constraint is (subscriberId, creatorId) — cross-tier moves on the same channel must DELETE first.

# Add a free subscriber
curl -s -X POST "https://api.dcast.pro/api/v1/subscribers" \
  -H "Authorization: Bearer pk_YOUR_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{"email":"[email protected]","tierId":"<free-tier-id>","displayName":"Viewer","source":"newsletter"}'

# Count subscribers
curl -s -H "Authorization: Bearer pk_YOUR_KEY_HERE" "https://api.dcast.pro/api/v1/subscribers/count"

# Unsubscribe
curl -s -X DELETE -H "Authorization: Bearer pk_YOUR_KEY_HERE" \
  "https://api.dcast.pro/api/v1/subscribers/<subscriberUserId>"

Security & scope

pk_* keys are creator-scoped: they can sell access (tiers, checkouts), read history (payments, revenue, purchases), and manage subscribers on their own channel. They cannot initiate payout / withdrawal flows, cannot read other creators' revenue, cannot mutate User.balance, and cannot bypass the paid-tier checkout path. Connect onboarding and dashboard-link minting are admin-only and return 403 ADMIN_ONLY when called with a creator key; GET /monetization/connect-status is read-only and open to any pk_* key on its own account.

Related Documentation

For stream creation and management see Streams API. For analytics and metrics see Analytics API.

Roadmap

Tier management is available now: POST /tiers, PATCH /tiers/:id, DELETE /tiers/:id (above). There is no PUT /tiers/:id (use PATCH) and no separate Stripe-sync endpoint: POST /tiers creates the Stripe product and prices itself when the Connect account is connected. Not implemented yet: gift checkout, and a method to grant a specific viewer access to a paid video or tier. See Roadmap.

Monetization API — dcast.pro API docs