openapi: 3.0.0
paths:
  /v1/auth/keys:
    post:
      description: >-
        Creates a new API key for the authenticated user. Requires a dashboard
        session token or existing API key. The full key is only returned once —
        store it securely.
      operationId: AuthController_createKey
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateApiKeyDto'
      responses:
        '201':
          description: API key created. The raw key is returned only once.
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                    format: uuid
                  name:
                    type: string
                  key:
                    type: string
                    description: Full API key — shown once, store securely
                  prefix:
                    type: string
                  createdAt:
                    type: string
                    format: date-time
                  expiresAt:
                    type: string
                    format: date-time
                    nullable: true
      security:
        - bearer: []
      summary: Generate a new API key
      tags: &ref_0
        - Authentication
    get:
      operationId: AuthController_listKeys
      parameters: []
      responses:
        '200':
          description: List of API keys (without raw key values)
      security:
        - bearer: []
      summary: List all API keys for the authenticated user
      tags: *ref_0
  /v1/auth/keys/{id}:
    delete:
      operationId: AuthController_revokeKey
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '204':
          description: Key revoked successfully
        '404':
          description: Key not found
      security:
        - bearer: []
      summary: Revoke an API key
      tags: *ref_0
  /v1/oauth/x/authorize:
    get:
      description: >-
        Redirects the user to Twitter to grant permissions. Pass the Posbly
        `user_id` so the connection can be linked on callback.
      operationId: OAuthController_startX
      parameters:
        - name: user_id
          required: false
          in: query
          description: Posbly user UUID
          schema:
            type: string
        - name: connect_code
          required: false
          in: query
          description: Short-lived code from POST /v1/auth/connect-code (dashboard use)
          schema:
            type: string
      responses:
        '302':
          description: Redirect to Twitter OAuth consent screen
      summary: Start X (Twitter) OAuth 1.0a flow
      tags: &ref_1
        - OAuth
  /v1/oauth/x/callback:
    get:
      description: >-
        Twitter redirects here after the user grants/denies access. On success
        the connection is stored and the browser is sent back to posbly.com.
      operationId: OAuthController_callbackX
      parameters:
        - name: oauth_token
          required: false
          in: query
          schema:
            type: string
        - name: oauth_verifier
          required: false
          in: query
          schema:
            type: string
        - name: denied
          required: false
          in: query
          schema:
            type: string
      responses:
        '302':
          description: Redirect to posbly.com dashboard
      summary: X OAuth 1.0a callback
      tags: *ref_1
  /v1/oauth/meta/authorize:
    get:
      description: >-
        Redirects the user to Facebook to grant page and Instagram permissions.
        Both Facebook Pages and linked Instagram business accounts are
        registered in one flow.
      operationId: OAuthController_startMeta
      parameters:
        - name: user_id
          required: false
          in: query
          description: Posbly user UUID
          schema:
            type: string
        - name: connect_code
          required: false
          in: query
          description: Short-lived code from POST /v1/auth/connect-code (dashboard use)
          schema:
            type: string
        - name: api_version
          required: false
          in: query
          description: Meta Graph API version override (v19.0–v25.0)
          schema:
            type: string
      responses:
        '302':
          description: Redirect to Facebook OAuth consent screen
      summary: Start Meta (Facebook + Instagram) OAuth flow
      tags: *ref_1
  /v1/oauth/meta/callback:
    get:
      description: >-
        Facebook redirects here. Exchanges code for long-lived tokens (60-day),
        registers all authorized Pages + linked Instagram accounts.
      operationId: OAuthController_callbackMeta
      parameters:
        - name: code
          required: false
          in: query
          schema:
            type: string
        - name: state
          required: false
          in: query
          schema:
            type: string
        - name: error
          required: false
          in: query
          schema:
            type: string
      responses:
        '302':
          description: Redirect to posbly.com dashboard
      summary: Meta OAuth callback
      tags: *ref_1
  /v1/oauth/instagram/authorize:
    get:
      description: Redirects the user to Instagram to grant permissions.
      operationId: OAuthController_startInstagram
      parameters:
        - name: user_id
          required: true
          in: query
          description: Posbly user UUID
          schema:
            type: string
        - name: connect_code
          required: true
          in: query
          schema:
            type: string
        - name: api_version
          required: false
          in: query
          description: Meta Graph API version override (v19.0–v25.0)
          schema:
            type: string
      responses:
        '302':
          description: Redirect to Instagram OAuth consent screen
      summary: Start Instagram OAuth flow
      tags: *ref_1
  /v1/oauth/instagram/callback:
    get:
      operationId: OAuthController_callbackInstagram
      parameters:
        - name: code
          required: false
          in: query
          schema:
            type: string
        - name: state
          required: false
          in: query
          schema:
            type: string
        - name: error
          required: false
          in: query
          schema:
            type: string
      responses:
        '302':
          description: Redirect to posbly.com dashboard
      summary: Instagram OAuth callback
      tags: *ref_1
  /v1/oauth/instagram/webhook:
    get:
      description: >-
        Meta hub.challenge verification endpoint. Register this URL in your Meta
        app webhooks config with WEBHOOK_SECRET as the verify token.
      operationId: OAuthController_instagramWebhookVerify
      parameters:
        - name: hub.mode
          required: false
          in: query
          schema:
            type: string
        - name: hub.challenge
          required: false
          in: query
          schema:
            type: string
        - name: hub.verify_token
          required: false
          in: query
          schema:
            type: string
      responses:
        '200':
          description: ''
      summary: Instagram webhook verification
      tags: *ref_1
  /v1/oauth/instagram/deauthorize:
    post:
      description: Called by Meta when a user removes the app. Deactivates the connection.
      operationId: OAuthController_instagramDeauthorize
      parameters: []
      responses:
        '200':
          description: ''
      summary: Instagram deauthorize callback
      tags: *ref_1
  /v1/oauth/instagram/data-deletion:
    post:
      description: >-
        Called by Meta when a user requests data deletion. Removes their
        connection and returns a confirmation.
      operationId: OAuthController_instagramDataDeletion
      parameters: []
      responses:
        '200':
          description: ''
      summary: Instagram data deletion callback
      tags: *ref_1
  /v1/oauth/threads/authorize:
    get:
      description: Redirects the user to Threads to grant permissions.
      operationId: OAuthController_startThreads
      parameters:
        - name: user_id
          required: true
          in: query
          description: Posbly user UUID
          schema:
            type: string
        - name: connect_code
          required: true
          in: query
          schema:
            type: string
      responses:
        '302':
          description: Redirect to Threads OAuth consent screen
      summary: Start Threads OAuth flow
      tags: *ref_1
  /v1/oauth/threads/callback:
    get:
      operationId: OAuthController_callbackThreads
      parameters:
        - name: code
          required: false
          in: query
          schema:
            type: string
        - name: state
          required: false
          in: query
          schema:
            type: string
        - name: error
          required: false
          in: query
          schema:
            type: string
      responses:
        '302':
          description: Redirect to posbly.com dashboard
      summary: Threads OAuth callback
      tags: *ref_1
  /v1/oauth/threads/deauthorize:
    post:
      description: >-
        Called by Meta when a user removes the Threads app. Deactivates the
        connection.
      operationId: OAuthController_threadsDeauthorize
      parameters: []
      responses:
        '200':
          description: ''
      summary: Threads deauthorize callback
      tags: *ref_1
  /v1/oauth/threads/data-deletion:
    post:
      description: >-
        Called by Meta when a user requests data deletion. Removes their
        connection and returns a confirmation.
      operationId: OAuthController_threadsDataDeletion
      parameters: []
      responses:
        '200':
          description: ''
      summary: Threads data deletion callback
      tags: *ref_1
  /v1/oauth/pinterest/authorize:
    get:
      description: Redirects the user to Pinterest to grant boards and pins permissions.
      operationId: OAuthController_startPinterest
      parameters:
        - name: user_id
          required: true
          in: query
          description: Posbly user UUID
          schema:
            type: string
        - name: connect_code
          required: true
          in: query
          schema:
            type: string
      responses:
        '302':
          description: Redirect to Pinterest OAuth consent screen
      summary: Start Pinterest OAuth 2.0 flow
      tags: *ref_1
  /v1/oauth/pinterest/callback:
    get:
      description: >-
        Pinterest redirects here after the user authorizes. Exchanges code for
        tokens and registers the account connection.
      operationId: OAuthController_callbackPinterest
      parameters:
        - name: code
          required: false
          in: query
          schema:
            type: string
        - name: state
          required: false
          in: query
          schema:
            type: string
        - name: error
          required: false
          in: query
          schema:
            type: string
      responses:
        '302':
          description: Redirect to posbly.com dashboard
      summary: Pinterest OAuth callback
      tags: *ref_1
  /v1/oauth/linkedin/authorize:
    get:
      description: >-
        Redirects the user to LinkedIn to grant posting permissions. Pass the
        Posbly `user_id` so the connection can be linked on callback.
      operationId: OAuthController_startLinkedin
      parameters:
        - name: user_id
          required: true
          in: query
          description: Posbly user UUID
          schema:
            type: string
        - name: connect_code
          required: true
          in: query
          schema:
            type: string
      responses:
        '302':
          description: Redirect to LinkedIn OAuth consent screen
      summary: Start LinkedIn OAuth 2.0 flow
      tags: *ref_1
  /v1/oauth/linkedin/callback:
    get:
      description: >-
        LinkedIn redirects here after the user authorizes. Exchanges code for
        tokens and registers the account connection.
      operationId: OAuthController_callbackLinkedin
      parameters:
        - name: code
          required: false
          in: query
          schema:
            type: string
        - name: state
          required: false
          in: query
          schema:
            type: string
        - name: error
          required: false
          in: query
          schema:
            type: string
      responses:
        '302':
          description: Redirect to posbly.com dashboard
      summary: LinkedIn OAuth callback
      tags: *ref_1
  /v1/oauth/linkedin/organizations:
    get:
      description: >-
        Returns the LinkedIn company/organization pages that the connected user
        is an administrator of. Pass the `connection_id` of the personal
        LinkedIn connection.
      operationId: OAuthController_getLinkedinOrganizations
      parameters:
        - name: connection_id
          required: true
          in: query
          description: PlatformConnection UUID of the personal LinkedIn connection
          schema:
            type: string
      responses:
        '200':
          description: Array of organizations
      security:
        - bearer: []
      summary: List LinkedIn organizations the user can post as
      tags: *ref_1
  /v1/oauth/linkedin/organizations/connect:
    post:
      description: >-
        Creates a new PlatformConnection that posts on behalf of a LinkedIn
        company page. Broadcasts targeting this connection will use the
        organization as the post author.
      operationId: OAuthController_connectLinkedinOrg
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - connection_id
                - org_id
                - org_name
              properties:
                connection_id:
                  type: string
                  description: UUID of the personal LinkedIn connection
                org_id:
                  type: string
                  description: LinkedIn organization numeric ID
                org_name:
                  type: string
                  description: Display name of the organization
      responses:
        '201':
          description: Organization connection created
      security:
        - bearer: []
      summary: Create a LinkedIn organization page connection
      tags: *ref_1
  /v1/oauth/youtube/authorize:
    get:
      description: >-
        Redirects the user to Google to grant YouTube upload and read
        permissions. Requires a Google Cloud project with the YouTube Data API
        v3 enabled.
      operationId: OAuthController_startYoutube
      parameters:
        - name: user_id
          required: true
          in: query
          description: Posbly user UUID
          schema:
            type: string
        - name: connect_code
          required: true
          in: query
          schema:
            type: string
      responses:
        '302':
          description: Redirect to Google OAuth consent screen
      summary: Start YouTube OAuth 2.0 flow
      tags: *ref_1
  /v1/oauth/youtube/callback:
    get:
      description: >-
        Google redirects here after the user grants access. Exchanges the code
        for tokens and registers the YouTube channel connection.
      operationId: OAuthController_callbackYoutube
      parameters:
        - name: code
          required: false
          in: query
          schema:
            type: string
        - name: state
          required: false
          in: query
          schema:
            type: string
        - name: error
          required: false
          in: query
          schema:
            type: string
      responses:
        '302':
          description: Redirect to posbly.com dashboard
      summary: YouTube OAuth callback
      tags: *ref_1
  /v1/oauth/tiktok/authorize:
    get:
      description: >-
        Redirects the user to TikTok to grant video publishing permissions.
        Requires a TikTok Developer app with Content Posting API access.
      operationId: OAuthController_startTikTok
      parameters:
        - name: user_id
          required: true
          in: query
          description: Posbly user UUID
          schema:
            type: string
        - name: connect_code
          required: true
          in: query
          schema:
            type: string
      responses:
        '302':
          description: Redirect to TikTok OAuth consent screen
      summary: Start TikTok OAuth 2.0 flow
      tags: *ref_1
  /v1/oauth/tiktok/callback:
    get:
      description: >-
        TikTok redirects here after the user grants access. Exchanges the code
        for tokens and registers the TikTok account connection.
      operationId: OAuthController_callbackTikTok
      parameters:
        - name: code
          required: false
          in: query
          schema:
            type: string
        - name: state
          required: false
          in: query
          schema:
            type: string
        - name: error
          required: false
          in: query
          schema:
            type: string
      responses:
        '302':
          description: Redirect to posbly.com dashboard
      summary: TikTok OAuth callback
      tags: *ref_1
  /v1/oauth/bluesky/connect:
    post:
      description: >-
        Authenticates with Bluesky using a handle and app password (not your
        main password). Stores session tokens and creates a platform connection.
        Bluesky does not use standard OAuth — generate an app password at
        bsky.app → Settings → App Passwords.
      operationId: OAuthController_connectBluesky
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BlueskyConnectDto'
      responses:
        '200':
          description: Account connected successfully
          content:
            application/json:
              schema:
                example:
                  success: true
                  platform: BLUESKY
                  account_name: '@user.bsky.social'
                  did: did:plc:abc123
      security:
        - bearer: []
      summary: Connect a Bluesky account
      tags: *ref_1
  /v1/oauth/mastodon/authorize:
    get:
      description: >-
        Redirects the user to their Mastodon instance to grant permissions. Pass
        the instance hostname (e.g. mastodon.social) and the Posbly user_id or
        connect_code. The backend dynamically registers an OAuth app on the
        instance if needed.
      operationId: OAuthController_startMastodon
      parameters:
        - name: user_id
          required: false
          in: query
          description: Posbly user UUID
          schema:
            type: string
        - name: connect_code
          required: false
          in: query
          description: Short-lived code from POST /v1/auth/connect-code (dashboard use)
          schema:
            type: string
        - name: instance
          required: true
          in: query
          description: Mastodon instance hostname (e.g. mastodon.social)
          schema:
            example: mastodon.social
            type: string
      responses:
        '302':
          description: Redirect to Mastodon OAuth consent screen
      summary: Start Mastodon OAuth 2.0 flow
      tags: *ref_1
  /v1/oauth/mastodon/callback:
    get:
      description: >-
        Mastodon redirects here after the user grants access. Exchanges the code
        for an access token and registers the Mastodon account connection.
      operationId: OAuthController_callbackMastodon
      parameters:
        - name: code
          required: false
          in: query
          schema:
            type: string
        - name: state
          required: false
          in: query
          schema:
            type: string
        - name: error
          required: false
          in: query
          schema:
            type: string
      responses:
        '302':
          description: Redirect to posbly.com dashboard
      summary: Mastodon OAuth callback
      tags: *ref_1
  /v1/oauth/mastodon/connect:
    post:
      description: >-
        Connects a Mastodon account using a self-generated access token. Useful
        for power users who prefer not to go through the OAuth redirect flow.
        Generate a token at your instance → Settings → Development → New
        Application (grant read:accounts, write:statuses, and write:media
        scopes).
      operationId: OAuthController_connectMastodon
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MastodonConnectDto'
      responses:
        '200':
          description: Account connected successfully
          content:
            application/json:
              schema:
                example:
                  success: true
                  platform: MASTODON
                  account_name: '@user@mastodon.social'
                  id: '123456789'
      security:
        - bearer: []
      summary: Connect a Mastodon account (direct token)
      tags: *ref_1
  /v1/users/me:
    post:
      description: >-
        Creates or updates a user record by email. Returns a session token for
        dashboard use. Called by the Posbly web app after Google sign-in.
      operationId: UsersController_upsertMe
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpsertUserDto'
      responses:
        '200':
          description: User upserted, session token returned
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                    format: uuid
                  email:
                    type: string
                  name:
                    type: string
                    nullable: true
                  session_token:
                    type: string
      summary: Upsert user and get session token
      tags: &ref_2
        - Users
  /v1/users/me/stats:
    get:
      operationId: UsersController_getStats
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                properties:
                  posts_this_month:
                    type: number
                  credits_remaining:
                    type: number
                  connected_accounts:
                    type: number
      security:
        - bearer: []
      summary: Get dashboard stats for the authenticated user
      tags: *ref_2
  /v1/users/me/timezone:
    patch:
      operationId: UsersController_updateTimezone
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateTimezoneDto'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                properties:
                  timezone:
                    type: string
      security:
        - bearer: []
      summary: Update timezone preference for the authenticated user
      tags: *ref_2
  /v1/account:
    get:
      description: >-
        Returns basic user info for the authenticated API key or session token.
        Returns 200 on valid key, 401 on invalid/missing key.
      operationId: AccountController_getAccount
      parameters: []
      responses:
        '200':
          description: Authenticated user info
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                    format: uuid
                  email:
                    type: string
                  name:
                    type: string
                    nullable: true
                  timezone:
                    type: string
        '401':
          description: Unauthorized – invalid or missing API key
      security:
        - bearer: []
      summary: Get authenticated account info
      tags:
        - Account
  /v1/media:
    get:
      description: Lists files stored in S3 for the authenticated user.
      operationId: MediaController_list
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                properties:
                  files:
                    type: array
                    items:
                      type: object
                      properties:
                        url:
                          type: string
                        key:
                          type: string
                        size:
                          type: number
                        lastModified:
                          type: string
      security:
        - bearer: []
      summary: List uploaded media files
      tags: &ref_3
        - Media
    post:
      description: >-
        Upload an image or video file and receive a hosted URL you can use in
        `POST /v1/broadcast` as `media_url` or inside `carousel_items`.
        Supported formats: JPEG, PNG, GIF, WebP, MP4, MOV, AVI, MPEG. Max size:
        500 MB. Videos over 100 MB incur an additional charge of $0.01 per 100
        MB above the free tier.
      operationId: MediaController_upload
      parameters: []
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required:
                - file
              properties:
                file:
                  type: string
                  format: binary
                  description: The media file to upload (image or video)
      responses:
        '201':
          description: File uploaded successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  url:
                    type: string
                    example: https://api.posbly.com/v1/media/user123/1234567890-abc.jpg
                  download_url:
                    type: string
                    example: >-
                      https://api.posbly.com/v1/media/user123/1234567890-abc.jpg?dl=1
                  content_type:
                    type: string
                    example: image/jpeg
                  size:
                    type: number
                    example: 204800
                  media_type:
                    type: string
                    enum:
                      - image
                      - video
                    example: image
        '400':
          description: Invalid file type or size
        '401':
          description: Unauthorized
      security:
        - bearer: []
      summary: Upload a media file
      tags: *ref_3
  /v1/broadcast:
    post:
      description: >-
        Dispatches posts to one or more platforms asynchronously. Each platform
        is an independent job. Returns immediately with job status.


        Supports two content modes:


        **JSON mode** (`Content-Type: application/json`) — pass `media_url` as a
        public URL (e.g. from `POST /v1/media`):

        ```json

        {
          "platforms": { "instagram": ["@brand"] },
          "content": { "text": "Hello!", "media_url": "https://...", "media_type": "image" }
        }

        ```


        **Multipart mode** (`Content-Type: multipart/form-data`) — attach files
        directly and let the API upload them:

        - `data` field: the full JSON payload (same structure as JSON mode,
        without `media_url` / `carousel_items`)

        - `media` field: a single image or video file

        - `carousel` field: up to 10 files for a carousel post (repeat the field
        for each file)
      operationId: BroadcastController_create
      parameters: []
      requestBody:
        required: true
        content:
          application/json: &ref_4
            schema:
              oneOf:
                - $ref: '#/components/schemas/CreateBroadcastDto'
                - type: object
                  description: Multipart upload mode
                  properties:
                    data:
                      type: string
                      description: >-
                        JSON-encoded CreateBroadcastDto (same structure as the
                        JSON body)
                    media:
                      type: string
                      format: binary
                      description: Single image or video file
                    carousel:
                      type: array
                      items:
                        type: string
                        format: binary
                      description: Up to 10 files for a carousel post
                  required:
                    - data
          multipart/form-data: *ref_4
      responses:
        '202':
          description: Broadcast accepted and jobs enqueued
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BroadcastResponseDto'
        '400':
          description: Invalid request (validation error, insufficient credits, etc.)
        '401':
          description: Unauthorized
      security: &ref_5
        - bearer: []
      summary: Create a broadcast
      tags: &ref_6
        - Broadcast
    get:
      operationId: BroadcastController_list
      parameters:
        - name: limit
          required: false
          in: query
          schema:
            example: 20
            type: number
        - name: offset
          required: false
          in: query
          schema:
            example: 0
            type: number
        - name: scheduled_only
          required: false
          in: query
          description: Return only upcoming scheduled broadcasts
          schema:
            example: false
            type: boolean
      responses:
        '200':
          description: Paginated list of broadcasts
      security: *ref_5
      summary: List broadcasts
      tags: *ref_6
  /v1/broadcast/{broadcast_id}:
    get:
      operationId: BroadcastController_findOne
      parameters:
        - name: broadcast_id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: Broadcast with per-platform job statuses
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BroadcastResponseDto'
        '404':
          description: Broadcast not found
      security: *ref_5
      summary: Get broadcast status
      tags: *ref_6
    delete:
      operationId: BroadcastController_remove
      parameters:
        - name: broadcast_id
          required: true
          in: path
          schema:
            type: string
      responses:
        '204':
          description: Broadcast deleted
        '404':
          description: Broadcast not found
      security: *ref_5
      summary: Cancel and delete a scheduled broadcast
      tags: *ref_6
  /v1/jobs/{job_id}/insights:
    get:
      description: >-
        Returns engagement metrics for a completed broadcast job. **Facebook** —
        impressions, reach, reactions by type, comments, shares (requires
        `read_insights` + `pages_read_engagement`). **TikTok** — views, likes,
        comments, shares (requires `video.publish`). Returns 400 for platforms
        that do not support insights.
      operationId: BroadcastController_getJobInsights
      parameters:
        - name: job_id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: Post insights
          content:
            application/json:
              schema:
                properties:
                  job_id:
                    type: string
                  platform:
                    type: string
                    example: FACEBOOK
                  post_id:
                    type: string
                  post_url:
                    type: string
                    nullable: true
                  insights:
                    type: object
                    properties:
                      impressions:
                        type: number
                      reach:
                        type: number
                      engaged_users:
                        type: number
                      reactions:
                        type: object
                        properties:
                          like:
                            type: number
                          love:
                            type: number
                          haha:
                            type: number
                          wow:
                            type: number
                          sad:
                            type: number
                          angry:
                            type: number
                      comments:
                        type: number
                      shares:
                        type: number
        '400':
          description: Job not completed or platform does not support insights
        '404':
          description: Job not found
      security: *ref_5
      summary: Get post insights for a completed job
      tags: *ref_6
  /v1/jobs/{job_id}:
    get:
      operationId: BroadcastController_getJob
      parameters:
        - name: job_id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: Job details including error info
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BroadcastJobDto'
        '404':
          description: Job not found
      security: *ref_5
      summary: Get individual job detail
      tags: *ref_6
  /v1/webhooks:
    post:
      description: >-
        Register a callback URL to receive events. The signing secret is
        returned only once — store it to verify incoming deliveries.
      operationId: WebhooksController_create
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateWebhookDto'
      responses:
        '201':
          description: Webhook registered
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                  url:
                    type: string
                  events:
                    type: array
                    items:
                      type: string
                  secret:
                    type: string
                    description: Signing secret — shown once
                  created_at:
                    type: string
                    format: date-time
      security: &ref_7
        - bearer: []
      summary: Register a webhook
      tags: &ref_8
        - Webhooks
    get:
      operationId: WebhooksController_list
      parameters: []
      responses:
        '200':
          description: Array of webhooks (no secrets)
      security: *ref_7
      summary: List registered webhooks
      tags: *ref_8
  /v1/webhooks/{id}:
    delete:
      operationId: WebhooksController_delete
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '204':
          description: Webhook deleted
        '404':
          description: Webhook not found
      security: *ref_7
      summary: Delete a webhook
      tags: *ref_8
  /v1/billing/balance:
    get:
      operationId: BillingController_getBalance
      parameters: []
      responses:
        '200':
          description: ''
      security:
        - ApiKey: []
      summary: Get current credit balance
      tags: &ref_9
        - billing
  /v1/billing/transactions:
    get:
      operationId: BillingController_getTransactions
      parameters:
        - name: limit
          required: false
          in: query
          schema:
            type: number
        - name: offset
          required: false
          in: query
          schema:
            type: number
      responses:
        '200':
          description: ''
      security:
        - ApiKey: []
      summary: List credit transactions
      tags: *ref_9
  /v1/billing/daily-usage:
    get:
      operationId: BillingController_getDailyUsage
      parameters: []
      responses:
        '200':
          description: ''
      security:
        - ApiKey: []
      summary: Get daily credit usage for the last 30 days
      tags: *ref_9
  /v1/billing/monthly-usage:
    get:
      operationId: BillingController_getMonthlyUsage
      parameters: []
      responses:
        '200':
          description: Daily usage data for the last 30 days
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    date:
                      type: string
                      example: '2026-03-12'
                    credits:
                      type: number
                      example: 0.15
                      description: Credits used on this date
                    posts:
                      type: number
                      example: 3
                      description: Number of posts made on this date
      security:
        - ApiKey: []
      summary: Get daily usage stats (credits and posts) for the last 30 days
      tags: *ref_9
  /v1/billing/spending-limit:
    get:
      operationId: BillingController_getSpendingLimit
      parameters: []
      responses:
        '200':
          description: ''
      security:
        - ApiKey: []
      summary: Get spending limit and monthly usage
      tags: *ref_9
    put:
      operationId: BillingController_setSpendingLimit
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SetSpendingLimitDto'
      responses:
        '200':
          description: ''
      security:
        - ApiKey: []
      summary: Set monthly spending limit
      tags: *ref_9
  /v1/billing/checkout:
    post:
      operationId: BillingController_createCheckout
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateCheckoutDto'
      responses:
        '201':
          description: ''
      security:
        - ApiKey: []
      summary: Create a Stripe checkout session to purchase credits
      tags: *ref_9
  /v1/billing/checkout/verify:
    get:
      operationId: BillingController_verifyCheckout
      parameters:
        - name: session_id
          required: true
          in: query
          schema:
            type: string
      responses:
        '200':
          description: ''
      security:
        - ApiKey: []
      summary: Verify a completed Stripe checkout session and credit the balance
      tags: *ref_9
  /v1/billing/webhook:
    post:
      operationId: BillingController_stripeWebhook
      parameters:
        - name: stripe-signature
          required: true
          in: header
          schema:
            type: string
      responses:
        '200':
          description: ''
      summary: Stripe webhook endpoint (do not call directly)
      tags: *ref_9
  /v1/connections:
    post:
      description: >-
        Store OAuth2 credentials for a social platform account. The access token
        is encrypted at rest. For Instagram and Facebook, store the long-lived
        page access token here.
      operationId: ConnectionsController_create
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateConnectionDto'
      responses:
        '201':
          description: Connection registered
        '409':
          description: Connection already exists
      security: &ref_10
        - bearer: []
      summary: Register a platform connection
      tags: &ref_11
        - Connections
    get:
      operationId: ConnectionsController_list
      parameters: []
      responses:
        '200':
          description: Array of connections (no tokens in response)
      security: *ref_10
      summary: List platform connections
      tags: *ref_11
  /v1/connections/{id}:
    patch:
      description: >-
        Merge metadata into the connection. Use this to set platform-specific
        config like Pinterest `board_id`.
      operationId: ConnectionsController_update
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateConnectionDto'
      responses:
        '200':
          description: Connection updated
        '404':
          description: Connection not found
      security: *ref_10
      summary: Update connection metadata
      tags: *ref_11
    delete:
      operationId: ConnectionsController_delete
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '204':
          description: Connection removed
        '404':
          description: Connection not found
      security: *ref_10
      summary: Remove a platform connection
      tags: *ref_11
  /v1/connections/{id}/boards:
    get:
      description: >-
        Returns the boards for a Pinterest connection. Use the board `id` when
        setting `metadata.board_id`.
      operationId: ConnectionsController_getBoards
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: Array of boards
        '400':
          description: Connection is not Pinterest
        '404':
          description: Connection not found
      security: *ref_10
      summary: List Pinterest boards
      tags: *ref_11
    post:
      description: >-
        Creates a new board for a Pinterest connection. Only available when
        PINTEREST_SANDBOX is enabled.
      operationId: ConnectionsController_createBoard
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateBoardDto'
      responses:
        '201':
          description: Board created
        '400':
          description: Connection is not Pinterest
        '404':
          description: Connection not found
      security: *ref_10
      summary: Create a Pinterest board
      tags: *ref_11
  /v1/connections/{id}/boards/{boardId}:
    delete:
      description: >-
        Deletes a board for a Pinterest connection. Only available when
        PINTEREST_SANDBOX is enabled.
      operationId: ConnectionsController_deleteBoard
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
        - name: boardId
          required: true
          in: path
          schema:
            type: string
      responses:
        '204':
          description: Board deleted
        '400':
          description: Connection is not Pinterest
        '404':
          description: Connection not found
      security: *ref_10
      summary: Delete a Pinterest board
      tags: *ref_11
  /v1/connections/{id}/pins:
    get:
      description: >-
        Returns pins for a Pinterest connection. Optionally filter by board.
        Requires `pins:read` scope.
      operationId: ConnectionsController_getPins
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
        - name: board_id
          required: false
          in: query
          description: Filter pins by board ID
          schema:
            type: string
        - name: page_size
          required: false
          in: query
          description: 'Number of pins to return (default: 25, max: 250)'
          schema:
            type: number
      responses:
        '200':
          description: Array of pins
        '400':
          description: Connection is not Pinterest
        '404':
          description: Connection not found
      security: *ref_10
      summary: List Pinterest pins
      tags: *ref_11
  /v1/connections/{id}/pins/{pinId}/analytics:
    get:
      description: Returns analytics for a specific pin. Requires `pins:read` scope.
      operationId: ConnectionsController_getPinAnalytics
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
        - name: pinId
          required: true
          in: path
          schema:
            type: string
        - name: start_date
          required: true
          in: query
          description: Start date (YYYY-MM-DD)
          schema:
            type: string
        - name: end_date
          required: true
          in: query
          description: End date (YYYY-MM-DD)
          schema:
            type: string
        - name: metric_types
          required: true
          in: query
          description: >-
            Comma-separated metrics: IMPRESSION, OUTBOUND_CLICK, PIN_CLICK,
            SAVE, SAVE_RATE
          schema:
            type: string
      responses:
        '200':
          description: Pin analytics data
        '400':
          description: Connection is not Pinterest
        '404':
          description: Connection not found
      security: *ref_10
      summary: Get Pinterest pin analytics
      tags: *ref_11
  /v1/connections/{id}/analytics:
    get:
      description: >-
        Returns analytics for the Pinterest user account. Requires
        `user_accounts:read` scope.
      operationId: ConnectionsController_getPinterestAnalytics
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
        - name: start_date
          required: true
          in: query
          description: Start date (YYYY-MM-DD)
          schema:
            type: string
        - name: end_date
          required: true
          in: query
          description: End date (YYYY-MM-DD)
          schema:
            type: string
        - name: metric_types
          required: true
          in: query
          description: >-
            Comma-separated metrics: IMPRESSION, OUTBOUND_CLICK, PIN_CLICK,
            SAVE, SAVE_RATE
          schema:
            type: string
      responses:
        '200':
          description: Account analytics data
        '400':
          description: Connection is not Pinterest
        '404':
          description: Connection not found
      security: *ref_10
      summary: Get Pinterest account analytics
      tags: *ref_11
  /v1/connections/{id}/catalogs:
    get:
      description: >-
        Returns product catalogs for a Pinterest connection. Requires
        `catalogs:read` scope.
      operationId: ConnectionsController_getPinterestCatalogs
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: Array of catalogs
        '400':
          description: Connection is not Pinterest
        '404':
          description: Connection not found
      security: *ref_10
      summary: List Pinterest catalogs
      tags: *ref_11
  /v1/connections/{id}/ads:
    get:
      description: >-
        Returns ad accounts accessible to the Pinterest connection. Requires
        `ads:read` scope.
      operationId: ConnectionsController_getPinterestAdAccounts
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: Array of ad accounts
        '400':
          description: Connection is not Pinterest
        '404':
          description: Connection not found
      security: *ref_10
      summary: List Pinterest ad accounts
      tags: *ref_11
  /v1/connections/{id}/events:
    post:
      description: >-
        Creates an event on LinkedIn on behalf of the connected person or
        organization page. Uses the `rw_events` scope (Events Management API
        product).
      operationId: ConnectionsController_createLinkedInEvent
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateLinkedInEventDto'
      responses:
        '201':
          description: Event created
        '400':
          description: Connection is not LinkedIn or missing URN
        '404':
          description: Connection not found
      security: *ref_10
      summary: Create a LinkedIn event
      tags: *ref_11
    get:
      description: >-
        Returns events for the connected LinkedIn person or organization. Uses
        the `rw_events` scope.
      operationId: ConnectionsController_listLinkedInEvents
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
        - name: limit
          required: false
          in: query
          description: 'Number of events to return (default: 10, max: 50)'
          schema:
            type: number
      responses:
        '200':
          description: Array of LinkedIn events
        '400':
          description: Connection is not LinkedIn
        '404':
          description: Connection not found
      security: *ref_10
      summary: List LinkedIn events
      tags: *ref_11
  /v1/connections/{id}/events/{eventId}:
    get:
      description: >-
        Returns full details of a LinkedIn event by its numeric ID. Uses the
        `rw_events` scope.
      operationId: ConnectionsController_getLinkedInEvent
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
        - name: eventId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: LinkedIn event details
        '400':
          description: Connection is not LinkedIn
        '404':
          description: Connection or event not found
      security: *ref_10
      summary: Get a LinkedIn event
      tags: *ref_11
    patch:
      description: >-
        Partially updates a LinkedIn event. Only fields included in the request
        body are changed. Uses the `rw_events` scope.
      operationId: ConnectionsController_updateLinkedInEvent
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
        - name: eventId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateLinkedInEventDto'
      responses:
        '200':
          description: Updated event
        '400':
          description: Connection is not LinkedIn
        '404':
          description: Connection or event not found
      security: *ref_10
      summary: Update a LinkedIn event
      tags: *ref_11
    delete:
      description: Permanently deletes a LinkedIn event. Uses the `rw_events` scope.
      operationId: ConnectionsController_deleteLinkedInEvent
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
        - name: eventId
          required: true
          in: path
          schema:
            type: string
      responses:
        '204':
          description: Event deleted
        '400':
          description: Connection is not LinkedIn
        '404':
          description: Connection or event not found
      security: *ref_10
      summary: Delete a LinkedIn event
      tags: *ref_11
  /v1/connections/{id}/profile:
    get:
      description: >-
        Returns extended profile information for a TikTok connection including
        follower count, likes, video count, and verification status. Uses the
        `user.info.basic` permission.
      operationId: ConnectionsController_getTikTokProfile
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: TikTok user profile
          content:
            application/json:
              schema:
                properties:
                  open_id:
                    type: string
                  display_name:
                    type: string
                  bio_description:
                    type: string
                    nullable: true
                  avatar_url:
                    type: string
                    nullable: true
                  profile_deep_link:
                    type: string
                    nullable: true
                  is_verified:
                    type: boolean
                  follower_count:
                    type: number
                  following_count:
                    type: number
                  likes_count:
                    type: number
                  video_count:
                    type: number
        '400':
          description: Connection is not TikTok
        '404':
          description: Connection not found
      security: *ref_10
      summary: Get TikTok account profile and stats
      tags: *ref_11
  /v1/connections/{id}/videos:
    get:
      description: >-
        Returns recent published videos for a TikTok connection with view, like,
        comment and share counts. Uses the `video.publish` permission.
      operationId: ConnectionsController_getTikTokVideos
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
        - name: limit
          required: false
          in: query
          description: 'Number of videos to return (default: 10, max: 20)'
          schema:
            type: number
      responses:
        '200':
          description: Array of TikTok videos with stats
        '400':
          description: Connection is not TikTok
        '404':
          description: Connection not found
      security: *ref_10
      summary: List TikTok published videos
      tags: *ref_11
  /v1/connections/{id}/insights:
    get:
      description: >-
        Returns page-level analytics for a Facebook connection. Uses the
        `read_insights` permission. Available metrics: `page_impressions`,
        `page_engaged_users`, `page_fans`, `page_views_total`,
        `page_post_engagements`.
      operationId: ConnectionsController_getPageInsights
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
        - name: period
          required: false
          in: query
          description: 'Aggregation period (default: week)'
          schema:
            enum:
              - day
              - week
              - days_28
              - month
            type: string
        - name: since
          required: false
          in: query
          description: Start of date range (ISO date string)
          schema:
            type: string
        - name: until
          required: false
          in: query
          description: End of date range (ISO date string)
          schema:
            type: string
      responses:
        '200':
          description: Page insights data
        '400':
          description: Connection is not Facebook
        '404':
          description: Connection not found
      security: *ref_10
      summary: Get Facebook page insights
      tags: *ref_11
  /v1/connections/{id}/posts:
    get:
      description: >-
        Returns recent posts published on the Facebook page with engagement
        counts. Uses the `pages_read_engagement` permission.
      operationId: ConnectionsController_getPagePosts
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
        - name: limit
          required: false
          in: query
          description: 'Number of posts to return (default: 10, max: 100)'
          schema:
            type: number
      responses:
        '200':
          description: Array of recent page posts
        '400':
          description: Connection is not Facebook
        '404':
          description: Connection not found
      security: *ref_10
      summary: List recent Facebook page posts
      tags: *ref_11
  /v1/connections/{id}/posts/{postId}/comments:
    get:
      description: >-
        Returns comments on the specified post including author info and like
        counts. Uses the `pages_read_user_content` permission.
      operationId: ConnectionsController_getPostComments
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
        - name: postId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: Array of comments
        '400':
          description: Connection is not Facebook
        '404':
          description: Connection not found
      security: *ref_10
      summary: List comments on a Facebook post
      tags: *ref_11
    post:
      description: >-
        Publishes a comment on the specified post on behalf of the Facebook
        page. Uses the `pages_manage_engagement` permission.
      operationId: ConnectionsController_replyToPost
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
        - name: postId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ReplyCommentDto'
      responses:
        '201':
          description: Comment published
          content:
            application/json:
              schema:
                properties:
                  commentId:
                    type: string
        '400':
          description: Connection is not Facebook
        '404':
          description: Connection not found
      security: *ref_10
      summary: Reply to a Facebook post
      tags: *ref_11
  /v1/connections/{id}/posts/{postId}/comments/{commentId}:
    delete:
      description: >-
        Hides or deletes the specified comment on behalf of the Facebook page.
        Uses the `pages_manage_engagement` permission.
      operationId: ConnectionsController_deleteComment
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
        - name: postId
          required: true
          in: path
          schema:
            type: string
        - name: commentId
          required: true
          in: path
          schema:
            type: string
      responses:
        '204':
          description: Comment deleted
        '400':
          description: Connection is not Facebook
        '404':
          description: Connection not found
      security: *ref_10
      summary: Delete a comment on a Facebook post
      tags: *ref_11
  /v1/connections/{id}/media/{mediaId}/comments:
    get:
      description: >-
        Returns top-level comments on the specified Instagram post or reel.
        Requires the `instagram_business_manage_comments` permission.
      operationId: ConnectionsController_getInstagramComments
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
        - name: mediaId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: Array of comments with author, text, timestamp, and like count
        '400':
          description: Connection is not Instagram
        '404':
          description: Connection not found
      security: *ref_10
      summary: List comments on an Instagram media object
      tags: *ref_11
    post:
      description: >-
        Adds a comment on the specified media object, or a reply when `mediaId`
        is a comment ID. Requires the `instagram_business_manage_comments`
        permission.
      operationId: ConnectionsController_replyToInstagramComment
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
        - name: mediaId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/InstagramCommentReplyDto'
      responses:
        '201':
          description: Comment created
          content:
            application/json:
              schema:
                properties:
                  id:
                    type: string
        '400':
          description: Connection is not Instagram
        '404':
          description: Connection not found
      security: *ref_10
      summary: Reply to an Instagram post or comment
      tags: *ref_11
  /v1/connections/{id}/media/{mediaId}/comments/{commentId}:
    patch:
      description: >-
        Sets the hidden state of the specified comment. Requires the
        `instagram_business_manage_comments` permission.
      operationId: ConnectionsController_hideInstagramComment
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
        - name: mediaId
          required: true
          in: path
          schema:
            type: string
        - name: commentId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/HideCommentDto'
      responses:
        '200':
          description: Comment hidden state updated
        '400':
          description: Connection is not Instagram
        '404':
          description: Connection not found
      security: *ref_10
      summary: Hide or unhide an Instagram comment
      tags: *ref_11
    delete:
      description: >-
        Permanently removes the specified comment. Requires the
        `instagram_business_manage_comments` permission.
      operationId: ConnectionsController_deleteInstagramComment
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
        - name: mediaId
          required: true
          in: path
          schema:
            type: string
        - name: commentId
          required: true
          in: path
          schema:
            type: string
      responses:
        '204':
          description: Comment deleted
        '400':
          description: Connection is not Instagram
        '404':
          description: Connection not found
      security: *ref_10
      summary: Delete an Instagram comment
      tags: *ref_11
  /v1/connections/{id}/media/{mediaId}/insights:
    get:
      description: >-
        Returns engagement metrics for the specified post or reel. Available
        metrics: `impressions`, `reach`, `likes`, `comments`, `saved`, `shares`,
        `plays`. Requires the `instagram_business_manage_insights` permission.
      operationId: ConnectionsController_getInstagramMediaInsights
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
        - name: mediaId
          required: true
          in: path
          schema:
            type: string
        - name: metrics
          required: false
          in: query
          description: 'Comma-separated list of metrics (default: all)'
          schema:
            type: string
      responses:
        '200':
          description: Media insights data
        '400':
          description: Connection is not Instagram
        '404':
          description: Connection not found
      security: *ref_10
      summary: Get insights for an Instagram media object
      tags: *ref_11
  /v1/connections/{id}/account-insights:
    get:
      description: >-
        Returns time-series analytics for the connected Instagram Business
        account. Available metrics: `impressions`, `reach`, `accounts_engaged`,
        `profile_views`, `follower_count`. Requires the
        `instagram_business_manage_insights` permission.
      operationId: ConnectionsController_getInstagramAccountInsights
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
        - name: metrics
          required: false
          in: query
          description: Comma-separated list of metrics
          schema:
            type: string
        - name: period
          required: false
          in: query
          description: 'Aggregation period (default: day)'
          schema:
            enum:
              - day
              - week
              - month
            type: string
        - name: since
          required: false
          in: query
          description: Start of date range (Unix timestamp or ISO date)
          schema:
            type: string
        - name: until
          required: false
          in: query
          description: End of date range (Unix timestamp or ISO date)
          schema:
            type: string
      responses:
        '200':
          description: Account insights data
        '400':
          description: Connection is not Instagram
        '404':
          description: Connection not found
      security: *ref_10
      summary: Get Instagram account-level insights
      tags: *ref_11
  /v1/connections/{id}/mentions:
    get:
      description: >-
        Returns posts where the connected Threads user was @mentioned. Requires
        the `threads_manage_mentions` scope.
      operationId: ConnectionsController_getThreadsMentions
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: Array of mention objects
          content:
            application/json:
              schema:
                properties:
                  data:
                    type: array
                    items:
                      properties:
                        id:
                          type: string
                        text:
                          type: string
                        timestamp:
                          type: string
                        media_type:
                          type: string
                        permalink:
                          type: string
                  paging:
                    type: object
        '400':
          description: Connection is not Threads
        '404':
          description: Connection not found
      security: *ref_10
      summary: List Threads mentions
      tags: *ref_11
  /v1/connections/{id}/mentions/{mentionId}/reply:
    post:
      description: >-
        Publishes a reply to the specified post or mention. Requires the
        `threads_manage_mentions` scope to reply to mentions.
      operationId: ConnectionsController_replyToThreadsMention
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
        - name: mentionId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ThreadsReplyDto'
      responses:
        '201':
          description: Reply published
          content:
            application/json:
              schema:
                properties:
                  postId:
                    type: string
                  postUrl:
                    type: string
        '400':
          description: Connection is not Threads
        '404':
          description: Connection not found
      security: *ref_10
      summary: Reply to a Threads mention or post
      tags: *ref_11
