{"openapi":"3.0.3","info":{"title":"Teve API","version":"1.1.0","description":"Complete REST API for the Teve video broadcasting platform. Use it from your website, a React front-end, or native mobile apps to run the whole product: authentication, streams and playback URLs, the media directory (VOD) and playlists, live/VOD failover, scheduling, subscriptions and billing, monetization, IPTV, CDN, custom domains and white-label branding, support tickets, notifications, analytics, roles, and API-key management.\n\n## Base URL\n```\nhttps://app.teve.ca/api/v1\n```\nEvery path in this document is relative to that base. The unversioned `https://app.teve.ca/api/...` routes are identical in behaviour; `/api/v1` is the stable, versioned alias to build against.\n\n## Authentication\nTwo credential types are accepted on every authenticated endpoint, sent the same way:\n\n```\nAuthorization: Bearer <token>\n```\n\n1. **API key** — `tev_live_xxxxxxxxxxxxxxxx`. Create and revoke keys in the Teve console under **Developer → API Keys**, or via `/keys`. Keys carry a scope: `read` (GET/HEAD only) or `write` (all methods). A key may also be passed as `X-API-Key: tev_live_...`. Best for servers, cron jobs, and back-end integrations.\n2. **User JWT** — obtained from `POST /auth/login` (or `/auth/google`, `/auth/apple`). Represents a signed-in person and inherits that user's role and plan. Best for mobile apps and browser front-ends where a real user signs in.\n\n`/public/*`, `/auth/*` (login/register/OTP/social), `/settings/public`, `/settings/social-public`, `/subscription/plans`, and `/branding` need no credential.\n\n## Responses\nSuccess bodies are JSON. The versioned resource endpoints wrap payloads as `{ \"data\": ... , \"meta\": { ... } }`. Errors are `{ \"error\": { \"code\": \"invalid_api_key\", \"message\": \"...\" } }` with a matching HTTP status (some legacy routes return `{ \"success\": false, \"error\": \"...\" }`).\n\n## Rate limits\n120 requests/minute per key or token on authenticated endpoints; 60/minute on public endpoints. `429` with `Retry-After` when exceeded.\n\n## White-label\nWhite-label branding for a customer domain is served from `GET /branding`, keyed off the request `Host` header. The primary hosts (`app.teve.ca`, `teve.ca`) always return the default Teve brand. \"Powered by Siman Tech\" and copyright are fixed and never overridden."},"servers":[{"url":"https://app.teve.ca/api/v1"}],"security":[{"BearerAuth":[]}],"tags":[{"name":"Meta","description":"API metadata and machine-readable docs."},{"name":"Auth","description":"Registration, login (password / OTP / Google / Apple), email verification, current user, password change."},{"name":"Account","description":"The authenticated account, its plan, limits and usage."},{"name":"Streams","description":"Live RTMP→HLS/DASH streams: CRUD, ingest credentials, live status, key rotation."},{"name":"Media","description":"The media directory (VOD): directories, assets, upload, captions, downloads."},{"name":"Playlists","description":"Ordered collections of VOD assets for playout and failover."},{"name":"Scheduling","description":"Playlist schedules and playout mode (live vs. playlist)."},{"name":"Failover","description":"Automatic switch to VOD playout when a live stream drops, and back when it returns."},{"name":"Subscription","description":"Plans, the current subscription, usage and payment history."},{"name":"Billing","description":"Stripe payment intents, checkout sessions, customer portal, cancel / reactivate."},{"name":"Monetization","description":"Ad configuration and paywall / monetization feature flags."},{"name":"IPTV","description":"IPTV channel line-up and generated M3U playlists."},{"name":"CDN","description":"CDN provider configuration and delivery status."},{"name":"Domains","description":"Custom domains and per-account white-label branding (logo + brand name)."},{"name":"Branding","description":"Public white-label resolution by Host."},{"name":"Tickets","description":"Support tickets and threaded replies."},{"name":"Notifications","description":"In-app notifications and history."},{"name":"Analytics","description":"Watch time, sessions, unique viewers and bandwidth."},{"name":"Roles","description":"Roles, permissions and assignment (admin)."},{"name":"Settings","description":"Platform settings; most require admin, a few are public."},{"name":"SEO","description":"SEO / GEO / AEO metadata: the merged <head> payload for any route, global config, FAQ schema, robots.txt, sitemap.xml and llms.txt — all managed from the admin console."},{"name":"API Keys","description":"Create, list and revoke your own API keys."}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"tev_live_... (API key) or a user JWT from /auth/login","description":"Send either an API key (tev_live_...) or a user JWT. An API key may alternatively be sent as the X-API-Key header."}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}}}}},"Playback":{"type":"object","properties":{"hls":{"type":"string","example":"https://app.teve.ca/mystream/index.m3u8"},"hls_master":{"type":"string","nullable":true},"dash":{"type":"string","nullable":true},"renditions":{"type":"object","properties":{"uhd":{"type":"string","nullable":true},"fhd":{"type":"string","nullable":true},"hd":{"type":"string","nullable":true},"sd":{"type":"string","nullable":true}}},"embed":{"type":"string"}}},"Stream":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"description":{"type":"string","nullable":true},"status":{"type":"string","enum":["inactive","active","broadcasting","disconnected"]},"is_live":{"type":"boolean"},"viewers":{"type":"integer"},"created_at":{"type":"string","format":"date-time"},"ingest":{"$ref":"#/components/schemas/Ingest"},"playback":{"$ref":"#/components/schemas/Playback"}}},"VodAsset":{"type":"object","properties":{"id":{"type":"integer"},"title":{"type":"string"},"filename":{"type":"string"},"duration_seconds":{"type":"number","nullable":true},"size_bytes":{"type":"integer","nullable":true},"status":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"playback":{"type":"object","properties":{"url":{"type":"string","nullable":true}}}}},"Playlist":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"description":{"type":"string","nullable":true},"items":{"type":"integer"},"loop":{"type":"boolean"},"shuffle":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"}}},"Plan":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"price_monthly":{"type":"integer","description":"cents"},"price_yearly":{"type":"integer","description":"cents"},"max_streams":{"type":"integer"},"max_storage_gb":{"type":"integer"},"max_bandwidth_gb":{"type":"integer"},"allow_custom_domains":{"type":"boolean"},"allow_iptv":{"type":"boolean"},"allow_cdn_config":{"type":"boolean"},"max_iptv_channels":{"type":"integer"},"max_bitrate_kbps":{"type":"integer","nullable":true},"max_resolution":{"type":"string","nullable":true},"max_fps":{"type":"integer","nullable":true}}},"EncoderLimits":{"type":"object","properties":{"max_bitrate":{"type":"integer","description":"bits per second"},"max_resolution":{"type":"string","example":"1920x1080"},"max_fps":{"type":"integer","example":30}}},"Ingest":{"type":"object","description":"Publish target for the owner. In GET /streams (collection) stream_key is null unless ?include=ingest is passed.","properties":{"rtmp_url":{"type":"string","example":"rtmp://app.teve.ca/live/<key>"},"rtmps_url":{"type":"string","nullable":true,"description":"TLS ingest; null until the origin terminates TLS on the ingest port"},"stream_key":{"type":"string","nullable":true},"limits":{"$ref":"#/components/schemas/EncoderLimits"}}},"IngestError":{"type":"object","nullable":true,"properties":{"code":{"type":"string","enum":["key_invalid","key_in_use","plan_limit_reached","account_suspended","bitrate_exceeded"]},"message":{"type":"string"},"at":{"type":"string","format":"date-time"}}},"StreamStatus":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"status":{"type":"string"},"is_live":{"type":"boolean"},"viewers":{"type":"integer"},"bandwidth_bytes_per_sec":{"type":"integer"},"last_active_at":{"type":"string","format":"date-time","nullable":true},"publishing_from":{"type":"string","nullable":true,"description":"ingest client IP while publishing"},"last_ingest_error":{"$ref":"#/components/schemas/IngestError"}}},"IngestStats":{"type":"object","properties":{"connected":{"type":"boolean"},"stream_id":{"type":"integer"},"uptime_seconds":{"type":"integer","nullable":true},"received_bytes":{"type":"integer","nullable":true},"ingest_bitrate_bps":{"type":"integer","nullable":true},"video":{"type":"object","properties":{"width":{"type":"integer","nullable":true},"height":{"type":"integer","nullable":true},"fps":{"type":"number","nullable":true},"codec":{"type":"string","nullable":true},"bitrate_bps":{"type":"integer","nullable":true}}},"audio":{"type":"object","properties":{"codec":{"type":"string","nullable":true},"sample_rate":{"type":"integer","nullable":true},"channels":{"type":"integer","nullable":true},"bitrate_bps":{"type":"integer","nullable":true}}},"dropped_frames":{"type":"integer","nullable":true,"description":"not exposed by the ingest server"},"keyframe_interval_seconds":{"type":"number","nullable":true,"description":"not exposed by the ingest server"}}}},"parameters":{"IdInPath":{"name":"id","in":"path","required":true,"schema":{"type":"integer"}},"Period":{"name":"period","in":"query","schema":{"type":"string","enum":["today","week","month","year"],"default":"week"}}},"responses":{"Unauthorized":{"description":"Missing or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"Authenticated but not allowed (wrong scope, role, or plan)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/":{"get":{"tags":["Meta"],"summary":"API metadata","security":[],"responses":{"200":{"description":"Name, version and docs URLs"}}}},"/openapi.json":{"get":{"tags":["Meta"],"summary":"This document","security":[],"responses":{"200":{"description":"OpenAPI 3 spec"}}}},"/docs":{"get":{"tags":["Meta"],"summary":"Rendered API reference (Redoc)","security":[],"responses":{"200":{"description":"HTML"}}}},"/public/streams/{name}":{"get":{"tags":["Streams"],"summary":"Public stream status (no credential)","description":"Live/offline state and playback URLs for a stream, by name or key. Safe to call from a browser.","security":[],"parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Stream","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Stream"}}}}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/auth/register":{"post":{"tags":["Auth"],"summary":"Register with email + password","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["username","email","password"],"properties":{"username":{"type":"string"},"email":{"type":"string"},"password":{"type":"string"}}}}}},"responses":{"201":{"description":"Created"},"400":{"description":"Validation error"}}}},"/auth/login":{"post":{"tags":["Auth"],"summary":"Log in, returns a user JWT","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["password"],"properties":{"username":{"type":"string"},"email":{"type":"string"},"password":{"type":"string"}}}}}},"responses":{"200":{"description":"{ token, user, hasSubscription }"},"400":{"description":"Missing fields"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/auth/google":{"post":{"tags":["Auth"],"summary":"Sign in / up with a Google ID token","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["token"],"properties":{"token":{"type":"string","description":"Google credential (ID token)"}}}}}},"responses":{"200":{"description":"{ token, user }"}}}},"/auth/apple":{"post":{"tags":["Auth"],"summary":"Sign in / up with an Apple identity token","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["token"],"properties":{"token":{"type":"string"},"name":{"type":"string","nullable":true}}}}}},"responses":{"200":{"description":"{ token, user }"}}}},"/auth/send-otp":{"post":{"tags":["Auth"],"summary":"Send a registration OTP","security":[],"responses":{"200":{"description":"OTP sent"}}}},"/auth/verify-otp-register":{"post":{"tags":["Auth"],"summary":"Verify OTP and complete registration","security":[],"responses":{"200":{"description":"Registered"}}}},"/auth/resend-otp":{"post":{"tags":["Auth"],"summary":"Resend a registration OTP","security":[],"responses":{"200":{"description":"Sent"}}}},"/auth/send-otp-login":{"post":{"tags":["Auth"],"summary":"Send a login OTP","security":[],"responses":{"200":{"description":"Sent"}}}},"/auth/verify-otp-login":{"post":{"tags":["Auth"],"summary":"Verify a login OTP, returns a user JWT","security":[],"responses":{"200":{"description":"{ token, user }"}}}},"/auth/verify-email":{"get":{"tags":["Auth"],"summary":"Verify an email address from a link token","security":[],"parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Verified"}}}},"/auth/resend-verification":{"post":{"tags":["Auth"],"summary":"Resend the verification email","security":[],"responses":{"200":{"description":"Sent"}}}},"/auth/me":{"get":{"tags":["Auth"],"summary":"The current user (JWT only)","responses":{"200":{"description":"User"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/auth/change-password":{"post":{"tags":["Auth"],"summary":"Change the current user's password","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["currentPassword","newPassword"],"properties":{"currentPassword":{"type":"string"},"newPassword":{"type":"string"}}}}}},"responses":{"200":{"description":"Changed"}}}},"/auth/users":{"get":{"tags":["Auth"],"summary":"List users (admin)","responses":{"200":{"description":"OK"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/auth/users/{id}":{"put":{"tags":["Auth"],"summary":"Update a user (admin)","parameters":[{"$ref":"#/components/parameters/IdInPath"}],"responses":{"200":{"description":"OK"}}},"delete":{"tags":["Auth"],"summary":"Delete a user (admin)","parameters":[{"$ref":"#/components/parameters/IdInPath"}],"responses":{"200":{"description":"Deleted"}}}},"/account":{"get":{"tags":["Account"],"summary":"Account, plan, limits and usage","responses":{"200":{"description":"OK"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/streams":{"get":{"tags":["Streams"],"summary":"List your streams","responses":{"200":{"description":"OK"}},"parameters":[{"name":"include","in":"query","required":false,"schema":{"type":"string","enum":["ingest"]},"description":"Pass `ingest` to include ingest.stream_key in the collection response."}]},"post":{"tags":["Streams"],"summary":"Create a stream","x-scope":"write","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string"},"description":{"type":"string"}}}}}},"responses":{"201":{"description":"Created"},"422":{"description":"Validation error"}}}},"/streams/{id}":{"get":{"tags":["Streams"],"summary":"Get a stream","parameters":[{"$ref":"#/components/parameters/IdInPath"}],"responses":{"200":{"description":"OK"},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"tags":["Streams"],"summary":"Update name / description","x-scope":"write","parameters":[{"$ref":"#/components/parameters/IdInPath"}],"responses":{"200":{"description":"OK"}}},"delete":{"tags":["Streams"],"summary":"Delete a stream","x-scope":"write","parameters":[{"$ref":"#/components/parameters/IdInPath"}],"responses":{"200":{"description":"Deleted"}}}},"/streams/{id}/status":{"get":{"tags":["Streams"],"summary":"Lightweight live status","parameters":[{"$ref":"#/components/parameters/IdInPath"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/StreamStatus"}}}}}}},"description":"Single-publisher policy: a second connection on a live key is rejected (last_ingest_error.code = \"key_in_use\"); the running broadcast is never evicted. `is_live` flips within ~2s of connect/disconnect."}},"/streams/{id}/key/rotate":{"post":{"tags":["Streams"],"summary":"Rotate the stream key","x-scope":"write","parameters":[{"$ref":"#/components/parameters/IdInPath"}],"responses":{"200":{"description":"OK"}}}},"/media/directories":{"get":{"tags":["Media"],"summary":"List media directories","responses":{"200":{"description":"OK"}}},"post":{"tags":["Media"],"summary":"Create a directory","x-scope":"write","responses":{"201":{"description":"Created"}}}},"/media/directories/{id}":{"get":{"tags":["Media"],"summary":"Get a directory","parameters":[{"$ref":"#/components/parameters/IdInPath"}],"responses":{"200":{"description":"OK"}}},"put":{"tags":["Media"],"summary":"Rename / update a directory","x-scope":"write","parameters":[{"$ref":"#/components/parameters/IdInPath"}],"responses":{"200":{"description":"OK"}}},"delete":{"tags":["Media"],"summary":"Delete a directory","x-scope":"write","parameters":[{"$ref":"#/components/parameters/IdInPath"}],"responses":{"200":{"description":"Deleted"}}}},"/media/assets":{"get":{"tags":["Media"],"summary":"List VOD assets","responses":{"200":{"description":"Array of VodAsset"}}}},"/media/assets/upload":{"post":{"tags":["Media"],"summary":"Upload a video file","x-scope":"write","requestBody":{"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary"},"directory_id":{"type":"integer"},"title":{"type":"string"}}}}}},"responses":{"201":{"description":"Created"}}}},"/media/assets/{id}":{"get":{"tags":["Media"],"summary":"Get a VOD asset","parameters":[{"$ref":"#/components/parameters/IdInPath"}],"responses":{"200":{"description":"VodAsset"}}},"put":{"tags":["Media"],"summary":"Update title / description / directory","x-scope":"write","parameters":[{"$ref":"#/components/parameters/IdInPath"}],"responses":{"200":{"description":"OK"}}},"delete":{"tags":["Media"],"summary":"Delete a VOD asset","x-scope":"write","parameters":[{"$ref":"#/components/parameters/IdInPath"}],"responses":{"200":{"description":"Deleted"}}}},"/media/assets/{id}/thumbnail":{"get":{"tags":["Media"],"summary":"Asset thumbnail image","parameters":[{"$ref":"#/components/parameters/IdInPath"}],"responses":{"200":{"description":"image/jpeg"}}}},"/media/assets/{id}/download":{"get":{"tags":["Media"],"summary":"Download the original file","parameters":[{"$ref":"#/components/parameters/IdInPath"}],"responses":{"200":{"description":"video/*"}}}},"/media/stream/{assetId}":{"get":{"tags":["Media"],"summary":"Progressive playback of an asset","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"video/*"}}}},"/media/assets/{id}/captions":{"get":{"tags":["Media"],"summary":"List caption tracks","parameters":[{"$ref":"#/components/parameters/IdInPath"}],"responses":{"200":{"description":"OK"}}},"post":{"tags":["Media"],"summary":"Add a caption track","x-scope":"write","parameters":[{"$ref":"#/components/parameters/IdInPath"}],"responses":{"201":{"description":"Created"}}}},"/media/assets/{id}/captions/{language}":{"delete":{"tags":["Media"],"summary":"Remove a caption track","x-scope":"write","parameters":[{"$ref":"#/components/parameters/IdInPath"},{"name":"language","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted"}}}},"/playlists":{"get":{"tags":["Playlists"],"summary":"List playlists","responses":{"200":{"description":"OK"}}},"post":{"tags":["Playlists"],"summary":"Create a playlist","x-scope":"write","responses":{"201":{"description":"Created"}}}},"/playlists/{id}":{"get":{"tags":["Playlists"],"summary":"Get a playlist with items","parameters":[{"$ref":"#/components/parameters/IdInPath"}],"responses":{"200":{"description":"OK"}}},"put":{"tags":["Playlists"],"summary":"Update a playlist (items, loop, shuffle)","x-scope":"write","parameters":[{"$ref":"#/components/parameters/IdInPath"}],"responses":{"200":{"description":"OK"}}},"delete":{"tags":["Playlists"],"summary":"Delete a playlist","x-scope":"write","parameters":[{"$ref":"#/components/parameters/IdInPath"}],"responses":{"200":{"description":"Deleted"}}}},"/media/schedules":{"get":{"tags":["Scheduling"],"summary":"List playout schedules","responses":{"200":{"description":"OK"}}},"post":{"tags":["Scheduling"],"summary":"Create a schedule","x-scope":"write","responses":{"201":{"description":"Created"}}}},"/media/schedules/{id}":{"put":{"tags":["Scheduling"],"summary":"Update a schedule","x-scope":"write","parameters":[{"$ref":"#/components/parameters/IdInPath"}],"responses":{"200":{"description":"OK"}}},"delete":{"tags":["Scheduling"],"summary":"Delete a schedule","x-scope":"write","parameters":[{"$ref":"#/components/parameters/IdInPath"}],"responses":{"200":{"description":"Deleted"}}}},"/media/mode":{"get":{"tags":["Scheduling"],"summary":"Current playout mode (live or playlist)","responses":{"200":{"description":"OK"}}}},"/media/mode/playlist":{"post":{"tags":["Scheduling"],"summary":"Switch playout to a playlist","x-scope":"write","responses":{"200":{"description":"OK"}}}},"/media/mode/live":{"post":{"tags":["Scheduling"],"summary":"Switch playout back to live","x-scope":"write","responses":{"200":{"description":"OK"}}}},"/media/failover/settings":{"get":{"tags":["Failover"],"summary":"Get the account auto-failover toggle","description":"When ON, a dropped live stream falls back to a shuffled playout of every active asset in the media directory after a 10-second grace period, and switches back the instant RTMP reconnects.","responses":{"200":{"description":"{ enabled: boolean }"}}},"put":{"tags":["Failover"],"summary":"Set the account auto-failover toggle","x-scope":"write","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["enabled"],"properties":{"enabled":{"type":"boolean"}}}}}},"responses":{"200":{"description":"OK"}}}},"/media/failover/status":{"get":{"tags":["Failover"],"summary":"Current failover activity","responses":{"200":{"description":"OK"}}}},"/media/failover/{streamId}":{"get":{"tags":["Failover"],"summary":"Failover config for a stream","parameters":[{"name":"streamId","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"OK"}}}},"/media/failover/{streamId}/start":{"post":{"tags":["Failover"],"summary":"Force failover playout to start","x-scope":"write","parameters":[{"name":"streamId","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"OK"}}}},"/media/failover/{streamId}/stop":{"post":{"tags":["Failover"],"summary":"Force failover playout to stop","x-scope":"write","parameters":[{"name":"streamId","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"OK"}}}},"/subscription/plans":{"get":{"tags":["Subscription"],"summary":"Public plan catalogue","security":[],"responses":{"200":{"description":"Array of Plan"}}}},"/subscription/my-subscription":{"get":{"tags":["Subscription"],"summary":"The current subscription with computed state","description":"Returns state (active / trialing / past_due / expired), days_remaining, is_entitled and plan_id.","responses":{"200":{"description":"OK"}}}},"/subscription/payments":{"get":{"tags":["Subscription"],"summary":"Your payment history","responses":{"200":{"description":"OK"}}}},"/subscription/check-status":{"get":{"tags":["Subscription"],"summary":"Entitlement check","responses":{"200":{"description":"OK"}}}},"/subscription/usage":{"get":{"tags":["Subscription"],"summary":"Usage against plan limits","responses":{"200":{"description":"OK"}}}},"/subscription/admin/plans":{"get":{"tags":["Subscription"],"summary":"List all plans incl. hidden (admin)","responses":{"200":{"description":"OK"},"403":{"$ref":"#/components/responses/Forbidden"}}},"post":{"tags":["Subscription"],"summary":"Create a plan (admin)","x-scope":"write","responses":{"201":{"description":"Created"}}}},"/subscription/admin/plans/{id}":{"put":{"tags":["Subscription"],"summary":"Update a plan incl. white-label / IPTV / CDN feature flags (admin)","x-scope":"write","parameters":[{"$ref":"#/components/parameters/IdInPath"}],"responses":{"200":{"description":"OK"}}},"delete":{"tags":["Subscription"],"summary":"Delete a plan (admin)","x-scope":"write","parameters":[{"$ref":"#/components/parameters/IdInPath"}],"responses":{"200":{"description":"Deleted"}}}},"/subscription/admin/subscriptions":{"get":{"tags":["Subscription"],"summary":"All subscriptions (admin)","responses":{"200":{"description":"OK"}}}},"/subscription/admin/payments":{"get":{"tags":["Subscription"],"summary":"All payments (admin)","responses":{"200":{"description":"OK"}}}},"/subscription/admin/revenue":{"get":{"tags":["Subscription"],"summary":"Revenue analytics (admin)","responses":{"200":{"description":"OK"}}}},"/billing/create-payment-intent":{"post":{"tags":["Billing"],"summary":"Create a Stripe PaymentIntent","x-scope":"write","responses":{"200":{"description":"{ clientSecret }"}}}},"/billing/confirm-payment":{"post":{"tags":["Billing"],"summary":"Confirm payment and activate / upgrade / renew the subscription","x-scope":"write","responses":{"200":{"description":"OK"}}}},"/billing/create-checkout-session":{"post":{"tags":["Billing"],"summary":"Create a Stripe Checkout session","x-scope":"write","responses":{"200":{"description":"{ url }"}}}},"/billing/checkout-session/{sessionId}":{"get":{"tags":["Billing"],"summary":"Retrieve a checkout session","parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK"}}}},"/billing/create-portal-session":{"post":{"tags":["Billing"],"summary":"Open the Stripe customer portal","x-scope":"write","responses":{"200":{"description":"{ url }"}}}},"/billing/cancel-subscription":{"post":{"tags":["Billing"],"summary":"Cancel at period end","x-scope":"write","responses":{"200":{"description":"OK"}}}},"/billing/reactivate-subscription":{"post":{"tags":["Billing"],"summary":"Undo a pending cancellation","x-scope":"write","responses":{"200":{"description":"OK"}}}},"/monetization/settings":{"get":{"tags":["Monetization"],"summary":"Get monetization settings","responses":{"200":{"description":"OK"}}},"put":{"tags":["Monetization"],"summary":"Update monetization settings","x-scope":"write","responses":{"200":{"description":"OK"}}}},"/monetization/ad-config":{"get":{"tags":["Monetization"],"summary":"Ad configuration for the player","responses":{"200":{"description":"OK"}}}},"/monetization/ads/validate":{"post":{"tags":["Monetization"],"summary":"Validate a VAST / ad tag URL","x-scope":"write","responses":{"200":{"description":"OK"}}}},"/monetization/features":{"get":{"tags":["Monetization"],"summary":"Monetization feature flags for the current plan","responses":{"200":{"description":"OK"}}}},"/iptv/channels":{"get":{"tags":["IPTV"],"summary":"List channels","responses":{"200":{"description":"OK"}}},"post":{"tags":["IPTV"],"summary":"Create a channel","x-scope":"write","responses":{"201":{"description":"Created"}}}},"/iptv/channels/active":{"get":{"tags":["IPTV"],"summary":"List active channels","responses":{"200":{"description":"OK"}}}},"/iptv/categories":{"get":{"tags":["IPTV"],"summary":"List channel categories","responses":{"200":{"description":"OK"}}}},"/iptv/channels/{id}":{"get":{"tags":["IPTV"],"summary":"Get a channel","parameters":[{"$ref":"#/components/parameters/IdInPath"}],"responses":{"200":{"description":"OK"}}},"put":{"tags":["IPTV"],"summary":"Update a channel","x-scope":"write","parameters":[{"$ref":"#/components/parameters/IdInPath"}],"responses":{"200":{"description":"OK"}}},"delete":{"tags":["IPTV"],"summary":"Delete a channel","x-scope":"write","parameters":[{"$ref":"#/components/parameters/IdInPath"}],"responses":{"200":{"description":"Deleted"}}}},"/iptv/channels/reorder":{"post":{"tags":["IPTV"],"summary":"Reorder channels","x-scope":"write","responses":{"200":{"description":"OK"}}}},"/iptv/playlist.m3u":{"get":{"tags":["IPTV"],"summary":"Generated M3U playlist","responses":{"200":{"description":"audio/x-mpegurl"}}}},"/iptv/status":{"get":{"tags":["IPTV"],"summary":"IPTV entitlement and counts","responses":{"200":{"description":"OK"}}}},"/cdn/configs":{"get":{"tags":["CDN"],"summary":"List CDN configs","responses":{"200":{"description":"OK"}}},"post":{"tags":["CDN"],"summary":"Create a CDN config","x-scope":"write","responses":{"201":{"description":"Created"}}}},"/cdn/configs/{id}":{"get":{"tags":["CDN"],"summary":"Get a CDN config","parameters":[{"$ref":"#/components/parameters/IdInPath"}],"responses":{"200":{"description":"OK"}}},"put":{"tags":["CDN"],"summary":"Update a CDN config","x-scope":"write","parameters":[{"$ref":"#/components/parameters/IdInPath"}],"responses":{"200":{"description":"OK"}}},"delete":{"tags":["CDN"],"summary":"Delete a CDN config","x-scope":"write","parameters":[{"$ref":"#/components/parameters/IdInPath"}],"responses":{"200":{"description":"Deleted"}}}},"/cdn/configs/{id}/test":{"post":{"tags":["CDN"],"summary":"Test a CDN config","x-scope":"write","parameters":[{"$ref":"#/components/parameters/IdInPath"}],"responses":{"200":{"description":"OK"}}}},"/cdn/configs/{id}/set-default":{"post":{"tags":["CDN"],"summary":"Make a config the default","x-scope":"write","parameters":[{"$ref":"#/components/parameters/IdInPath"}],"responses":{"200":{"description":"OK"}}}},"/cdn/providers":{"get":{"tags":["CDN"],"summary":"Supported CDN providers","responses":{"200":{"description":"OK"}}}},"/cdn/default":{"get":{"tags":["CDN"],"summary":"The default CDN config","responses":{"200":{"description":"OK"}}}},"/cdn/status":{"get":{"tags":["CDN"],"summary":"CDN entitlement and health","responses":{"200":{"description":"OK"}}}},"/domains":{"get":{"tags":["Domains"],"summary":"List your custom domains","responses":{"200":{"description":"OK"}}},"post":{"tags":["Domains"],"summary":"Add a custom domain","x-scope":"write","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["domain"],"properties":{"domain":{"type":"string","example":"watch.mybrand.com"}}}}}},"responses":{"201":{"description":"Created — returns DNS instructions"}}}},"/domains/status":{"get":{"tags":["Domains"],"summary":"White-label entitlement for the current plan","responses":{"200":{"description":"OK"}}}},"/domains/branding":{"get":{"tags":["Domains"],"summary":"Get this account's white-label branding","responses":{"200":{"description":"{ brand_name, logo_url }"}}},"put":{"tags":["Domains"],"summary":"Set the white-label brand name","x-scope":"write","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"brand_name":{"type":"string"}}}}}},"responses":{"200":{"description":"OK"}}}},"/domains/branding/logo":{"get":{"tags":["Domains"],"summary":"The account's white-label logo","security":[],"responses":{"200":{"description":"image/*"}}},"post":{"tags":["Domains"],"summary":"Upload a white-label logo (≤ 2 MB, png/jpg/webp/svg)","x-scope":"write","requestBody":{"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"logo":{"type":"string","format":"binary"}}}}}},"responses":{"200":{"description":"OK"}}},"delete":{"tags":["Domains"],"summary":"Remove the white-label logo","x-scope":"write","responses":{"200":{"description":"OK"}}}},"/domains/{id}":{"get":{"tags":["Domains"],"summary":"Get a custom domain","parameters":[{"$ref":"#/components/parameters/IdInPath"}],"responses":{"200":{"description":"OK"}}},"put":{"tags":["Domains"],"summary":"Update a custom domain","x-scope":"write","parameters":[{"$ref":"#/components/parameters/IdInPath"}],"responses":{"200":{"description":"OK"}}},"delete":{"tags":["Domains"],"summary":"Remove a custom domain","x-scope":"write","parameters":[{"$ref":"#/components/parameters/IdInPath"}],"responses":{"200":{"description":"Deleted"}}}},"/domains/{id}/verification-instructions":{"get":{"tags":["Domains"],"summary":"DNS records to add","parameters":[{"$ref":"#/components/parameters/IdInPath"}],"responses":{"200":{"description":"OK"}}}},"/domains/{id}/verify":{"post":{"tags":["Domains"],"summary":"Check DNS and verify the domain","x-scope":"write","parameters":[{"$ref":"#/components/parameters/IdInPath"}],"responses":{"200":{"description":"OK"}}}},"/domains/{id}/ssl-status":{"get":{"tags":["Domains"],"summary":"TLS certificate status","parameters":[{"$ref":"#/components/parameters/IdInPath"}],"responses":{"200":{"description":"OK"}}}},"/branding":{"get":{"tags":["Branding"],"summary":"Resolve white-label branding by Host","description":"Reads the request Host header. Primary hosts return the default Teve brand; a verified custom domain returns that account's brand_name and logo_url. \"Powered by Siman Tech\" is always present.","security":[],"responses":{"200":{"description":"{ custom, brandName, logoUrl, poweredBy }"}}}},"/branding/logo":{"get":{"tags":["Branding"],"summary":"White-label logo for the current Host","security":[],"responses":{"200":{"description":"image/*"}}}},"/tickets":{"get":{"tags":["Tickets"],"summary":"List your tickets","responses":{"200":{"description":"OK"}}},"post":{"tags":["Tickets"],"summary":"Open a ticket","x-scope":"write","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["subject","message"],"properties":{"subject":{"type":"string"},"message":{"type":"string"},"priority":{"type":"string","enum":["low","normal","high"]}}}}}},"responses":{"201":{"description":"Created"}}}},"/tickets/metadata":{"get":{"tags":["Tickets"],"summary":"Categories, priorities and statuses","responses":{"200":{"description":"OK"}}}},"/tickets/my-stats":{"get":{"tags":["Tickets"],"summary":"Your ticket counts","responses":{"200":{"description":"OK"}}}},"/tickets/{id}":{"get":{"tags":["Tickets"],"summary":"Get a ticket with replies","parameters":[{"$ref":"#/components/parameters/IdInPath"}],"responses":{"200":{"description":"OK"}}},"put":{"tags":["Tickets"],"summary":"Update a ticket (status, etc.)","x-scope":"write","parameters":[{"$ref":"#/components/parameters/IdInPath"}],"responses":{"200":{"description":"OK"}}}},"/tickets/{id}/replies":{"post":{"tags":["Tickets"],"summary":"Reply to a ticket","x-scope":"write","parameters":[{"$ref":"#/components/parameters/IdInPath"}],"responses":{"201":{"description":"Created"}}}},"/notifications":{"get":{"tags":["Notifications"],"summary":"List notifications","responses":{"200":{"description":"OK"}}}},"/notifications/unread-count":{"get":{"tags":["Notifications"],"summary":"Unread count","responses":{"200":{"description":"{ count }"}}}},"/notifications/{id}/read":{"put":{"tags":["Notifications"],"summary":"Mark one read","x-scope":"write","parameters":[{"$ref":"#/components/parameters/IdInPath"}],"responses":{"200":{"description":"OK"}}}},"/notifications/read-all":{"put":{"tags":["Notifications"],"summary":"Mark all read","x-scope":"write","responses":{"200":{"description":"OK"}}}},"/notifications/history":{"get":{"tags":["Notifications"],"summary":"Full notification history","responses":{"200":{"description":"OK"}}}},"/analytics/summary":{"get":{"tags":["Analytics"],"summary":"Account-wide analytics summary","parameters":[{"$ref":"#/components/parameters/Period"}],"responses":{"200":{"description":"OK"}}}},"/analytics/streams/{id}":{"get":{"tags":["Analytics"],"summary":"Per-stream analytics with a daily series","parameters":[{"$ref":"#/components/parameters/IdInPath"},{"$ref":"#/components/parameters/Period"}],"responses":{"200":{"description":"OK"}}}},"/roles":{"get":{"tags":["Roles"],"summary":"List roles (admin)","responses":{"200":{"description":"OK"}}},"post":{"tags":["Roles"],"summary":"Create a role (admin)","x-scope":"write","responses":{"201":{"description":"Created"}}}},"/roles/permissions":{"get":{"tags":["Roles"],"summary":"All assignable permissions","responses":{"200":{"description":"OK"}}}},"/roles/{id}":{"get":{"tags":["Roles"],"summary":"Get a role","parameters":[{"$ref":"#/components/parameters/IdInPath"}],"responses":{"200":{"description":"OK"}}},"put":{"tags":["Roles"],"summary":"Update a role","x-scope":"write","parameters":[{"$ref":"#/components/parameters/IdInPath"}],"responses":{"200":{"description":"OK"}}},"delete":{"tags":["Roles"],"summary":"Delete a role","x-scope":"write","parameters":[{"$ref":"#/components/parameters/IdInPath"}],"responses":{"200":{"description":"Deleted"}}}},"/roles/assign":{"post":{"tags":["Roles"],"summary":"Assign a role to a user (admin)","x-scope":"write","responses":{"200":{"description":"OK"}}}},"/roles/user/permissions":{"get":{"tags":["Roles"],"summary":"The current user's effective permissions","responses":{"200":{"description":"OK"}}}},"/settings/public":{"get":{"tags":["Settings"],"summary":"Public platform settings","security":[],"responses":{"200":{"description":"OK"}},"description":"Public settings. Includes `mobile: { min_version, message }` — the force-upgrade gate for the mobile app (both null = no gate)."}},"/settings/social-public":{"get":{"tags":["Settings"],"summary":"Public social-login config (client IDs, enabled flag)","security":[],"responses":{"200":{"description":"OK"}}}},"/settings":{"get":{"tags":["Settings"],"summary":"All settings (admin)","responses":{"200":{"description":"OK"}}}},"/settings/social":{"get":{"tags":["Settings"],"summary":"Social-login settings (admin)","responses":{"200":{"description":"OK"}}},"put":{"tags":["Settings"],"summary":"Update social-login settings (admin)","x-scope":"write","responses":{"200":{"description":"OK"}}}},"/settings/smtp":{"get":{"tags":["Settings"],"summary":"SMTP settings (admin)","responses":{"200":{"description":"OK"}}},"put":{"tags":["Settings"],"summary":"Update SMTP settings (admin)","x-scope":"write","responses":{"200":{"description":"OK"}}}},"/settings/email-templates":{"get":{"tags":["Settings"],"summary":"Email templates (admin)","responses":{"200":{"description":"OK"}}}},"/seo/meta":{"get":{"tags":["SEO"],"summary":"Merged <head> metadata for a route","security":[],"description":"Everything a front-end needs to render <head> for one path: title, meta tags (description, keywords, robots, verification), canonical, Open Graph, Twitter cards, an array of JSON-LD blocks (Organization, WebSite, WebPage, FAQPage, plus any page-specific schema), FAQ pairs, the AI/answer-engine summary, and analytics IDs. Global config is merged with the per-page override for that path.","parameters":[{"name":"path","in":"query","required":true,"schema":{"type":"string","example":"/pricing"}}],"responses":{"200":{"description":"OK"}}}},"/seo/config":{"get":{"tags":["SEO"],"summary":"Global SEO / GEO / AEO configuration","security":[],"responses":{"200":{"description":"OK"}}}},"/seo/faqs":{"get":{"tags":["SEO"],"summary":"FAQ pairs for a route (feeds FAQPage schema)","security":[],"parameters":[{"name":"path","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"OK"}}}},"/seo/og-image":{"get":{"tags":["SEO"],"summary":"Default social / OG image","security":[],"responses":{"200":{"description":"image/*"},"302":{"description":"Redirect to an external image URL"}}}},"/seo/robots.txt":{"get":{"tags":["SEO"],"summary":"Generated robots.txt (also at /robots.txt)","security":[],"responses":{"200":{"description":"text/plain"}}}},"/seo/sitemap.xml":{"get":{"tags":["SEO"],"summary":"Generated sitemap (also at /sitemap.xml)","security":[],"responses":{"200":{"description":"application/xml"}}}},"/seo/llms.txt":{"get":{"tags":["SEO"],"summary":"Generated llms.txt for AI crawlers (also at /llms.txt)","security":[],"responses":{"200":{"description":"text/plain"}}}},"/seo/admin/config":{"get":{"tags":["SEO"],"summary":"Get global config (admin)","responses":{"200":{"description":"OK"},"403":{"$ref":"#/components/responses/Forbidden"}}},"put":{"tags":["SEO"],"summary":"Update global config — deep-merged (admin)","x-scope":"write","responses":{"200":{"description":"OK"}}}},"/seo/admin/og-image":{"post":{"tags":["SEO"],"summary":"Upload the default OG image (admin, ≤ 4 MB)","x-scope":"write","requestBody":{"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"image":{"type":"string","format":"binary"}}}}}},"responses":{"200":{"description":"OK"}}}},"/seo/admin/pages":{"get":{"tags":["SEO"],"summary":"List per-route overrides (admin)","responses":{"200":{"description":"OK"}}},"post":{"tags":["SEO"],"summary":"Create / upsert a route override (admin)","x-scope":"write","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["path"],"properties":{"path":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"},"keywords":{"type":"string"},"og_image":{"type":"string"},"canonical":{"type":"string"},"robots":{"type":"string","example":"noindex,nofollow"},"json_ld":{"type":"string","description":"extra JSON-LD (object or array), as a JSON string"},"changefreq":{"type":"string"},"priority":{"type":"number"},"in_sitemap":{"type":"boolean"}}}}}},"responses":{"200":{"description":"OK"}}}},"/seo/admin/pages/{id}":{"put":{"tags":["SEO"],"summary":"Update a route override (admin)","x-scope":"write","parameters":[{"$ref":"#/components/parameters/IdInPath"}],"responses":{"200":{"description":"OK"}}},"delete":{"tags":["SEO"],"summary":"Delete a route override (admin)","x-scope":"write","parameters":[{"$ref":"#/components/parameters/IdInPath"}],"responses":{"200":{"description":"OK"}}}},"/seo/admin/faqs":{"get":{"tags":["SEO"],"summary":"List FAQ pairs (admin)","responses":{"200":{"description":"OK"}}},"post":{"tags":["SEO"],"summary":"Create a FAQ pair (admin)","x-scope":"write","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["question","answer"],"properties":{"question":{"type":"string"},"answer":{"type":"string"},"page_path":{"type":"string","nullable":true,"description":"null = site-wide"},"sort_order":{"type":"integer"}}}}}},"responses":{"200":{"description":"OK"}}}},"/seo/admin/faqs/{id}":{"put":{"tags":["SEO"],"summary":"Update a FAQ pair (admin)","x-scope":"write","parameters":[{"$ref":"#/components/parameters/IdInPath"}],"responses":{"200":{"description":"OK"}}},"delete":{"tags":["SEO"],"summary":"Delete a FAQ pair (admin)","x-scope":"write","parameters":[{"$ref":"#/components/parameters/IdInPath"}],"responses":{"200":{"description":"OK"}}}},"/keys":{"get":{"tags":["API Keys"],"summary":"List your API keys","responses":{"200":{"description":"OK"}}},"post":{"tags":["API Keys"],"summary":"Create an API key","x-scope":"write","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"scopes":{"type":"string","example":"read,write"}}}}}},"responses":{"201":{"description":"Created — the key is shown once"}}}},"/keys/{id}":{"delete":{"tags":["API Keys"],"summary":"Revoke an API key","x-scope":"write","parameters":[{"$ref":"#/components/parameters/IdInPath"}],"responses":{"200":{"description":"Revoked"}}}},"/auth/logout":{"post":{"tags":["Auth"],"summary":"Revoke the current session token (server-side logout)","description":"The JWT stays cryptographically valid until its `exp` (30 days by default) but is rejected from here on. Idempotent.","security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Logged out"}}}},"/streams/{id}/ingest-stats":{"get":{"tags":["Streams"],"summary":"What the server received on the ingest connection","parameters":[{"$ref":"#/components/parameters/IdInPath"}],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/IngestStats"}}}}}}}}},"/streams/{id}/events":{"get":{"tags":["Streams"],"summary":"Live stream-state stream (Server-Sent Events)","description":"text/event-stream. Emits `event: status` with `{ stream_id, status, is_live, at }` on every change, plus an initial snapshot and a heartbeat comment every 25s. Use this instead of polling /status.","parameters":[{"$ref":"#/components/parameters/IdInPath"}],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"SSE stream","content":{"text/event-stream":{}}}}}},"/streams/{id}/publish/reset":{"post":{"tags":["Streams"],"summary":"Force-drop the current publisher for this key","description":"Escape hatch when a ghost session is holding the key and every reconnect is refused as key_in_use.","parameters":[{"$ref":"#/components/parameters/IdInPath"}],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"stream_id":{"type":"integer"},"dropped":{"type":"boolean"}}}}}}}}}}},"/media/assets/upload/tus":{"post":{"tags":["Media"],"summary":"Create a resumable upload (TUS 1.0.0)","description":"Resumable recording upload. Standard TUS: POST to create (send `Tus-Resumable: 1.0.0`,\n`Upload-Length`, base64 `Upload-Metadata`), then PATCH chunks at `Upload-Offset`,\nHEAD to learn the current offset after an interruption, DELETE to abort.\n\nMetadata keys: `filename`, `title`, `stream_id`, `recorded_at`, `duration_seconds`.\nOn completion a vod_assets row is created with `source = \"mobile\"`; its id is returned\nin the `X-Teve-Asset-Id` response header on the final PATCH.\n\nLimits: max 5 GB (VOD_MAX_UPLOAD_BYTES). Container/codec: MP4 / H.264 / AAC.\nThe single-shot `POST /media/assets/upload` still works for small files.","security":[{"BearerAuth":[]}],"parameters":[{"name":"Tus-Resumable","in":"header","required":true,"schema":{"type":"string","enum":["1.0.0"]}},{"name":"Upload-Length","in":"header","required":true,"schema":{"type":"integer"}},{"name":"Upload-Metadata","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"201":{"description":"Upload created; `Location` header holds the upload URL"},"204":{"description":"Chunk stored (PATCH); `Upload-Offset` is the new offset"},"413":{"description":"Exceeds the max upload size"}}}},"/notifications/devices":{"post":{"tags":["Notifications"],"summary":"Register a mobile push device (Expo token)","security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["token"],"properties":{"token":{"type":"string","example":"ExponentPushToken[xxxxxxxx]"},"platform":{"type":"string","enum":["ios","android"]},"app_version":{"type":"string"}}}}}},"responses":{"200":{"description":"Registered"}}},"get":{"tags":["Notifications"],"summary":"List the current user's push devices","security":[{"BearerAuth":[]}],"responses":{"200":{"description":"OK"}}}},"/notifications/devices/{token}":{"delete":{"tags":["Notifications"],"summary":"Unregister a push device","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string"}}],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Removed"}}}}}}