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.

Account, keys & quota

Health, profile (read + write), avatar upload, embed allowlist, and storage quota. Every endpoint here is creator-scoped to the API key owner.

GET /health

No authentication. Use for uptime checks and load balancers.

curl -s https://api.dcast.pro/api/v1/health

GET /me

Full profile + key metadata. Response shape mirrors PATCH /me.

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

Response:

{
  "success": true,
  "data": {
    "keyId": "...",
    "keyName": "Production key",
    "scope": "FULL_ACCESS",
    "permissions": [],
    "rateLimit": 1000,
    "usageCount": 1234,
    "lastUsedAt": "2026-05-25T12:00:00.000Z",
    "createdAt": "2026-05-01T10:00:00.000Z",
    "plan": "PRO",
    "balance": 0,
    "avatar": "/avatars/abc.webp",
    "subscribersCount": 0,
    "totalVideosCount": 0,
    "totalLiveStreamsCount": 0,
    "embedAllowlist": ["yoursite.com", "*.yoursite.com"],
    "user": {
      "id": "...",
      "email": "[email protected]",
      "username": "yourname",
      "displayName": "Your Name",
      "name": "Your Name",
      "tagline": "Short one-liner",
      "bio": "Longer description",
      "website": "https://yoursite.com",
      "avatar": "/avatars/abc.webp",
      "avatarUrl": "https://api.dcast.pro/avatars/abc.webp",
      "coverImage": "https://...",
      "heroImageUrl": "https://...",
      "heroImagePosition": "center",
      "primaryColor": "#1d4ed8",
      "socials": { "twitter": "https://x.com/you", "youtube": "..." },
      "siteSettings": { },
      "country": "US",
      "region": "CA",
      "city": "San Francisco",
      "preferredLanguage": "en",
      "referralCode": "ref_abc",
      "emailVerified": true,
      "siteActive": true,
      "createdAt": "2026-05-01T10:00:00.000Z",
      "updatedAt": "2026-05-25T12:00:00.000Z"
    }
  }
}

Note on profile location: the profile object is returned at data.user (not data.profile). Top-level shortcuts data.avatar, data.plan, data.balance mirror selected fields for backward compatibility with pre-2026-05-25 clients.

PATCH /me

Update profile. Whitelisted writable fields:

  • displayName — sanitized; longer than 120 chars is truncated
  • name — sanitized; longer than 120 chars is truncated
  • tagline — sanitized; longer than 240 chars is truncated
  • bio — sanitized; longer than 4000 chars is truncated
  • website — must be valid http(s) URL
  • socials — object with keys: twitter, x, instagram, youtube, tiktok, facebook, linkedin, github, twitch, discord, telegram, website, threads, mastodon, bluesky. Each value must be a valid URL or omitted.
  • siteSettings — JSON object, deep-merged into existing (never wholesale-overwrites)
  • primaryColor — hex string (#RRGGBB)
  • preferredLanguage — short string
  • coverImage — /storage/... path, http(s) URL, or null to clear. Multipart upload via POST /me/cover.
  • heroImageUrl — creator-site hero banner. /storage/... path, http(s) URL, or null to clear. Mirrors what the cabinet /dashboard/site-customization editor writes via /api/creator/site/upload-hero-image.
  • heroImagePosition — CSS object-position keyword or percentage (e.g. center, top, 50% 30%). Max 60 chars.
curl -s -X PATCH https://api.dcast.pro/api/v1/me \
  -H "Authorization: Bearer pk_YOUR_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{
    "displayName": "New Display Name",
    "tagline": "Streaming weekly",
    "website": "https://yoursite.com",
    "socials": { "twitter": "https://x.com/me", "youtube": "https://youtube.com/@me" }
  }'

Returns the updated profile in the same shape as GET /me. Identity, plan and billing fields (id, email, username, role, plan, balance, …) are refused with 403 FORBIDDEN_FIELD naming the field in error.field; other unknown fields (including country, region, city) are ignored. Invalid URL or wrong type → 400 with the offending field key.

POST /me/avatar · DELETE /me/avatar

Multipart upload (multipart/form-data, field name file). Accepted MIME types: image/jpeg, image/png, image/webp, image/avif, image/gif. Hard cap 5 MB.

curl -s -X POST https://api.dcast.pro/api/v1/me/avatar \
  -H "Authorization: Bearer pk_YOUR_KEY_HERE" \
  -F "file=@/path/to/avatar.png"

Errors: 415 UNSUPPORTED_MEDIA_TYPE (wrong MIME), 413 PAYLOAD_TOO_LARGE (>5 MB), 400 NO_FILE (missing field). Successful response includes the new avatarUrl.

