# generated by datamodel-codegen:
#   filename:  openapi.yaml
#   timestamp: 2026-02-07T15:49:58+00:00

from __future__ import annotations

from datetime import date as date_aliased
from enum import Enum
from typing import Annotated, Any, Dict, List

from pydantic import AnyUrl, AwareDatetime, BaseModel, Field


class ErrorResponse(BaseModel):
    error: str | None = None
    details: Dict[str, Any] | None = None


class FoodMenuLabel(BaseModel):
    displayName: str
    """
    Display name of the item/section/menu
    """
    description: str | None = None
    """
    Optional description
    """
    languageCode: str | None = None
    """
    BCP-47 language code (e.g. en, es)
    """


class Money(BaseModel):
    currencyCode: str
    """
    ISO 4217 currency code (e.g. USD, EUR)
    """
    units: str
    """
    Whole units of the amount
    """
    nanos: int | None = None
    """
    Nano units (10^-9) of the amount
    """


class FoodMenuItemAttributes(BaseModel):
    price: Money | None = None
    spiciness: str | None = None
    """
    Spiciness level (e.g. MILD, MEDIUM, HOT)
    """
    allergen: List[str] | None = None
    """
    Allergens (e.g. DAIRY, GLUTEN, SHELLFISH)
    """
    dietaryRestriction: List[str] | None = None
    """
    Dietary labels (e.g. VEGETARIAN, VEGAN, GLUTEN_FREE)
    """
    servesNumPeople: int | None = None
    """
    Number of people the item serves
    """
    preparationMethods: List[str] | None = None
    """
    Preparation methods (e.g. GRILLED, FRIED)
    """
    mediaKeys: List[str] | None = None
    """
    Media references for item photos
    """


class Option(BaseModel):
    labels: List[FoodMenuLabel] | None = None
    attributes: FoodMenuItemAttributes | None = None


class FoodMenuItem(BaseModel):
    labels: List[FoodMenuLabel]
    attributes: FoodMenuItemAttributes | None = None
    options: List[Option] | None = None
    """
    Item variants/options (e.g. sizes, preparations)
    """


class FoodMenuSection(BaseModel):
    labels: List[FoodMenuLabel]
    items: List[FoodMenuItem] | None = None


class FoodMenu(BaseModel):
    labels: List[FoodMenuLabel]
    sections: List[FoodMenuSection] | None = None
    cuisines: List[str] | None = None
    """
    Cuisine types (e.g. AMERICAN, ITALIAN, JAPANESE)
    """
    sourceUrl: str | None = None
    """
    URL of the original menu source
    """


class DateRange(BaseModel):
    startDate: date_aliased | None = None
    endDate: date_aliased | None = None


class DailyView(BaseModel):
    date: date_aliased | None = None
    views: int | None = None
    estimatedMinutesWatched: float | None = None
    averageViewDuration: float | None = None
    """
    Average view duration in seconds
    """
    subscribersGained: int | None = None
    subscribersLost: int | None = None
    likes: int | None = None
    comments: int | None = None
    shares: int | None = None


class ScopeStatus(BaseModel):
    hasAnalyticsScope: bool | None = None


class YouTubeDailyViewsResponse(BaseModel):
    success: Annotated[bool | None, Field(examples=[True])] = None
    videoId: str | None = None
    """
    The YouTube video ID
    """
    dateRange: DateRange | None = None
    totalViews: int | None = None
    """
    Sum of views across all days in the range
    """
    dailyViews: List[DailyView] | None = None
    lastSyncedAt: AwareDatetime | None = None
    """
    When the data was last synced from YouTube
    """
    scopeStatus: ScopeStatus | None = None


class ScopeStatus1(BaseModel):
    hasAnalyticsScope: Annotated[bool | None, Field(examples=[False])] = None
    requiresReauthorization: Annotated[bool | None, Field(examples=[True])] = None
    reauthorizeUrl: AnyUrl | None = None
    """
    URL to redirect user for reauthorization
    """


class YouTubeScopeMissingResponse(BaseModel):
    success: Annotated[bool | None, Field(examples=[False])] = None
    error: Annotated[
        str | None,
        Field(
            examples=[
                "To access daily video analytics, please reconnect your YouTube account to grant the required permissions."
            ]
        ),
    ] = None
    code: Annotated[str | None, Field(examples=["youtube_analytics_scope_missing"])] = (
        None
    )
    scopeStatus: ScopeStatus1 | None = None


class Event(Enum):
    POST_SCHEDULED = "post.scheduled"
    POST_PUBLISHED = "post.published"
    POST_FAILED = "post.failed"
    POST_PARTIAL = "post.partial"
    ACCOUNT_CONNECTED = "account.connected"
    ACCOUNT_DISCONNECTED = "account.disconnected"
    MESSAGE_RECEIVED = "message.received"


class Webhook(BaseModel):
    """
    Individual webhook configuration for receiving real-time notifications
    """

    field_id: Annotated[str | None, Field(alias="_id")] = None
    """
    Unique webhook identifier
    """
    name: Annotated[str | None, Field(max_length=50)] = None
    """
    Webhook name (for identification)
    """
    url: AnyUrl | None = None
    """
    Webhook endpoint URL
    """
    secret: str | None = None
    """
    Secret key for HMAC-SHA256 signature (not returned in responses for security)
    """
    events: List[Event] | None = None
    """
    Events subscribed to
    """
    isActive: bool | None = None
    """
    Whether webhook delivery is enabled
    """
    lastFiredAt: AwareDatetime | None = None
    """
    Timestamp of last successful webhook delivery
    """
    failureCount: int | None = None
    """
    Consecutive delivery failures (resets on success, webhook disabled at 10)
    """
    customHeaders: Dict[str, str] | None = None
    """
    Custom headers included in webhook requests
    """


class Event1(Enum):
    POST_SCHEDULED = "post.scheduled"
    POST_PUBLISHED = "post.published"
    POST_FAILED = "post.failed"
    POST_PARTIAL = "post.partial"
    ACCOUNT_CONNECTED = "account.connected"
    ACCOUNT_DISCONNECTED = "account.disconnected"
    MESSAGE_RECEIVED = "message.received"
    WEBHOOK_TEST = "webhook.test"


class Status(Enum):
    SUCCESS = "success"
    FAILED = "failed"


class WebhookLog(BaseModel):
    """
    Webhook delivery log entry
    """

    field_id: Annotated[str | None, Field(alias="_id")] = None
    webhookId: str | None = None
    """
    ID of the webhook that was triggered
    """
    webhookName: str | None = None
    """
    Name of the webhook that was triggered
    """
    event: Event1 | None = None
    url: AnyUrl | None = None
    status: Status | None = None
    statusCode: int | None = None
    """
    HTTP status code from webhook endpoint
    """
    requestPayload: Dict[str, Any] | None = None
    """
    Payload sent to webhook endpoint
    """
    responseBody: str | None = None
    """
    Response body from webhook endpoint (truncated to 10KB)
    """
    errorMessage: str | None = None
    """
    Error message if delivery failed
    """
    attemptNumber: int | None = None
    """
    Delivery attempt number (max 3 retries)
    """
    responseTime: int | None = None
    """
    Response time in milliseconds
    """
    createdAt: AwareDatetime | None = None


