{"schemes":["http"],"swagger":"2.0","info":{"description":"Backend for the Astir family streaming platform: auth, parent/child profiles with permissions, content catalog, subscriptions & payments (Click), and signed streaming URLs.\n\n**Localization:** pass `?lang=uz|ru|en` (or `Accept-Language`) on any catalog/billing endpoint to receive resolved strings instead of the raw i18n map. Tag names, titles, and descriptions are all resolved. See `docs/i18n-localization.md` for the full spec.\n\n**Streaming grants:** `GET /stream/{id}/grant` returns `duration_sec` (full video length), `preview_only`, and `preview_until_sec` (15 s for non-subscribers). Display `duration_sec` in the player timeline; enforce `preview_until_sec` client-side — the server hard-blocks segments beyond that point regardless.","title":"Astir Streaming API","termsOfService":"https://astir-animation.uz/terms","contact":{"name":"Astir Team","email":"support@astir-animation.uz"},"license":{"name":"Proprietary"},"version":"0.1.0"},"host":"test-api.astir-animation.uz","basePath":"/api/v1","paths":{"/admin/cards":{"get":{"security":[{"BearerAuth":[]}],"description":"Returns all saved cards platform-wide. Optionally filter by user_id. Token hash is included for fingerprinting; the raw token is never exposed.","produces":["application/json"],"tags":["cards"],"summary":"List all cards (admin)","parameters":[{"type":"string","description":"Filter by user UUID","name":"user_id","in":"query"},{"type":"integer","description":"Page size","name":"limit","in":"query"},{"type":"integer","description":"Offset","name":"offset","in":"query"}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/handler.AdminCardListResponse"}}}}},"/admin/faqs":{"get":{"security":[{"BearerAuth":[]}],"description":"Returns all FAQs including inactive ones, with full I18n maps for editing.","produces":["application/json"],"tags":["faq"],"summary":"List all FAQs (admin)","responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/domain.FAQ"}}}}},"post":{"security":[{"BearerAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["faq"],"summary":"Create a FAQ entry","parameters":[{"description":"FAQ payload","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/handler.FAQRequest"}}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/domain.FAQ"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/handler.ErrorResponse"}}}}},"/admin/faqs/{id}":{"put":{"security":[{"BearerAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["faq"],"summary":"Update a FAQ entry","parameters":[{"type":"string","description":"FAQ ID","name":"id","in":"path","required":true},{"description":"FAQ payload","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/handler.FAQRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/domain.FAQ"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/handler.ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/handler.ErrorResponse"}}}},"delete":{"security":[{"BearerAuth":[]}],"produces":["application/json"],"tags":["faq"],"summary":"Delete a FAQ entry","parameters":[{"type":"string","description":"FAQ ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/handler.MessageResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/handler.ErrorResponse"}}}}},"/admin/support/chats":{"get":{"security":[{"BearerAuth":[]}],"produces":["application/json"],"tags":["support-admin"],"summary":"[Admin] List support chats, most-recent activity first","parameters":[{"type":"integer","description":"Pagination limit (default 20)","name":"limit","in":"query"},{"type":"integer","description":"Pagination offset","name":"offset","in":"query"}],"responses":{"200":{"description":"OK","schema":{"type":"object","additionalProperties":true}}}}},"/admin/support/chats/{id}":{"get":{"security":[{"BearerAuth":[]}],"produces":["application/json"],"tags":["support-admin"],"summary":"[Admin] Get a support chat","parameters":[{"type":"string","description":"Chat ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/domain.SupportChat"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/handler.ErrorResponse"}}}}},"/admin/support/chats/{id}/messages":{"get":{"security":[{"BearerAuth":[]}],"produces":["application/json"],"tags":["support-admin"],"summary":"[Admin] List messages in a support chat","parameters":[{"type":"string","description":"Chat ID","name":"id","in":"path","required":true},{"type":"integer","description":"Page size (default 50, max 200)","name":"limit","in":"query"},{"type":"string","description":"Return messages older than this message ID","name":"before","in":"query"},{"type":"string","description":"Return messages newer than this message ID","name":"after","in":"query"}],"responses":{"200":{"description":"OK","schema":{"type":"object","additionalProperties":true}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/handler.ErrorResponse"}}}},"post":{"security":[{"BearerAuth":[]}],"consumes":["multipart/form-data"],"produces":["application/json"],"tags":["support-admin"],"summary":"[Admin] Reply in a support chat (with optional attachment up to 50 MB)","parameters":[{"type":"string","description":"Chat ID","name":"id","in":"path","required":true},{"type":"string","description":"Message text","name":"body","in":"formData"},{"type":"file","description":"Attachment (max 50 MB)","name":"file","in":"formData"}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/domain.SupportMessage"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/handler.ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/handler.ErrorResponse"}}}}},"/admin/support/chats/{id}/read":{"post":{"security":[{"BearerAuth":[]}],"produces":["application/json"],"tags":["support-admin"],"summary":"[Admin] Mark a support chat as read on the admin side","parameters":[{"type":"string","description":"Chat ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/handler.MessageResponse"}}}}},"/admin/transactions":{"get":{"security":[{"BearerAuth":[]}],"produces":["application/json"],"tags":["billing"],"summary":"All transactions (admin)","parameters":[{"enum":["pending","succeeded","failed","refunded","canceled"],"type":"string","description":"Filter by status","name":"status","in":"query"},{"type":"integer","description":"Page size","name":"limit","in":"query"},{"type":"integer","description":"Offset","name":"offset","in":"query"}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/handler.TxListResponse"}}}}},"/auth/apple":{"post":{"description":"Accepts an Apple identity token from ASAuthorizationAppleIDCredential. Verifies the token with Apple's public keys, then finds or creates a parent account and returns a token pair. The user's name (given_name, family_name) is only provided by Apple on the very first sign-in — pass empty strings on subsequent calls.","consumes":["application/json"],"produces":["application/json"],"tags":["auth"],"summary":"Sign in with Apple (iOS)","parameters":[{"description":"Apple identity token + optional name","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/handler.AppleAuthRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/service.TokenPair"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/handler.ErrorResponse"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/handler.ErrorResponse"}}}}},"/auth/child/confirm":{"post":{"security":[{"BearerAuth":[]}],"description":"Single endpoint for the parent QR scanner. The backend detects the code type automatically.\\n- Pairing code (from /auth/child/init): child_id is required; links the device and optionally sets initial permissions.\\n- Extension code (from /children/{id}/extend/init): child_id is ignored; grants 2 more hours of watch time.\\nThe response field \"action\" tells the client which case was handled.","consumes":["application/json"],"produces":["application/json"],"tags":["children"],"summary":"Parent scans a child QR — handles both device pairing and session extension","parameters":[{"description":"Scanned code. child_id required only for pairing.","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/handler.ScanQRRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/service.ScanResult"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/handler.ErrorResponse"}},"410":{"description":"Gone","schema":{"$ref":"#/definitions/handler.ErrorResponse"}}}}},"/auth/child/init":{"post":{"description":"The child's device calls this first. Display the returned QR on the child's screen. The parent scans it and calls POST /auth/child/confirm to select which child profile this device belongs to. Poll GET /auth/child/{device_id}/status until status=confirmed.","consumes":["application/json"],"produces":["application/json"],"tags":["auth"],"summary":"Child device initiates pairing — returns a QR the parent scans","parameters":[{"description":"device_name and device_fingerprint","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/handler.PairRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/service.PairingTicket"}},"500":{"description":"Internal Server Error","schema":{"$ref":"#/definitions/handler.ErrorResponse"}}}}},"/auth/child/{device_id}/status":{"get":{"description":"Returns status=pending until the parent confirms. Once confirmed, includes access_token and child profile so the device can start streaming.","produces":["application/json"],"tags":["auth"],"summary":"Child device polls for pairing confirmation","parameters":[{"type":"string","description":"Device ID returned by /auth/child/init","name":"device_id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/service.ChildPairingStatus"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/handler.ErrorResponse"}}}}},"/auth/forgot-password":{"post":{"description":"Same as /auth/otp/request but semantically for password reset. Follow up with /auth/otp/verify then /auth/reset-password.","consumes":["application/json"],"produces":["application/json"],"tags":["auth"],"summary":"Start password-reset flow (sends OTP to email)","parameters":[{"description":"Email","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/handler.ForgotPasswordRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/service.OTPRequestResult"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/handler.ErrorResponse"}}}}},"/auth/google":{"post":{"description":"Accepts a Google ID token obtained from the Google Sign-In SDK on Android or iOS. Verifies the token with Google, then finds or creates a parent account and returns a token pair. On first sign-in the account is created automatically — no separate registration step needed.","consumes":["application/json"],"produces":["application/json"],"tags":["auth"],"summary":"Sign in with Google (Android / iOS)","parameters":[{"description":"Google ID token","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/handler.GoogleAuthRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/service.TokenPair"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/handler.ErrorResponse"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/handler.ErrorResponse"}}}}},"/auth/login":{"post":{"description":"Exchange email+password for an access/refresh token pair. Parents use the OTP flow.","consumes":["application/json"],"produces":["application/json"],"tags":["auth"],"summary":"Log in (staff only — super_admin, admin)","parameters":[{"description":"Staff credentials","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/handler.LoginRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/service.TokenPair"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/handler.ErrorResponse"}}}}},"/auth/logout":{"post":{"consumes":["application/json"],"produces":["application/json"],"tags":["auth"],"summary":"Log out (revoke refresh token)","parameters":[{"description":"Refresh token to revoke","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/handler.RefreshRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/handler.MessageResponse"}}}}},"/auth/me":{"get":{"security":[{"BearerAuth":[]}],"produces":["application/json"],"tags":["auth"],"summary":"Return the current user","responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/domain.User"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/handler.ErrorResponse"}}}},"put":{"security":[{"BearerAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["auth"],"summary":"Update current user's profile","parameters":[{"description":"Fields to update","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/handler.UpdateProfileRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/domain.User"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/handler.ErrorResponse"}}}}},"/auth/me/avatar":{"post":{"security":[{"BearerAuth":[]}],"consumes":["multipart/form-data"],"produces":["application/json"],"tags":["auth"],"summary":"Upload a profile avatar image","parameters":[{"type":"file","description":"Avatar image (jpg/png/webp)","name":"file","in":"formData","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/domain.User"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/handler.ErrorResponse"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/handler.ErrorResponse"}}}}},"/auth/otp/login":{"post":{"description":"Called when VerifyOTP returned user_exists=true. Validates the password and returns tokens.","consumes":["application/json"],"produces":["application/json"],"tags":["auth"],"summary":"Complete login after OTP verify (existing users)","parameters":[{"description":"Email + password","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/handler.OTPLoginRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/service.TokenPair"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/handler.ErrorResponse"}}}}},"/auth/otp/request":{"post":{"description":"Starts the parent auth flow. In dev (OTP_DEFAULT_CODE set), the code is echoed back in debug_code and no email is sent.","consumes":["application/json"],"produces":["application/json"],"tags":["auth"],"summary":"Request an OTP code sent to an email address","parameters":[{"description":"Email to OTP","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/handler.OTPRequestRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/service.OTPRequestResult"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/handler.ErrorResponse"}}}}},"/auth/otp/verify":{"post":{"description":"Verifies the OTP. If user_exists=true the response already contains access_token + refresh_token — the user is logged in. If user_exists=false proceed to POST /auth/register with last_name, name and password.","consumes":["application/json"],"produces":["application/json"],"tags":["auth"],"summary":"Verify OTP code","parameters":[{"description":"Email + OTP code","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/handler.OTPVerifyRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/service.OTPVerifyResult"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/handler.ErrorResponse"}}}}},"/auth/refresh":{"post":{"consumes":["application/json"],"produces":["application/json"],"tags":["auth"],"summary":"Refresh an access token","parameters":[{"description":"Refresh token","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/handler.RefreshRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/service.TokenPair"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/handler.ErrorResponse"}}}}},"/auth/register":{"post":{"description":"Called when VerifyOTP returned user_exists=false. Creates the account with name (required), last_name (optional), and password, then returns tokens.","consumes":["application/json"],"produces":["application/json"],"tags":["auth"],"summary":"Register a new parent account after OTP verify","parameters":[{"description":"Registration data","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/handler.RegisterRequest"}}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/service.TokenPair"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/handler.ErrorResponse"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/handler.ErrorResponse"}},"409":{"description":"Conflict","schema":{"$ref":"#/definitions/handler.ErrorResponse"}}}}},"/auth/reset-password":{"post":{"description":"Call after /auth/forgot-password → /auth/otp/verify. Uses the verified OTP session stored in Redis.","consumes":["application/json"],"produces":["application/json"],"tags":["auth"],"summary":"Set a new password after OTP verify","parameters":[{"description":"Email + new password","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/handler.ResetPasswordRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/handler.MessageResponse"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/handler.ErrorResponse"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/handler.ErrorResponse"}}}}},"/auth/tv/confirm":{"post":{"security":[{"BearerAuth":[]}],"description":"Authenticated parent calls this after scanning the TV's QR code. Links the TV to the parent's account. Returns the profile list (\"who's watching?\").","consumes":["application/json"],"produces":["application/json"],"tags":["tv"],"summary":"TV: parent confirms pairing by scanning QR","parameters":[{"description":"Pairing code from QR","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/handler.TVConfirmRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/service.TVStatusResult"}}}}},"/auth/tv/init":{"post":{"description":"TV calls this on first launch. Displays the returned QR on screen. Parent scans it with their authenticated app (/auth/tv/confirm). TV then polls /auth/tv/:device_id/status until confirmed.","consumes":["application/json"],"produces":["application/json"],"tags":["tv"],"summary":"TV: generate pairing QR","parameters":[{"description":"TV device info","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/handler.TVInitRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/service.TVInitTicket"}}}}},"/auth/tv/profile":{"post":{"security":[{"BearerAuth":[]}],"description":"TV calls this with its device_token (tv audience). Pass child_id to stream as a child; omit (or null) to stream as the parent. Returns a short-lived streaming access_token.","consumes":["application/json"],"produces":["application/json"],"tags":["tv"],"summary":"TV: select who is watching","parameters":[{"description":"Profile to activate","name":"body","in":"body","schema":{"$ref":"#/definitions/handler.TVSelectProfileRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/service.TVStreamSession"}}}}},"/auth/tv/profiles":{"get":{"security":[{"BearerAuth":[]}],"description":"TV calls this with its device_token to refresh the list of available profiles without re-selecting.","produces":["application/json"],"tags":["tv"],"summary":"TV: get current profile list","responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/service.TVProfileList"}}}}},"/auth/tv/{device_id}/status":{"get":{"description":"TV polls this after displaying the QR. Returns status \"pending\" until parent confirms. Once confirmed, returns a device_token and profile list.","produces":["application/json"],"tags":["tv"],"summary":"TV: poll for pairing confirmation","parameters":[{"type":"string","description":"Device ID from /auth/tv/init","name":"device_id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/service.TVStatusResult"}}}}},"/billing/charge/recurring":{"post":{"security":[{"BearerAuth":[]}],"description":"Calls Click's POST /card_token/payment. No user redirect needed — the charge is silent. The card must have been verified via POST /payments/click/card/verify first.","consumes":["application/json"],"produces":["application/json"],"tags":["billing"],"summary":"Charge a verified card token (recurring / subscription renewal)","parameters":[{"description":"Plan + card","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/handler.RecurringChargeRequest"}}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/service.CheckoutResult"}}}}},"/billing/checkout":{"post":{"security":[{"BearerAuth":[]}],"description":"Creates a pending subscription + transaction and returns the provider checkout URL. Redirect the user to `checkout_url`. In dev (no Click secrets), the URL points at the mock endpoint.","consumes":["application/json"],"produces":["application/json"],"tags":["billing"],"summary":"Start a subscription checkout","parameters":[{"description":"Checkout request","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/handler.CheckoutRequest"}}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/service.CheckoutResult"}}}}},"/billing/checkout/deeplink":{"post":{"security":[{"BearerAuth":[]}],"description":"Creates a pending subscription + transaction and returns a Click deeplink URL. Open the URL on the device — the Click app intercepts it, or the browser payment page opens as fallback.","consumes":["application/json"],"produces":["application/json"],"tags":["billing"],"summary":"Start a deeplink / hosted-page checkout","parameters":[{"description":"Plan to subscribe to","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/handler.DeeplinkCheckoutRequest"}}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/service.CheckoutResult"}}}}},"/billing/subscriptions":{"get":{"security":[{"BearerAuth":[]}],"produces":["application/json"],"tags":["billing"],"summary":"My subscriptions","responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/domain.Subscription"}}}}}},"/billing/transactions":{"get":{"security":[{"BearerAuth":[]}],"produces":["application/json"],"tags":["billing"],"summary":"My transactions","parameters":[{"type":"integer","description":"Page size","name":"limit","in":"query"},{"type":"integer","description":"Offset","name":"offset","in":"query"}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/handler.TxListResponse"}}}}},"/cards":{"get":{"security":[{"BearerAuth":[]}],"produces":["application/json"],"tags":["cards"],"summary":"List my cards","responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/domain.Card"}}}}},"post":{"security":[{"BearerAuth":[]}],"description":"Only brand + masked PAN + provider token are stored; raw PAN is never persisted. Works with the Click dev stub today — wire production to the provider's frontend tokenisation.","consumes":["application/json"],"produces":["application/json"],"tags":["cards"],"summary":"Tokenise and save a card","parameters":[{"description":"Card data","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/handler.CardRequest"}}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/domain.Card"}}}}},"/cards/{id}":{"delete":{"security":[{"BearerAuth":[]}],"produces":["application/json"],"tags":["cards"],"summary":"Delete a card","parameters":[{"type":"string","description":"Card ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/handler.MessageResponse"}}}}},"/cards/{id}/default":{"post":{"security":[{"BearerAuth":[]}],"produces":["application/json"],"tags":["cards"],"summary":"Set a card as default","parameters":[{"type":"string","description":"Card ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/handler.MessageResponse"}}}}},"/categories":{"get":{"produces":["application/json"],"tags":["categories"],"summary":"List categories","parameters":[{"enum":["film","series","cartoon","other"],"type":"string","description":"Filter by kind","name":"kind","in":"query"},{"enum":["uz","ru","en"],"type":"string","description":"Resolve i18n fields to this locale","name":"lang","in":"query"}],"responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/domain.Category"}}}}},"post":{"security":[{"BearerAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["categories"],"summary":"Create a category","parameters":[{"description":"Category payload","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/handler.CategoryRequest"}}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/domain.Category"}}}}},"/categories/{id}":{"put":{"security":[{"BearerAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["categories"],"summary":"Update a category","parameters":[{"type":"string","description":"Category ID","name":"id","in":"path","required":true},{"description":"Category payload","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/handler.CategoryRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/domain.Category"}}}},"delete":{"security":[{"BearerAuth":[]}],"produces":["application/json"],"tags":["categories"],"summary":"Delete a category","parameters":[{"type":"string","description":"Category ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/handler.MessageResponse"}}}}},"/categories/{id}/poster":{"get":{"produces":["application/octet-stream"],"tags":["categories"],"summary":"Serve a category's poster image (public)","parameters":[{"type":"string","description":"Category ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK"},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/handler.ErrorResponse"}}}},"post":{"security":[{"BearerAuth":[]}],"consumes":["multipart/form-data"],"produces":["application/json"],"tags":["categories"],"summary":"Upload a poster image for a category","parameters":[{"type":"string","description":"Category ID","name":"id","in":"path","required":true},{"type":"file","description":"Image file (jpg/png/webp)","name":"file","in":"formData","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/domain.Category"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/handler.ErrorResponse"}}}}},"/children":{"get":{"security":[{"BearerAuth":[]}],"produces":["application/json"],"tags":["children"],"summary":"List my children","responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/domain.Child"}}}}},"post":{"security":[{"BearerAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["children"],"summary":"Create a child profile","parameters":[{"description":"Child data","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/handler.ChildCreateRequest"}}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/domain.Child"}}}}},"/children/{id}":{"put":{"security":[{"BearerAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["children"],"summary":"Update a child profile","parameters":[{"type":"string","description":"Child ID","name":"id","in":"path","required":true},{"description":"Child data","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/handler.ChildUpdateRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/domain.Child"}}}},"delete":{"security":[{"BearerAuth":[]}],"produces":["application/json"],"tags":["children"],"summary":"Delete a child profile","parameters":[{"type":"string","description":"Child ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/handler.MessageResponse"}}}}},"/children/{id}/devices":{"get":{"security":[{"BearerAuth":[]}],"produces":["application/json"],"tags":["children"],"summary":"List paired devices for a child","parameters":[{"type":"string","description":"Child ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/domain.ChildDevice"}}}}}},"/children/{id}/devices/{device_id}":{"delete":{"security":[{"BearerAuth":[]}],"produces":["application/json"],"tags":["children"],"summary":"Revoke a paired child device","parameters":[{"type":"string","description":"Child ID","name":"id","in":"path","required":true},{"type":"string","description":"Device ID","name":"device_id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/handler.MessageResponse"}}}}},"/children/{id}/extend/init":{"post":{"security":[{"BearerAuth":[]}],"description":"Alternative to PIN entry. The child's device shows this QR; the parent scans it and calls POST /auth/child/extend/confirm to grant 2 more hours.","produces":["application/json"],"tags":["children"],"summary":"Child initiates QR-based extension — returns a QR the parent scans","parameters":[{"type":"string","description":"Child ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/service.ExtensionTicket"}}}}},"/children/{id}/extend/pin":{"post":{"security":[{"BearerAuth":[]}],"description":"Called by the child's device when the watch window expires. Requires a PIN previously set by the parent.","consumes":["application/json"],"produces":["application/json"],"tags":["children"],"summary":"Child enters PIN to extend watch session by 2 hours","parameters":[{"type":"string","description":"Child ID","name":"id","in":"path","required":true},{"description":"4-digit PIN","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/handler.ExtendByPINRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/handler.MessageResponse"}}}}},"/children/{id}/extend/{ticket_id}/status":{"get":{"security":[{"BearerAuth":[]}],"description":"Returns \"pending\" until the parent scans the QR. Once confirmed, returns \"confirmed\" with extends_until.","produces":["application/json"],"tags":["children"],"summary":"Child polls extension ticket status","parameters":[{"type":"string","description":"Child ID","name":"id","in":"path","required":true},{"type":"string","description":"Ticket ID from init response","name":"ticket_id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/service.ExtensionTicketStatus"}}}}},"/children/{id}/permissions":{"get":{"security":[{"BearerAuth":[]}],"produces":["application/json"],"tags":["children"],"summary":"List permissions applied to a child","parameters":[{"type":"string","description":"Child ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/domain.ChildPermission"}}}}},"post":{"security":[{"BearerAuth":[]}],"description":"Rules target a category OR a specific content item. Time windows are minutes-of-day; WeekdayMask is a 7-bit field (Mon=bit0..Sun=bit6, 127=every day).","consumes":["application/json"],"produces":["application/json"],"tags":["children"],"summary":"Attach an allow/deny rule to a child","parameters":[{"type":"string","description":"Child ID","name":"id","in":"path","required":true},{"description":"Rule","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/handler.PermissionRequest"}}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/domain.ChildPermission"}}}}},"/children/{id}/permissions/{rule_id}":{"put":{"security":[{"BearerAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["children"],"summary":"Update a child's permission rule in real time","parameters":[{"type":"string","description":"Child ID","name":"id","in":"path","required":true},{"type":"string","description":"Permission ID","name":"rule_id","in":"path","required":true},{"description":"Rule","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/handler.PermissionRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/domain.ChildPermission"}}}},"delete":{"security":[{"BearerAuth":[]}],"produces":["application/json"],"tags":["children"],"summary":"Remove a permission rule","parameters":[{"type":"string","description":"Child ID","name":"id","in":"path","required":true},{"type":"string","description":"Permission ID","name":"rule_id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/handler.MessageResponse"}}}}},"/children/{id}/pin":{"put":{"security":[{"BearerAuth":[]}],"description":"The PIN lets the child self-extend their watch session by 2 hours without needing a parent scan.","consumes":["application/json"],"produces":["application/json"],"tags":["children"],"summary":"Set or replace the 4-digit PIN for a child (parent)","parameters":[{"type":"string","description":"Child ID","name":"id","in":"path","required":true},{"description":"4-digit PIN","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/handler.SetPINRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/handler.MessageResponse"}}}}},"/comments/{id}":{"put":{"security":[{"BearerAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["comments"],"summary":"Edit your own comment","parameters":[{"type":"string","description":"Comment ID","name":"id","in":"path","required":true},{"description":"Comment body","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/handler.CommentRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/domain.Comment"}}}},"delete":{"security":[{"BearerAuth":[]}],"produces":["application/json"],"tags":["comments"],"summary":"Delete a comment (owner or admin)","parameters":[{"type":"string","description":"Comment ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/handler.MessageResponse"}}}}},"/content":{"get":{"description":"Returns a unified list where each item has `item_type`: \"content\" (stream directly) or \"series\" (tap → GET /series/:id/episodes). Non-admin requests exclude individual episodes. Use `?kind=` to filter: \"film\" | \"cartoon\" | \"other\" returns only matching content; \"series\" returns only series cards. Omit `kind` to get everything. Pass `?lang=uz|ru|en` (or Accept-Language) for localized strings.","produces":["application/json"],"tags":["content"],"summary":"List content items (films, cartoons) + series cards","parameters":[{"enum":["film","cartoon","series","other"],"type":"string","description":"Filter by content type","name":"kind","in":"query"},{"type":"string","description":"Filter by specific category ID","name":"category_id","in":"query"},{"type":"string","description":"Title search","name":"q","in":"query"},{"type":"boolean","description":"Show all statuses and include individual episodes (admin only)","name":"admin","in":"query"},{"type":"integer","description":"Page size (default 20)","name":"limit","in":"query"},{"type":"integer","description":"Offset (default 0)","name":"offset","in":"query"},{"type":"string","description":"Comma-separated tag UUIDs (all must match)","name":"tag_ids","in":"query"},{"type":"integer","description":"Minimum age rating","name":"min_age","in":"query"},{"type":"integer","description":"Maximum age rating","name":"max_age","in":"query"},{"type":"boolean","description":"Only content liked by the authenticated user","name":"liked","in":"query"},{"enum":["uz","ru","en"],"type":"string","description":"Resolve i18n fields to this locale","name":"lang","in":"query"}],"responses":{"200":{"description":"Raw i18n map (admin, no lang param)","schema":{"$ref":"#/definitions/handler.ContentListResponse"}}}},"post":{"security":[{"BearerAuth":[]}],"description":"Use this to create a shell, then call `/content/{id}/upload` to attach the source file.","consumes":["application/json"],"produces":["application/json"],"tags":["content"],"summary":"Create content metadata (no file)","parameters":[{"description":"Content payload","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/handler.ContentRequest"}}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/domain.Content"}}}}},"/content/{id}":{"get":{"description":"Returns a localized response when `?lang=uz|ru|en` is provided or an `Accept-Language` header is sent. Omit both to receive the raw i18n map (admin use).","produces":["application/json"],"tags":["content"],"summary":"Get a content item","parameters":[{"type":"string","description":"Content ID","name":"id","in":"path","required":true},{"enum":["uz","ru","en"],"type":"string","description":"Resolve i18n fields to this locale","name":"lang","in":"query"}],"responses":{"200":{"description":"Localized response","schema":{"$ref":"#/definitions/handler.LocalizedContent"}}}},"put":{"security":[{"BearerAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["content"],"summary":"Update content","parameters":[{"type":"string","description":"Content ID","name":"id","in":"path","required":true},{"description":"Content payload","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/handler.ContentRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/domain.Content"}}}},"delete":{"security":[{"BearerAuth":[]}],"produces":["application/json"],"tags":["content"],"summary":"Delete content","parameters":[{"type":"string","description":"Content ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/handler.MessageResponse"}}}}},"/content/{id}/block":{"get":{"security":[{"BearerAuth":[]}],"produces":["application/json"],"tags":["blocks"],"summary":"Check whether the current user has blocked a content item","parameters":[{"type":"string","description":"Content ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/handler.BlockStatusResponse"}}}},"post":{"security":[{"BearerAuth":[]}],"description":"Blocked content is hidden from all listings and cannot be streamed. Idempotent.","produces":["application/json"],"tags":["blocks"],"summary":"Block a content item","parameters":[{"type":"string","description":"Content ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/handler.MessageResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/handler.ErrorResponse"}}}},"delete":{"security":[{"BearerAuth":[]}],"description":"Removes the block — the content reappears in listings. Idempotent.","produces":["application/json"],"tags":["blocks"],"summary":"Unblock a content item","parameters":[{"type":"string","description":"Content ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/handler.MessageResponse"}}}}},"/content/{id}/comments":{"get":{"produces":["application/json"],"tags":["comments"],"summary":"List comments for a content item","parameters":[{"type":"string","description":"Content ID","name":"id","in":"path","required":true},{"type":"integer","description":"Page size","name":"limit","in":"query"},{"type":"integer","description":"Offset","name":"offset","in":"query"}],"responses":{"200":{"description":"OK","schema":{"type":"object"}}}},"post":{"security":[{"BearerAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["comments"],"summary":"Post a comment on a content item","parameters":[{"type":"string","description":"Content ID","name":"id","in":"path","required":true},{"description":"Comment body","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/handler.CommentRequest"}}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/domain.Comment"}}}}},"/content/{id}/like":{"get":{"security":[{"BearerAuth":[]}],"produces":["application/json"],"tags":["likes"],"summary":"Check whether the current user has liked a content item","parameters":[{"type":"string","description":"Content ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/handler.LikeStatusResponse"}}}},"post":{"security":[{"BearerAuth":[]}],"description":"Idempotent — liking an already-liked item is a no-op.","produces":["application/json"],"tags":["likes"],"summary":"Like a content item","parameters":[{"type":"string","description":"Content ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/handler.MessageResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/handler.ErrorResponse"}}}},"delete":{"security":[{"BearerAuth":[]}],"description":"Idempotent — unliking a non-liked item is a no-op.","produces":["application/json"],"tags":["likes"],"summary":"Unlike a content item","parameters":[{"type":"string","description":"Content ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/handler.MessageResponse"}}}}},"/content/{id}/likes/count":{"get":{"produces":["application/json"],"tags":["likes"],"summary":"Get the number of likes for a content item (public)","parameters":[{"type":"string","description":"Content ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/handler.LikeCountResponse"}}}}},"/content/{id}/poster":{"get":{"description":"Returns 404 if no poster has been uploaded. No signature","produces":["application/octet-stream"],"tags":["content"],"summary":"Serve a content item's poster image (public)","parameters":[{"type":"string","description":"Content ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK"},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/handler.ErrorResponse"}}}},"post":{"security":[{"BearerAuth":[]}],"description":"Replaces any existing poster. Posters are served publicly","consumes":["multipart/form-data"],"produces":["application/json"],"tags":["content"],"summary":"Upload a poster image for a content item","parameters":[{"type":"string","description":"Content ID","name":"id","in":"path","required":true},{"type":"file","description":"Image file (jpg/png/webp)","name":"file","in":"formData","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/domain.Content"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/handler.ErrorResponse"}}}}},"/content/{id}/rating":{"get":{"produces":["application/json"],"tags":["ratings"],"summary":"Get rating stats for a content item","parameters":[{"type":"string","description":"Content ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/handler.RatingStatsResponse"}}}},"post":{"security":[{"BearerAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["ratings"],"summary":"Rate a content item","parameters":[{"type":"string","description":"Content ID","name":"id","in":"path","required":true},{"description":"Rating payload","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/handler.RateRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/handler.MessageResponse"}}}}},"/content/{id}/renditions":{"get":{"produces":["application/json"],"tags":["content"],"summary":"List the HLS renditions emitted for a content item","parameters":[{"type":"string","description":"Content ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/handler.RenditionInfo"}}}}}},"/content/{id}/tags":{"put":{"security":[{"BearerAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["tags"],"summary":"Replace all tags for a content item","parameters":[{"type":"string","description":"Content ID","name":"id","in":"path","required":true},{"description":"Tag IDs","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/handler.SetContentTagsRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/handler.MessageResponse"}}}}},"/content/{id}/transcoding":{"get":{"produces":["application/json"],"tags":["content"],"summary":"Latest transcoder job for a content item","parameters":[{"type":"string","description":"Content ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/handler.TranscodingStatus"}}}}},"/content/{id}/upload":{"post":{"security":[{"BearerAuth":[]}],"description":"Streams the multipart upload to local storage and flips the item to \"ready\". Until the transcoder is wired up, the raw file is served directly via /media with a signed URL.","consumes":["multipart/form-data"],"produces":["application/json"],"tags":["content"],"summary":"Upload a source video file for a content item","parameters":[{"type":"string","description":"Content ID","name":"id","in":"path","required":true},{"type":"file","description":"Video file","name":"file","in":"formData","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/domain.Content"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/handler.ErrorResponse"}}}}},"/faqs":{"get":{"description":"Returns all active FAQs ordered by sort_order. Supports locale resolution via ?lang=uz|ru|en or Accept-Language header.","produces":["application/json"],"tags":["faq"],"summary":"List active FAQs","parameters":[{"type":"string","description":"Locale (uz|ru|en)","name":"lang","in":"query"}],"responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/handler.LocalizedFAQ"}}}}}},"/me/blocks":{"get":{"security":[{"BearerAuth":[]}],"description":"Returns a unified list of ContentListItems. Excludes episodes and series (only films/cartoons supported here). Pass `?lang=uz|ru|en` for localized strings.","produces":["application/json"],"tags":["blocks"],"summary":"List all content items blocked by the current user","parameters":[{"type":"integer","description":"Page size","name":"limit","in":"query"},{"type":"integer","description":"Offset","name":"offset","in":"query"},{"enum":["uz","ru","en"],"type":"string","description":"Resolve i18n fields to this locale","name":"lang","in":"query"}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/handler.LocalizedContentListResponse"}}}}},"/me/history":{"get":{"security":[{"BearerAuth":[]}],"produces":["application/json"],"tags":["watch"],"summary":"List watch history for the authenticated viewer","parameters":[{"type":"integer","description":"Page size (default 20)","name":"limit","in":"query"},{"type":"integer","description":"Offset","name":"offset","in":"query"}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/handler.HistoryListResponse"}}}}},"/me/likes":{"get":{"security":[{"BearerAuth":[]}],"produces":["application/json"],"tags":["likes"],"summary":"List all content items liked by the current user","parameters":[{"type":"integer","description":"Page size","name":"limit","in":"query"},{"type":"integer","description":"Offset","name":"offset","in":"query"}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/handler.LikesListResponse"}}}}},"/media/{path}":{"get":{"description":"Validates the HMAC signature + re-evaluates live parent rules on every request, so parents can revoke access in real time. Shared links without a valid (viewer, exp, sig) trio are rejected.","produces":["application/octet-stream"],"tags":["streaming"],"summary":"Serve a signed media asset","parameters":[{"enum":["user","child"],"type":"string","description":"viewer kind","name":"viewer","in":"query","required":true},{"type":"string","description":"viewer id (user_id or child_id)","name":"vid","in":"query","required":true},{"type":"string","description":"expiry unix seconds","name":"exp","in":"query","required":true},{"type":"string","description":"HMAC signature","name":"sig","in":"query","required":true},{"type":"string","description":"relative storage path","name":"path","in":"path","required":true}],"responses":{"200":{"description":"OK"},"403":{"description":"Forbidden","schema":{"$ref":"#/definitions/handler.ErrorResponse"}}}}},"/payments/click/card/request":{"post":{"security":[{"BearerAuth":[]}],"description":"Calls Click's POST /card_token/request. Click sends an SMS OTP to the phone linked to the card. Stores a pending (unverified) card and returns its ID. Follow up with POST /payments/click/card/verify.","consumes":["application/json"],"produces":["application/json"],"tags":["cards"],"summary":"Step 1 — request a Click card token","parameters":[{"description":"Card data","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/handler.ClickCardTokenRequestBody"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/service.CardTokenRequestResult"}}}}},"/payments/click/card/verify":{"post":{"security":[{"BearerAuth":[]}],"description":"Calls Click's POST /card_token/verify. On success the card is marked verified and ready for recurring charges.","consumes":["application/json"],"produces":["application/json"],"tags":["cards"],"summary":"Step 2 — verify card token with SMS OTP","parameters":[{"description":"OTP confirmation","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/handler.ClickCardTokenVerifyBody"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/domain.Card"}}}}},"/payments/click/mock-pay":{"get":{"description":"Only available when CLICK_SECRET_KEY is empty. Flips a pending checkout to succeeded so you can test subscription activation without a real gateway.","produces":["application/json"],"tags":["billing"],"summary":"DEV ONLY — mark a pending Click transaction as paid","parameters":[{"type":"string","description":"provider_ref returned by /billing/checkout","name":"ref","in":"query","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/domain.Transaction"}}}}},"/payments/click/payment/{payment_id}/reversal":{"delete":{"security":[{"BearerAuth":[]}],"description":"Calls Click's DELETE /payment/reversal. Only payments from the current reporting month can be reversed. Also marks the local transaction as refunded.","produces":["application/json"],"tags":["billing"],"summary":"Reverse (refund) a Click payment","parameters":[{"type":"integer","description":"Click payment_id","name":"payment_id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/handler.MessageResponse"}}}}},"/payments/click/payment/{payment_id}/status":{"get":{"security":[{"BearerAuth":[]}],"description":"Calls Click's GET /payment/status/{service_id}/{payment_id}. Payment statuses: 0=created, 1=processing, 2=succeeded, <0=error.","produces":["application/json"],"tags":["billing"],"summary":"Check Click payment status","parameters":[{"type":"integer","description":"Click payment_id","name":"payment_id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/payment.ClickPaymentStatusResult"}}}}},"/payments/click/webhook":{"post":{"description":"Gateway callback — validates sign_string and updates the transaction + subscription.","consumes":["application/json"],"produces":["application/json"],"tags":["billing"],"summary":"Click webhook endpoint","responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/handler.MessageResponse"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/handler.ErrorResponse"}}}}},"/plans":{"get":{"produces":["application/json"],"tags":["plans"],"summary":"List subscription plans","parameters":[{"type":"boolean","description":"Only active (default true for public)","name":"only_active","in":"query"},{"enum":["uz","ru","en"],"type":"string","description":"Resolve i18n fields to this locale","name":"lang","in":"query"}],"responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/domain.SubscriptionPlan"}}}}},"post":{"security":[{"BearerAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["plans"],"summary":"Create a plan","parameters":[{"description":"Plan","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/handler.PlanRequest"}}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/domain.SubscriptionPlan"}}}}},"/plans/{id}":{"put":{"security":[{"BearerAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["plans"],"summary":"Update a plan","parameters":[{"type":"string","description":"Plan ID","name":"id","in":"path","required":true},{"description":"Plan","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/handler.PlanRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/domain.SubscriptionPlan"}}}},"delete":{"security":[{"BearerAuth":[]}],"produces":["application/json"],"tags":["plans"],"summary":"Delete a plan","parameters":[{"type":"string","description":"Plan ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/handler.MessageResponse"}}}}},"/recommendations":{"get":{"produces":["application/json"],"tags":["recommendations"],"summary":"List active recommendations (populated)","responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/service.PopulatedRecommendation"}}}}},"post":{"security":[{"BearerAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["recommendations"],"summary":"Create a recommendation slot","parameters":[{"description":"Recommendation payload","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/handler.RecommendationRequest"}}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/domain.Recommendation"}}}}},"/recommendations/personalized":{"get":{"security":[{"BearerAuth":[]}],"produces":["application/json"],"tags":["recommendations"],"summary":"Get personalized content recommendations based on watch history","parameters":[{"type":"integer","description":"Max results (default 20)","name":"limit","in":"query"}],"responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/domain.Content"}}}}}},"/recommendations/{id}":{"put":{"security":[{"BearerAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["recommendations"],"summary":"Update a recommendation slot","parameters":[{"type":"string","description":"Recommendation ID","name":"id","in":"path","required":true},{"description":"Recommendation payload","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/handler.RecommendationRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/domain.Recommendation"}}}},"delete":{"security":[{"BearerAuth":[]}],"produces":["application/json"],"tags":["recommendations"],"summary":"Delete a recommendation slot","parameters":[{"type":"string","description":"Recommendation ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/handler.MessageResponse"}}}}},"/series":{"get":{"produces":["application/json"],"tags":["series"],"summary":"List all active series","responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/domain.Series"}}}}},"post":{"security":[{"BearerAuth":[]}],"description":"`kind` must be \"seasons\" (episodes grouped by season) or \"episodes\" (flat list, e.g. Tom & Jerry). Defaults to \"episodes\" if omitted.","consumes":["application/json"],"produces":["application/json"],"tags":["series"],"summary":"Create a series","parameters":[{"description":"Series payload","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/handler.SeriesRequest"}}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/domain.Series"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/handler.ErrorResponse"}}}}},"/series/{id}":{"get":{"produces":["application/json"],"tags":["series"],"summary":"Get a series by ID","parameters":[{"type":"string","description":"Series ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/domain.Series"}}}},"put":{"security":[{"BearerAuth":[]}],"description":"`kind` controls episode display: \"seasons\" (grouped by season) or \"episodes\" (flat list).","consumes":["application/json"],"produces":["application/json"],"tags":["series"],"summary":"Update a series","parameters":[{"type":"string","description":"Series ID","name":"id","in":"path","required":true},{"description":"Series payload","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/handler.SeriesRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/domain.Series"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/handler.ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/handler.ErrorResponse"}}}},"delete":{"security":[{"BearerAuth":[]}],"produces":["application/json"],"tags":["series"],"summary":"Delete a series","parameters":[{"type":"string","description":"Series ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/handler.MessageResponse"}}}}},"/series/{id}/episodes":{"get":{"produces":["application/json"],"tags":["series"],"summary":"List episodes of a series ordered by season/episode number","parameters":[{"type":"string","description":"Series ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/domain.Content"}}}}}},"/series/{id}/poster":{"get":{"produces":["application/octet-stream"],"tags":["series"],"summary":"Serve a series poster image (public)","parameters":[{"type":"string","description":"Series ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK"}}},"post":{"security":[{"BearerAuth":[]}],"consumes":["multipart/form-data"],"produces":["application/json"],"tags":["series"],"summary":"Upload a poster for a series","parameters":[{"type":"string","description":"Series ID","name":"id","in":"path","required":true},{"type":"file","description":"Image file","name":"file","in":"formData","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/domain.Series"}}}}},"/stream/{id}/grant":{"get":{"security":[{"BearerAuth":[]}],"description":"Call this right before playback. The server checks the caller's subscription (or, for a child token, parent subscription + allow/deny rules) and returns a short-lived signed URL bound to the viewer's id. Hand the URL to your <video> tag or HLS player.","produces":["application/json"],"tags":["streaming"],"summary":"Mint a signed streaming URL","parameters":[{"type":"string","description":"Content ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/service.StreamGrant"}},"403":{"description":"Forbidden","schema":{"$ref":"#/definitions/handler.ErrorResponse"}}}}},"/stream/{id}/progress":{"put":{"security":[{"BearerAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["watch"],"summary":"Save playback progress for a stream","parameters":[{"type":"string","description":"Content ID","name":"id","in":"path","required":true},{"description":"Progress payload","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/handler.SaveProgressRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/handler.MessageResponse"}}}}},"/support/attachments/{path}":{"get":{"security":[{"BearerAuth":[]}],"produces":["application/octet-stream"],"tags":["support"],"summary":"Serve a support chat attachment","parameters":[{"type":"string","description":"Attachment path","name":"path","in":"path","required":true}],"responses":{"200":{"description":"OK"},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/handler.ErrorResponse"}}}}},"/support/chat":{"get":{"security":[{"BearerAuth":[]}],"produces":["application/json"],"tags":["support"],"summary":"Get the current user's support chat (created lazily on first call)","responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/domain.SupportChat"}}}}},"/support/chat/messages":{"get":{"security":[{"BearerAuth":[]}],"description":"Returns messages ordered ascending by time. Use `after` to poll for new messages, or `before` to load older history.","produces":["application/json"],"tags":["support"],"summary":"List messages in the current user's support chat","parameters":[{"type":"integer","description":"Page size (default 50, max 200)","name":"limit","in":"query"},{"type":"string","description":"Return messages older than this message ID","name":"before","in":"query"},{"type":"string","description":"Return messages newer than this message ID (use for polling)","name":"after","in":"query"}],"responses":{"200":{"description":"OK","schema":{"type":"object","additionalProperties":true}}}},"post":{"security":[{"BearerAuth":[]}],"consumes":["multipart/form-data"],"produces":["application/json"],"tags":["support"],"summary":"Send a message in the current user's support chat (with optional attachment up to 50 MB)","parameters":[{"type":"string","description":"Message text","name":"body","in":"formData"},{"type":"file","description":"Attachment (max 50 MB)","name":"file","in":"formData"}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/domain.SupportMessage"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/handler.ErrorResponse"}}}}},"/support/chat/read":{"post":{"security":[{"BearerAuth":[]}],"produces":["application/json"],"tags":["support"],"summary":"Mark the user's support chat as read","responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/handler.MessageResponse"}}}}},"/tags":{"get":{"produces":["application/json"],"tags":["tags"],"summary":"List tags","responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/domain.Tag"}}}}},"post":{"security":[{"BearerAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["tags"],"summary":"Create a tag","parameters":[{"description":"Tag payload","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/handler.TagRequest"}}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/domain.Tag"}}}}},"/tags/{id}":{"put":{"security":[{"BearerAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["tags"],"summary":"Update a tag","parameters":[{"type":"string","description":"Tag ID","name":"id","in":"path","required":true},{"description":"Tag payload","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/handler.TagRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/domain.Tag"}}}},"delete":{"security":[{"BearerAuth":[]}],"produces":["application/json"],"tags":["tags"],"summary":"Delete a tag","parameters":[{"type":"string","description":"Tag ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/handler.MessageResponse"}}}}},"/tv-devices":{"get":{"security":[{"BearerAuth":[]}],"description":"Parent lists all TV devices linked to their account.","produces":["application/json"],"tags":["tv"],"summary":"List paired TV devices","responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/domain.TVDevice"}}}}}},"/tv-devices/{id}":{"delete":{"security":[{"BearerAuth":[]}],"description":"Parent revokes a TV device. The TV can no longer select profiles or stream.","produces":["application/json"],"tags":["tv"],"summary":"Revoke a paired TV device","parameters":[{"type":"string","description":"TV Device ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/handler.MessageResponse"}}}}},"/users":{"get":{"security":[{"BearerAuth":[]}],"produces":["application/json"],"tags":["users"],"summary":"List users (super_admin only)","parameters":[{"enum":["super_admin","admin","parent"],"type":"string","description":"Filter by role","name":"role","in":"query"},{"type":"integer","description":"Page size (default 50, max 200)","name":"limit","in":"query"},{"type":"integer","description":"Offset","name":"offset","in":"query"}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/handler.UsersListResponse"}},"403":{"description":"Forbidden","schema":{"$ref":"#/definitions/handler.ErrorResponse"}}}},"post":{"security":[{"BearerAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["users"],"summary":"Create an admin or super_admin (super_admin only)","parameters":[{"description":"Staff data","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/handler.CreateStaffRequest"}}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/domain.User"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/handler.ErrorResponse"}}}}},"/users/{id}":{"get":{"security":[{"BearerAuth":[]}],"produces":["application/json"],"tags":["users"],"summary":"Get a user by ID (super_admin only)","parameters":[{"type":"string","description":"User ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/domain.User"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/handler.ErrorResponse"}}}},"put":{"security":[{"BearerAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["users"],"summary":"Update a user's profile and role (super_admin only)","parameters":[{"type":"string","description":"User ID","name":"id","in":"path","required":true},{"description":"User data","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/handler.UpdateUserRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/domain.User"}}}},"delete":{"security":[{"BearerAuth":[]}],"produces":["application/json"],"tags":["users"],"summary":"Delete a user","parameters":[{"type":"string","description":"User ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/handler.MessageResponse"}}}}},"/users/{id}/active":{"patch":{"security":[{"BearerAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["users"],"summary":"Activate or deactivate a user","parameters":[{"type":"string","description":"User ID","name":"id","in":"path","required":true},{"description":"Active flag","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/handler.ActivateRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/domain.User"}}}}},"/users/{id}/avatar":{"get":{"produces":["application/octet-stream"],"tags":["auth"],"summary":"Serve a user's profile avatar (public)","parameters":[{"type":"string","description":"User ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK"},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/handler.ErrorResponse"}}}}},"/users/{id}/cards":{"get":{"security":[{"BearerAuth":[]}],"description":"Returns all saved cards belonging to the given user. Token hash included; raw token never exposed.","produces":["application/json"],"tags":["cards"],"summary":"List cards for a specific user (admin)","parameters":[{"type":"string","description":"User ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/handler.AdminCardView"}}}}}},"/users/{id}/children":{"get":{"security":[{"BearerAuth":[]}],"produces":["application/json"],"tags":["children"],"summary":"List children for any parent (admin)","parameters":[{"type":"string","description":"Parent user ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/domain.Child"}}}}}},"/users/{id}/plan":{"post":{"security":[{"BearerAuth":[]}],"description":"Cancels the user's current active/pending subscriptions and creates a fresh active one for the given plan. Logs a manual succeeded transaction with amount 0 for auditing.","consumes":["application/json"],"produces":["application/json"],"tags":["users"],"summary":"Assign a plan to a user (super_admin)","parameters":[{"type":"string","description":"User ID","name":"id","in":"path","required":true},{"description":"Plan assignment","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/handler.AssignPlanRequest"}}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/domain.Subscription"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/handler.ErrorResponse"}}}}},"/users/{id}/subscriptions":{"get":{"security":[{"BearerAuth":[]}],"produces":["application/json"],"tags":["users"],"summary":"List a user's subscriptions (super_admin)","parameters":[{"type":"string","description":"User ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/domain.Subscription"}}}}}},"/users/{user_id}/children/{child_id}":{"delete":{"security":[{"BearerAuth":[]}],"produces":["application/json"],"tags":["children"],"summary":"Delete a child (admin)","parameters":[{"type":"string","description":"Parent user ID","name":"user_id","in":"path","required":true},{"type":"string","description":"Child ID","name":"child_id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/handler.MessageResponse"}}}}},"/users/{user_id}/children/{child_id}/active":{"patch":{"security":[{"BearerAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["children"],"summary":"Activate or deactivate a child (admin)","parameters":[{"type":"string","description":"Parent user ID","name":"user_id","in":"path","required":true},{"type":"string","description":"Child ID","name":"child_id","in":"path","required":true},{"description":"Active flag","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/handler.ActivateChildRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/domain.Child"}}}}}},"definitions":{"domain.Card":{"type":"object","properties":{"brand":{"description":"visa / mastercard / humo / uzcard","type":"string"},"created_at":{"type":"string"},"expiry_month":{"type":"integer"},"expiry_year":{"type":"integer"},"holder_name":{"type":"string"},"id":{"type":"string"},"is_default":{"type":"boolean"},"masked_pan":{"description":"e.g. 8600****1234","type":"string"},"provider":{"$ref":"#/definitions/domain.PaymentProvider"},"updated_at":{"type":"string"},"user_id":{"type":"string"},"verified_at":{"type":"string"}}},"domain.Category":{"type":"object","properties":{"active":{"type":"boolean"},"created_at":{"type":"string"},"description":{"type":"object"},"id":{"type":"string"},"kind":{"$ref":"#/definitions/domain.CategoryKind"},"name":{"type":"object"},"parent_category_id":{"type":"string"},"poster_url":{"type":"string"},"slug":{"type":"string"},"sort_order":{"type":"integer"},"updated_at":{"type":"string"}}},"domain.CategoryKind":{"type":"string","enum":["film","series","cartoon","other"],"x-enum-varnames":["KindFilm","KindSeries","KindCartoon","KindOther"]},"domain.Child":{"type":"object","properties":{"active":{"type":"boolean"},"age":{"type":"integer"},"avatar_url":{"type":"string"},"created_at":{"type":"string"},"extended_until":{"type":"string"},"id":{"type":"string"},"name":{"type":"string"},"parent_id":{"type":"string"},"updated_at":{"type":"string"}}},"domain.ChildDevice":{"type":"object","properties":{"child_id":{"type":"string"},"created_at":{"type":"string"},"device_fingerprint":{"type":"string"},"device_name":{"type":"string"},"id":{"type":"string"},"last_seen_at":{"type":"string"},"paired_at":{"type":"string"},"pairing_expires_at":{"type":"string"},"revoked_at":{"type":"string"},"updated_at":{"type":"string"}}},"domain.ChildPermission":{"type":"object","properties":{"category_id":{"type":"string"},"child_id":{"type":"string"},"content_id":{"type":"string"},"created_at":{"type":"string"},"daily_limit_minutes":{"description":"0 = unlimited","type":"integer"},"id":{"type":"string"},"mode":{"$ref":"#/definitions/domain.PermissionMode"},"updated_at":{"type":"string"},"watch_from_min":{"description":"minutes since midnight","type":"integer"},"watch_until_min":{"description":"minutes since midnight (0..1440)","type":"integer"},"weekday_mask":{"description":"Weekday bitmap: bit0=Mon ... bit6=Sun. 127 = every day.","type":"integer"}}},"domain.Comment":{"type":"object","properties":{"body":{"type":"string"},"content_id":{"type":"string"},"created_at":{"type":"string"},"id":{"type":"string"},"updated_at":{"type":"string"},"user_id":{"type":"string"}}},"domain.Content":{"type":"object","properties":{"age_rating":{"type":"integer"},"category_id":{"type":"string"},"created_at":{"type":"string"},"created_by_id":{"type":"string"},"description":{"type":"object"},"duration_sec":{"type":"integer"},"episode_number":{"type":"integer"},"id":{"type":"string"},"poster_url":{"type":"string"},"published":{"type":"boolean"},"published_at":{"type":"string"},"season_number":{"type":"integer"},"series_id":{"type":"string"},"slug":{"type":"string"},"source_path":{"description":"relative under STORAGE_PATH","type":"string"},"status":{"$ref":"#/definitions/domain.ContentStatus"},"title":{"type":"object"},"updated_at":{"type":"string"},"views_count":{"description":"ViewsCount is incremented atomically each time a stream grant is issued.\nIt counts grant events (play button presses), not individual HLS segments.","type":"integer"},"year":{"type":"integer"}}},"domain.ContentStatus":{"type":"string","enum":["uploaded","transcoding","ready","failed"],"x-enum-varnames":["ContentUploaded","ContentTranscoding","ContentReady","ContentFailed"]},"domain.FAQ":{"type":"object","properties":{"active":{"type":"boolean"},"answer":{"type":"object"},"created_at":{"type":"string"},"id":{"type":"string"},"question":{"type":"object"},"sort_order":{"type":"integer"},"updated_at":{"type":"string"}}},"domain.PaymentProvider":{"type":"string","enum":["click","payme","stripe","manual"],"x-enum-varnames":["ProviderClick","ProviderPayme","ProviderStripe","ProviderManual"]},"domain.PermissionMode":{"type":"string","enum":["allow","deny"],"x-enum-varnames":["PermAllow","PermDeny"]},"domain.Recommendation":{"type":"object","properties":{"active":{"type":"boolean"},"created_at":{"type":"string"},"id":{"type":"string"},"reference_id":{"type":"string"},"sort_order":{"type":"integer"},"type":{"$ref":"#/definitions/domain.RecommendationType"},"updated_at":{"type":"string"}}},"domain.RecommendationType":{"type":"string","enum":["category","content","series"],"x-enum-varnames":["RecCategory","RecContent","RecSeries"]},"domain.Role":{"description":"super_admin=platform owner, admin=content staff, parent=paying customer.","type":"string","enum":["super_admin","admin","parent"],"x-enum-varnames":["RoleSuperAdmin","RoleAdmin","RoleParent"]},"domain.Series":{"type":"object","properties":{"active":{"type":"boolean"},"created_at":{"type":"string"},"description":{"type":"object"},"id":{"type":"string"},"kind":{"description":"Kind controls how the client groups and displays episodes.\nDefaults to \"episodes\" so existing rows keep working after migration.","allOf":[{"$ref":"#/definitions/domain.SeriesKind"}]},"poster_url":{"type":"string"},"slug":{"type":"string"},"title":{"type":"object"},"updated_at":{"type":"string"}}},"domain.SeriesKind":{"type":"string","enum":["seasons","episodes"],"x-enum-varnames":["SeriesKindSeasons","SeriesKindEpisodes"]},"domain.Subscription":{"type":"object","properties":{"auto_renew":{"type":"boolean"},"created_at":{"type":"string"},"ends_at":{"type":"string"},"id":{"type":"string"},"plan_id":{"type":"string"},"starts_at":{"type":"string"},"status":{"$ref":"#/definitions/domain.SubscriptionStatus"},"updated_at":{"type":"string"},"user_id":{"type":"string"}}},"domain.SubscriptionPlan":{"type":"object","properties":{"active":{"type":"boolean"},"created_at":{"type":"string"},"currency":{"type":"string"},"description":{"type":"object"},"duration_days":{"type":"integer"},"id":{"type":"string"},"max_children":{"type":"integer"},"name":{"type":"object"},"package_code":{"type":"string"},"price_cents":{"description":"minor units (tiyin for UZS)","type":"integer"},"slug":{"type":"string"},"spic":{"description":"Fiscalization fields — required by Uzbekistan law for every paid transaction.\nSPIC is the 17-character ИКПУ commodity classification code.\nPackageCode identifies the unit-of-measure package from the national catalog.\nVATPercent is the НДС rate (0, 12, or 15); VAT is calculated inclusive of price.","type":"string"},"updated_at":{"type":"string"},"vat_percent":{"type":"integer"}}},"domain.SubscriptionStatus":{"type":"string","enum":["active","pending","expired","canceled"],"x-enum-varnames":["SubActive","SubPending","SubExpired","SubCanceled"]},"domain.SupportChat":{"type":"object","properties":{"admin_unread_count":{"type":"integer"},"created_at":{"type":"string"},"id":{"type":"string"},"last_message_at":{"type":"string"},"last_message_preview":{"type":"string"},"updated_at":{"type":"string"},"user":{"$ref":"#/definitions/domain.User"},"user_id":{"type":"string"},"user_unread_count":{"type":"integer"}}},"domain.SupportMessage":{"type":"object","properties":{"attachment_name":{"type":"string"},"attachment_size":{"type":"integer"},"attachment_type":{"type":"string"},"attachment_url":{"type":"string"},"body":{"type":"string"},"chat_id":{"type":"string"},"created_at":{"type":"string"},"id":{"type":"string"},"sender":{"$ref":"#/definitions/domain.User"},"sender_id":{"type":"string"},"sender_role":{"$ref":"#/definitions/domain.SupportSenderRole"},"updated_at":{"type":"string"}}},"domain.SupportSenderRole":{"type":"string","enum":["user","admin"],"x-enum-varnames":["SupportSenderUser","SupportSenderAdmin"]},"domain.TVDevice":{"type":"object","properties":{"confirmed_at":{"type":"string"},"created_at":{"type":"string"},"device_fingerprint":{"type":"string"},"device_name":{"type":"string"},"id":{"type":"string"},"last_seen_at":{"type":"string"},"pairing_expires_at":{"type":"string"},"parent_id":{"type":"string"},"revoked_at":{"type":"string"},"updated_at":{"type":"string"}}},"domain.Tag":{"type":"object","properties":{"active":{"type":"boolean"},"created_at":{"type":"string"},"id":{"type":"string"},"name":{"type":"object"},"slug":{"type":"string"},"updated_at":{"type":"string"}}},"domain.Transaction":{"type":"object","properties":{"amount_cents":{"type":"integer"},"card_id":{"type":"string"},"created_at":{"type":"string"},"currency":{"type":"string"},"description":{"type":"string"},"fiscal_sent_at":{"type":"string"},"id":{"type":"string"},"kind":{"$ref":"#/definitions/domain.TransactionKind"},"plan_id":{"type":"string"},"processed_at":{"type":"string"},"provider":{"$ref":"#/definitions/domain.PaymentProvider"},"provider_ref":{"type":"string"},"status":{"$ref":"#/definitions/domain.TransactionStatus"},"subscription_id":{"type":"string"},"updated_at":{"type":"string"},"user_id":{"type":"string"}}},"domain.TransactionKind":{"type":"string","enum":["subscription","topup","refund"],"x-enum-varnames":["TxSubscription","TxTopup","TxRefund"]},"domain.TransactionStatus":{"type":"string","enum":["pending","succeeded","failed","refunded","canceled"],"x-enum-varnames":["TxPending","TxSucceeded","TxFailed","TxRefunded","TxCanceled"]},"domain.User":{"type":"object","properties":{"active":{"type":"boolean"},"avatar_url":{"type":"string"},"created_at":{"type":"string"},"email":{"type":"string"},"id":{"type":"string"},"last_login_at":{"type":"string"},"last_name":{"type":"string"},"name":{"type":"string"},"phone":{"type":"string"},"role":{"$ref":"#/definitions/domain.Role"},"updated_at":{"type":"string"}}},"handler.ActivateChildRequest":{"type":"object","properties":{"active":{"type":"boolean"}}},"handler.ActivateRequest":{"type":"object","properties":{"active":{"type":"boolean"}}},"handler.AdminCardListResponse":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/definitions/handler.AdminCardView"}},"limit":{"type":"integer"},"offset":{"type":"integer"},"total":{"type":"integer"}}},"handler.AdminCardView":{"type":"object","properties":{"brand":{"type":"string"},"created_at":{"type":"string"},"expiry_month":{"type":"integer"},"expiry_year":{"type":"integer"},"holder_name":{"type":"string"},"id":{"type":"string"},"is_default":{"type":"boolean"},"masked_pan":{"type":"string"},"provider":{"$ref":"#/definitions/domain.PaymentProvider"},"token_hash":{"description":"SHA-256 fingerprint — NOT the raw token","type":"string"},"user_id":{"type":"string"},"verified_at":{"type":"string"}}},"handler.AppleAuthRequest":{"type":"object","required":["identity_token"],"properties":{"family_name":{"description":"FamilyName is the user's last name. Same first-sign-in-only restriction.","type":"string","example":"Karimov"},"given_name":{"description":"GivenName is the user's first name. Apple only provides this on the very\nfirst sign-in; pass an empty string on subsequent sign-ins.","type":"string","example":"Alisher"},"identity_token":{"description":"IdentityToken is the raw identityToken string from ASAuthorizationAppleIDCredential.","type":"string","example":"eyJhbGci..."}}},"handler.AssignPlanRequest":{"type":"object","required":["plan_id"],"properties":{"duration_days":{"type":"integer"},"plan_id":{"type":"string"}}},"handler.BlockStatusResponse":{"type":"object","properties":{"blocked":{"type":"boolean"}}},"handler.BlocksListResponse":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/definitions/domain.Content"}},"limit":{"type":"integer"},"offset":{"type":"integer"},"total":{"type":"integer"}}},"handler.CardRequest":{"type":"object","required":["expiry_month","expiry_year","holder_name","pan","provider"],"properties":{"cvc":{"type":"string"},"expiry_month":{"type":"integer"},"expiry_year":{"type":"integer"},"holder_name":{"type":"string"},"pan":{"type":"string","example":"8600123412341234"},"provider":{"enum":["click","payme","stripe"],"allOf":[{"$ref":"#/definitions/domain.PaymentProvider"}]}}},"handler.CategoryRequest":{"type":"object","required":["kind","name"],"properties":{"active":{"type":"boolean"},"description":{"type":"object"},"kind":{"enum":["film","series","cartoon","other"],"allOf":[{"$ref":"#/definitions/domain.CategoryKind"}]},"name":{"type":"object"},"parent_category_id":{"type":"string"},"poster_url":{"type":"string"},"sort_order":{"type":"integer"}}},"handler.CheckoutRequest":{"type":"object","required":["plan_id","provider"],"properties":{"card_id":{"type":"string"},"plan_id":{"type":"string"},"provider":{"enum":["click","payme","stripe"],"allOf":[{"$ref":"#/definitions/domain.PaymentProvider"}],"example":"click"}}},"handler.ChildCreateRequest":{"type":"object","required":["name"],"properties":{"age":{"type":"integer","maximum":21,"minimum":0},"avatar_url":{"type":"string"},"name":{"type":"string","minLength":1}}},"handler.ChildUpdateRequest":{"type":"object","required":["name"],"properties":{"active":{"type":"boolean"},"age":{"type":"integer"},"avatar_url":{"type":"string"},"name":{"type":"string"}}},"handler.ClickCardTokenRequestBody":{"type":"object","required":["expire_date","pan"],"properties":{"expire_date":{"type":"string","example":"0327"},"pan":{"type":"string","example":"8600550000003244"}}},"handler.ClickCardTokenVerifyBody":{"type":"object","required":["card_id","sms_code"],"properties":{"card_id":{"type":"string"},"sms_code":{"type":"integer"}}},"handler.CommentRequest":{"type":"object","required":["body"],"properties":{"body":{"type":"string"}}},"handler.ContentListItem":{"type":"object","properties":{"age_rating":{"type":"integer"},"avg_rating":{"type":"number"},"category_id":{"description":"--- content-only (item_type == \"content\") ---","type":"string"},"description":{"type":"object"},"duration_sec":{"type":"integer"},"episode_count":{"type":"integer"},"episode_number":{"type":"integer"},"id":{"description":"--- shared ---","type":"string"},"is_liked":{"type":"boolean"},"item_type":{"description":"\"content\" | \"series\"","type":"string"},"likes_count":{"type":"integer"},"poster_url":{"type":"string"},"published":{"type":"boolean"},"rating_count":{"type":"integer"},"season_count":{"type":"integer"},"season_number":{"type":"integer"},"series_id":{"type":"string"},"series_kind":{"description":"--- series-only (item_type == \"series\") ---","allOf":[{"$ref":"#/definitions/domain.SeriesKind"}]},"slug":{"type":"string"},"status":{"$ref":"#/definitions/domain.ContentStatus"},"tags":{"type":"array","items":{"$ref":"#/definitions/domain.Tag"}},"title":{"type":"object"},"views_count":{"type":"integer"}}},"handler.ContentListResponse":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/definitions/handler.ContentListItem"}},"limit":{"type":"integer"},"offset":{"type":"integer"},"total":{"type":"integer"}}},"handler.ContentRequest":{"type":"object","required":["category_id","title"],"properties":{"age_rating":{"type":"integer"},"category_id":{"type":"string"},"description":{"type":"object"},"duration_sec":{"type":"integer"},"episode_number":{"type":"integer"},"poster_url":{"type":"string"},"published":{"type":"boolean"},"season_number":{"type":"integer"},"series_id":{"type":"string"},"source_path":{"type":"string","example":"uploads/original/sample.mp4"},"tag_ids":{"type":"array","items":{"type":"string"}},"title":{"type":"object"},"year":{"type":"integer"}}},"handler.CreateStaffRequest":{"type":"object","required":["email","name","password","role"],"properties":{"email":{"type":"string"},"name":{"type":"string"},"password":{"type":"string","minLength":8},"role":{"enum":["admin","super_admin"],"allOf":[{"$ref":"#/definitions/domain.Role"}]}}},"handler.DeeplinkCheckoutRequest":{"type":"object","required":["plan_id"],"properties":{"plan_id":{"type":"string"}}},"handler.ErrorResponse":{"description":"Error body returned by any failing endpoint.","type":"object","properties":{"error":{"type":"string","example":"invalid credentials"},"message":{"$ref":"#/definitions/handler.I18nMessage"}}},"handler.ExtendByPINRequest":{"type":"object","required":["pin"],"properties":{"pin":{"type":"string","example":"1234"}}},"handler.FAQRequest":{"type":"object","required":["answer","question"],"properties":{"active":{"type":"boolean"},"answer":{"type":"object"},"question":{"type":"object"},"sort_order":{"type":"integer"}}},"handler.ForgotPasswordRequest":{"type":"object","required":["email"],"properties":{"email":{"type":"string","example":"user@example.com"}}},"handler.GoogleAuthRequest":{"type":"object","required":["id_token"],"properties":{"id_token":{"description":"IDToken is the raw id_token string from Google Sign-In on Android or iOS.","type":"string","example":"eyJhbGci..."}}},"handler.HistoryListResponse":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/definitions/service.HistoryItem"}},"limit":{"type":"integer"},"offset":{"type":"integer"},"total":{"type":"integer"}}},"handler.I18nMessage":{"type":"object","properties":{"en":{"type":"string"},"ru":{"type":"string"},"uz":{"type":"string"}}},"handler.LikeCountResponse":{"type":"object","properties":{"count":{"type":"integer"}}},"handler.LikeStatusResponse":{"type":"object","properties":{"liked":{"type":"boolean"}}},"handler.LikesListResponse":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/definitions/domain.Content"}},"limit":{"type":"integer"},"offset":{"type":"integer"},"total":{"type":"integer"}}},"handler.LocalizedContent":{"type":"object","properties":{"age_rating":{"type":"integer"},"avg_rating":{"type":"number"},"category_id":{"type":"string"},"created_at":{"type":"string"},"description":{"type":"string"},"duration_sec":{"type":"integer"},"episode_number":{"type":"integer"},"id":{"type":"string"},"is_liked":{"type":"boolean"},"likes_count":{"type":"integer"},"poster_url":{"type":"string"},"published":{"type":"boolean"},"rating_count":{"type":"integer"},"season_number":{"type":"integer"},"series_id":{"type":"string"},"slug":{"type":"string"},"source_path":{"type":"string"},"status":{"$ref":"#/definitions/domain.ContentStatus"},"tags":{"type":"array","items":{"$ref":"#/definitions/handler.LocalizedTag"}},"title":{"type":"string"},"views_count":{"type":"integer"},"year":{"type":"integer"}}},"handler.LocalizedContentListItem":{"type":"object","properties":{"age_rating":{"type":"integer"},"avg_rating":{"type":"number"},"category_id":{"description":"content-only","type":"string"},"description":{"type":"string"},"duration_sec":{"type":"integer"},"episode_count":{"type":"integer"},"episode_number":{"type":"integer"},"id":{"description":"shared","type":"string"},"is_liked":{"type":"boolean"},"item_type":{"description":"\"content\" | \"series\"","type":"string"},"likes_count":{"type":"integer"},"poster_url":{"type":"string"},"published":{"type":"boolean"},"rating_count":{"type":"integer"},"season_count":{"type":"integer"},"season_number":{"type":"integer"},"series_id":{"type":"string"},"series_kind":{"description":"series-only","allOf":[{"$ref":"#/definitions/domain.SeriesKind"}]},"slug":{"type":"string"},"status":{"$ref":"#/definitions/domain.ContentStatus"},"tags":{"type":"array","items":{"$ref":"#/definitions/handler.LocalizedTag"}},"title":{"type":"string"},"views_count":{"type":"integer"}}},"handler.LocalizedContentListResponse":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/definitions/handler.LocalizedContentListItem"}},"limit":{"type":"integer"},"offset":{"type":"integer"},"total":{"type":"integer"}}},"handler.LocalizedFAQ":{"type":"object","properties":{"answer":{"type":"string"},"id":{"type":"string"},"question":{"type":"string"},"sort_order":{"type":"integer"}}},"handler.LocalizedTag":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string"}}},"handler.LoginRequest":{"type":"object","required":["email","password"],"properties":{"email":{"type":"string","example":"admin@astir-animation.uz"},"password":{"type":"string","example":"SuperAdmin123!"}}},"handler.MessageResponse":{"type":"object","properties":{"message":{"type":"string","example":"ok"}}},"handler.OTPLoginRequest":{"type":"object","required":["email","password"],"properties":{"email":{"type":"string","example":"user@example.com"},"password":{"type":"string"}}},"handler.OTPRequestRequest":{"type":"object","required":["email"],"properties":{"email":{"type":"string","example":"user@example.com"}}},"handler.OTPVerifyRequest":{"type":"object","required":["code","email"],"properties":{"code":{"type":"string","example":"1234"},"email":{"type":"string","example":"user@example.com"}}},"handler.PairRequest":{"type":"object","properties":{"device_fingerprint":{"type":"string"},"device_name":{"type":"string"}}},"handler.PermissionRequest":{"type":"object","required":["mode"],"properties":{"category_id":{"type":"string"},"content_id":{"type":"string"},"daily_limit_minutes":{"type":"integer"},"mode":{"enum":["allow","deny"],"allOf":[{"$ref":"#/definitions/domain.PermissionMode"}]},"watch_from_min":{"type":"integer","example":0},"watch_until_min":{"type":"integer","example":1320},"weekday_mask":{"type":"integer","example":127}}},"handler.PlanRequest":{"type":"object","required":["currency","duration_days","name","price_cents"],"properties":{"active":{"type":"boolean"},"currency":{"type":"string","example":"UZS"},"description":{"type":"object"},"duration_days":{"type":"integer"},"max_children":{"type":"integer"},"name":{"type":"object"},"package_code":{"type":"string","example":"796"},"price_cents":{"type":"integer"},"spic":{"description":"Fiscalization (required by Uzbekistan law)","type":"string","example":"10304001001000000"},"vat_percent":{"type":"integer","example":12}}},"handler.RateRequest":{"type":"object","required":["score"],"properties":{"score":{"type":"integer","maximum":5,"minimum":1,"example":4}}},"handler.RatingStatsResponse":{"type":"object","properties":{"avg_rating":{"type":"number"},"rating_count":{"type":"integer"},"user_rating":{"type":"integer"}}},"handler.RecommendationRequest":{"type":"object","required":["reference_id","type"],"properties":{"active":{"type":"boolean"},"reference_id":{"type":"string"},"sort_order":{"type":"integer"},"type":{"enum":["category","content","series"],"allOf":[{"$ref":"#/definitions/domain.RecommendationType"}]}}},"handler.RecurringChargeRequest":{"type":"object","required":["card_id","plan_id"],"properties":{"card_id":{"type":"string"},"plan_id":{"type":"string"}}},"handler.RefreshRequest":{"type":"object","required":["refresh_token"],"properties":{"refresh_token":{"type":"string"}}},"handler.RegisterRequest":{"type":"object","required":["email","name","password"],"properties":{"email":{"type":"string","example":"user@example.com"},"last_name":{"type":"string","example":"Karimov"},"name":{"type":"string","example":"Alisher"},"password":{"type":"string","minLength":6}}},"handler.RenditionInfo":{"type":"object","properties":{"bitrate_kbps":{"type":"integer"},"duration_sec":{"type":"integer"},"id":{"type":"string"},"label":{"type":"string"},"resolution":{"type":"string"}}},"handler.ResetPasswordRequest":{"type":"object","required":["email","password"],"properties":{"email":{"type":"string","example":"user@example.com"},"password":{"type":"string","minLength":6}}},"handler.SaveProgressRequest":{"type":"object","properties":{"position_sec":{"type":"integer","minimum":0,"example":120}}},"handler.ScanQRRequest":{"type":"object","required":["code"],"properties":{"child_id":{"type":"string"},"code":{"type":"string"},"permissions":{"type":"array","items":{"$ref":"#/definitions/handler.PermissionRequest"}}}},"handler.SeriesRequest":{"type":"object","required":["title"],"properties":{"active":{"type":"boolean"},"description":{"type":"object"},"kind":{"enum":["seasons","episodes"],"allOf":[{"$ref":"#/definitions/domain.SeriesKind"}]},"title":{"type":"object"}}},"handler.SetContentTagsRequest":{"type":"object","properties":{"tag_ids":{"type":"array","items":{"type":"string"}}}},"handler.SetPINRequest":{"type":"object","required":["pin"],"properties":{"pin":{"type":"string","example":"1234"}}},"handler.TVConfirmRequest":{"type":"object","required":["code"],"properties":{"code":{"type":"string"}}},"handler.TVInitRequest":{"type":"object","required":["device_fingerprint","device_name"],"properties":{"device_fingerprint":{"type":"string"},"device_name":{"type":"string"}}},"handler.TVSelectProfileRequest":{"type":"object","properties":{"child_id":{"type":"string"}}},"handler.TagRequest":{"type":"object","required":["name"],"properties":{"active":{"type":"boolean"},"name":{"type":"object"}}},"handler.TranscodingStatus":{"type":"object","properties":{"error":{"type":"string"},"finished_at":{"type":"string"},"job_id":{"type":"string"},"progress":{"type":"integer"},"stage":{"type":"string"},"started_at":{"type":"string"},"state":{"type":"string"}}},"handler.TxListResponse":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/definitions/domain.Transaction"}},"limit":{"type":"integer"},"offset":{"type":"integer"},"total":{"type":"integer"}}},"handler.UpdateProfileRequest":{"type":"object","properties":{"last_name":{"type":"string"},"name":{"type":"string"}}},"handler.UpdateUserRequest":{"type":"object","required":["last_name","name","role"],"properties":{"last_name":{"type":"string"},"name":{"type":"string"},"role":{"enum":["super_admin","admin","parent"],"allOf":[{"$ref":"#/definitions/domain.Role"}]}}},"handler.UsersListResponse":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/definitions/domain.User"}},"limit":{"type":"integer"},"offset":{"type":"integer"},"total":{"type":"integer"}}},"payment.ClickPaymentStatusResult":{"type":"object","properties":{"paymentID":{"type":"integer"},"paymentStatus":{"type":"integer"}}},"service.CardTokenRequestResult":{"type":"object","properties":{"card_id":{"type":"string"},"phone_number":{"type":"string"}}},"service.CheckoutResult":{"type":"object","properties":{"checkout_url":{"type":"string"},"subscription":{"$ref":"#/definitions/domain.Subscription"},"transaction":{"$ref":"#/definitions/domain.Transaction"}}},"service.ChildPairingStatus":{"type":"object","properties":{"access_token":{"type":"string"},"child":{"$ref":"#/definitions/domain.Child"},"device_id":{"type":"string"},"expires_at":{"type":"string"},"refresh_expires_at":{"type":"string"},"refresh_token":{"type":"string"},"status":{"description":"\"pending\" | \"confirmed\"","type":"string"}}},"service.ExtensionTicket":{"type":"object","properties":{"expires_at":{"type":"string"},"qr_base64":{"type":"string"},"qr_payload":{"type":"string"},"ticket_id":{"type":"string"}}},"service.ExtensionTicketStatus":{"type":"object","properties":{"extends_until":{"type":"string"},"status":{"description":"\"pending\" | \"confirmed\"","type":"string"},"ticket_id":{"type":"string"}}},"service.HistoryItem":{"type":"object","properties":{"child_id":{"type":"string"},"content":{"$ref":"#/definitions/domain.Content"},"content_id":{"type":"string"},"created_at":{"type":"string"},"ended_at":{"type":"string"},"id":{"type":"string"},"last_position_sec":{"type":"integer"},"seconds_watched":{"type":"integer"},"started_at":{"type":"string"},"updated_at":{"type":"string"},"user_id":{"type":"string"}}},"service.OTPRequestResult":{"type":"object","properties":{"debug_code":{"description":"DebugCode is only set when OTP_DEFAULT_CODE is non-empty (dev mode).","type":"string"},"email":{"type":"string"},"expires_at":{"type":"string"}}},"service.OTPVerifyResult":{"type":"object","properties":{"access_expires_at":{"type":"string"},"access_token":{"type":"string"},"email":{"type":"string"},"refresh_expires_at":{"type":"string"},"refresh_token":{"type":"string"},"user":{"$ref":"#/definitions/domain.User"},"user_exists":{"type":"boolean"}}},"service.PairingTicket":{"type":"object","properties":{"code":{"type":"string"},"device_id":{"type":"string"},"expires_at":{"type":"string"},"qr_base64":{"description":"PNG base64 for display","type":"string"},"qr_payload":{"description":"payload encoded in the QR","type":"string"}}},"service.PopulatedRecommendation":{"type":"object","properties":{"active":{"type":"boolean"},"category":{"$ref":"#/definitions/domain.Category"},"content":{"$ref":"#/definitions/domain.Content"},"created_at":{"type":"string"},"episode_count":{"type":"integer"},"id":{"type":"string"},"reference_id":{"type":"string"},"season_count":{"type":"integer"},"series":{"$ref":"#/definitions/domain.Series"},"sort_order":{"type":"integer"},"type":{"$ref":"#/definitions/domain.RecommendationType"},"updated_at":{"type":"string"}}},"service.RenditionInfo":{"type":"object","properties":{"bitrate_kbps":{"description":"video bitrate","type":"integer"},"label":{"description":"e.g. \"Full HD\"","type":"string"},"resolution":{"description":"e.g. \"720p\"","type":"string"},"url":{"description":"signed media URL for this variant playlist","type":"string"}}},"service.ScanResult":{"type":"object","properties":{"action":{"description":"Action is \"pairing\" when a new device was linked, \"extension\" when a\n2-hour watch extension was granted.","type":"string"},"device":{"$ref":"#/definitions/domain.ChildDevice"},"extends_until":{"type":"string"},"permissions":{"type":"array","items":{"$ref":"#/definitions/domain.ChildPermission"}}}},"service.StreamGrant":{"type":"object","properties":{"content_id":{"type":"string"},"duration_sec":{"description":"DurationSec is the full content duration in seconds, regardless of\nwhether this is a preview grant. Clients should display this in the\nprogress bar so users see the real length even when access is limited.","type":"integer"},"expires_at":{"type":"integer"},"is_hls":{"description":"IsHLS is true when MediaURL points at an HLS master playlist. Players\ncan use this to decide whether to hand the URL to hls.js vs a plain\n<video src>.","type":"boolean"},"last_position_sec":{"type":"integer"},"media_url":{"type":"string"},"preview_only":{"description":"PreviewOnly is true when the caller has no active subscription.\nPlayers must stop playback at PreviewUntilSec seconds.","type":"boolean"},"preview_until_sec":{"type":"integer"},"rendition_list":{"type":"array","items":{"$ref":"#/definitions/service.RenditionInfo"}},"renditions":{"type":"integer"},"viewer_id":{"type":"string"},"viewer_kind":{"type":"string"}}},"service.TVChildProfile":{"type":"object","properties":{"active":{"type":"boolean"},"age":{"type":"integer"},"avatar_url":{"type":"string"},"id":{"type":"string"},"name":{"type":"string"}}},"service.TVInitTicket":{"type":"object","properties":{"code":{"type":"string"},"device_id":{"type":"string"},"expires_at":{"type":"string"},"qr_base64":{"type":"string"},"qr_payload":{"type":"string"}}},"service.TVParentProfile":{"type":"object","properties":{"avatar_url":{"type":"string"},"id":{"type":"string"},"last_name":{"type":"string"},"name":{"type":"string"}}},"service.TVProfileList":{"type":"object","properties":{"children":{"type":"array","items":{"$ref":"#/definitions/service.TVChildProfile"}},"parent":{"$ref":"#/definitions/service.TVParentProfile"}}},"service.TVStatusResult":{"type":"object","properties":{"device_token":{"type":"string"},"profiles":{"$ref":"#/definitions/service.TVProfileList"},"status":{"description":"\"pending\" | \"confirmed\"","type":"string"},"token_expires_at":{"type":"string"}}},"service.TVStreamSession":{"type":"object","properties":{"access_token":{"type":"string"},"expires_at":{"type":"string"},"profile":{},"profile_type":{"description":"\"parent\" | \"child\"","type":"string"},"profiles":{"$ref":"#/definitions/service.TVProfileList"}}},"service.TokenPair":{"type":"object","properties":{"access_expires_at":{"type":"string"},"access_token":{"type":"string"},"refresh_expires_at":{"type":"string"},"refresh_token":{"type":"string"},"user":{"$ref":"#/definitions/domain.User"}}}},"securityDefinitions":{"BearerAuth":{"description":"Type \"Bearer {access_token}\" (access tokens are issued by /auth/login and /auth/refresh).","type":"apiKey","name":"Authorization","in":"header"}}}