# Remove avatar
curl -s -X DELETE https://api.dcast.pro/api/v1/me/avatar \
  -H "Authorization: Bearer pk_YOUR_KEY_HERE"

Destructive: DELETE /me/avatar physically removes the avatar file from storage in addition to nulling the DB field. There is no soft-delete / undo. Keep your own copy of the original file if you might need to restore it. PATCH /me with an avatar field is ignored — to change the avatar, POST /me/avatar with the new file.

POST /me/cover · DELETE /me/cover

Multipart upload of the storefront cover/banner strip (multipart/form-data, field name file). Accepted MIME types: image/jpeg, image/png, image/webp, image/avif, image/gif. Hard cap 12 MB.

curl -s -X POST https://api.dcast.pro/api/v1/me/cover \
  -H "Authorization: Bearer pk_YOUR_KEY_HERE" \
  -F "file=@/path/to/cover.png"

Errors: 415 UNSUPPORTED_MEDIA_TYPE (wrong MIME), 413 PAYLOAD_TOO_LARGE (>12 MB), 400 NO_FILE (missing field). Successful response includes coverImage (path) and coverImageUrl (absolute URL).

# Remove cover
curl -s -X DELETE https://api.dcast.pro/api/v1/me/cover \
  -H "Authorization: Bearer pk_YOUR_KEY_HERE"

Destructive: DELETE /me/cover physically removes the cover file from storage in addition to nulling coverImage. There is no soft-delete / undo.

POST /me/hero-video · DELETE /me/hero-video

Storefront hero video (separate from the hero image — both can be set; UI picks one). Field name file; accepted MIME: video/mp4, video/webm, video/quicktime; hard cap 100 MB (override via PARTNER_HERO_VIDEO_MAX_BYTES env).

curl -s -X POST https://api.dcast.pro/api/v1/me/hero-video \
  -H "Authorization: Bearer pk_YOUR_KEY_HERE" \
  -F "file=@/path/to/hero.mp4"
# → { data: { heroVideo, heroVideoUrl, sizeBytes, mimeType } }

POST /me/slider-images · DELETE /me/slider-images

Storefront image-slider — multi-file upload. Up to 10 files per request (12 MB each). Appends to User.heroSliderImages (JSON array of public paths). DELETE removes one image by its image path.

# Upload (multipart array — field name "files")
curl -s -X POST https://api.dcast.pro/api/v1/me/slider-images \
  -H "Authorization: Bearer pk_YOUR_KEY_HERE" \
  -F "files=@/path/to/slide1.jpg" \
  -F "files=@/path/to/slide2.jpg"
# → { data: { sliderImages: [<all current>], added: [<just added>] } }

# Remove one by its public path
curl -s -X DELETE \
  -H "Authorization: Bearer pk_YOUR_KEY_HERE" \
  "https://api.dcast.pro/api/v1/me/slider-images?image=/storage/creators/<userId>/slider/slider-...png"
# → { data: { sliderImages: [<remaining>] } } | 404 NOT_FOUND if image not in list

POST /me/hero · DELETE /me/hero

Multipart upload of the storefront hero banner image (multipart/form-data, field name file). Accepted MIME types: image/jpeg, image/png, image/webp, image/avif, image/gif. Hard cap 12 MB. Mirrors what the cabinet's storefront editor writes — uploads land at /storage/creators/<userId>/hero/hero-<ts>-<rand>.<ext> and User.heroImageUrl is updated atomically.

An optional position form field updates heroImagePosition in the same call (saves a follow-up PATCH /me). Accepted values: any CSS object-position keyword or percentage pair (center, top, 50% 30%; max 60 chars).

curl -s -X POST https://api.dcast.pro/api/v1/me/hero \
  -H "Authorization: Bearer pk_YOUR_KEY_HERE" \
  -F "file=@/path/to/hero.png" \
  -F "position=center"

Response body shape:

{
  "success": true,
  "data": {
    "hero": "/storage/creators/<userId>/hero/hero-<ts>-<rand>.png",
    "heroUrl": "https://dcast.tv/storage/creators/<userId>/hero/hero-<ts>-<rand>.png",
    "sizeBytes": 184320,
    "mimeType": "image/png",
    "heroImagePosition": "center"
  }
}

Errors: 415 UNSUPPORTED_MEDIA_TYPE (wrong MIME), 413 PAYLOAD_TOO_LARGE (>12 MB), 400 NO_FILE (missing file field).