class Event2(Enum):
    POST_SCHEDULED = "post.scheduled"
    POST_PUBLISHED = "post.published"
    POST_FAILED = "post.failed"
    POST_PARTIAL = "post.partial"


class Platform(BaseModel):
    platform: str | None = None
    status: str | None = None
    publishedUrl: str | None = None
    error: str | None = None


class Post(BaseModel):
    id: str | None = None
    content: str | None = None
    status: str | None = None
    scheduledFor: AwareDatetime | None = None
    publishedAt: AwareDatetime | None = None
    platforms: List[Platform] | None = None


class WebhookPayloadPost(BaseModel):
    """
    Webhook payload for post events
    """

    event: Event2 | None = None
    post: Post | None = None
    timestamp: AwareDatetime | None = None


class Event3(Enum):
    ACCOUNT_CONNECTED = "account.connected"


class Account(BaseModel):
    accountId: str | None = None
    """
    The account's unique identifier (same as used in /v1/accounts/{accountId})
    """
    profileId: str | None = None
    """
    The profile's unique identifier this account belongs to
    """
    platform: str | None = None
    username: str | None = None
    displayName: str | None = None


class WebhookPayloadAccountConnected(BaseModel):
    """
    Webhook payload for account connected events
    """

    event: Event3 | None = None
    account: Account | None = None
    timestamp: AwareDatetime | None = None


class Event4(Enum):
    ACCOUNT_DISCONNECTED = "account.disconnected"


class DisconnectionType(Enum):
    """
    Whether the disconnection was intentional (user action) or unintentional (token expired/revoked)
    """

    INTENTIONAL = "intentional"
    UNINTENTIONAL = "unintentional"


class Account1(BaseModel):
    accountId: str | None = None
    """
    The account's unique identifier (same as used in /v1/accounts/{accountId})
    """
    profileId: str | None = None
    """
    The profile's unique identifier this account belongs to
    """
    platform: str | None = None
    username: str | None = None
    displayName: str | None = None
    disconnectionType: DisconnectionType | None = None
    """
    Whether the disconnection was intentional (user action) or unintentional (token expired/revoked)
    """
    reason: str | None = None
    """
    Human-readable reason for the disconnection
    """


class WebhookPayloadAccountDisconnected(BaseModel):
    """
    Webhook payload for account disconnected events
    """

    event: Event4 | None = None
    account: Account1 | None = None
    timestamp: AwareDatetime | None = None


class Event5(Enum):
    MESSAGE_RECEIVED = "message.received"


class Platform1(Enum):
    INSTAGRAM = "instagram"
    FACEBOOK = "facebook"
    TELEGRAM = "telegram"


class Direction(Enum):
    INCOMING = "incoming"


class Attachment(BaseModel):
    type: str | None = None
    """
    Attachment type (image, video, file, sticker, audio)
    """
    url: str | None = None
    """
    Attachment URL (may expire for Meta platforms)
    """
    payload: Dict[str, Any] | None = None
    """
    Additional attachment metadata
    """


class Sender(BaseModel):
    id: str | None = None
    name: str | None = None
    username: str | None = None
    picture: str | None = None


class Message(BaseModel):
    id: str | None = None
    """
    Internal message ID
    """
    conversationId: str | None = None
    """
    Internal conversation ID
    """
    platform: Platform1 | None = None
    platformMessageId: str | None = None
    """
    Platform's message ID
    """
    direction: Direction | None = None
    text: str | None = None
    """
    Message text content
    """
    attachments: List[Attachment] | None = None
    sender: Sender | None = None
    sentAt: AwareDatetime | None = None
    isRead: bool | None = None


class Status1(Enum):
    ACTIVE = "active"
    ARCHIVED = "archived"


class Conversation(BaseModel):
    id: str | None = None
    platformConversationId: str | None = None
    participantId: str | None = None
    participantName: str | None = None
    participantUsername: str | None = None
    participantPicture: str | None = None
    status: Status1 | None = None


class Account2(BaseModel):
    id: str | None = None
    """
    Social account ID
    """
    platform: str | None = None
    username: str | None = None
    displayName: str | None = None


class WebhookPayloadMessage(BaseModel):
    """
    Webhook payload for message received events (DMs from Instagram, Telegram)
    """

    event: Event5 | None = None
    message: Message | None = None
    conversation: Conversation | None = None
    account: Account2 | None = None
    timestamp: AwareDatetime | None = None


class PostId(BaseModel):
    """
    Populated post reference
    """

    field_id: Annotated[str | None, Field(alias="_id")] = None
    content: str | None = None
    status: str | None = None


class Platform2(Enum):
    TIKTOK = "tiktok"
    INSTAGRAM = "instagram"
    FACEBOOK = "facebook"
    YOUTUBE = "youtube"
    LINKEDIN = "linkedin"
    TWITTER = "twitter"
    THREADS = "threads"
    PINTEREST = "pinterest"
    REDDIT = "reddit"
    BLUESKY = "bluesky"
    GOOGLEBUSINESS = "googlebusiness"
    TELEGRAM = "telegram"
    SNAPCHAT = "snapchat"


class Action(Enum):
    """
    Type of action logged:
    - `publish` - Initial publish attempt
    - `retry` - Retry after failure
    - `media_upload` - Media upload step
    - `rate_limit_pause` - Account paused due to rate limits
    - `token_refresh` - Token was refreshed
    - `cancelled` - Post was cancelled

    """

    PUBLISH = "publish"
    RETRY = "retry"
    MEDIA_UPLOAD = "media_upload"
    RATE_LIMIT_PAUSE = "rate_limit_pause"
    TOKEN_REFRESH = "token_refresh"
    CANCELLED = "cancelled"


class Status2(Enum):
    SUCCESS = "success"
    FAILED = "failed"
    PENDING = "pending"
    SKIPPED = "skipped"


class Request(BaseModel):
    contentPreview: str | None = None
    """
    First 200 chars of caption
    """
    mediaCount: int | None = None
    mediaTypes: List[str] | None = None
    mediaUrls: List[str] | None = None
    """
    URLs of media items sent to platform
    """
    scheduledFor: AwareDatetime | None = None
    rawBody: str | None = None
    """
    Full request body JSON (max 5000 chars)
    """


class Response(BaseModel):
    platformPostId: str | None = None
    """
    ID returned by platform on success
    """
    platformPostUrl: str | None = None
    """
    URL of published post
    """
    errorMessage: str | None = None
    """
    Error message on failure
    """
    errorCode: str | None = None
    """
    Platform-specific error code
    """
    rawBody: str | None = None
    """
    Full response body JSON (max 5000 chars)
    """


class PostLog(BaseModel):
    """
    Publishing log entry showing details of a post publishing attempt
    """

    field_id: Annotated[str | None, Field(alias="_id")] = None
    postId: str | PostId | None = None
    userId: str | None = None
    profileId: str | None = None
    platform: Platform2 | None = None
    accountId: str | None = None
    accountUsername: str | None = None
    action: Action | None = None
    """
    Type of action logged:
    - `publish` - Initial publish attempt
    - `retry` - Retry after failure
    - `media_upload` - Media upload step
    - `rate_limit_pause` - Account paused due to rate limits
    - `token_refresh` - Token was refreshed
    - `cancelled` - Post was cancelled

    """
    status: Status2 | None = None
    statusCode: int | None = None
    """
    HTTP status code from platform API
    """
    endpoint: str | None = None
    """
    Platform API endpoint called
    """
    request: Request | None = None
    response: Response | None = None
    durationMs: int | None = None
    """
    How long the operation took in milliseconds
    """
    attemptNumber: int | None = None
    """
    Attempt number (1 for first try, 2+ for retries)
    """
    createdAt: AwareDatetime | None = None