info:
  title: Posbly API
  description: >
    **Posbly** is an API-first social media broadcasting service for automation
    builders and AI agents.


    ## Authentication


    All endpoints (except key generation and OAuth flows) require a Bearer API
    key:

    ```

    Authorization: Bearer pk_your_api_key_here

    ```


    Generate a key with `POST /v1/auth/keys`.


    ## Platforms Supported


    - **X (Twitter)** — text + optional image/video

    - **Instagram** — image, Reel, or carousel (up to 10 items)

    - **Facebook Pages** — text, link, or photo post

    - **Pinterest** — Pin with image/video to a board

    - **Threads** — text, image, or video

    - **LinkedIn** — personal profiles and company pages

    - **Bluesky** — AT Protocol, uses App Password

    - **YouTube** — video uploads (first text line = title)

    - **TikTok** — video uploads via PULL_FROM_URL


    ## Connecting Social Accounts


    Use the OAuth flow to connect accounts. Redirect users (with their session
    token) to:

    ```

    GET /v1/oauth/x/connect?token=<session_token>

    GET /v1/oauth/meta/connect?token=<session_token>

    GET /v1/oauth/instagram/connect?token=<session_token>

    ```

    After authorization, the callback stores credentials and redirects to the
    dashboard.


    **Bluesky:** uses App Password instead of OAuth — POST directly to
    `/v1/oauth/bluesky/connect`.


    ## Quick Start


    ```bash

    # 1. List connected accounts to find account names

    curl https://api.posbly.com/v1/connections \
      -H "Authorization: Bearer pk_..."

    # 2. Broadcast to X and Instagram using account names from step 1

    curl -X POST https://api.posbly.com/v1/broadcast \
      -H "Authorization: Bearer pk_..." \
      -H "Content-Type: application/json" \
      -d '{
        "platforms": {
          "x": ["@myhandle"],
          "instagram": ["@mybrand"]
        },
        "content": {
          "text": "Hello from Posbly!",
          "media_url": "https://api.posbly.com/v1/media/user/img.jpg",
          "media_type": "image"
        }
      }'
    ```
  version: 1.0.0
  contact:
    name: Posbly
    url: https://posbly.com
    email: api@posbly.com
  license:
    name: MIT
    url: https://opensource.org/licenses/MIT