# Remove hero
curl -s -X DELETE https://api.dcast.pro/api/v1/me/hero \
  -H "Authorization: Bearer pk_YOUR_KEY_HERE"

Destructive: DELETE /me/hero physically removes hero image files from storage in addition to nulling heroImageUrl. There is no soft-delete / undo. The endpoint only manages the hero image — any hero video set via the cabinet (heroVideoUrl) is left untouched and must be cleared through the dashboard. To reposition an existing hero without re-uploading, send PATCH /me with {"heroImagePosition":"top"}.

POST /me/og-image · DELETE /me/og-image

Open Graph share image (used when your channel page is linked in social-media previews / chat unfurls). Same multipart shape as /me/hero: field file, image MIME, 12 MB cap. Updates User.ogImageUrl.

curl -s -X POST https://api.dcast.pro/api/v1/me/og-image \
  -H "Authorization: Bearer pk_YOUR_KEY_HERE" \
  -F "file=@/path/to/og.png"
# Response: { data: { og, ogUrl, sizeBytes, mimeType } }

POST /me/navigation-logo · DELETE /me/navigation-logo

Storefront navigation / header logo. Same multipart shape. Updates User.navigationLogoUrl.

curl -s -X POST https://api.dcast.pro/api/v1/me/navigation-logo \
  -H "Authorization: Bearer pk_YOUR_KEY_HERE" \
  -F "file=@/path/to/logo.png"

Site templates & activation

Apply a published template (mapping its siteColors / siteFonts / siteLayout / siteSettings / hero-overlay defaults onto your account in one request), then flip the storefront on/off.

# List templates
curl -s -H "Authorization: Bearer pk_YOUR_KEY_HERE" https://api.dcast.pro/api/v1/me/site/templates
# → 200 { data: [{ id, slug, name, description, category, thumbnailUrl, ... }] }

# Apply one
curl -s -X POST -H "Authorization: Bearer pk_YOUR_KEY_HERE" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"slug":"streamer"}' https://api.dcast.pro/api/v1/me/site/apply-template
# → 200 { data: { applied: { id, slug, name }, user: {...partial user with the new template fields...} } }

# Read site status (siteActive + subdomain + customDomain + verification)
curl -s -H "Authorization: Bearer pk_YOUR_KEY_HERE" https://api.dcast.pro/api/v1/me/site/status

# Toggle site live/offline
curl -s -X POST -H "Authorization: Bearer pk_YOUR_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{"active":true}' https://api.dcast.pro/api/v1/me/site/activate
# 409 NO_HOSTNAME if you call active=true without a configured subdomain
# or a verified customDomain (set them with the endpoints below).

Custom domain + subdomain

Two paths to give your storefront a public hostname:

  • Subdomain — yourname.dcast.pro. Free on all plans.
  • Custom domain — watch.yourbrand.com. Requires PRO or VIP plan and DNS NS change to ours. CF zone is provisioned automatically.

Subdomain

# Claim
POST https://api.dcast.pro/api/v1/me/subdomain   { "subdomain": "yourname" }
# → 201 { data: { subdomain, hostname: "yourname.dcast.pro", subdomainDnsStatus: "pending" } }
# 409 SUBDOMAIN_TAKEN | 409 SUBDOMAIN_RESERVED | 400 invalid format

# Read state
GET https://api.dcast.pro/api/v1/me/subdomain
# → 200 { data: { subdomain, subdomainActive, subdomainDnsStatus, subdomainDnsCheckedAt } }

# Disconnect
DELETE https://api.dcast.pro/api/v1/me/subdomain

Subdomain regex: ^[a-z0-9](?:[a-z0-9-]{1,30}[a-z0-9])?$ (1-32 chars, no leading/trailing hyphen). DNS pool provisioning runs out-of-band; poll GET /me/site/status until subdomainDnsStatus="active".

Custom domain

# 1. Claim — provisions a CF zone, returns assigned nameservers
POST https://api.dcast.pro/api/v1/me/custom-domain   { "domain": "watch.yourbrand.com" }
# → 201 { data: { domain, nameservers: ["..ns.cloudflare.com", "..ns.cloudflare.com"],
#                 zoneStatus: "pending", instructions: "Point your domain's NS to..." } }
# 403 PLAN_LIMIT (not on PRO/VIP) | 409 DOMAIN_TAKEN | 400 invalid hostname

# 2. Change your domain's NS records at your registrar (Namecheap / GoDaddy / etc.)
#    to the two nameservers in the response. Propagation: up to 24h.