class PostId1(BaseModel):
    """
    Populated post with full details
    """

    field_id: Annotated[str | None, Field(alias="_id")] = None
    content: str | None = None
    status: str | None = None
    scheduledFor: AwareDatetime | None = None
    platforms: List[Dict[str, Any]] | None = None
    mediaItems: List[Dict[str, Any]] | None = None


class AccountId(BaseModel):
    """
    Populated account reference
    """

    field_id: Annotated[str | None, Field(alias="_id")] = None
    platform: str | None = None
    username: str | None = None
    displayName: str | None = None


class PostLogDetail(PostLog):
    postId: PostId1 | None = None
    """
    Populated post with full details
    """
    accountId: AccountId | None = None
    """
    Populated account reference
    """
    metadata: Dict[str, Any] | None = None
    """
    Additional metadata (e.g., rate limit info)
    """


class Type(Enum):
    IMAGE = "image"
    VIDEO = "video"
    GIF = "gif"
    DOCUMENT = "document"


class MediaItem(BaseModel):
    """
    Media referenced in posts. URLs must be publicly reachable over HTTPS by the destination platforms.
    When using third‑party storage, ensure signed links remain valid until upload completes.

    **Uploading Media:**
    Use `POST /v1/media/presign` to get a presigned URL, then upload your file directly to cloud storage.
    Supports files up to 5GB. See the `/v1/media/presign` endpoint documentation for details.

    **Automatic Media Compression:**
    Late automatically compresses media that exceeds platform limits, so you don't need to worry about file size restrictions. Compression happens server-side during publishing.

    **Image compression by platform:**
    - Twitter/X: >5 MB
    - Instagram: >8 MB
    - Threads: >8 MB
    - Facebook: >10 MB
    - LinkedIn: >8 MB
    - TikTok: >20 MB
    - Pinterest: >32 MB
    - Reddit: >20 MB
    - Snapchat: >20 MB
    - Telegram: >10 MB
    - Bluesky: >1 MB
    - YouTube (thumbnails): >2 MB
    - Google Business: >5 MB

    **Video compression by platform:**
    - Twitter/X: >512 MB
    - Instagram Stories: >100 MB
    - Instagram Reels: >300 MB
    - Facebook: >4 GB
    - LinkedIn: >5 GB
    - TikTok: >4 GB
    - Pinterest: >2 GB
    - Snapchat: >500 MB
    - Telegram: >50 MB

    Note: Videos larger than 200 MB may not be compressed due to server timeout constraints. For best results, compress very large videos before uploading.

    """

    type: Type | None = None
    url: AnyUrl | None = None
    filename: str | None = None
    size: int | None = None
    """
    Optional file size in bytes
    """
    mimeType: str | None = None
    """
    Optional MIME type (e.g. image/jpeg, video/mp4)
    """
    thumbnail: AnyUrl | None = None
    """
    Optional thumbnail image URL for videos
    """
    instagramThumbnail: AnyUrl | None = None
    """
    Optional custom cover image URL for Instagram Reels
    """
    tiktokProcessed: bool | None = None
    """
    Internal flag indicating the image was resized for TikTok
    """


class ErrorCategory(Enum):
    """
    Error category for programmatic handling:
    - auth_expired: Token expired or revoked, account needs reconnection
    - user_content: Content doesn't meet platform requirements (too long, wrong format, etc.)
    - user_abuse: Rate limits, spam detection, excessive posting
    - account_issue: Account configuration problems (missing board, inactive account)
    - platform_rejected: Platform rules violated (banned, suspended, policy violation)
    - platform_error: Platform-side issues (5xx errors, maintenance)
    - system_error: Late infrastructure issues (timeouts, network errors)
    - unknown: Unclassified error

    """

    AUTH_EXPIRED = "auth_expired"
    USER_CONTENT = "user_content"
    USER_ABUSE = "user_abuse"
    ACCOUNT_ISSUE = "account_issue"
    PLATFORM_REJECTED = "platform_rejected"
    PLATFORM_ERROR = "platform_error"
    SYSTEM_ERROR = "system_error"
    UNKNOWN = "unknown"


class ErrorSource(Enum):
    """
    Who/what caused the error:
    - user: User action required (fix content, reconnect account)
    - platform: Platform-side issue (outage, API change)
    - system: Late system issue (rare)

    """

    USER = "user"
    PLATFORM = "platform"
    SYSTEM = "system"


class Status3(Enum):
    DRAFT = "draft"
    SCHEDULED = "scheduled"
    PUBLISHING = "publishing"
    PUBLISHED = "published"
    FAILED = "failed"
    PARTIAL = "partial"


class Visibility(Enum):
    PUBLIC = "public"
    PRIVATE = "private"
    UNLISTED = "unlisted"


class ThreadItem(BaseModel):
    content: str | None = None
    mediaItems: List[MediaItem] | None = None


class TwitterPlatformData(BaseModel):
    threadItems: List[ThreadItem] | None = None
    """
    Sequence of tweets in a thread. First item is the root tweet.
    """


class ThreadsPlatformData(BaseModel):
    """
    Constraints:
    - Carousel posts support up to 10 images (no videos in carousels).
    - Single posts support one image or one video.
    - Videos must be H.264/AAC MP4 format, max 5 minutes duration.
    - Images must be JPEG or PNG, max 8 MB each.
    - threadItems creates a reply chain (Threads equivalent of Twitter threads).

    """

    threadItems: List[ThreadItem] | None = None
    """
    Sequence of posts in a Threads thread (root then replies in order).
    """


class ContentType(Enum):
    """
    Set to 'story' to publish as a Facebook Page Story (24-hour ephemeral content). Requires media.
    """

    STORY = "story"


class FacebookPlatformData(BaseModel):
    """
    Constraints:
    - Posts cannot mix videos and images.
    - Multiple images supported via attached_media (up to 10 images for feed posts).
    - Multiple videos in the same post are not supported.
    - Stories require media (single image or video); text captions are not displayed with stories.
    - Stories are ephemeral (disappear after 24 hours).
    - Use pageId to post to multiple pages from the same account connection.

    """

    contentType: ContentType | None = None
    """
    Set to 'story' to publish as a Facebook Page Story (24-hour ephemeral content). Requires media.
    """
    firstComment: str | None = None
    """
    Optional first comment to post immediately after publishing (feed posts only, not stories)
    """
    pageId: str | None = None
    """
    Target Facebook Page ID for multi-page posting.
    If omitted, uses the selected/default page on the connection.
    Use GET /api/v1/accounts/{id}/facebook-page to list available pages.

    """


class ContentType1(Enum):
    """
    Set to 'story' to publish as a Story. Default posts become Reels or feed depending on media.
    """

    STORY = "story"