tags: []
servers:
  - url: http://localhost:8080
    description: Local Development
  - url: https://api.posbly.com
    description: Production
components:
  securitySchemes:
    bearer:
      scheme: bearer
      bearerFormat: JWT
      type: http
  schemas:
    CreateApiKeyDto:
      type: object
      properties:
        name:
          type: string
          example: Production Key
          description: Human-readable name for the key
        expiresAt:
          format: date-time
          type: string
          example: '2027-01-01T00:00:00Z'
          description: Optional expiry date (ISO 8601)
      required:
        - name
    BlueskyConnectDto:
      type: object
      properties:
        identifier:
          type: string
          description: Your Bluesky handle or email (e.g. user.bsky.social)
          example: user.bsky.social
        appPassword:
          type: string
          description: >-
            A Bluesky app password (not your main account password). Generate
            one at bsky.app → Settings → App Passwords.
          example: xxxx-xxxx-xxxx-xxxx
      required:
        - identifier
        - appPassword
    MastodonConnectDto:
      type: object
      properties:
        instance:
          type: string
          description: >-
            Your Mastodon instance hostname or URL (e.g. mastodon.social or
            https://mastodon.social)
          example: mastodon.social
        access_token:
          type: string
          description: >-
            A Mastodon access token with read:accounts, write:statuses, and
            write:media scopes. Generate one at your instance → Settings →
            Development → New Application.
          example: your_mastodon_access_token_here
      required:
        - instance
        - access_token
    UpsertUserDto:
      type: object
      properties:
        email:
          type: string
          example: user@example.com
        name:
          type: string
          example: Jane Doe
      required:
        - email
    UpdateTimezoneDto:
      type: object
      properties:
        timezone:
          type: string
          example: America/New_York
      required:
        - timezone
    BroadcastJobDto:
      type: object
      properties:
        job_id:
          type: string
        connection_id:
          type: string
          nullable: true
        platform:
          type: string
        status:
          type: string
        post_url:
          type: string
          nullable: true
        published_at:
          type: string
          nullable: true
        external_post_id:
          type: string
          nullable: true
        error:
          type: object
          nullable: true
        attempts:
          type: number
        completed_at:
          type: string
          nullable: true
      required:
        - job_id
        - platform
        - status
        - attempts
    BroadcastResponseDto:
      type: object
      properties:
        broadcast_id:
          type: string
          format: uuid
        status:
          type: string
          enum:
            - PENDING
            - PROCESSING
            - COMPLETED
            - PARTIAL
            - FAILED
        metadata:
          type: object
          nullable: true
          description: Arbitrary metadata echoed from request
        idempotent:
          type: boolean
          description: True when returning a cached result due to idempotency key
        scheduled_for:
          type: string
          nullable: true
          format: date-time
          description: UTC-normalised scheduled time
        schedule_at:
          type: string
          nullable: true
        created_at:
          type: string
          format: date-time
        jobs:
          description: >-
            Per-account job results. Each entry corresponds to one connection_id
            in the request.
          type: array
          items:
            $ref: '#/components/schemas/BroadcastJobDto'
      required:
        - broadcast_id
        - status
        - idempotent
        - created_at
        - jobs
    CreateWebhookDto:
      type: object
      properties:
        url:
          type: string
          example: https://example.com/hooks/posbly
          description: URL to deliver webhook events to
        events:
          type: array
          example:
            - JOB_COMPLETED
            - JOB_FAILED
          description: Events to subscribe to. Defaults to all events.
          items:
            type: string
            enum:
              - JOB_COMPLETED
              - JOB_FAILED
              - BROADCAST_COMPLETED
              - BROADCAST_FAILED
      required:
        - url
    SetSpendingLimitDto:
      type: object
      properties:
        limit_usd:
          type: number
          description: Monthly spending limit in USD (0 = no limit)
          example: 5
      required:
        - limit_usd
    CreateCheckoutDto:
      type: object
      properties:
        amount_dollars:
          type: number
          description: Amount in USD dollars (min $0.10)
          example: 10
      required:
        - amount_dollars
    CreateConnectionDto:
      type: object
      properties:
        platform:
          type: string
          enum:
            - X
            - INSTAGRAM
            - FACEBOOK
            - PINTEREST
            - THREADS
            - LINKEDIN
            - BLUESKY
            - YOUTUBE
            - TIKTOK
            - MASTODON
          example: X
          description: The social platform
        external_account_id:
          type: string
          example: '12345678'
          description: Your account/user/page ID on the platform
        account_name:
          type: string
          example: my_twitter_account
          description: Display name for the connection
        access_token:
          type: string
          example: oauth2_access_token_here
          description: Platform OAuth2 access token
        refresh_token:
          type: string
          description: OAuth2 refresh token (if available)
        token_expires_at:
          type: string
          example: '2026-01-01T00:00:00Z'
          description: Token expiry date (ISO 8601)
        scopes:
          type: array
          items:
            type: string
          example:
            - tweet.write
            - users.read
          description: OAuth2 scopes granted
      required:
        - platform
        - external_account_id
        - access_token
    UpdateConnectionDto:
      type: object
      properties:
        metadata:
          type: object
          example:
            board_id: '123456789'
          description: >-
            Key-value metadata to merge into the connection. For Pinterest, set
            board_id here.
      required:
        - metadata
    CreateBoardDto:
      type: object
      properties:
        name:
          type: string
          description: Board name (max 50 characters)
          example: My Inspiration Board
        description:
          type: string
          description: Optional board description (max 500 characters)
          example: A collection of inspiring ideas and designs
      required:
        - name
    CreateLinkedInEventDto:
      type: object
      properties:
        name:
          type: string
          description: Event title
          example: Q2 Product Launch
          maxLength: 200
        description:
          type: string
          description: Event description
          example: Join us for our quarterly product launch...
          maxLength: 5000
        startAt:
          type: string
          description: Event start date and time (ISO 8601)
          example: '2026-05-01T14:00:00Z'
        endAt:
          type: string
          description: Event end date and time (ISO 8601)
          example: '2026-05-01T16:00:00Z'
        eventType:
          type: string
          description: LinkedIn event type
          enum: &ref_12
            - PROFESSIONAL_EVENT
            - WEBINAR
            - VIRTUAL_EVENT
          default: PROFESSIONAL_EVENT
        timeZone:
          type: string
          description: IANA time zone identifier
          example: America/New_York
        onlineMeetingUrl:
          type: string
          description: Online meeting URL for virtual events
          example: https://zoom.us/j/123456789
        registrationUrl:
          type: string
          description: External registration URL
          example: https://eventbrite.com/e/...
      required:
        - name
        - startAt
    UpdateLinkedInEventDto:
      type: object
      properties:
        name:
          type: string
          description: Event title
          example: Q2 Product Launch
          maxLength: 200
        description:
          type: string
          description: Event description
          example: Join us for our quarterly product launch...
          maxLength: 5000
        startAt:
          type: string
          description: Event start date and time (ISO 8601)
          example: '2026-05-01T14:00:00Z'
        endAt:
          type: string
          description: Event end date and time (ISO 8601)
          example: '2026-05-01T16:00:00Z'
        eventType:
          type: string
          description: LinkedIn event type
          enum: *ref_12
          default: PROFESSIONAL_EVENT
        timeZone:
          type: string
          description: IANA time zone identifier
          example: America/New_York
        onlineMeetingUrl:
          type: string
          description: Online meeting URL for virtual events
          example: https://zoom.us/j/123456789
        registrationUrl:
          type: string
          description: External registration URL
          example: https://eventbrite.com/e/...
    ReplyCommentDto:
      type: object
      properties:
        message:
          type: string
          description: The comment text to publish on behalf of the page
          maxLength: 8000
      required:
        - message
    InstagramCommentReplyDto:
      type: object
      properties:
        message:
          type: string
          description: Comment or reply text
          maxLength: 2200
      required:
        - message
    HideCommentDto:
      type: object
      properties:
        hide:
          type: boolean
          description: true to hide the comment, false to unhide it
      required:
        - hide
    ThreadsReplyDto:
      type: object
      properties:
        text:
          type: string
          description: Reply text (max 500 characters)
          maxLength: 500
        media_url:
          type: string
          description: Optional media URL for the reply
        media_type:
          type: string
          description: Media type
          enum:
            - image
            - video
      required:
        - text