# 3. Re-fetch nameservers (idempotent — also creates zone if step 1's CF call failed)
GET https://api.dcast.pro/api/v1/me/custom-domain/nameservers
# → 200 { data: { domain, zoneId, nameservers, zoneStatus, instructions } }

# 4. Verify (resolves your domain's NS; if NS matches what CF assigned, marks the
#    domain verified + flips SSL to active)
GET https://api.dcast.pro/api/v1/me/custom-domain/verify
# → 200 { data: { domain, verified: true, sslStatus: "active", nsMatch: true,
#                 nsExpected: [...], nsActual: [...] } }
# 200 with verified=false until NS resolves to what we expect.

# Read current state
GET https://api.dcast.pro/api/v1/me/custom-domain
# → 200 { data: { customDomain, customDomainVerified, customDomainVerifiedAt,
#                 customDomainSslStatus, cloudflareZoneId, cloudflareNameservers,
#                 cloudflareZoneStatus } }

# Disconnect — clears the column + nulls the CF zone reference (zone teardown
# on the CF side is a manual operator step; the domain just goes back to your
# registrar's default behavior).
DELETE https://api.dcast.pro/api/v1/me/custom-domain

GET /me/payouts — Stripe Connect readiness

Lets your integration answer "can I accept payments?" before configuring monetization on a video / stream. Returns the live Stripe Connect status of your account so your UI can render the right onboarding CTA.

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

# When NO Connect account yet:
{ "success": true, "data": {
  "connected": false, "chargesEnabled": false, "payoutsEnabled": false,
  "detailsSubmitted": false, "requirements": null, "stripeAccountId": null,
  "onboardingUrl": null,
  "message": "No Stripe Connect account yet. Connect a payout account in the DCAST dashboard under Monetization -> Settings; onboarding is not available through the API."
} }

# When onboarded:
{ "success": true, "data": {
  "connected": true, "chargesEnabled": true, "payoutsEnabled": true,
  "detailsSubmitted": true, "requirements": { ... Stripe Requirements shape ... },
  "stripeAccountId": "acct_...",
  "message": "Ready to accept payments + receive payouts."
} }

The not-connected message points to the DCAST dashboard, Monetization → Settings, where the payout account is connected; POST /v1/monetization/connect-onboard is admin-only and answers 403 ADMIN_ONLY to a partner key.

GET /me/api-keys

List your own partner API keys (this account's pk_'s). Key material (key field) is not selected — a listed key is never re-readable, only replaceable.

curl -s -H "Authorization: Bearer pk_YOUR_KEY_HERE" https://api.dcast.pro/api/v1/me/api-keys
# → { data: [{ id, name, scope, permissions, rateLimit, lastUsedAt, usageCount,
#              allowedIps, allowedDomains, embedAllowlist, isActive, expiresAt,
#              createdAt, updatedAt }] }

POST /me/api-keys

Mint a role-based sub-key. Owner-class credential required: authenticate with a FULL_ACCESS key, or send an X-User-Token from POST /auth/login. A READ_ONLY or STANDARD key is refused with 403 OWNER_SCOPE_REQUIRED — otherwise a low-privilege key could mint itself a high-privilege one. The plaintext pk_ is returned once, in this response, and never again.

curl -s -X POST -H "Authorization: Bearer pk_YOUR_FULL_ACCESS_KEY" \
  -H "Content-Type: application/json" \
  -d '{"role":"content_manager","name":"CI pipeline"}' \
  https://api.dcast.pro/api/v1/me/api-keys
# → { data: { id, key: "pk_…", name, role, scope, permissions, … } }

DELETE /me/api-keys/:id

Revoke a key. Revoking flips isActive to false and keeps the row — the audit question after an incident is which key was live and when it was killed, and a deleted row cannot answer it. An owner-class credential may revoke any key on the account, and any key may revoke itself, so whoever notices a leak can stop it. Another account's key id reads as 404, never 403.

curl -s -X DELETE -H "Authorization: Bearer pk_YOUR_KEY_HERE" \
  https://api.dcast.pro/api/v1/me/api-keys/KEY_ID

GET /me/tiers

Alias of GET /tiers — your own subscription tier catalogue with per-tier subscribersCount denorm.

GET /me/restream-accounts

Your connected platform accounts for restreaming (YouTube / Twitch / FB / etc.). OAuth tokens (accessToken, refreshToken) are not selected; only metadata visible to you.

curl -s -H "Authorization: Bearer pk_YOUR_KEY_HERE" https://api.dcast.pro/api/v1/me/restream-accounts
# → { data: [{ id, platform, platformAccountId, displayName, avatarUrl,
#              scope, expiresAt, revokedAt, metadata, createdAt, updatedAt }] }