class GraduationStrategy(Enum):
    """
    The graduation strategy specifies when a trial reel becomes a regular reel:
    - MANUAL: The trial reel can only be manually graduated from the native Instagram app.
    - SS_PERFORMANCE: The trial reel will be automatically graduated if it performs well with non-followers.

    """

    MANUAL = "MANUAL"
    SS_PERFORMANCE = "SS_PERFORMANCE"


class TrialParams(BaseModel):
    """
    Trial Reels configuration. Trial reels are only shared to non-followers initially.
    They can later be "graduated" (converted to regular reels visible to followers)
    either manually in the Instagram app or automatically based on performance.
    Only applies to Reels (video posts).

    """

    graduationStrategy: GraduationStrategy | None = None
    """
    The graduation strategy specifies when a trial reel becomes a regular reel:
    - MANUAL: The trial reel can only be manually graduated from the native Instagram app.
    - SS_PERFORMANCE: The trial reel will be automatically graduated if it performs well with non-followers.

    """


class UserTag(BaseModel):
    username: Annotated[str, Field(examples=["friend_username"])]
    """
    Instagram username (@ symbol is optional and will be removed automatically)
    """
    x: Annotated[float, Field(examples=[0.5], ge=0.0, le=1.0)]
    """
    X coordinate position from left edge (0.0 = left, 0.5 = center, 1.0 = right)
    """
    y: Annotated[float, Field(examples=[0.5], ge=0.0, le=1.0)]
    """
    Y coordinate position from top edge (0.0 = top, 0.5 = center, 1.0 = bottom)
    """


class InstagramPlatformData(BaseModel):
    """
    Constraints:
    - Feed posts require images with aspect ratio between 0.8 (4:5 portrait) and 1.91 (1.91:1 landscape).
    - Images outside this range (e.g., 9:16 Stories/TikTok format) must use contentType 'story'.
    - Validation happens at post creation; invalid images are rejected immediately with helpful error messages.
    - Carousels support up to 10 media items.
    - Stories require media; no captions are published with Stories.
    - User tags: coordinates range from 0.0 to 1.0 representing position from top-left corner. Tagged users receive notifications.

    **Automatic Compression (similar to Bluesky):**
    - All images (story, post, carousel, thumbnails) exceeding 8 MB are automatically compressed using quality reduction and resizing.
    - Story videos exceeding 100 MB are automatically compressed.
    - Reel videos exceeding 300 MB are automatically compressed.
    - Compression uses Sharp (images) and FFmpeg (videos) to maintain quality while meeting size limits.
    - Original files are preserved; compressed versions are uploaded to blob storage automatically.

    """

    contentType: ContentType1 | None = None
    """
    Set to 'story' to publish as a Story. Default posts become Reels or feed depending on media.
    """
    shareToFeed: bool = True
    """
    For Reels only. When true (default), the Reel appears on both the Reels tab and your main profile feed. Set to false to post to the Reels tab only.
    """
    collaborators: List[str] | None = None
    """
    Up to 3 Instagram usernames to invite as collaborators (feed/Reels only)
    """
    firstComment: str | None = None
    """
    Optional first comment to add after the post is created (not applied to Stories)
    """
    trialParams: TrialParams | None = None
    """
    Trial Reels configuration. Trial reels are only shared to non-followers initially.
    They can later be "graduated" (converted to regular reels visible to followers)
    either manually in the Instagram app or automatically based on performance.
    Only applies to Reels (video posts).

    """
    userTags: List[UserTag] | None = None
    """
    Tag Instagram users in photos by username and position coordinates. Only works for single image posts and the first image of carousel posts. Not supported for stories or videos.
    """
    audioName: Annotated[str | None, Field(examples=["My Podcast Intro"])] = None
    """
    Custom name for the original audio in Reels. Replaces the default "Original Audio" label.
    Only applies to Reels (video posts). Can only be set once - either during creation or
    later from the Instagram audio page in the app.

    """
    thumbOffset: Annotated[int | None, Field(examples=[5000], ge=0)] = None
    """
    Millisecond offset from the start of the video to use as the Reel thumbnail.
    Only applies to Reels (video posts). If a custom thumbnail URL (instagramThumbnail
    in mediaItems) is provided, it takes priority and this offset is ignored.
    Defaults to 0 (first frame).

    """


class LinkedInPlatformData(BaseModel):
    """
    Constraints:
    - Multi-image posts support up to 20 images.
    - Multi-video posts are not supported.
    - Single PDF document posts are supported (max 100MB, ~300 pages). Documents cannot be mixed with other media.
    - Post ID is returned in the x-restli-id response header.
    - Link previews are automatically generated for URLs when no media is attached (can be disabled with disableLinkPreview).
    - Use organizationUrn to post to multiple organizations from the same account connection.

    """

    organizationUrn: str | None = None
    """
    Target LinkedIn Organization URN for multi-organization posting.
    Format: "urn:li:organization:123456789"
    If omitted, uses the selected/default organization on the connection.
    Use GET /api/v1/accounts/{id}/linkedin-organizations to list available organizations.

    """
    firstComment: str | None = None
    """
    Optional first comment to add after the post is created
    """
    disableLinkPreview: bool | None = None
    """
    Set to true to disable automatic link previews for URLs in the post content (default is false)
    """


class PinterestPlatformData(BaseModel):
    title: Annotated[str | None, Field(max_length=100)] = None
    """
    Pin title. Defaults to first line of content or "Pin". Must be ≤ 100 characters.
    """
    boardId: str | None = None
    """
    Target Pinterest board ID. If omitted, the first available board is used.
    """
    link: AnyUrl | None = None
    """
    Destination link (pin URL)
    """
    coverImageUrl: AnyUrl | None = None
    """
    Optional cover image for video pins
    """
    coverImageKeyFrameTime: int | None = None
    """
    Optional key frame time in seconds for derived video cover
    """


class Visibility1(Enum):
    """
    Video visibility setting:
    - public: Anyone can search for and watch (default)
    - unlisted: Only people with the link can watch
    - private: Only you and people you specifically share with can watch

    """

    PUBLIC = "public"
    PRIVATE = "private"
    UNLISTED = "unlisted"


