{
  "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": [
          "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": [
          "Authentication"
        ]
      }
    },
    "/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": [
          "Authentication"
        ]
      }
    },
    "/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": [
          "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": [
          "OAuth"
        ]
      }
    },
    "/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": [
          "OAuth"
        ]
      }
    },
    "/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": [
          "OAuth"
        ]
      }
    },
    "/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": [
          "OAuth"
        ]
      }
    },
    "/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": [
          "OAuth"
        ]
      }
    },
    "/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": [
          "OAuth"
        ]
      }
    },
    "/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": [
          "OAuth"
        ]
      }
    },
    "/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": [
          "OAuth"
        ]
      }
    },
    "/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": [
          "OAuth"
        ]
      }
    },
    "/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": [
          "OAuth"
        ]
      }
    },
    "/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": [
          "OAuth"
        ]
      }
    },
    "/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": [
          "OAuth"
        ]
      }
    },
    "/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": [
          "OAuth"
        ]
      }
    },
    "/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": [
          "OAuth"
        ]
      }
    },
    "/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": [
          "OAuth"
        ]
      }
    },
    "/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": [
          "OAuth"
        ]
      }
    },
    "/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": [
          "OAuth"
        ]
      }
    },
    "/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": [
          "OAuth"
        ]
      }
    },
    "/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": [
          "OAuth"
        ]
      }
    },
    "/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": [
          "OAuth"
        ]
      }
    },
    "/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": [
          "OAuth"
        ]
      }
    },
    "/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": [
          "OAuth"
        ]
      }
    },
    "/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": [
          "OAuth"
        ]
      }
    },
    "/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": [
          "OAuth"
        ]
      }
    },
    "/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": [
          "OAuth"
        ]
      }
    },
    "/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": [
          "OAuth"
        ]
      }
    },
    "/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": [
          "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": [
          "Users"
        ]
      }
    },
    "/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": [
          "Users"
        ]
      }
    },
    "/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": [
          "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": [
          "Media"
        ]
      }
    },
    "/v1/broadcast": {
      "post": {
        "description": "Dispatches posts to one or more platforms asynchronously. Each platform is an independent job. Returns immediately with job status.\n\nSupports two content modes:\n\n**JSON mode** (`Content-Type: application/json`) — pass `media_url` as a public URL (e.g. from `POST /v1/media`):\n```json\n{\n  \"platforms\": { \"instagram\": [\"@brand\"] },\n  \"content\": { \"text\": \"Hello!\", \"media_url\": \"https://...\", \"media_type\": \"image\" }\n}\n```\n\n**Multipart mode** (`Content-Type: multipart/form-data`) — attach files directly and let the API upload them:\n- `data` field: the full JSON payload (same structure as JSON mode, without `media_url` / `carousel_items`)\n- `media` field: a single image or video file\n- `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": {
              "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": {
              "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"
                    ]
                  }
                ]
              }
            }
          }
        },
        "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": [
          {
            "bearer": []
          }
        ],
        "summary": "Create a broadcast",
        "tags": [
          "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": [
          {
            "bearer": []
          }
        ],
        "summary": "List broadcasts",
        "tags": [
          "Broadcast"
        ]
      }
    },
    "/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": [
          {
            "bearer": []
          }
        ],
        "summary": "Get broadcast status",
        "tags": [
          "Broadcast"
        ]
      },
      "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": [
          {
            "bearer": []
          }
        ],
        "summary": "Cancel and delete a scheduled broadcast",
        "tags": [
          "Broadcast"
        ]
      }
    },
    "/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": [
          {
            "bearer": []
          }
        ],
        "summary": "Get post insights for a completed job",
        "tags": [
          "Broadcast"
        ]
      }
    },
    "/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": [
          {
            "bearer": []
          }
        ],
        "summary": "Get individual job detail",
        "tags": [
          "Broadcast"
        ]
      }
    },
    "/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": [
          {
            "bearer": []
          }
        ],
        "summary": "Register a webhook",
        "tags": [
          "Webhooks"
        ]
      },
      "get": {
        "operationId": "WebhooksController_list",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Array of webhooks (no secrets)"
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "List registered webhooks",
        "tags": [
          "Webhooks"
        ]
      }
    },
    "/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": [
          {
            "bearer": []
          }
        ],
        "summary": "Delete a webhook",
        "tags": [
          "Webhooks"
        ]
      }
    },
    "/v1/billing/balance": {
      "get": {
        "operationId": "BillingController_getBalance",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ],
        "summary": "Get current credit balance",
        "tags": [
          "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": [
          "billing"
        ]
      }
    },
    "/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": [
          "billing"
        ]
      }
    },
    "/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": [
          "billing"
        ]
      }
    },
    "/v1/billing/spending-limit": {
      "get": {
        "operationId": "BillingController_getSpendingLimit",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ],
        "summary": "Get spending limit and monthly usage",
        "tags": [
          "billing"
        ]
      },
      "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": [
          "billing"
        ]
      }
    },
    "/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": [
          "billing"
        ]
      }
    },
    "/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": [
          "billing"
        ]
      }
    },
    "/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": [
          "billing"
        ]
      }
    },
    "/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": [
          {
            "bearer": []
          }
        ],
        "summary": "Register a platform connection",
        "tags": [
          "Connections"
        ]
      },
      "get": {
        "operationId": "ConnectionsController_list",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Array of connections (no tokens in response)"
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "List platform connections",
        "tags": [
          "Connections"
        ]
      }
    },
    "/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": [
          {
            "bearer": []
          }
        ],
        "summary": "Update connection metadata",
        "tags": [
          "Connections"
        ]
      },
      "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": [
          {
            "bearer": []
          }
        ],
        "summary": "Remove a platform connection",
        "tags": [
          "Connections"
        ]
      }
    },
    "/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": [
          {
            "bearer": []
          }
        ],
        "summary": "List Pinterest boards",
        "tags": [
          "Connections"
        ]
      },
      "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": [
          {
            "bearer": []
          }
        ],
        "summary": "Create a Pinterest board",
        "tags": [
          "Connections"
        ]
      }
    },
    "/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": [
          {
            "bearer": []
          }
        ],
        "summary": "Delete a Pinterest board",
        "tags": [
          "Connections"
        ]
      }
    },
    "/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": [
          {
            "bearer": []
          }
        ],
        "summary": "List Pinterest pins",
        "tags": [
          "Connections"
        ]
      }
    },
    "/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": [
          {
            "bearer": []
          }
        ],
        "summary": "Get Pinterest pin analytics",
        "tags": [
          "Connections"
        ]
      }
    },
    "/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": [
          {
            "bearer": []
          }
        ],
        "summary": "Get Pinterest account analytics",
        "tags": [
          "Connections"
        ]
      }
    },
    "/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": [
          {
            "bearer": []
          }
        ],
        "summary": "List Pinterest catalogs",
        "tags": [
          "Connections"
        ]
      }
    },
    "/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": [
          {
            "bearer": []
          }
        ],
        "summary": "List Pinterest ad accounts",
        "tags": [
          "Connections"
        ]
      }
    },
    "/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": [
          {
            "bearer": []
          }
        ],
        "summary": "Create a LinkedIn event",
        "tags": [
          "Connections"
        ]
      },
      "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": [
          {
            "bearer": []
          }
        ],
        "summary": "List LinkedIn events",
        "tags": [
          "Connections"
        ]
      }
    },
    "/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": [
          {
            "bearer": []
          }
        ],
        "summary": "Get a LinkedIn event",
        "tags": [
          "Connections"
        ]
      },
      "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": [
          {
            "bearer": []
          }
        ],
        "summary": "Update a LinkedIn event",
        "tags": [
          "Connections"
        ]
      },
      "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": [
          {
            "bearer": []
          }
        ],
        "summary": "Delete a LinkedIn event",
        "tags": [
          "Connections"
        ]
      }
    },
    "/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": [
          {
            "bearer": []
          }
        ],
        "summary": "Get TikTok account profile and stats",
        "tags": [
          "Connections"
        ]
      }
    },
    "/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": [
          {
            "bearer": []
          }
        ],
        "summary": "List TikTok published videos",
        "tags": [
          "Connections"
        ]
      }
    },
    "/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": [
          {
            "bearer": []
          }
        ],
        "summary": "Get Facebook page insights",
        "tags": [
          "Connections"
        ]
      }
    },
    "/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": [
          {
            "bearer": []
          }
        ],
        "summary": "List recent Facebook page posts",
        "tags": [
          "Connections"
        ]
      }
    },
    "/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": [
          {
            "bearer": []
          }
        ],
        "summary": "List comments on a Facebook post",
        "tags": [
          "Connections"
        ]
      },
      "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": [
          {
            "bearer": []
          }
        ],
        "summary": "Reply to a Facebook post",
        "tags": [
          "Connections"
        ]
      }
    },
    "/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": [
          {
            "bearer": []
          }
        ],
        "summary": "Delete a comment on a Facebook post",
        "tags": [
          "Connections"
        ]
      }
    },
    "/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": [
          {
            "bearer": []
          }
        ],
        "summary": "List comments on an Instagram media object",
        "tags": [
          "Connections"
        ]
      },
      "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": [
          {
            "bearer": []
          }
        ],
        "summary": "Reply to an Instagram post or comment",
        "tags": [
          "Connections"
        ]
      }
    },
    "/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": [
          {
            "bearer": []
          }
        ],
        "summary": "Hide or unhide an Instagram comment",
        "tags": [
          "Connections"
        ]
      },
      "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": [
          {
            "bearer": []
          }
        ],
        "summary": "Delete an Instagram comment",
        "tags": [
          "Connections"
        ]
      }
    },
    "/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": [
          {
            "bearer": []
          }
        ],
        "summary": "Get insights for an Instagram media object",
        "tags": [
          "Connections"
        ]
      }
    },
    "/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": [
          {
            "bearer": []
          }
        ],
        "summary": "Get Instagram account-level insights",
        "tags": [
          "Connections"
        ]
      }
    },
    "/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": [
          {
            "bearer": []
          }
        ],
        "summary": "List Threads mentions",
        "tags": [
          "Connections"
        ]
      }
    },
    "/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": [
          {
            "bearer": []
          }
        ],
        "summary": "Reply to a Threads mention or post",
        "tags": [
          "Connections"
        ]
      }
    }
  },
  "info": {
    "title": "Posbly API",
    "description": "**Posbly** is an API-first social media broadcasting service for automation builders and AI agents.\n\n## Authentication\n\nAll endpoints (except key generation and OAuth flows) require a Bearer API key:\n```\nAuthorization: Bearer pk_your_api_key_here\n```\n\nGenerate a key with `POST /v1/auth/keys`.\n\n## Platforms Supported\n\n- **X (Twitter)** — text + optional image/video\n- **Instagram** — image, Reel, or carousel (up to 10 items)\n- **Facebook Pages** — text, link, or photo post\n- **Pinterest** — Pin with image/video to a board\n- **Threads** — text, image, or video\n- **LinkedIn** — personal profiles and company pages\n- **Bluesky** — AT Protocol, uses App Password\n- **YouTube** — video uploads (first text line = title)\n- **TikTok** — video uploads via PULL_FROM_URL\n\n## Connecting Social Accounts\n\nUse the OAuth flow to connect accounts. Redirect users (with their session token) to:\n```\nGET /v1/oauth/x/connect?token=<session_token>\nGET /v1/oauth/meta/connect?token=<session_token>\nGET /v1/oauth/instagram/connect?token=<session_token>\n```\nAfter authorization, the callback stores credentials and redirects to the dashboard.\n\n**Bluesky:** uses App Password instead of OAuth — POST directly to `/v1/oauth/bluesky/connect`.\n\n## Quick Start\n\n```bash\n# 1. List connected accounts to find account names\ncurl https://api.posbly.com/v1/connections \\\n  -H \"Authorization: Bearer pk_...\"\n\n# 2. Broadcast to X and Instagram using account names from step 1\ncurl -X POST https://api.posbly.com/v1/broadcast \\\n  -H \"Authorization: Bearer pk_...\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"platforms\": {\n      \"x\": [\"@myhandle\"],\n      \"instagram\": [\"@mybrand\"]\n    },\n    \"content\": {\n      \"text\": \"Hello from Posbly!\",\n      \"media_url\": \"https://api.posbly.com/v1/media/user/img.jpg\",\n      \"media_type\": \"image\"\n    }\n  }'\n```\n",
    "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": [
              "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": [
              "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/..."
          }
        }
      },
      "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"
        ]
      }
    }
  }
}