GET /me/bookings

Bookings you made AS A CUSTOMER (you bought another creator's product). Different from GET /booking/bookings which is the creator-side view of incoming bookings on YOUR products.

curl -s -H "Authorization: Bearer pk_YOUR_KEY_HERE" \
  "https://api.dcast.pro/api/v1/me/bookings?status=CONFIRMED&page=1&limit=25"
# ?status accepts PENDING|CONFIRMED|IN_PROGRESS|COMPLETED|CANCELED|REFUNDED|NO_SHOW

GET /me/notifications

Your notifications. Includes unreadCount denorm at the top level for badge UIs.

curl -s -H "Authorization: Bearer pk_YOUR_KEY_HERE" \
  "https://api.dcast.pro/api/v1/me/notifications?unread=true&page=1&limit=25"
# → { data: [{ id, userId, type, title, body, metadata, read, createdAt }],
#     pagination: { page, limit, total, pages },
#     unreadCount: <int> }

Embed allowlist

One list per account (not per key): the sites that may show this account's videos and streams. It is the same list as the dashboard's embed settings, and an empty list means every site. It restricts two things at once:

  • where the DCAST /embed iframe may be framed (frame-ancestors on the embed page);
  • which sites' own browser players may read playback (access-token, master, keys, segments) cross-origin. A player on your own site additionally needs a PRO or VIP plan — see Paid catalog → Option B.

Entry forms: yoursite.com (also covers www.yoursite.com), *.yoursite.com (any subdomain, not the apex). A scheme, a trailing slash and a port are dropped on save.

GET /me → embedAllowlist: [...]

Returned in the GET /me payload above.

PATCH /me/embed-allowlist

curl -s -X PATCH https://api.dcast.pro/api/v1/me/embed-allowlist \
  -H "Authorization: Bearer pk_YOUR_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{"domains":["yoursite.com","*.yoursite.com"]}'

# 200
{ "success": true, "data": { "embedAllowlist": ["yoursite.com", "*.yoursite.com"], "scope": "account" } }

Validation: each entry must be a domain in one of the forms above; lowercased, duplicates removed, max 50 entries; anything else is400 BAD_REQUEST and nothing is written. Returns the full updated list. To clear (every site allowed): {"domains":[]}. The per-key embedAllowlist field that key listings still show is deprecated and is not written by this endpoint.

Permissions / scope

scope on a pk_* key is one of FULL_ACCESS (all methods), STANDARD (every method except DELETE) or READ_ONLY (GET / HEAD / OPTIONS only); a method outside the scope is refused 403 INSUFFICIENT_SCOPE. permissions narrows a key further (e.g. videos:*, streams:read, finance:read, *); an empty list means the scope alone decides. Keys minted with POST /me/api-keys take their scope and permissions from role, one of owner, admin, content_manager, stream_technician, moderator, viewer, observer, editor (lowercase; anything else is 400 VALIDATION_ERROR).

Reserved permission tokens (subject to change): videos:write, videos:delete, streams:write, streams:delete, restreams:write, restreams:delete, rooms:write, rooms:delete, streams:operate, streams:moderate, finance:read, finance:write. None of these grant admin operations or payout flows under any scope.

GET /quota

Storage and usage quota for the partner account (used vs limit, formatted bytes, warnings when applicable).

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

data holds used, limit, available (bytes), usedPercent, isExceeded, formatted.{used, limit, available}, warnings, quotaStatus (OK | NEAR_LIMIT | CRITICAL | EXCEEDED_GRACE | EXCEEDED_HARD) and gracePeriodDaysRemaining (days left in the 14-day grace period once isExceeded is true, otherwise null; it is not a billing-cycle reset).

Security & scope

pk_* keys are creator-scoped. They can read and mutate only resources owned by the account that minted them. They cannot:

  • create API keys unless the key is FULL_ACCESS (or the call carries the owner's X-User-Token) — see API keys above
  • read other creators' private profiles, videos, streams, or analytics
  • initiate or configure payout / withdrawal flows (admin-only; reading /me/payouts is allowed — see Monetization)
  • manage infrastructure, fleet routing, or any server-side metadata

Cross-creator access on owner-only endpoints returns 404 NOT_FOUND (we never reveal whether a foreign id exists). Admin-gated endpoints return 403 ADMIN_ONLY when called with a non-admin key.

Related

Upload responses may include quota details when limits are hit (QUOTA_EXCEEDED). Account-level rollups also appear under Analytics (/analytics/account; aliases /account/analytics, /summary, /stats).

Account, keys & quota — dcast.pro API docs