class YouTubePlatformData(BaseModel):
    """
    YouTube video upload settings:
    - Videos ≤ 3 minutes are automatically detected as YouTube Shorts
    - Videos > 3 minutes become regular YouTube videos
    - Custom thumbnails supported for regular videos (via mediaItem.thumbnail)
    - Custom thumbnails NOT supported for Shorts via API
    - Scheduled videos are uploaded immediately as the specified visibility and published at scheduled time
    - Visibility defaults to "public" if not specified
    - madeForKids defaults to false (not child-directed)
    - Set containsSyntheticMedia: true if your video contains AI-generated content

    """

    title: Annotated[str | None, Field(max_length=100)] = None
    """
    Video title. Defaults to first line of content or "Untitled Video". Must be ≤ 100 characters.
    """
    visibility: Visibility1 = "public"
    """
    Video visibility setting:
    - public: Anyone can search for and watch (default)
    - unlisted: Only people with the link can watch
    - private: Only you and people you specifically share with can watch

    """
    madeForKids: bool = False
    """
    COPPA compliance: Audience designation for the video.
    - true: Video is made for kids (child-directed content)
    - false: Video is NOT made for kids (default)

    This field maps to YouTube's `selfDeclaredMadeForKids` setting. Videos marked as made for kids
    have restricted features (no comments, no notifications, limited ad targeting).

    IMPORTANT: If not specified, defaults to false. YouTube requires this to be explicitly set,
    otherwise the video may be blocked from views until configured in YouTube Studio.

    """
    firstComment: Annotated[str | None, Field(max_length=10000)] = None
    """
    Optional first comment to post immediately after video upload. Up to 10,000 characters (YouTube's comment limit).
    """
    containsSyntheticMedia: bool = False
    """
    AI-generated content disclosure flag. Set to true if your video contains AI-generated or synthetic content
    that could be mistaken for real people, places, or events. This helps viewers understand when realistic
    content has been created or altered using AI. YouTube may add a label to videos when this is set.
    Added to YouTube Data API in October 2024.

    """
    categoryId: str = "22"
    """
    YouTube video category ID. Defaults to '22' (People & Blogs).
    Common categories: 1 (Film & Animation), 2 (Autos & Vehicles), 10 (Music),
    15 (Pets & Animals), 17 (Sports), 20 (Gaming), 22 (People & Blogs),
    23 (Comedy), 24 (Entertainment), 25 (News & Politics), 26 (Howto & Style),
    27 (Education), 28 (Science & Technology).

    """


class Type1(Enum):
    """
    Button action type:
    - LEARN_MORE: Link to more information
    - BOOK: Booking/reservation link
    - ORDER: Online ordering link
    - SHOP: E-commerce/shopping link
    - SIGN_UP: Registration/signup link
    - CALL: Phone call action

    """

    LEARN_MORE = "LEARN_MORE"
    BOOK = "BOOK"
    ORDER = "ORDER"
    SHOP = "SHOP"
    SIGN_UP = "SIGN_UP"
    CALL = "CALL"


class CallToAction(BaseModel):
    """
    Optional call-to-action button displayed on the post
    """

    type: Type1
    """
    Button action type:
    - LEARN_MORE: Link to more information
    - BOOK: Booking/reservation link
    - ORDER: Online ordering link
    - SHOP: E-commerce/shopping link
    - SIGN_UP: Registration/signup link
    - CALL: Phone call action

    """
    url: AnyUrl
    """
    Destination URL for the CTA button (required when callToAction is provided)
    """


class GoogleBusinessPlatformData(BaseModel):
    """
    Google Business Profile post settings:
    - Posts support text content and a single image (no videos)
    - Images must be publicly accessible URLs
    - Call-to-action buttons drive user engagement
    - Posts appear on your Google Business Profile and in Google Search/Maps
    - Use locationId to post to multiple locations from the same account connection

    """

    locationId: str | None = None
    """
    Target Google Business location ID for multi-location posting.
    Format: "locations/123456789"
    If omitted, uses the selected/default location on the connection.
    Use GET /api/v1/accounts/{id}/gmb-locations to list available locations.

    """
    callToAction: CallToAction | None = None
    """
    Optional call-to-action button displayed on the post
    """


class CommercialContentType(Enum):
    """
    Type of commercial content disclosure
    """

    NONE = "none"
    BRAND_ORGANIC = "brand_organic"
    BRAND_CONTENT = "brand_content"


class MediaType(Enum):
    """
    Optional override. Defaults based on provided media items.
    """

    VIDEO = "video"
    PHOTO = "photo"


class TikTokPlatformData(BaseModel):
    """
    TikTok platform-specific settings for video/photo posting.

    **Constraints:**
    - Photo carousels support up to 35 images.
    - **Title length limits**:
      - Videos: up to 2200 chars (full content used as title)
      - Photos: content is automatically truncated to 90 chars for title (hashtags/URLs stripped). Use 'description' field for longer text (up to 4000 chars).
    - privacyLevel must be chosen from creator_info.privacy_level_options (no defaulting).
    - allowDuet and allowStitch required for videos; allowComment for all.
    - contentPreviewConfirmed and expressConsentGiven must be true before posting.

    **Note:** Both camelCase and snake_case field names are accepted for backwards compatibility.
    The nested `tiktokSettings` object format is also still supported but deprecated.

    """

    draft: bool | None = None
    """
    When true, Late sends the post to the TikTok Creator Inbox as a draft instead of publishing it immediately. When omitted or false, TikTok uses direct posting (live publish) as usual.

    """
    privacyLevel: str | None = None
    """
    One of the values returned by the TikTok creator info API for the account
    """
    allowComment: bool | None = None
    """
    Allow comments on the post
    """
    allowDuet: bool | None = None
    """
    Allow duets (required for video posts)
    """
    allowStitch: bool | None = None
    """
    Allow stitches (required for video posts)
    """
    commercialContentType: CommercialContentType | None = None
    """
    Type of commercial content disclosure
    """
    brandPartnerPromote: bool | None = None
    """
    Whether the post promotes a brand partner
    """
    isBrandOrganicPost: bool | None = None
    """
    Whether the post is a brand organic post
    """
    contentPreviewConfirmed: bool | None = None
    """
    User has confirmed they previewed the content
    """
    expressConsentGiven: bool | None = None
    """
    User has given express consent for posting
    """
    mediaType: MediaType | None = None
    """
    Optional override. Defaults based on provided media items.
    """
    videoCoverTimestampMs: Annotated[int | None, Field(ge=0)] = None
    """
    Optional for video posts. Timestamp in milliseconds to select which frame to use as thumbnail (defaults to 1000ms/1 second).
    """
    photoCoverIndex: Annotated[int | None, Field(ge=0)] = None
    """
    Optional for photo carousels. Index of image to use as cover, 0-based (defaults to 0/first image).
    """
    autoAddMusic: bool | None = None
    """
    When true, TikTok may add recommended music (photos only)
    """
    videoMadeWithAi: bool | None = None
    """
    Set true to disclose AI-generated content
    """
    description: Annotated[str | None, Field(max_length=4000)] = None
    """
    Optional long-form description for photo posts (max 4000 chars).
    Recommended for photo posts when content exceeds 90 characters, as photo titles are automatically truncated to 90 chars (after stripping hashtags/URLs).

    """


class TikTokSettings(BaseModel):
    """
    **DEPRECATED**: Use flat properties directly in TikTokPlatformData instead.
    This nested format is supported for backwards compatibility only.

    """

    privacyLevel: str | None = None
    allowComment: bool | None = None
    allowDuet: bool | None = None
    allowStitch: bool | None = None
    commercialContentType: str | None = None
    brandPartnerPromote: bool | None = None
    isBrandOrganicPost: bool | None = None
    contentPreviewConfirmed: bool | None = None
    expressConsentGiven: bool | None = None
    mediaType: str | None = None
    videoCoverTimestampMs: int | None = None
    photoCoverIndex: int | None = None
    autoAddMusic: bool | None = None
    videoMadeWithAi: bool | None = None
    description: str | None = None


class ParseMode(Enum):
    """
    Text formatting mode for the message (default is HTML)
    """

    HTML = "HTML"
    MARKDOWN = "Markdown"
    MARKDOWN_V2 = "MarkdownV2"


class TelegramPlatformData(BaseModel):
    """
    Telegram channel/group posting settings:
    - Supports text, images (up to 10), videos (up to 10), and mixed media albums
    - Posts to channels display the channel name and logo as author
    - Posts to groups display the bot name (Late) as author
    - Message IDs are returned for analytics tracking
    - Captions support up to 1024 characters for media posts, 4096 for text-only

    **Analytics:**
    - **Not available via API.** The Telegram Bot API does not expose message analytics (views, forwards, reactions).
    - View counts are only visible to channel admins directly in the Telegram app.
    - This is a Telegram platform limitation that affects all third-party tools.

    """

    parseMode: ParseMode | None = None
    """
    Text formatting mode for the message (default is HTML)
    """
    disableWebPagePreview: bool | None = None
    """
    Disable link preview generation for URLs in the message
    """
    disableNotification: bool | None = None
    """
    Send the message silently (users will receive notification without sound)
    """
    protectContent: bool | None = None
    """
    Protect message content from forwarding and saving
    """


class ContentType2(Enum):
    """
    Type of Snapchat content to publish:
    - `story` - Ephemeral snap visible for 24 hours (default)
    - `saved_story` - Permanent story saved to Public Profile
    - `spotlight` - Video posted to Spotlight (Snapchat's TikTok-like feed)

    """

    STORY = "story"
    SAVED_STORY = "saved_story"
    SPOTLIGHT = "spotlight"


class SnapchatPlatformData(BaseModel):
    """
    Snapchat Public Profile API constraints:

    **General Requirements:**
    - Snapchat requires a Public Profile to publish content
    - Media is required for all content types (no text-only posts)
    - Only one media item per post is supported
    - Media is automatically encrypted using AES-256-CBC before upload

    **Content Types:**
    - **Story** (default): Ephemeral content visible for 24 hours. No caption/text supported.
    - **Saved Story**: Permanent story on your Public Profile. Uses post content as title (max 45 chars).
    - **Spotlight**: Video content for Snapchat's entertainment feed. Supports description (max 160 chars) with hashtags.

    **Media Constraints:**
    - Images: max 20 MB, JPEG/PNG format
    - Videos: max 500 MB, MP4 format, 5-60 seconds duration, minimum 540x960px resolution
    - Aspect ratio: 9:16 recommended

    **Analytics:**
    - Views, screenshots, shares, unique viewers, completion rate available
    - Analytics are fetched per content type (story/saved_story/spotlight)

    """

    contentType: ContentType2 = "story"
    """
    Type of Snapchat content to publish:
    - `story` - Ephemeral snap visible for 24 hours (default)
    - `saved_story` - Permanent story saved to Public Profile
    - `spotlight` - Video posted to Spotlight (Snapchat's TikTok-like feed)

    """


class QueueSlot(BaseModel):
    dayOfWeek: Annotated[int | None, Field(ge=0, le=6)] = None
    """
    Day of week (0=Sunday, 6=Saturday)
    """
    time: Annotated[str | None, Field(pattern="^([0-1][0-9]|2[0-3]):[0-5][0-9]$")] = (
        None
    )
    """
    Time in HH:mm format (24-hour)
    """


class QueueSchedule(BaseModel):
    field_id: Annotated[str | None, Field(alias="_id")] = None
    """
    Unique queue identifier
    """
    profileId: str | None = None
    """
    Profile ID this queue belongs to
    """
    name: str | None = None
    """
    Queue name (e.g., "Morning Posts", "Evening Content")
    """
    timezone: str | None = None
    """
    IANA timezone (e.g., America/New_York)
    """
    slots: List[QueueSlot] | None = None
    active: bool | None = None
    """
    Whether the queue is active
    """
    isDefault: bool | None = None
    """
    Whether this is the default queue for the profile (used when no queueId specified)
    """
    createdAt: AwareDatetime | None = None
    updatedAt: AwareDatetime | None = None


class Pagination(BaseModel):
    page: int | None = None
    limit: int | None = None
    total: int | None = None
    pages: int | None = None


class Profile(BaseModel):
    field_id: Annotated[str | None, Field(alias="_id")] = None
    userId: str | None = None
    name: str | None = None
    description: str | None = None
    color: str | None = None
    isDefault: bool | None = None
    isOverLimit: bool | None = None
    """
    Only present when `includeOverLimit=true` is used. Indicates if this profile
    exceeds the user's plan limit. Over-limit profiles cannot be used for posting
    but can be managed (disconnected accounts, deleted).

    """
    createdAt: AwareDatetime | None = None


class SocialAccount(BaseModel):
    field_id: Annotated[str | None, Field(alias="_id")] = None
    platform: str | None = None
    profileId: str | Profile | None = None
    username: str | None = None
    displayName: str | None = None
    profileUrl: str | None = None
    """
    Full profile URL for the connected account. Available for all platforms:
    - Twitter/X: https://x.com/{username}
    - Instagram: https://instagram.com/{username}
    - TikTok: https://tiktok.com/@{username}
    - YouTube: https://youtube.com/@{handle} or https://youtube.com/channel/{id}
    - LinkedIn Personal: https://www.linkedin.com/in/{vanityName}/
    - LinkedIn Organization: https://www.linkedin.com/company/{vanityName}/
    - Threads: https://threads.net/@{username}
    - Pinterest: https://pinterest.com/{username}
    - Reddit: https://reddit.com/user/{username}
    - Bluesky: https://bsky.app/profile/{handle}
    - Facebook: https://facebook.com/{username} or https://facebook.com/{pageId}
    - Google Business: Google Maps URL for the business location

    """
    isActive: bool | None = None
    followersCount: float | None = None
    """
    Follower count (only included if user has analytics add-on)
    """
    followersLastUpdated: AwareDatetime | None = None
    """
    Last time follower count was updated (only included if user has analytics add-on)
    """


class AccountWithFollowerStats(SocialAccount):
    profilePicture: str | None = None
    currentFollowers: float | None = None
    """
    Current follower count
    """
    lastUpdated: AwareDatetime | None = None
    growth: float | None = None
    """
    Follower change over period
    """
    growthPercentage: float | None = None
    """
    Percentage growth
    """
    dataPoints: float | None = None
    """
    Number of historical snapshots
    """


class ApiKey(BaseModel):
    id: str | None = None
    name: str | None = None
    keyPreview: str | None = None
    expiresAt: AwareDatetime | None = None
    createdAt: AwareDatetime | None = None
    key: str | None = None
    """
    Returned only once, on creation
    """


class BillingPeriod(Enum):
    MONTHLY = "monthly"
    YEARLY = "yearly"


class Limits(BaseModel):
    uploads: int | None = None
    profiles: int | None = None


class Usage(BaseModel):
    uploads: int | None = None
    profiles: int | None = None
    lastReset: AwareDatetime | None = None


class UsageStats(BaseModel):
    planName: str | None = None
    billingPeriod: BillingPeriod | None = None
    signupDate: AwareDatetime | None = None
    limits: Limits | None = None
    usage: Usage | None = None


class PostAnalytics(BaseModel):
    impressions: Annotated[int | None, Field(examples=[0])] = None
    reach: Annotated[int | None, Field(examples=[0])] = None
    likes: Annotated[int | None, Field(examples=[0])] = None
    comments: Annotated[int | None, Field(examples=[0])] = None
    shares: Annotated[int | None, Field(examples=[0])] = None
    saves: Annotated[int | None, Field(examples=[0])] = None
    """
    Number of saves/bookmarks (Instagram, Pinterest)
    """
    clicks: Annotated[int | None, Field(examples=[0])] = None
    views: Annotated[int | None, Field(examples=[0])] = None
    engagementRate: Annotated[float | None, Field(examples=[0])] = None
    lastUpdated: AwareDatetime | None = None


class AccountMetrics(BaseModel):
    followers: int | None = None
    """
    Followers/fans count (e.g., Instagram, Facebook Pages, Twitter)
    """
    subscribers: int | None = None
    """
    Subscribers count (e.g., YouTube)
    """
    lastUpdated: AwareDatetime | None = None


class PlatformAnalytics(BaseModel):
    platform: str | None = None
    status: str | None = None
    accountId: str | None = None
    accountUsername: str | None = None
    analytics: PostAnalytics | None = None
    accountMetrics: AccountMetrics | None = None


class AnalyticsOverview(BaseModel):
    totalPosts: int | None = None
    publishedPosts: int | None = None
    scheduledPosts: int | None = None
    lastSync: AwareDatetime | None = None


class AnalyticsSinglePostResponse(BaseModel):
    postId: str | None = None
    status: str | None = None
    content: str | None = None
    scheduledFor: AwareDatetime | None = None
    publishedAt: AwareDatetime | None = None
    analytics: PostAnalytics | None = None
    platformAnalytics: List[PlatformAnalytics] | None = None
    platform: str | None = None
    platformPostUrl: AnyUrl | None = None
    isExternal: bool | None = None


class MediaType1(Enum):
    IMAGE = "image"
    VIDEO = "video"
    GIF = "gif"
    DOCUMENT = "document"


class Post2(BaseModel):
    field_id: Annotated[str | None, Field(alias="_id")] = None
    content: str | None = None
    scheduledFor: AwareDatetime | None = None
    publishedAt: AwareDatetime | None = None
    status: str | None = None
    analytics: PostAnalytics | None = None
    platforms: List[PlatformAnalytics] | None = None
    platform: str | None = None
    platformPostUrl: AnyUrl | None = None
    isExternal: bool | None = None
    thumbnailUrl: AnyUrl | None = None
    mediaType: MediaType1 | None = None
    mediaItems: List[MediaItem] | None = None


class AnalyticsListResponse(BaseModel):
    overview: AnalyticsOverview | None = None
    posts: List[Post2] | None = None
    pagination: Pagination | None = None
    accounts: List[SocialAccount] | None = None
    """
    Connected social accounts (followerCount and followersLastUpdated only included if user has analytics add-on)
    """
    hasAnalyticsAccess: bool | None = None
    """
    Whether user has analytics add-on access
    """


class Aggregation(Enum):
    TOTAL = "TOTAL"


class Analytics(BaseModel):
    impressions: int | None = None
    """
    Total impressions across all posts
    """
    reach: int | None = None
    """
    Unique members reached across all posts
    """
    reactions: int | None = None
    """
    Total reactions across all posts
    """
    comments: int | None = None
    """
    Total comments across all posts
    """
    shares: int | None = None
    """
    Total reshares across all posts
    """
    engagementRate: float | None = None
    """
    Overall engagement rate as percentage
    """


class LinkedInAggregateAnalyticsTotalResponse(BaseModel):
    """
    Response for TOTAL aggregation (lifetime totals)
    """

    accountId: str | None = None
    platform: Annotated[str | None, Field(examples=["linkedin"])] = None
    accountType: Annotated[str | None, Field(examples=["personal"])] = None
    username: str | None = None
    aggregation: Aggregation | None = None
    dateRange: DateRange | None = None
    analytics: Analytics | None = None
    note: str | None = None
    lastUpdated: AwareDatetime | None = None


class Aggregation1(Enum):
    DAILY = "DAILY"


class Impression(BaseModel):
    date: date_aliased | None = None
    count: int | None = None


class Reaction(BaseModel):
    date: date_aliased | None = None
    count: int | None = None


class Comment(BaseModel):
    date: date_aliased | None = None
    count: int | None = None


class Share(BaseModel):
    date: date_aliased | None = None
    count: int | None = None


class Analytics1(BaseModel):
    """
    Daily breakdown of each metric. Each metric contains an array of date/count pairs.
    Note: 'reach' (MEMBERS_REACHED) is not available with DAILY aggregation per LinkedIn API limitations.

    """

    impressions: List[Impression] | None = None
    reactions: List[Reaction] | None = None
    comments: List[Comment] | None = None
    shares: List[Share] | None = None


class LinkedInAggregateAnalyticsDailyResponse(BaseModel):
    """
    Response for DAILY aggregation (time series breakdown)
    """

    accountId: str | None = None
    platform: Annotated[str | None, Field(examples=["linkedin"])] = None
    accountType: Annotated[str | None, Field(examples=["personal"])] = None
    username: str | None = None
    aggregation: Aggregation1 | None = None
    dateRange: DateRange | None = None
    analytics: Analytics1 | None = None
    """
    Daily breakdown of each metric. Each metric contains an array of date/count pairs.
    Note: 'reach' (MEMBERS_REACHED) is not available with DAILY aggregation per LinkedIn API limitations.

    """
    skippedMetrics: List[str] | None = None
    """
    Metrics that were skipped due to API limitations
    """
    note: str | None = None
    lastUpdated: AwareDatetime | None = None


class PostDeleteResponse(BaseModel):
    message: str | None = None


class ProfilesListResponse(BaseModel):
    profiles: List[Profile] | None = None


class ProfileGetResponse(BaseModel):
    profile: Profile | None = None


class ProfileCreateResponse(BaseModel):
    message: str | None = None
    profile: Profile | None = None


class ProfileUpdateResponse(BaseModel):
    message: str | None = None
    profile: Profile | None = None


class ProfileDeleteResponse(BaseModel):
    message: str | None = None


class AccountsListResponse(BaseModel):
    accounts: List[SocialAccount] | None = None
    hasAnalyticsAccess: bool | None = None
    """
    Whether user has analytics add-on access
    """


class AccountGetResponse(BaseModel):
    account: SocialAccount | None = None


class DateRange3(BaseModel):
    from_: Annotated[AwareDatetime | None, Field(alias="from")] = None
    to: AwareDatetime | None = None


class Aggregation2(Enum):
    DAILY = "daily"
    WEEKLY = "weekly"
    MONTHLY = "monthly"


class FollowerStatsResponse(BaseModel):
    accounts: List[AccountWithFollowerStats] | None = None
    dateRange: DateRange3 | None = None
    aggregation: Aggregation2 | None = None


class Type2(Enum):
    IMAGE = "image"
    VIDEO = "video"
    DOCUMENT = "document"


class UploadedFile(BaseModel):
    type: Type2 | None = None
    url: AnyUrl | None = None
    filename: str | None = None
    size: int | None = None
    mimeType: str | None = None


class MediaUploadResponse(BaseModel):
    files: List[UploadedFile] | None = None


class Status4(Enum):
    PENDING = "pending"
    COMPLETED = "completed"
    EXPIRED = "expired"


class UploadTokenResponse(BaseModel):
    token: str | None = None
    uploadUrl: AnyUrl | None = None
    expiresAt: AwareDatetime | None = None
    status: Status4 | None = None


class UploadTokenStatusResponse(BaseModel):
    token: str | None = None
    status: Status4 | None = None
    files: List[UploadedFile] | None = None
    createdAt: AwareDatetime | None = None
    expiresAt: AwareDatetime | None = None
    completedAt: AwareDatetime | None = None


class QueueSlotsResponse(BaseModel):
    exists: bool | None = None
    schedule: QueueSchedule | None = None
    nextSlots: List[AwareDatetime] | None = None


class QueueUpdateResponse(BaseModel):
    success: bool | None = None
    schedule: QueueSchedule | None = None
    nextSlots: List[AwareDatetime] | None = None
    reshuffledCount: int | None = None


class QueueDeleteResponse(BaseModel):
    success: bool | None = None
    deleted: bool | None = None


class QueuePreviewResponse(BaseModel):
    profileId: str | None = None
    count: int | None = None
    slots: List[AwareDatetime] | None = None


class QueueNextSlotResponse(BaseModel):
    profileId: str | None = None
    nextSlot: AwareDatetime | None = None
    timezone: str | None = None


class DownloadFormat(BaseModel):
    formatId: str | None = None
    ext: str | None = None
    resolution: str | None = None
    filesize: int | None = None
    quality: str | None = None


class DownloadResponse(BaseModel):
    url: AnyUrl | None = None
    title: str | None = None
    thumbnail: AnyUrl | None = None
    duration: int | None = None
    formats: List[DownloadFormat] | None = None


class TranscriptSegment(BaseModel):
    text: str | None = None
    start: float | None = None
    duration: float | None = None


class TranscriptResponse(BaseModel):
    transcript: str | None = None
    segments: List[TranscriptSegment] | None = None
    language: str | None = None


class Status6(Enum):
    SAFE = "safe"
    BANNED = "banned"
    RESTRICTED = "restricted"
    UNKNOWN = "unknown"


class HashtagInfo(BaseModel):
    hashtag: str | None = None
    status: Status6 | None = None
    postCount: int | None = None


class HashtagCheckResponse(BaseModel):
    hashtags: List[HashtagInfo] | None = None


class CaptionResponse(BaseModel):
    caption: str | None = None


class User(BaseModel):
    field_id: Annotated[str | None, Field(alias="_id")] = None
    email: str | None = None
    name: str | None = None
    role: str | None = None
    createdAt: AwareDatetime | None = None


class UsersListResponse(BaseModel):
    users: List[User] | None = None


class UserGetResponse(BaseModel):
    user: User | None = None


class PlatformTarget(BaseModel):
    platform: Annotated[str | None, Field(examples=["twitter"])] = None
    """
    Supported values: twitter, threads, instagram, youtube, facebook, linkedin, pinterest, reddit, tiktok, bluesky, googlebusiness, telegram
    """
    accountId: str | SocialAccount | None = None
    customContent: str | None = None
    customMedia: List[MediaItem] | None = None
    scheduledFor: AwareDatetime | None = None
    """
    Optional per-platform scheduled time override (uses post.scheduledFor when omitted)
    """
    platformSpecificData: (
        TwitterPlatformData
        | ThreadsPlatformData
        | FacebookPlatformData
        | InstagramPlatformData
        | LinkedInPlatformData
        | PinterestPlatformData
        | YouTubePlatformData
        | GoogleBusinessPlatformData
        | TikTokPlatformData
        | TelegramPlatformData
        | SnapchatPlatformData
        | None
    ) = None
    """
    Platform-specific overrides and options.
    """
    status: Annotated[str | None, Field(examples=["pending"])] = None
    """
    Platform-specific status: pending, publishing, published, failed
    """
    platformPostId: Annotated[str | None, Field(examples=["1234567890123456789"])] = (
        None
    )
    """
    The native post ID on the platform (populated after successful publish)
    """
    platformPostUrl: Annotated[
        AnyUrl | None,
        Field(examples=["https://twitter.com/acmecorp/status/1234567890123456789"]),
    ] = None
    """
    Public URL of the published post on the platform.
    Populated after successful publish. For immediate posts (publishNow=true), 
    this is included in the response. For scheduled posts, fetch the post 
    via GET /v1/posts/{postId} after the scheduled time.

    """
    publishedAt: AwareDatetime | None = None
    """
    Timestamp when the post was published to this platform
    """
    errorMessage: str | None = None
    """
    Human-readable error message when status is 'failed'.
    Contains platform-specific error details explaining why the publish failed.
    Examples:
    - "Instagram access token has expired. Please reconnect your account."
    - "Post text exceeds the 500 character limit for Threads."
    - "You do not have enough karma to post in this subreddit."
    - "Video is too long for Reels. Facebook Reels must be 90 seconds or less."

    """
    errorCategory: ErrorCategory | None = None
    """
    Error category for programmatic handling:
    - auth_expired: Token expired or revoked, account needs reconnection
    - user_content: Content doesn't meet platform requirements (too long, wrong format, etc.)
    - user_abuse: Rate limits, spam detection, excessive posting
    - account_issue: Account configuration problems (missing board, inactive account)
    - platform_rejected: Platform rules violated (banned, suspended, policy violation)
    - platform_error: Platform-side issues (5xx errors, maintenance)
    - system_error: Late infrastructure issues (timeouts, network errors)
    - unknown: Unclassified error

    """
    errorSource: ErrorSource | None = None
    """
    Who/what caused the error:
    - user: User action required (fix content, reconnect account)
    - platform: Platform-side issue (outage, API change)
    - system: Late system issue (rare)

    """


class Post1(BaseModel):
    field_id: Annotated[str | None, Field(alias="_id")] = None
    userId: str | User | None = None
    title: str | None = None
    """
    YouTube: title must be ≤ 100 characters.

    """
    content: str | None = None
    mediaItems: List[MediaItem] | None = None
    platforms: List[PlatformTarget] | None = None
    scheduledFor: AwareDatetime | None = None
    timezone: str | None = None
    status: Status3 | None = None
    tags: List[str] | None = None
    """
    YouTube tag constraints when targeting YouTube:
    - No count cap; duplicates removed.
    - Each tag must be ≤ 100 chars.
    - Combined characters across all tags ≤ 500.

    """
    hashtags: List[str] | None = None
    mentions: List[str] | None = None
    visibility: Visibility | None = None
    metadata: Dict[str, Any] | None = None
    queuedFromProfile: str | None = None
    """
    Profile ID if the post was scheduled via the queue
    """
    queueId: str | None = None
    """
    Queue ID if the post was scheduled via a specific queue
    """
    createdAt: AwareDatetime | None = None
    updatedAt: AwareDatetime | None = None


class PostsListResponse(BaseModel):
    posts: List[Post1] | None = None
    pagination: Pagination | None = None


class PostGetResponse(BaseModel):
    post: Post1 | None = None


class PostCreateResponse(BaseModel):
    message: str | None = None
    post: Post1 | None = None


class PostUpdateResponse(BaseModel):
    message: str | None = None
    post: Post1 | None = None


class PostRetryResponse(BaseModel):
    message: str | None = None
    post: Post1 | None = None
