{
  "openapi": "3.0.3",
  "info": {
    "title": "Publora API",
    "description": "Affordable REST API for scheduling and publishing social media posts\nacross X/Twitter, LinkedIn, Instagram, Threads, TikTok, YouTube,\nFacebook, Bluesky, Mastodon, and Telegram.\n\nAll plans include full API access. Starting at $5.40/month (yearly) or $9/month.\n14-day free trial, no credit card needed.\n\n## Workspace API\nThe Workspace API allows you to manage multiple users under a single account.\n**To enable Workspace access, please contact Publora support at serge@publora.com.**\n",
    "version": "1.0.0",
    "contact": {
      "email": "serge@publora.com",
      "url": "https://publora.com"
    }
  },
  "servers": [
    {
      "url": "https://api.publora.com/api/v1",
      "description": "Production"
    }
  ],
  "security": [
    {
      "ApiKeyAuth": [

      ]
    }
  ],
  "tags": [
    {
      "name": "Posts",
      "description": "Create, update, delete, and list posts"
    },
    {
      "name": "Connections",
      "description": "Manage platform connections"
    },
    {
      "name": "Media",
      "description": "Upload media files"
    },
    {
      "name": "LinkedIn Analytics",
      "description": "LinkedIn-specific analytics and interactions"
    },
    {
      "name": "Webhooks",
      "description": "Manage webhook endpoints for real-time notifications"
    },
    {
      "name": "Workspace",
      "description": "Manage multiple users (requires Workspace access - contact support)"
    },
    {
      "name": "Logs",
      "description": "Post logs and debugging"
    }
  ],
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "x-publora-key",
        "description": "API key from Settings > API Keys. Format: sk_timestamp.hexstring"
      }
    },
    "parameters": {
      "XPubloraClient": {
        "name": "x-publora-client",
        "in": "header",
        "required": false,
        "description": "Optional client identifier accepted by every API-key-authenticated operation. Any non-empty value is preserved; `api` is used when absent. Setting `mcp` triggers the MCP access entitlement check.",
        "schema": {
          "type": "string"
        }
      }
    },
    "schemas": {
      "YouTubePlaylistReference": {
        "type": "object",
        "nullable": true,
        "additionalProperties": false,
        "default": {
          "id": "",
          "platformId": ""
        },
        "description": "Reference to a YouTube playlist for platformSettings.youtube.playlist. Send empty strings for both fields to clear the playlist.",
        "properties": {
          "id": {
            "type": "string",
            "nullable": true,
            "default": "",
            "description": "YouTube playlist id",
            "example": "PLxxxxxxxx"
          },
          "platformId": {
            "type": "string",
            "nullable": true,
            "default": "",
            "description": "Compound YouTube platform id from platforms; must match the single selected YouTube channel",
            "example": "youtube-UCxxxxxxxx"
          }
        }
      },
      "YouTubeThumbnailReference": {
        "type": "object",
        "nullable": true,
        "additionalProperties": false,
        "description": "Reference to a Publora-tracked custom thumbnail for platformSettings.youtube.thumbnail. Send an empty url to clear the thumbnail. Set via update-post only (the upload requires a postGroupId).",
        "properties": {
          "mediaId": {
            "type": "string",
            "nullable": true,
            "description": "Publora media id returned by the YouTube thumbnail upload endpoint",
            "example": "665f..."
          },
          "id": {
            "type": "string",
            "nullable": true,
            "description": "REST alias for mediaId"
          },
          "url": {
            "type": "string",
            "nullable": true,
            "description": "Publora-hosted thumbnail URL returned by the upload endpoint",
            "example": "https://media.publora.com/..."
          },
          "path": {
            "type": "string",
            "nullable": true,
            "description": "REST alias for url"
          }
        }
      },
      "PlatformSettingsInput": {
        "type": "object",
        "additionalProperties": false,
        "description": "Per-platform settings for tiktok, instagram, youtube, threads, telegram, and linkedin. Unknown platform or nested paths are rejected with 400 PLATFORM_SETTING_UNKNOWN. Merged with defaults on create-post and with existing settings on update-post (omitted fields are preserved).",
        "properties": {
          "instagram": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "videoType": {
                "type": "string",
                "enum": [
                  "REELS",
                  "STORIES"
                ],
                "default": "REELS",
                "description": "How videos are published"
              },
              "coverUrl": {
                "type": "string",
                "description": "Custom Reels cover image (alias: cover_url). Publicly accessible http(s) URL to a JPEG — Instagram fetches it server-side at publish time. Empty string clears the custom cover. Invalid URLs are rejected with 400. Ignored for Stories and image posts.",
                "example": "https://cdn.example.com/covers/reel-cover.jpg"
              },
              "cover_url": {
                "type": "string",
                "description": "REST alias for coverUrl; normalized to coverUrl before storage",
                "example": "https://cdn.example.com/covers/reel-cover.jpg"
              },
              "shareToFeed": {
                "type": "boolean",
                "description": "For Reels, false prevents sharing the Reel to the main feed"
              }
            }
          },
          "tiktok": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "viewerSetting": {
                "type": "string",
                "enum": [
                  "PUBLIC_TO_EVERYONE",
                  "MUTUAL_FOLLOW_FRIENDS",
                  "FOLLOWER_OF_CREATOR",
                  "SELF_ONLY"
                ],
                "default": "PUBLIC_TO_EVERYONE"
              },
              "allowComments": {
                "type": "boolean",
                "default": true
              },
              "allowDuet": {
                "type": "boolean",
                "default": false
              },
              "allowStitch": {
                "type": "boolean",
                "default": false
              },
              "commercialContent": {
                "type": "boolean",
                "default": false
              },
              "brandOrganic": {
                "type": "boolean",
                "default": false
              },
              "brandedContent": {
                "type": "boolean",
                "default": false
              }
            }
          },
          "youtube": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "privacy": {
                "type": "string",
                "enum": [
                  "private",
                  "public",
                  "unlisted"
                ],
                "default": "public"
              },
              "title": {
                "type": "string",
                "default": ""
              },
              "madeForKids": {
                "type": "boolean",
                "default": false
              },
              "tags": {
                "description": "Video tags. REST accepts an array or a comma-separated string and caps the normalized combined tag budget at 500 characters.",
                "oneOf": [
                  {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  {
                    "type": "string"
                  }
                ]
              },
              "categoryId": {
                "type": "string",
                "description": "Optional YouTube snippet.categoryId"
              },
              "playlist": {
                "$ref": "#/components/schemas/YouTubePlaylistReference"
              },
              "thumbnail": {
                "$ref": "#/components/schemas/YouTubeThumbnailReference"
              }
            }
          },
          "threads": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "replyControl": {
                "type": "string",
                "enum": [
                  "everyone",
                  "accounts_you_follow",
                  "mentioned_only",
                  ""
                ],
                "default": ""
              }
            }
          },
          "telegram": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "disableNotification": {
                "type": "boolean",
                "default": false
              },
              "disableWebPagePreview": {
                "type": "boolean",
                "default": false
              },
              "protectContent": {
                "type": "boolean",
                "default": false
              }
            }
          },
          "linkedin": {
            "type": "object",
            "additionalProperties": false,
            "description": "LinkedIn repost (reshare) settings. When a repost is enabled, the group's content becomes the reshare commentary (3,000-char limit) and media is forbidden — scheduling a repost group with media fails validation with code MEDIA_TYPE_NOT_SUPPORTED.",
            "properties": {
              "repostEnabled": {
                "type": "boolean",
                "default": false
              },
              "repostParentUrn": {
                "type": "string",
                "default": "",
                "description": "Canonical urn:li:share:* or urn:li:ugcPost:* parent. Required when repostEnabled is true; an empty value means a normal post."
              },
              "repostVisibility": {
                "type": "string",
                "enum": [
                  "PUBLIC",
                  "CONNECTIONS",
                  ""
                ],
                "default": "",
                "description": "Repost visibility. CONNECTIONS is personal-profile-only; company-page reposts must use PUBLIC."
              }
            }
          }
        }
      },
      "PlatformConnection": {
        "type": "object",
        "properties": {
          "platformId": {
            "type": "string",
            "description": "Unique ID in format platform-id (e.g., twitter-123456789)",
            "example": "twitter-123456789"
          },
          "username": {
            "type": "string",
            "example": "@yourhandle"
          },
          "displayName": {
            "type": "string",
            "example": "Your Name"
          },
          "profileImageUrl": {
            "type": "string",
            "format": "uri",
            "example": "https://pbs.twimg.com/profile_images/..."
          },
          "profileUrl": {
            "type": "string",
            "format": "uri",
            "nullable": true,
            "description": "URL to the user's profile on the platform. Can be null if not available for the platform.",
            "example": "https://twitter.com/yourhandle"
          },
          "accessTokenExpiresAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "example": "2026-05-15T10:30:00.000Z"
          },
          "tokenStatus": {
            "type": "string",
            "enum": [
              "valid",
              "expiring_soon",
              "expired",
              "unknown"
            ],
            "description": "Current status of the access token",
            "example": "valid"
          },
          "tokenExpiresIn": {
            "type": "string",
            "nullable": true,
            "description": "Human-readable time until token expiration",
            "example": "29 days"
          },
          "lastSuccessfulPost": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Timestamp of last successful post"
          },
          "lastError": {
            "$ref": "#/components/schemas/ConnectionLastError"
          }
        }
      },
      "ConnectionLastError": {
        "type": "object",
        "nullable": true,
        "description": "Last connection error, or null",
        "properties": {
          "message": {
            "type": "string"
          },
          "occurredAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "LinkedInUrnTranslation": {
        "type": "object",
        "description": "Present only when LinkedIn translated an activity URN before processing the reaction",
        "required": [
          "from",
          "to"
        ],
        "properties": {
          "from": {
            "type": "string"
          },
          "to": {
            "type": "string"
          }
        }
      },
      "ManagedUser": {
        "type": "object",
        "properties": {
          "_id": {
            "type": "string",
            "description": "User ID",
            "example": "507f1f77bcf86cd799439011"
          },
          "username": {
            "type": "string",
            "description": "User email",
            "example": "user@example.com"
          },
          "displayName": {
            "type": "string",
            "example": "John Doe"
          },
          "role": {
            "type": "string",
            "enum": [
              "managed"
            ],
            "example": "managed"
          },
          "dailyPostsLeft": {
            "type": "integer",
            "example": 100
          },
          "connectionsPageUrl": {
            "type": "string",
            "format": "uri",
            "nullable": true,
            "description": "URL for user to connect their social accounts"
          },
          "connectionsPageTokenExpiresAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        }
      },
      "Webhook": {
        "type": "object",
        "properties": {
          "_id": {
            "type": "string",
            "example": "507f1f77bcf86cd799439011"
          },
          "name": {
            "type": "string",
            "example": "My Webhook"
          },
          "url": {
            "type": "string",
            "format": "uri",
            "example": "https://example.com/webhook"
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "post.scheduled",
                "post.published",
                "post.failed",
                "post.demoted",
                "token.expiring"
              ],
              "description": "token.expiring is defined and subscribable but is not currently dispatched; do not depend on it"
            },
            "example": [
              "post.published",
              "post.failed"
            ]
          },
          "isActive": {
            "type": "boolean",
            "example": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "PostLog": {
        "type": "object",
        "properties": {
          "_id": {
            "type": "string"
          },
          "postGroupId": {
            "type": "string"
          },
          "platform": {
            "type": "string"
          },
          "postId": {
            "type": "string"
          },
          "event": {
            "type": "string",
            "enum": [
              "scheduled",
              "processing",
              "publish_attempted",
              "publish_succeeded",
              "publish_failed",
              "publish_retry",
              "status_changed",
              "media_finalize_succeeded",
              "media_finalize_failed",
              "linkedin_document_upload_succeeded",
              "linkedin_document_upload_failed"
            ],
            "description": "Stable PostLog event type"
          },
          "error": {
            "type": "string",
            "description": "Optional; omitted when no error was stored"
          },
          "httpStatus": {
            "type": "integer",
            "nullable": true,
            "description": "Optional; may be null or omitted when not applicable"
          },
          "retryCount": {
            "type": "integer",
            "description": "Optional; omitted when no value was stored"
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Post": {
        "type": "object",
        "properties": {
          "platform": {
            "type": "string",
            "enum": [
              "twitter",
              "linkedin",
              "instagram",
              "threads",
              "tiktok",
              "youtube",
              "facebook",
              "bluesky",
              "mastodon",
              "telegram",
              "pinterest"
            ],
            "example": "twitter"
          },
          "platformId": {
            "type": "string",
            "nullable": true,
            "description": "Platform connection ID. Always present; null if the post has no connection resolved.",
            "example": "123456789"
          },
          "content": {
            "type": "string",
            "example": "Hello from Publora API!"
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "scheduled",
              "pending",
              "processing",
              "published",
              "failed"
            ],
            "example": "published"
          },
          "postedId": {
            "type": "string",
            "nullable": true,
            "description": "Platform-specific post ID. Always present; null until the post is published.",
            "example": "1234567890123456789"
          },
          "permalink": {
            "type": "string",
            "nullable": true,
            "description": "Public URL of the published post. Always present; null until the post\nis published, and null for platforms that expose no permalink.\n",
            "example": "https://x.com/publora/status/1234567890123456789"
          }
        }
      },
      "Warning": {
        "type": "object",
        "description": "A non-fatal notice about a request that still succeeded. The `warnings`\narray is only present when at least one warning applies — the key is\nomitted, not empty, otherwise.\n\nTreat `code` as the contract: `message` is human-readable and may change\nat any time. Which fields accompany `code` depends on the code itself.\n",
        "properties": {
          "code": {
            "type": "string",
            "enum": [
              "SCHEDULED_TIME_COERCED",
              "MEDIA_VALIDATION_PENDING"
            ],
            "description": "- `SCHEDULED_TIME_COERCED` — the requested `scheduledTime` was in the\n  past and was clamped to server time. Carries `requested` and\n  `effective`.\n- `MEDIA_VALIDATION_PENDING` — /update-post only. Media validation had\n  not finished, but the post was scheduled anyway; the media is\n  re-checked before publishing. Carries `mediaFileId`, `mediaStatus`,\n  `pendingCode`, and `attempts`. Accompanied by\n  `mediaValidationStatus: \"pending\"` on the response.\n",
            "example": "SCHEDULED_TIME_COERCED"
          },
          "message": {
            "type": "string",
            "description": "Human-readable explanation. Do not match on this string — match on code.",
            "example": "Requested scheduled time 2026-03-01T14:00:00.000Z was in the past and was changed to server time 2026-03-01T14:02:11.412Z."
          },
          "requested": {
            "type": "string",
            "format": "date-time",
            "description": "SCHEDULED_TIME_COERCED only. The scheduledTime you sent.",
            "example": "2026-03-01T14:00:00.000Z"
          },
          "effective": {
            "type": "string",
            "format": "date-time",
            "description": "SCHEDULED_TIME_COERCED only. The scheduledTime actually stored.",
            "example": "2026-03-01T14:02:11.412Z"
          },
          "mediaFileId": {
            "type": "string",
            "nullable": true,
            "description": "MEDIA_VALIDATION_PENDING only. The media file still being validated.",
            "example": "507f1f77bcf86cd799439012"
          },
          "mediaStatus": {
            "type": "string",
            "nullable": true,
            "description": "MEDIA_VALIDATION_PENDING only. Status of that media file when the response was sent.",
            "example": "uploading"
          },
          "pendingCode": {
            "type": "string",
            "nullable": true,
            "description": "MEDIA_VALIDATION_PENDING only. The last transient probe result.",
            "example": "MEDIA_VALIDATION_PENDING"
          },
          "attempts": {
            "type": "integer",
            "description": "MEDIA_VALIDATION_PENDING only. How many validation probes ran before responding.",
            "example": 3
          }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "description": "Human-readable message. Do not match on this string — match on code where present.",
            "example": "Invalid API key"
          },
          "code": {
            "type": "string",
            "description": "Stable machine-readable error code. Present on the newer error paths\n(scheduling, platformSettings validation, idempotency); older errors\nreturn `error` only. Always prefer this over the `error` text.\n",
            "example": "SCHEDULED_TIME_IN_PAST"
          },
          "field": {
            "type": "string",
            "description": "`PLATFORM_SETTING_UNKNOWN` only. The exact dotted path of the rejected\nkey, relative to the `platformSettings` object (no `platformSettings.`\nprefix).\n",
            "example": "youtube.thumbnail.typo"
          },
          "serverTime": {
            "type": "string",
            "format": "date-time",
            "description": "`SCHEDULED_TIME_IN_PAST` only. Current server time (UTC) when the\nrequest was rejected — compare against your clock to diagnose skew.\n",
            "example": "2026-03-01T14:02:11.412Z"
          }
        }
      },
      "MediaUrlRateLimitError": {
        "type": "object",
        "required": [
          "error",
          "code",
          "retryAfterSec"
        ],
        "properties": {
          "error": {
            "type": "string",
            "example": "Media URL ingestion rate limit reached (60 URLs/hour). Retry later or use the presigned get-upload-url flow."
          },
          "code": {
            "type": "string",
            "enum": [
              "MEDIA_URL_RATE_LIMITED"
            ]
          },
          "retryAfterSec": {
            "type": "integer",
            "description": "Seconds until this fixed-window limit may be retried",
            "example": 60
          }
        }
      }
    }
  },
  "paths": {
    "/platform-connections": {
      "parameters": [
        {
          "$ref": "#/components/parameters/XPubloraClient"
        }
      ],
      "get": {
        "summary": "List connected social accounts",
        "description": "Returns all social media accounts connected to your Publora account.",
        "operationId": "getPlatformConnections",
        "tags": [
          "Connections"
        ],
        "parameters": [
          {
            "name": "x-publora-user-id",
            "in": "header",
            "required": false,
            "description": "Managed user ID (workspace only)",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of connected accounts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "connections": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PlatformConnection"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Failed to fetch platform connections"
                }
              }
            }
          }
        }
      }
    },
    "/platform-limits": {
      "parameters": [
        {
          "$ref": "#/components/parameters/XPubloraClient"
        }
      ],
      "get": {
        "summary": "Get live per-platform posting limits",
        "description": "Return Publora's per-platform limits (characters, images, videos,\ndocuments, GIFs, thumbnails, and requirements) as live JSON, sourced\nfrom the shared @publora/platform-limits package. These are the\nAPI-specific limits Publora validates against before scheduling — use\nthem to drive client-side validation instead of hard-coding values.\nNo parameters.\n",
        "operationId": "getPlatformLimits",
        "tags": [
          "Posts"
        ],
        "responses": {
          "200": {
            "description": "Per-platform limits",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "schemaVersion": {
                      "type": "integer",
                      "example": 1
                    },
                    "source": {
                      "type": "string",
                      "example": "@publora/platform-limits"
                    },
                    "packageVersion": {
                      "type": "string",
                      "example": "1.0.0"
                    },
                    "limitsLastUpdated": {
                      "type": "string",
                      "example": "2026-03-11"
                    },
                    "supportedPlatforms": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "example": [
                        "twitter",
                        "instagram",
                        "threads",
                        "tiktok",
                        "linkedin",
                        "youtube",
                        "facebook",
                        "mastodon",
                        "bluesky",
                        "telegram",
                        "pinterest"
                      ]
                    },
                    "platforms": {
                      "type": "object",
                      "description": "Keyed by platform. Each value carries platform, displayName, characters, images, videos, requirements, plus documents, gifs, and thumbnails (null when the platform has no distinct spec).",
                      "additionalProperties": {
                        "type": "object"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/create-post": {
      "parameters": [
        {
          "$ref": "#/components/parameters/XPubloraClient"
        }
      ],
      "post": {
        "summary": "Create and schedule a social media post",
        "description": "Create a post to be published across one or more social media platforms.\nSupports text, images, and video (1 video per post).\nThe image count per post is platform-specific: Twitter/X, Bluesky, and\nMastodon allow up to 4; Instagram, LinkedIn, Facebook, and Telegram\nallow up to 10; Threads allows up to 20. There is no separate\n\"carousel\" field — a\ncarousel is formed implicitly by attaching multiple images to one\npostGroupId (Instagram: 2-10 images = carousel, 1 image = single photo),\nin upload order. See the Media Uploads guide for the full matrix.\nIf scheduledTime is provided, the post will be published at that time.\nIf omitted, the post is saved as a draft.\n",
        "operationId": "createPost",
        "tags": [
          "Posts"
        ],
        "parameters": [
          {
            "name": "x-publora-user-id",
            "in": "header",
            "required": false,
            "description": "Managed user ID (workspace only)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Opt-in idempotency. Omit the header for the previous behaviour —\nevery call creates a new post.\n\nSend a unique value (a UUID is a good choice) per logical operation\nto make retries safe:\n\n- Same key + identical body, original request finished → the original\n  status code and response body are replayed. No second post.\n- Same key + identical body, original request still in flight → `409`\n  (`IDEMPOTENCY_IN_FLIGHT`). Retry shortly.\n- Same key + different body → `422` (`IDEMPOTENCY_KEY_CONFLICT`).\n  Nothing is created.\n\nKeys are scoped to the acting user (the managed user when\n`x-publora-user-id` is set), so they never collide across accounts.\nRecords expire 24 hours after they are created; reusing a key after\nthat window is treated as a brand-new request.\n",
            "schema": {
              "type": "string"
            },
            "example": "6f1e2d3c-4b5a-4c7d-8e9f-0a1b2c3d4e5f"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "platforms"
                ],
                "properties": {
                  "content": {
                    "type": "string",
                    "description": "Normally required and non-empty. May be omitted or empty only when a targeted LinkedIn connection has repost intent via platformSettings.linkedin.repostEnabled=true or a non-empty repostParentUrn; repost validation still requires a valid parent/setting combination.",
                    "example": "Excited to share our new product launch! 🚀 #launch"
                  },
                  "platforms": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Array of platform connection IDs (format: platform-platformId)",
                    "example": [
                      "twitter-123456789",
                      "linkedin-ABC123"
                    ]
                  },
                  "scheduledTime": {
                    "type": "string",
                    "format": "date-time",
                    "description": "ISO 8601 UTC datetime for scheduling. Omit for draft.\n\nIf the time is in the past, it is clamped to the current server\ntime and a `SCHEDULED_TIME_COERCED` warning is returned — the\npost is still created. Read the `scheduledTime` in the response\nfor the time actually stored.\n\nTimes less than 5 minutes in the past are always tolerated this\nway. Strict rejection for a time 5 or more minutes in the past\nis scheduled to begin on **2026-08-25**, unless production\nconfiguration overrides that date. Before strict mode it is\nclamped with a warning. Send a future time to avoid both.\n",
                    "example": "2027-03-01T14:00:00.000Z"
                  },
                  "platformSettings": {
                    "$ref": "#/components/schemas/PlatformSettingsInput"
                  },
                  "mediaUrls": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 10,
                    "items": {
                      "type": "string",
                      "format": "uri"
                    },
                    "description": "Public https URLs downloaded server-side. Images: 25 MB each; videos: 150 MB each; aggregate: 300 MB. Ingestion is all-or-nothing."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Post created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "postGroupId": {
                      "type": "string",
                      "example": "507f1f77bcf86cd799439011"
                    },
                    "scheduledTime": {
                      "type": "string",
                      "format": "date-time",
                      "nullable": true,
                      "description": "The effective scheduled time that was actually stored. Always\npresent. `null` when the post was saved as a draft (no\nscheduledTime sent). This may differ from the value you sent\n— if so, a `SCHEDULED_TIME_COERCED` warning explains why.\nTrust this field over your requested value.\n",
                      "example": "2026-03-01T14:00:00.000Z"
                    },
                    "warnings": {
                      "type": "array",
                      "description": "Non-fatal notices about a request that still succeeded. Only\npresent when at least one warning applies.\n",
                      "items": {
                        "$ref": "#/components/schemas/Warning"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "missingContent": {
                    "value": {
                      "error": "Content is required"
                    }
                  },
                  "invalidTime": {
                    "value": {
                      "error": "Invalid scheduled time format"
                    }
                  },
                  "pastTime": {
                    "summary": "scheduledTime 5+ minutes in the past (strict mode)",
                    "value": {
                      "error": "Scheduled time is in the past. Server time is 2026-09-01T14:02:11.412Z UTC.",
                      "code": "SCHEDULED_TIME_IN_PAST",
                      "serverTime": "2026-09-01T14:02:11.412Z"
                    }
                  },
                  "unknownPlatformSetting": {
                    "summary": "Unrecognised platformSettings path — nothing was created",
                    "value": {
                      "error": "Unknown platformSettings path: youtube.thumbnail.typo",
                      "code": "PLATFORM_SETTING_UNKNOWN",
                      "field": "youtube.thumbnail.typo"
                    }
                  },
                  "idempotencyBodyTooComplex": {
                    "summary": "Idempotency-Key sent with an excessively nested body",
                    "value": {
                      "error": "Idempotency-Key request body is too deeply nested",
                      "code": "IDEMPOTENCY_BODY_TOO_COMPLEX"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Access entitlement or plan limit denied; inspect the returned code/error rather than matching an example string",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "An earlier request with this `Idempotency-Key` is still in flight.\nNothing was created by this call — retry shortly to receive the\noriginal response. Match on `code`; the `error` text varies.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "inFlight": {
                    "value": {
                      "error": "A request with this idempotency key is still in flight",
                      "code": "IDEMPOTENCY_IN_FLIGHT"
                    }
                  },
                  "superseded": {
                    "value": {
                      "error": "Idempotency claim was superseded by another request",
                      "code": "IDEMPOTENCY_IN_FLIGHT"
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "This `Idempotency-Key` was already used with a different request body.\nNothing was created — either replay the original body, or retry with a\nnew key.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Idempotency key was already used with a different request body",
                  "code": "IDEMPOTENCY_KEY_CONFLICT"
                }
              }
            }
          },
          "429": {
            "description": "Media URL ingestion rate limit (60 URLs per fixed one-hour window)",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until retry"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MediaUrlRateLimitError"
                },
                "example": {
                  "error": "Media URL ingestion rate limit reached (60 URLs/hour). Retry later or use the presigned get-upload-url flow.",
                  "code": "MEDIA_URL_RATE_LIMITED",
                  "retryAfterSec": 60
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/get-post/{postGroupId}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/XPubloraClient"
        }
      ],
      "get": {
        "summary": "Get post details and status",
        "description": "Retrieve the details, platform statuses, and posted IDs for a post group.",
        "operationId": "getPost",
        "tags": [
          "Posts"
        ],
        "parameters": [
          {
            "name": "postGroupId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "507f1f77bcf86cd799439011"
          },
          {
            "name": "x-publora-user-id",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Post details",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "postGroupId": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "draft",
                        "scheduled",
                        "published",
                        "failed",
                        "partially_published"
                      ],
                      "description": "Post-group status. Always present."
                    },
                    "scheduledTime": {
                      "type": "string",
                      "format": "date-time",
                      "nullable": true,
                      "description": "The effective scheduled time stored for this post group.\nAlways present; `null` for drafts and for groups that were\nnever given a time.\n",
                      "example": "2026-03-01T14:00:00.000Z"
                    },
                    "platformSettings": {
                      "type": "object",
                      "description": "Per-platform settings currently stored on this post group,\nkeyed by platform. Always present; `{}` when none are set.\nMirrors the platformSettings accepted by /create-post and\n/update-post.\n",
                      "example": {
                        "youtube": {
                          "privacy": "public",
                          "madeForKids": false
                        }
                      }
                    },
                    "platforms": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Platform connection IDs targeted by this post group (format:\nplatform-platformId). Always present; `[]` when none.\n",
                      "example": [
                        "twitter-123456789",
                        "linkedin-ABC123"
                      ]
                    },
                    "posts": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Post"
                      }
                    },
                    "media": {
                      "type": "array",
                      "description": "Attached media inventory in post-group order. Always present; empty when none is attached.",
                      "items": {
                        "type": "object",
                        "properties": {
                          "mediaId": {
                            "type": "string"
                          },
                          "sourceFileName": {
                            "type": "string",
                            "nullable": true
                          },
                          "fileName": {
                            "type": "string",
                            "nullable": true
                          },
                          "type": {
                            "type": "string",
                            "nullable": true
                          },
                          "mimeType": {
                            "type": "string",
                            "nullable": true
                          },
                          "status": {
                            "type": "string"
                          },
                          "failureReason": {
                            "type": "string",
                            "nullable": true
                          },
                          "url": {
                            "type": "string",
                            "nullable": true
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Post not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/update-post/{postGroupId}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/XPubloraClient"
        }
      ],
      "put": {
        "summary": "Update post timing, status, or platform settings",
        "description": "Modify the scheduled time, status, platformSettings, or mediaUrls of an existing\ndraft or scheduled post.\nOnly post groups whose status is draft or scheduled can be updated. `pending`\nand `processing` belong to separate processing/per-platform state, not\nto the post-group status enum.\nAt least one of status, scheduledTime, platformSettings, or mediaUrls must be provided.\nplatformSettings are merged per-platform with existing settings —\nomitted fields are preserved.\nUpdating a post group also updates all associated platform-specific posts.\n",
        "operationId": "updatePost",
        "tags": [
          "Posts"
        ],
        "parameters": [
          {
            "name": "postGroupId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "x-publora-user-id",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Opt-in idempotency. Omit the header for the previous behaviour —\nevery call applies the update again.\n\nSend a unique value (a UUID is a good choice) per logical operation\nto make retries safe:\n\n- Same key + identical body, original request finished → the original\n  status code and response body are replayed. The update is not\n  re-applied.\n- Same key + identical body, original request still in flight → `409`\n  (`IDEMPOTENCY_IN_FLIGHT`). Retry shortly.\n- Same key + different body → `422` (`IDEMPOTENCY_KEY_CONFLICT`).\n  Nothing is changed.\n\nThe key is bound to this `postGroupId` as well as the body, so the\nsame key reused against a different post counts as a different\nrequest. Keys are scoped to the acting user (the managed user when\n`x-publora-user-id` is set), so they never collide across accounts.\nRecords expire 24 hours after they are created; reusing a key after\nthat window is treated as a brand-new request.\n",
            "schema": {
              "type": "string"
            },
            "example": "6f1e2d3c-4b5a-4c7d-8e9f-0a1b2c3d4e5f"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "At least one of status, scheduledTime, platformSettings, or mediaUrls must be provided",
                "properties": {
                  "status": {
                    "type": "string",
                    "enum": [
                      "draft",
                      "scheduled"
                    ],
                    "description": "New status"
                  },
                  "scheduledTime": {
                    "type": "string",
                    "format": "date-time",
                    "description": "New ISO 8601 UTC scheduled time. Send a future time.\n\nIf the time is in the past, it is clamped to the current server\ntime and a `SCHEDULED_TIME_COERCED` warning is returned — the\nupdate still succeeds. Read the `scheduledTime` in the response\nfor the time actually stored.\n\nTimes less than 5 minutes in the past are always tolerated this\nway. Strict rejection for a time 5 or more minutes in the past\nis scheduled to begin on **2026-08-25**, unless production\nconfiguration overrides that date. Before strict mode it is\nclamped with a warning.\n\nThe same check applies to the post's existing scheduledTime when\nyou move a post to `status: \"scheduled\"` without sending a new\ntime — a long-stale draft can therefore be rejected.\n"
                  },
                  "platformSettings": {
                    "$ref": "#/components/schemas/PlatformSettingsInput"
                  },
                  "mediaUrls": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 10,
                    "items": {
                      "type": "string",
                      "format": "uri"
                    },
                    "description": "Public https URLs downloaded server-side and appended to existing media. Images: 25 MB each; videos: 150 MB each; aggregate: 300 MB. Ingestion is all-or-nothing."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Post updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "message": {
                      "type": "string",
                      "example": "Post updated successfully"
                    },
                    "scheduledTime": {
                      "type": "string",
                      "format": "date-time",
                      "nullable": true,
                      "description": "The effective scheduled time that was actually stored. Always\npresent. `null` when the post has no scheduled time (e.g. it\nwas moved back to draft). This may differ from the value you\nsent — if so, a `SCHEDULED_TIME_COERCED` warning explains\nwhy. Trust this field over your requested value.\n",
                      "example": "2026-03-01T14:00:00.000Z"
                    },
                    "mediaValidationStatus": {
                      "type": "string",
                      "enum": [
                        "pending"
                      ],
                      "description": "Only present when the post was scheduled while media\nvalidation was still unfinished. The media is re-checked\nbefore publishing. Accompanied by a\n`MEDIA_VALIDATION_PENDING` entry in `warnings`.\n",
                      "example": "pending"
                    },
                    "warnings": {
                      "type": "array",
                      "description": "Non-fatal notices about a request that still succeeded. Only\npresent when at least one warning applies.\n",
                      "items": {
                        "$ref": "#/components/schemas/Warning"
                      }
                    },
                    "postGroup": {
                      "type": "object",
                      "properties": {
                        "_id": {
                          "type": "string"
                        },
                        "status": {
                          "type": "string"
                        },
                        "scheduledTime": {
                          "type": "string",
                          "format": "date-time",
                          "description": "Only included if the post has a scheduled time. Duplicates\nthe top-level `scheduledTime`, which is always present and\nis the preferred field to read.\n"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "missingFields": {
                    "value": {
                      "error": "At least one of status, scheduledTime, platformSettings, or mediaUrls must be provided"
                    }
                  },
                  "invalidStatus": {
                    "value": {
                      "error": "Status must be either 'draft' or 'scheduled'"
                    }
                  },
                  "invalidTimeFormat": {
                    "value": {
                      "error": "Invalid scheduled time format"
                    }
                  },
                  "pastTime": {
                    "summary": "scheduledTime 5+ minutes in the past (strict mode)",
                    "value": {
                      "error": "Scheduled time is in the past. Server time is 2026-09-01T14:02:11.412Z UTC.",
                      "code": "SCHEDULED_TIME_IN_PAST",
                      "serverTime": "2026-09-01T14:02:11.412Z"
                    }
                  },
                  "unknownPlatformSetting": {
                    "summary": "Unrecognised platformSettings path — nothing was changed",
                    "value": {
                      "error": "Unknown platformSettings path: youtube.thumbnail.typo",
                      "code": "PLATFORM_SETTING_UNKNOWN",
                      "field": "youtube.thumbnail.typo"
                    }
                  },
                  "idempotencyBodyTooComplex": {
                    "summary": "Idempotency-Key sent with an excessively nested body",
                    "value": {
                      "error": "Idempotency-Key request body is too deeply nested",
                      "code": "IDEMPOTENCY_BODY_TOO_COMPLEX"
                    }
                  },
                  "cannotUpdate": {
                    "value": {
                      "error": "Cannot update post: post is currently in published status"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "An earlier request with this `Idempotency-Key` is still in flight.\nNothing was changed by this call — retry shortly to receive the\noriginal response. Match on `code`; the `error` text varies.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "inFlight": {
                    "value": {
                      "error": "A request with this idempotency key is still in flight",
                      "code": "IDEMPOTENCY_IN_FLIGHT"
                    }
                  },
                  "superseded": {
                    "value": {
                      "error": "Idempotency claim was superseded by another request",
                      "code": "IDEMPOTENCY_IN_FLIGHT"
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "This `Idempotency-Key` was already used with a different request body\n(or against a different postGroupId). Nothing was changed — either\nreplay the original body, or retry with a new key.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Idempotency key was already used with a different request body",
                  "code": "IDEMPOTENCY_KEY_CONFLICT"
                }
              }
            }
          },
          "404": {
            "description": "Post not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Post group not found"
                }
              }
            }
          },
          "429": {
            "description": "Media URL ingestion rate limit reached (60 URLs per fixed one-hour window)",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MediaUrlRateLimitError"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Failed to update post"
                }
              }
            }
          }
        }
      }
    },
    "/delete-post/{postGroupId}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/XPubloraClient"
        }
      ],
      "delete": {
        "summary": "Delete a scheduled post",
        "description": "Delete a post group and all associated data. This operation removes:\n- The post group record\n- All platform-specific posts (Twitter, LinkedIn, Instagram, etc.)\n- All media file records from the database\n- All media files from S3 storage\n\nThe database deletion is atomic (uses MongoDB transaction). S3 deletion\nfailures are logged but do not fail the request.\n",
        "operationId": "deletePost",
        "tags": [
          "Posts"
        ],
        "parameters": [
          {
            "name": "postGroupId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "x-publora-user-id",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Post deleted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Post group not found (ID doesn't exist or belongs to another user)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Post group not found"
                }
              }
            }
          },
          "500": {
            "description": "Server error during deletion (transaction rolled back)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Failed to delete post group"
                }
              }
            }
          }
        }
      }
    },
    "/media/{mediaId}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/XPubloraClient"
        }
      ],
      "delete": {
        "summary": "Delete an uploaded media file",
        "description": "Delete a media record and its stored object. If the media belonged to a\nscheduled post, the post is demoted to draft and must be scheduled again.\nMedia cannot be removed while its post is publishing or after the group\nhas reached published or failed status.\n",
        "operationId": "deleteMedia",
        "tags": [
          "Media"
        ],
        "parameters": [
          {
            "name": "mediaId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Media deleted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "success"
                  ],
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "postGroupDemoted": {
                      "type": "boolean"
                    },
                    "message": {
                      "type": "string"
                    },
                    "groupAlreadyMissing": {
                      "type": "boolean"
                    },
                    "mediaWasOrphan": {
                      "type": "boolean"
                    },
                    "thumbnailCleared": {
                      "type": "boolean"
                    },
                    "coverCleared": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid media ID, or media belongs to a published or failed post",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Media file not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Post is publishing or changed state concurrently; retry later",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Failed to delete media",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/get-upload-url": {
      "parameters": [
        {
          "$ref": "#/components/parameters/XPubloraClient"
        }
      ],
      "post": {
        "summary": "Get pre-signed URL for media upload",
        "description": "Get a pre-signed S3 URL to upload images, videos, or PDF documents.\nUpload the file directly to S3 via HTTP PUT, then create a post.\nUpload URLs expire in 1 hour. The image count per post is\nplatform-specific (up to 4 for Twitter/X, Bluesky, Mastodon; up to 10\nfor Instagram, LinkedIn, Facebook, Telegram; up to 20 for Threads), or 1 video per\npost. Attach multiple images to the same postGroupId to form a carousel;\nthese post-media count rules do not apply to PDF document assets.\n",
        "operationId": "getUploadUrl",
        "tags": [
          "Media"
        ],
        "parameters": [
          {
            "name": "x-publora-user-id",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "fileName",
                  "contentType",
                  "postGroupId"
                ],
                "properties": {
                  "fileName": {
                    "type": "string",
                    "example": "photo.jpg"
                  },
                  "contentType": {
                    "type": "string",
                    "description": "MIME type. It must match an image/*, video/*, or application/pdf upload.",
                    "example": "image/jpeg"
                  },
                  "type": {
                    "type": "string",
                    "enum": [
                      "image",
                      "video",
                      "document"
                    ],
                    "description": "Optional media-type hint; inferred from contentType when omitted",
                    "example": "image"
                  },
                  "postGroupId": {
                    "type": "string",
                    "description": "Post group to attach media to",
                    "example": "507f1f77bcf86cd799439011"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Upload URL generated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "uploadUrl": {
                      "type": "string",
                      "format": "uri",
                      "description": "Pre-signed S3 URL. PUT your file here."
                    },
                    "fileUrl": {
                      "type": "string",
                      "format": "uri",
                      "description": "Public URL after upload"
                    },
                    "mediaId": {
                      "type": "string",
                      "description": "Media record ID"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing required fields",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "missingFields": {
                    "value": {
                      "error": "fileName, contentType, and postGroupId are required"
                    }
                  },
                  "invalidPostGroupId": {
                    "value": {
                      "error": "Invalid postGroupId"
                    }
                  },
                  "unsupportedContentType": {
                    "value": {
                      "error": "Only video, image, and PDF files are allowed"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "serverError": {
                    "value": {
                      "error": "Server error"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/complete-media/{mediaFileId}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/XPubloraClient"
        }
      ],
      "post": {
        "summary": "Probe and finalize an uploaded media file",
        "description": "Optionally run the server-side media probe immediately after uploading\nbytes to the pre-signed URL. This call is not required: scheduling also\nprobes media that is still uploading.\n",
        "operationId": "completeMedia",
        "tags": [
          "Media"
        ],
        "parameters": [
          {
            "name": "mediaFileId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Media probe completed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "success",
                    "mediaFile"
                  ],
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "mediaFile": {
                      "type": "object",
                      "required": [
                        "_id"
                      ],
                      "properties": {
                        "_id": {
                          "type": "string"
                        },
                        "type": {
                          "type": "string",
                          "nullable": true
                        },
                        "mimeType": {
                          "type": "string",
                          "nullable": true
                        },
                        "fileName": {
                          "type": "string",
                          "nullable": true
                        },
                        "url": {
                          "type": "string",
                          "format": "uri",
                          "nullable": true
                        },
                        "status": {
                          "type": "string",
                          "nullable": true
                        },
                        "metadata": {
                          "type": "object",
                          "nullable": true,
                          "additionalProperties": true
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid media ID or uploaded media failed validation",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Media file not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Media was deleted or failed concurrently while being probed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Failed to complete media upload",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Media probe service or uploaded object is temporarily unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/upload-instagram-cover": {
      "parameters": [
        {
          "$ref": "#/components/parameters/XPubloraClient"
        }
      ],
      "post": {
        "summary": "Upload a custom Instagram Reel cover image",
        "description": "Upload a cover image for an Instagram Reel. The image is stored as a\nJPEG (PNG/WebP are transcoded server-side; transparency is flattened\nonto white) and a public URL is returned. Set that URL as\nplatformSettings.instagram.coverUrl via update-post to use it as the\nReel cover. The target post group must be an editable (draft or\nscheduled) post you own and not currently publishing.\nCovers apply to Reels only and are ignored for Stories and images.\n",
        "operationId": "uploadInstagramCover",
        "tags": [
          "Media"
        ],
        "parameters": [
          {
            "name": "x-publora-user-id",
            "in": "header",
            "required": false,
            "description": "Managed user ID (workspace only)",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "cover",
                  "postGroupId"
                ],
                "properties": {
                  "cover": {
                    "type": "string",
                    "format": "binary",
                    "description": "Cover image (JPEG, PNG, or WebP; 8 MB max). PNG/WebP are converted to JPEG."
                  },
                  "postGroupId": {
                    "type": "string",
                    "description": "Editable post group (draft or scheduled) to attach the cover to",
                    "example": "507f1f77bcf86cd799439011"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Cover uploaded",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "cover": {
                      "type": "object",
                      "properties": {
                        "mediaId": {
                          "type": "string",
                          "description": "Cover media record ID",
                          "example": "665f8a1b2c3d4e5f6a7b8c9d"
                        },
                        "url": {
                          "type": "string",
                          "format": "uri",
                          "description": "Public JPEG URL — set as platformSettings.instagram.coverUrl via update-post",
                          "example": "https://media.publora.com/images/665f...-reel-cover.jpg"
                        }
                      }
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "missingPostGroupId": {
                    "value": {
                      "error": "postGroupId is required"
                    }
                  },
                  "invalidPostGroupId": {
                    "value": {
                      "error": "Invalid postGroupId"
                    }
                  },
                  "missingFile": {
                    "value": {
                      "error": "cover file is required"
                    }
                  },
                  "badType": {
                    "value": {
                      "error": "Instagram covers must be JPEG, PNG, or WebP images"
                    }
                  },
                  "tooLarge": {
                    "value": {
                      "error": "Instagram covers must be 8 MB or smaller"
                    }
                  },
                  "tooLargeAfterTranscode": {
                    "value": {
                      "error": "Instagram cover exceeds 8 MB after JPEG conversion; use a smaller image"
                    }
                  },
                  "nonEditable": {
                    "value": {
                      "error": "Cannot upload Instagram cover for a non-editable post"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Post group not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Post group not found"
                }
              }
            }
          },
          "409": {
            "description": "Post group is currently publishing",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Post group is currently being published; cannot upload Instagram cover. Retry once publishing completes."
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Failed to upload Instagram cover"
                }
              }
            }
          }
        }
      }
    },
    "/upload-youtube-thumbnail": {
      "parameters": [
        {
          "$ref": "#/components/parameters/XPubloraClient"
        }
      ],
      "post": {
        "summary": "Upload a custom YouTube video thumbnail",
        "description": "Upload a custom thumbnail for a YouTube video post. The image is stored\nas Publora-tracked media and a mediaId + url are returned; attach them\nvia platformSettings.youtube.thumbnail on update-post. A thumbnail\ncannot be set on create-post — the upload requires an existing\npostGroupId (draft or scheduled) that is not currently publishing.\nJPEG or PNG, 2 MB max, minimum 640x360 px (1280x720 recommended).\n",
        "operationId": "uploadYouTubeThumbnail",
        "tags": [
          "Media"
        ],
        "parameters": [
          {
            "name": "x-publora-user-id",
            "in": "header",
            "required": false,
            "description": "Managed user ID (workspace only)",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "thumbnail",
                  "postGroupId"
                ],
                "properties": {
                  "thumbnail": {
                    "type": "string",
                    "format": "binary",
                    "description": "Thumbnail image (JPEG or PNG; 2 MB max; min 640x360 px, 1280x720 recommended)."
                  },
                  "postGroupId": {
                    "type": "string",
                    "description": "Editable post group (draft or scheduled) to attach the thumbnail to",
                    "example": "507f1f77bcf86cd799439011"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Thumbnail uploaded",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "thumbnail": {
                      "type": "object",
                      "properties": {
                        "mediaId": {
                          "type": "string",
                          "description": "Thumbnail media record ID — set as platformSettings.youtube.thumbnail.mediaId via update-post",
                          "example": "665f8a1b2c3d4e5f6a7b8c9d"
                        },
                        "url": {
                          "type": "string",
                          "format": "uri",
                          "description": "Public thumbnail URL — set as platformSettings.youtube.thumbnail.url via update-post",
                          "example": "https://media.publora.com/images/665f...-thumb.jpg"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "missingPostGroupId": {
                    "value": {
                      "error": "postGroupId is required"
                    }
                  },
                  "invalidPostGroupId": {
                    "value": {
                      "error": "Invalid postGroupId"
                    }
                  },
                  "missingFile": {
                    "value": {
                      "error": "thumbnail file is required"
                    }
                  },
                  "badType": {
                    "value": {
                      "error": "YouTube thumbnails must be JPEG or PNG images"
                    }
                  },
                  "tooLarge": {
                    "value": {
                      "error": "YouTube thumbnails must be 2 MB or smaller"
                    }
                  },
                  "tooSmall": {
                    "value": {
                      "error": "YouTube thumbnails must be at least 640x360 pixels"
                    }
                  },
                  "nonEditable": {
                    "value": {
                      "error": "Cannot upload YouTube thumbnail for a non-editable post"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Post group not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Post group not found"
                }
              }
            }
          },
          "409": {
            "description": "Post group is currently publishing",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Post group is currently being published; cannot upload YouTube thumbnail. Retry once publishing completes."
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Failed to upload YouTube thumbnail"
                }
              }
            }
          }
        }
      }
    },
    "/linkedin-post-statistics": {
      "parameters": [
        {
          "$ref": "#/components/parameters/XPubloraClient"
        }
      ],
      "post": {
        "summary": "Get LinkedIn post analytics",
        "description": "Retrieve analytics for a LinkedIn post: impressions, reach, reactions, comments, reshares.\nResults are cached for 2 hours. Use queryTypes: \"ALL\" to get all 5 metrics at once.\n",
        "operationId": "getLinkedInPostStatistics",
        "tags": [
          "LinkedIn Analytics"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "postedId",
                  "platformId"
                ],
                "properties": {
                  "postedId": {
                    "type": "string",
                    "description": "LinkedIn URN or share ID",
                    "example": "urn:li:share:7123456789012345678"
                  },
                  "platformId": {
                    "type": "string",
                    "description": "LinkedIn connection ID",
                    "example": "linkedin-Tz9W5i6ZYG"
                  },
                  "queryType": {
                    "type": "string",
                    "enum": [
                      "IMPRESSION",
                      "MEMBERS_REACHED",
                      "RESHARE",
                      "REACTION",
                      "COMMENT"
                    ],
                    "description": "Single metric to fetch"
                  },
                  "queryTypes": {
                    "oneOf": [
                      {
                        "type": "array",
                        "items": {
                          "type": "string",
                          "enum": [
                            "IMPRESSION",
                            "MEMBERS_REACHED",
                            "RESHARE",
                            "REACTION",
                            "COMMENT"
                          ]
                        }
                      },
                      {
                        "type": "string",
                        "enum": [
                          "ALL"
                        ]
                      }
                    ],
                    "description": "Multiple metrics or ALL",
                    "example": "ALL"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Statistics retrieved",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "object",
                      "description": "Single metric response",
                      "properties": {
                        "success": {
                          "type": "boolean"
                        },
                        "count": {
                          "type": "integer",
                          "example": 1542
                        },
                        "cached": {
                          "type": "boolean"
                        }
                      }
                    },
                    {
                      "type": "object",
                      "description": "Multiple metrics response",
                      "properties": {
                        "success": {
                          "type": "boolean"
                        },
                        "metrics": {
                          "type": "object",
                          "properties": {
                            "IMPRESSION": {
                              "type": "integer"
                            },
                            "MEMBERS_REACHED": {
                              "type": "integer"
                            },
                            "RESHARE": {
                              "type": "integer"
                            },
                            "REACTION": {
                              "type": "integer"
                            },
                            "COMMENT": {
                              "type": "integer"
                            }
                          }
                        },
                        "cached": {
                          "type": "boolean"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request - missing or invalid parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "missingFields": {
                    "value": {
                      "error": "postedId and platformId are required"
                    }
                  },
                  "invalidQueryType": {
                    "value": {
                      "error": "Invalid queryType. Must be one of: IMPRESSION, MEMBERS_REACHED, RESHARE, REACTION, COMMENT"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "LinkedIn connection not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "LinkedIn connection not found"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Failed to fetch LinkedIn post statistics"
                }
              }
            }
          }
        }
      }
    },
    "/linkedin-account-statistics": {
      "parameters": [
        {
          "$ref": "#/components/parameters/XPubloraClient"
        }
      ],
      "post": {
        "summary": "Get aggregated LinkedIn account analytics",
        "description": "Get total metrics across all published LinkedIn posts for an account.",
        "operationId": "getLinkedInAccountStatistics",
        "tags": [
          "LinkedIn Analytics"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "platformId"
                ],
                "properties": {
                  "platformId": {
                    "type": "string",
                    "example": "linkedin-Tz9W5i6ZYG"
                  },
                  "queryType": {
                    "type": "string",
                    "enum": [
                      "IMPRESSION",
                      "MEMBERS_REACHED",
                      "RESHARE",
                      "REACTION",
                      "COMMENT"
                    ],
                    "description": "Single metric to fetch"
                  },
                  "queryTypes": {
                    "oneOf": [
                      {
                        "type": "array",
                        "items": {
                          "type": "string",
                          "enum": [
                            "IMPRESSION",
                            "MEMBERS_REACHED",
                            "RESHARE",
                            "REACTION",
                            "COMMENT"
                          ]
                        }
                      },
                      {
                        "type": "string",
                        "enum": [
                          "ALL"
                        ]
                      }
                    ],
                    "description": "Multiple metrics or ALL",
                    "example": "ALL"
                  },
                  "aggregation": {
                    "type": "string",
                    "enum": [
                      "TOTAL",
                      "DAILY"
                    ],
                    "default": "TOTAL",
                    "description": "Aggregation type: TOTAL (default) or DAILY"
                  },
                  "dateRange": {
                    "type": "object",
                    "description": "Optional date range filter",
                    "properties": {
                      "start": {
                        "type": "object",
                        "properties": {
                          "year": {
                            "type": "integer"
                          },
                          "month": {
                            "type": "integer"
                          },
                          "day": {
                            "type": "integer"
                          }
                        }
                      },
                      "end": {
                        "type": "object",
                        "properties": {
                          "year": {
                            "type": "integer"
                          },
                          "month": {
                            "type": "integer"
                          },
                          "day": {
                            "type": "integer"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Account statistics",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "metrics": {
                      "type": "object",
                      "description": "Flexible object with dynamic metric keys (e.g., IMPRESSION, REACTION, etc.)",
                      "additionalProperties": {
                        "type": "integer"
                      }
                    },
                    "aggregation": {
                      "type": "string",
                      "enum": [
                        "TOTAL",
                        "DAILY"
                      ]
                    },
                    "cached": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "missingPlatformId": {
                    "value": {
                      "error": "platformId is required"
                    }
                  },
                  "invalidQueryType": {
                    "value": {
                      "error": "Invalid queryType"
                    }
                  },
                  "invalidAggregation": {
                    "value": {
                      "error": "Invalid aggregation"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "LinkedIn connection not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/linkedin-profile-summary": {
      "parameters": [
        {
          "$ref": "#/components/parameters/XPubloraClient"
        }
      ],
      "post": {
        "summary": "Get LinkedIn profile summary",
        "description": "Retrieve a summary of your LinkedIn profile analytics including follower counts\nand aggregated post engagement metrics. Optionally filter by date range to track\nfollower growth over a specific period.\n",
        "operationId": "getLinkedInProfileSummary",
        "tags": [
          "LinkedIn Analytics"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "platformId"
                ],
                "properties": {
                  "platformId": {
                    "type": "string",
                    "description": "LinkedIn connection ID (format: linkedin-{id})",
                    "example": "linkedin-Tz9W5i6ZYG"
                  },
                  "dateRange": {
                    "type": "object",
                    "description": "Optional date range for filtering metrics and calculating follower growth",
                    "properties": {
                      "start": {
                        "type": "object",
                        "properties": {
                          "year": {
                            "type": "integer",
                            "example": 2026
                          },
                          "month": {
                            "type": "integer",
                            "example": 1
                          },
                          "day": {
                            "type": "integer",
                            "example": 1
                          }
                        }
                      },
                      "end": {
                        "type": "object",
                        "properties": {
                          "year": {
                            "type": "integer",
                            "example": 2026
                          },
                          "month": {
                            "type": "integer",
                            "example": 1
                          },
                          "day": {
                            "type": "integer",
                            "example": 31
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Profile summary retrieved",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "partialData": {
                      "type": "boolean",
                      "description": "True if some data failed to fetch but partial results are available"
                    },
                    "profile": {
                      "type": "object",
                      "properties": {
                        "followers": {
                          "type": "object",
                          "properties": {
                            "total": {
                              "type": "integer",
                              "description": "Total follower count",
                              "example": 5000
                            },
                            "periodGrowth": {
                              "type": "integer",
                              "description": "Follower growth during date range (only present when dateRange is provided)",
                              "example": 150
                            }
                          }
                        },
                        "posts": {
                          "type": "object",
                          "properties": {
                            "totalImpressions": {
                              "type": "integer",
                              "example": 25000
                            },
                            "totalReactions": {
                              "type": "integer",
                              "example": 500
                            },
                            "totalComments": {
                              "type": "integer",
                              "example": 75
                            },
                            "totalReshares": {
                              "type": "integer",
                              "example": 30
                            },
                            "totalMembersReached": {
                              "type": "integer",
                              "example": 12000
                            }
                          }
                        }
                      }
                    },
                    "errors": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "metric",
                          "error"
                        ],
                        "properties": {
                          "metric": {
                            "type": "string"
                          },
                          "error": {
                            "type": "string"
                          }
                        }
                      },
                      "description": "List of errors when partialData is true",
                      "example": [
                        {
                          "metric": "followers",
                          "error": "Failed to fetch followers"
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "missingPlatformId": {
                    "value": {
                      "error": "platformId is required"
                    }
                  },
                  "invalidDateRange": {
                    "value": {
                      "error": "dateRange must be an object"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "LinkedIn connection not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "LinkedIn connection not found"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "All requests to LinkedIn failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Failed to fetch LinkedIn profile data"
                }
              }
            }
          }
        }
      }
    },
    "/linkedin-followers": {
      "parameters": [
        {
          "$ref": "#/components/parameters/XPubloraClient"
        }
      ],
      "post": {
        "summary": "Get LinkedIn follower statistics",
        "description": "Retrieve follower statistics for your LinkedIn account.\nUse period=\"lifetime\" (default) for total follower count, or period=\"daily\" with a dateRange for daily growth data.\nResults are cached for improved performance.\n",
        "operationId": "getLinkedInFollowers",
        "tags": [
          "LinkedIn Analytics"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "platformId"
                ],
                "properties": {
                  "platformId": {
                    "type": "string",
                    "description": "LinkedIn connection ID",
                    "example": "linkedin-Tz9W5i6ZYG"
                  },
                  "period": {
                    "type": "string",
                    "enum": [
                      "lifetime",
                      "daily"
                    ],
                    "default": "lifetime",
                    "description": "Type of statistics to retrieve",
                    "example": "lifetime"
                  },
                  "dateRange": {
                    "type": "object",
                    "description": "Required when period is 'daily'",
                    "properties": {
                      "start": {
                        "type": "object",
                        "properties": {
                          "year": {
                            "type": "integer",
                            "example": 2024
                          },
                          "month": {
                            "type": "integer",
                            "example": 1
                          },
                          "day": {
                            "type": "integer",
                            "example": 1
                          }
                        }
                      },
                      "end": {
                        "type": "object",
                        "properties": {
                          "year": {
                            "type": "integer",
                            "example": 2024
                          },
                          "month": {
                            "type": "integer",
                            "example": 1
                          },
                          "day": {
                            "type": "integer",
                            "example": 31
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Follower statistics retrieved",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "object",
                      "description": "Lifetime response",
                      "properties": {
                        "success": {
                          "type": "boolean",
                          "example": true
                        },
                        "followersCount": {
                          "type": "integer",
                          "example": 1234
                        },
                        "cached": {
                          "type": "boolean"
                        }
                      }
                    },
                    {
                      "type": "object",
                      "description": "Daily response",
                      "properties": {
                        "success": {
                          "type": "boolean",
                          "example": true
                        },
                        "data": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "date": {
                                "type": "string",
                                "example": "2024-01-15"
                              },
                              "count": {
                                "type": "integer",
                                "example": 10
                              }
                            }
                          }
                        },
                        "totalGrowth": {
                          "type": "integer",
                          "example": 25
                        },
                        "cached": {
                          "type": "boolean"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "missingPlatformId": {
                    "value": {
                      "error": "platformId is required"
                    }
                  },
                  "invalidPeriod": {
                    "value": {
                      "error": "Invalid period"
                    }
                  },
                  "missingDateRange": {
                    "value": {
                      "error": "dateRange is required for daily period"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "LinkedIn connection not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Failed to fetch statistics",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "fetchFailed": {
                    "value": {
                      "error": "Failed to fetch LinkedIn followers statistics"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/linkedin-reactions": {
      "parameters": [
        {
          "$ref": "#/components/parameters/XPubloraClient"
        }
      ],
      "post": {
        "summary": "Add a reaction to a LinkedIn post",
        "description": "React to a LinkedIn post with LIKE, PRAISE, EMPATHY, INTEREST, APPRECIATION, or ENTERTAINMENT.",
        "operationId": "addLinkedInReaction",
        "tags": [
          "LinkedIn Analytics"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "postedId",
                  "reactionType",
                  "platformId"
                ],
                "properties": {
                  "postedId": {
                    "type": "string",
                    "description": "LinkedIn URN (urn:li:share:*, urn:li:ugcPost:*, urn:li:activity:*)",
                    "example": "urn:li:share:7123456789012345678"
                  },
                  "reactionType": {
                    "type": "string",
                    "enum": [
                      "LIKE",
                      "PRAISE",
                      "EMPATHY",
                      "INTEREST",
                      "APPRECIATION",
                      "ENTERTAINMENT"
                    ],
                    "example": "LIKE"
                  },
                  "platformId": {
                    "type": "string",
                    "example": "linkedin-Tz9W5i6ZYG"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Reaction added",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "reaction": {
                      "type": "object",
                      "description": "Reaction details",
                      "properties": {
                        "reactionType": {
                          "type": "string",
                          "enum": [
                            "LIKE",
                            "PRAISE",
                            "EMPATHY",
                            "INTEREST",
                            "APPRECIATION",
                            "ENTERTAINMENT"
                          ]
                        },
                        "actor": {
                          "type": "string",
                          "description": "LinkedIn member URN"
                        },
                        "object": {
                          "type": "string",
                          "description": "LinkedIn post URN"
                        }
                      }
                    },
                    "urnTranslated": {
                      "$ref": "#/components/schemas/LinkedInUrnTranslation"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "missingFields": {
                    "value": {
                      "error": "postedId, reactionType, and platformId are required"
                    }
                  },
                  "invalidReactionType": {
                    "value": {
                      "error": "reactionType must be one of: LIKE, PRAISE, EMPATHY, INTEREST, APPRECIATION, ENTERTAINMENT"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "LinkedIn connection not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The requested reaction already exists",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "message",
                    "requestedReactionType"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "REACTION_ALREADY_EXISTS"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "requestedReactionType": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Remove a reaction from a LinkedIn post",
        "description": "Remove your reaction from a LinkedIn post.\nParameters can be provided either as query parameters OR in the request body.\n",
        "operationId": "removeLinkedInReaction",
        "tags": [
          "LinkedIn Analytics"
        ],
        "parameters": [
          {
            "name": "postedId",
            "in": "query",
            "description": "LinkedIn URN (can also be provided in request body)",
            "schema": {
              "type": "string"
            },
            "example": "urn:li:share:7123456789012345678"
          },
          {
            "name": "platformId",
            "in": "query",
            "description": "LinkedIn connection ID (can also be provided in request body)",
            "schema": {
              "type": "string"
            },
            "example": "linkedin-Tz9W5i6ZYG"
          }
        ],
        "requestBody": {
          "required": false,
          "description": "Alternative to query parameters - provide postedId and platformId in body",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "postedId": {
                    "type": "string",
                    "description": "LinkedIn URN (urn:li:share:*, urn:li:ugcPost:*, urn:li:activity:*)",
                    "example": "urn:li:share:7123456789012345678"
                  },
                  "platformId": {
                    "type": "string",
                    "example": "linkedin-Tz9W5i6ZYG"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Reaction removed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "reaction": {
                      "type": "object",
                      "description": "Details of the removed reaction",
                      "properties": {
                        "reactionType": {
                          "type": "string"
                        },
                        "actor": {
                          "type": "string"
                        },
                        "object": {
                          "type": "string"
                        }
                      }
                    },
                    "urnTranslated": {
                      "$ref": "#/components/schemas/LinkedInUrnTranslation"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "missingFields": {
                    "value": {
                      "error": "postedId and platformId are required"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "LinkedIn connection not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/linkedin-reshare": {
      "parameters": [
        {
          "$ref": "#/components/parameters/XPubloraClient"
        }
      ],
      "post": {
        "summary": "Reshare an existing LinkedIn post",
        "description": "Reshare (repost) an existing LinkedIn post to your own feed with optional commentary. Works for personal and company-page connections (authored as the member or the organization automatically).",
        "operationId": "createLinkedInReshare",
        "tags": [
          "LinkedIn Analytics"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "platformId",
                  "parent"
                ],
                "properties": {
                  "platformId": {
                    "type": "string",
                    "description": "LinkedIn connection ID",
                    "example": "linkedin-Tz9W5i6ZYG"
                  },
                  "parent": {
                    "type": "string",
                    "description": "URN of the post to reshare (urn:li:share:* or urn:li:ugcPost:*)",
                    "example": "urn:li:share:7123456789012345678"
                  },
                  "commentary": {
                    "type": "string",
                    "description": "Optional text added above the reshare (max 3000 characters)",
                    "example": "Great read — sharing with my network!"
                  },
                  "visibility": {
                    "type": "string",
                    "enum": [
                      "PUBLIC",
                      "CONNECTIONS"
                    ],
                    "default": "PUBLIC",
                    "description": "Optional visibility (case-insensitive). Defaults to PUBLIC. CONNECTIONS is personal-profile-only; organization/company-page reshares must use PUBLIC.",
                    "example": "PUBLIC"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Reshare created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "reshare": {
                      "type": "object",
                      "description": "LinkedIn API response for the created reshare",
                      "properties": {
                        "id": {
                          "type": "string",
                          "description": "URN of the new reshare post",
                          "example": "urn:li:share:7123456789012345678"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "missingFields": {
                    "value": {
                      "error": "platformId and parent are required"
                    }
                  },
                  "invalidParent": {
                    "value": {
                      "error": "parent must be a valid LinkedIn post URN (urn:li:share:<id> or urn:li:ugcPost:<id>)"
                    }
                  },
                  "commentaryTooLong": {
                    "value": {
                      "error": "commentary cannot exceed 3000 characters"
                    }
                  },
                  "invalidVisibility": {
                    "value": {
                      "error": "visibility must be one of: PUBLIC, CONNECTIONS"
                    }
                  },
                  "organizationConnectionsVisibility": {
                    "value": {
                      "error": "LinkedIn organization reposts cannot use CONNECTIONS visibility; choose PUBLIC"
                    }
                  },
                  "missingOrganizationId": {
                    "value": {
                      "error": "LinkedIn company connection is missing organizationId. Please reconnect the LinkedIn page."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Invalid API key or expired LinkedIn token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "tokenExpired": {
                    "value": {
                      "error": "LINKEDIN_TOKEN_EXPIRED"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "LinkedIn connection not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/list-posts": {
      "parameters": [
        {
          "$ref": "#/components/parameters/XPubloraClient"
        }
      ],
      "get": {
        "summary": "List posts with pagination and filters",
        "description": "Retrieve a paginated list of all your scheduled, draft, published, and failed posts.\n\n**Pagination behavior:** The `page` and `limit` parameters are silently clamped to valid ranges\nrather than returning errors. `page` is clamped to a minimum of 1, and `limit` is clamped to 1-100.\n\n**Platform filtering:** The `platform` filter uses case-insensitive prefix matching.\n",
        "operationId": "listPosts",
        "tags": [
          "Posts"
        ],
        "parameters": [
          {
            "name": "x-publora-user-id",
            "in": "header",
            "required": false,
            "description": "Managed user ID (workspace only)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "Page number (1-indexed). Values < 1 are silently clamped to 1.",
            "schema": {
              "type": "integer",
              "default": 1,
              "minimum": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Items per page. Values are silently clamped to range 1-100.",
            "schema": {
              "type": "integer",
              "default": 20,
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Filter by post status",
            "schema": {
              "type": "string",
              "enum": [
                "draft",
                "scheduled",
                "published",
                "failed",
                "partially_published"
              ]
            }
          },
          {
            "name": "platform",
            "in": "query",
            "required": false,
            "description": "Filter by platform (case-insensitive prefix matching)",
            "schema": {
              "type": "string",
              "enum": [
                "twitter",
                "linkedin",
                "instagram",
                "threads",
                "tiktok",
                "youtube",
                "facebook",
                "bluesky",
                "mastodon",
                "telegram",
                "pinterest"
              ]
            }
          },
          {
            "name": "sortBy",
            "in": "query",
            "required": false,
            "description": "Field to sort by",
            "schema": {
              "type": "string",
              "enum": [
                "createdAt",
                "updatedAt",
                "scheduledTime"
              ],
              "default": "createdAt"
            }
          },
          {
            "name": "sortOrder",
            "in": "query",
            "required": false,
            "description": "Sort order",
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "desc"
            }
          },
          {
            "name": "fromDate",
            "in": "query",
            "required": false,
            "description": "Filter posts scheduled after this ISO 8601 date",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "toDate",
            "in": "query",
            "required": false,
            "description": "Filter posts scheduled before this ISO 8601 date",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of posts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "posts": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "postGroupId": {
                            "type": "string",
                            "example": "507f1f77bcf86cd799439011"
                          },
                          "content": {
                            "type": "string",
                            "example": "Excited to share our new product launch!"
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "draft",
                              "scheduled",
                              "published",
                              "failed",
                              "partially_published"
                            ]
                          },
                          "scheduledTime": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "createdAt": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "updatedAt": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "platforms": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "platformId": {
                                  "type": "string"
                                },
                                "platform": {
                                  "type": "string"
                                },
                                "status": {
                                  "type": "string"
                                }
                              }
                            }
                          },
                          "mediaUrls": {
                            "type": "array",
                            "items": {
                              "type": "string",
                              "format": "uri"
                            }
                          }
                        }
                      }
                    },
                    "pagination": {
                      "type": "object",
                      "properties": {
                        "page": {
                          "type": "integer",
                          "example": 1
                        },
                        "limit": {
                          "type": "integer",
                          "example": 20
                        },
                        "totalItems": {
                          "type": "integer",
                          "example": 47
                        },
                        "totalPages": {
                          "type": "integer",
                          "example": 3
                        },
                        "hasNextPage": {
                          "type": "boolean",
                          "example": true
                        },
                        "hasPrevPage": {
                          "type": "boolean",
                          "example": false
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "invalidStatus": {
                    "value": {
                      "error": "Invalid status. Must be one of: draft, scheduled, published, failed, partially_published"
                    }
                  },
                  "invalidSortBy": {
                    "value": {
                      "error": "Invalid sortBy. Must be one of: createdAt, updatedAt, scheduledTime"
                    }
                  },
                  "invalidFromDate": {
                    "value": {
                      "error": "Invalid fromDate format"
                    }
                  },
                  "invalidToDate": {
                    "value": {
                      "error": "Invalid toDate format"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/test-connection/{platformId}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/XPubloraClient"
        }
      ],
      "post": {
        "summary": "Test platform connection health",
        "description": "Validate that a platform connection is working correctly.\nReturns token status, permissions, and last error if any.\n",
        "operationId": "testConnection",
        "tags": [
          "Connections"
        ],
        "parameters": [
          {
            "name": "platformId",
            "in": "path",
            "required": true,
            "description": "Platform connection ID (format: platform-id)",
            "schema": {
              "type": "string"
            },
            "example": "linkedin-Tz9W5i6ZYG"
          }
        ],
        "responses": {
          "200": {
            "description": "Connection test result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "platform": {
                      "type": "string",
                      "example": "linkedin"
                    },
                    "platformId": {
                      "type": "string",
                      "example": "linkedin-Tz9W5i6ZYG"
                    },
                    "username": {
                      "type": "string",
                      "example": "John Doe"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "ok",
                        "error"
                      ],
                      "example": "ok"
                    },
                    "message": {
                      "type": "string",
                      "example": "Connection is valid"
                    },
                    "permissions": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "example": [
                        "w_member_social",
                        "r_basicprofile"
                      ]
                    },
                    "tokenExpiresIn": {
                      "type": "string",
                      "nullable": true,
                      "example": "29 days"
                    },
                    "lastSuccessfulPost": {
                      "type": "string",
                      "format": "date-time",
                      "nullable": true
                    },
                    "lastError": {
                      "$ref": "#/components/schemas/ConnectionLastError"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid platformId format",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Connection not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/post-logs/{postGroupId}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/XPubloraClient"
        }
      ],
      "get": {
        "summary": "Get post publishing logs",
        "description": "Retrieve detailed logs for a post group showing the publishing process,\nincluding scheduling, processing, success, and failure events.\n",
        "operationId": "getPostLogs",
        "tags": [
          "Logs"
        ],
        "parameters": [
          {
            "name": "postGroupId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "507f1f77bcf86cd799439011"
          }
        ],
        "responses": {
          "200": {
            "description": "Post logs",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "logs": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PostLog"
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Post group not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/webhooks": {
      "parameters": [
        {
          "$ref": "#/components/parameters/XPubloraClient"
        }
      ],
      "get": {
        "summary": "List all webhooks",
        "description": "Retrieve all webhooks configured for your account.",
        "operationId": "listWebhooks",
        "tags": [
          "Webhooks"
        ],
        "responses": {
          "200": {
            "description": "List of webhooks",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "webhooks": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Webhook"
                      }
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create a webhook",
        "description": "Create a new webhook endpoint. Maximum 10 webhooks per user.\nThe webhook secret is only returned on creation - save it securely.\n",
        "operationId": "createWebhook",
        "tags": [
          "Webhooks"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name",
                  "url",
                  "events"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "Friendly name for the webhook",
                    "example": "Production webhook"
                  },
                  "url": {
                    "type": "string",
                    "format": "uri",
                    "description": "HTTPS URL to receive webhook events",
                    "example": "https://example.com/webhooks/publora"
                  },
                  "events": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "post.scheduled",
                        "post.published",
                        "post.failed",
                        "post.demoted",
                        "token.expiring"
                      ]
                    },
                    "description": "Events to subscribe to. token.expiring is defined but not currently dispatched; post.demoted fires when an attach/detach returns a scheduled group to draft.",
                    "example": [
                      "post.published",
                      "post.failed"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Webhook created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "webhook": {
                      "type": "object",
                      "properties": {
                        "_id": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string"
                        },
                        "url": {
                          "type": "string"
                        },
                        "events": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "secret": {
                          "type": "string",
                          "description": "Webhook secret for signature verification (only returned on create)"
                        },
                        "isActive": {
                          "type": "boolean"
                        },
                        "createdAt": {
                          "type": "string",
                          "format": "date-time"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "missingFields": {
                    "value": {
                      "error": "Name, URL, and at least one event are required"
                    }
                  },
                  "invalidUrl": {
                    "value": {
                      "error": "URL must use HTTPS"
                    }
                  },
                  "invalidEvents": {
                    "value": {
                      "error": "Invalid events"
                    }
                  },
                  "limitReached": {
                    "value": {
                      "error": "Maximum of 10 webhooks per user"
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/webhooks/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/XPubloraClient"
        }
      ],
      "patch": {
        "summary": "Update a webhook",
        "description": "Update an existing webhook's name, URL, events, or active status.",
        "operationId": "updateWebhook",
        "tags": [
          "Webhooks"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "url": {
                    "type": "string",
                    "format": "uri"
                  },
                  "events": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "post.scheduled",
                        "post.published",
                        "post.failed",
                        "post.demoted",
                        "token.expiring"
                      ],
                      "description": "token.expiring is defined and subscribable but is not currently dispatched"
                    }
                  },
                  "isActive": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Webhook updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "webhook": {
                      "$ref": "#/components/schemas/Webhook"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Webhook not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete a webhook",
        "description": "Delete a webhook endpoint.",
        "operationId": "deleteWebhook",
        "tags": [
          "Webhooks"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Webhook deleted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Webhook not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/webhooks/{id}/regenerate-secret": {
      "parameters": [
        {
          "$ref": "#/components/parameters/XPubloraClient"
        }
      ],
      "post": {
        "summary": "Regenerate webhook secret",
        "description": "Generate a new secret for a webhook. The old secret will be invalidated.\nMake sure to update your webhook handler with the new secret.\n",
        "operationId": "regenerateWebhookSecret",
        "tags": [
          "Webhooks"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Secret regenerated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "secret": {
                      "type": "string",
                      "description": "New webhook secret"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Webhook not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/linkedin-comments": {
      "parameters": [
        {
          "$ref": "#/components/parameters/XPubloraClient"
        }
      ],
      "post": {
        "summary": "Add a comment to a LinkedIn post",
        "description": "Post a comment on a LinkedIn post. Supports replies to existing comments\nvia the parentComment parameter. Raw input may be up to 10000 characters;\nafter mention processing, the text sent to LinkedIn must be at most 1250 characters.\nSupports @mentions using @{urn:li:person:ID|Name} or @{urn:li:organization:ID|Company} syntax.\n",
        "operationId": "createLinkedInComment",
        "tags": [
          "LinkedIn Analytics"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "postedId",
                  "message",
                  "platformId"
                ],
                "properties": {
                  "postedId": {
                    "type": "string",
                    "description": "LinkedIn post URN (urn:li:share:*, urn:li:ugcPost:*, or urn:li:activity:*). Activity URNs are attempted directly, with same-ID ugcPost/share fallbacks for retryable LinkedIn target errors.",
                    "example": "urn:li:share:7123456789012345678"
                  },
                  "message": {
                    "type": "string",
                    "description": "Raw comment input (max 10000 characters); after mention conversion, the sent text is limited to 1250 characters. Supports @{urn:li:person:ID|Name} and organization mentions.",
                    "example": "Great insights @{urn:li:person:ACoAABcD1234EfG|Jane Smith}! Thanks for sharing."
                  },
                  "platformId": {
                    "type": "string",
                    "description": "LinkedIn connection ID",
                    "example": "linkedin-Tz9W5i6ZYG"
                  },
                  "parentComment": {
                    "type": "string",
                    "description": "Optional parent comment URN for replies",
                    "example": "urn:li:comment:(urn:li:activity:123,456)"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Comment created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "comment": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "description": "Comment URN"
                        },
                        "message": {
                          "type": "string"
                        },
                        "actor": {
                          "type": "string"
                        },
                        "object": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "missingFields": {
                    "value": {
                      "error": "postedId, message, and platformId are required"
                    }
                  },
                  "invalidUrn": {
                    "value": {
                      "error": "postedId must be a valid LinkedIn URN"
                    }
                  },
                  "emptyMessage": {
                    "value": {
                      "error": "message cannot be empty"
                    }
                  },
                  "rawMessageTooLong": {
                    "value": {
                      "error": "comment text cannot exceed 10000 characters"
                    }
                  },
                  "processedMessageTooLong": {
                    "value": {
                      "error": "comment text cannot exceed 1250 characters after mention processing"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "LinkedIn connection not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete a comment from a LinkedIn post",
        "description": "Remove a comment from a LinkedIn post.\nParameters can be provided as query parameters or in the request body.\n",
        "operationId": "deleteLinkedInComment",
        "tags": [
          "LinkedIn Analytics"
        ],
        "parameters": [
          {
            "name": "postedId",
            "in": "query",
            "description": "LinkedIn post URN (can also be provided in request body)",
            "schema": {
              "type": "string"
            },
            "example": "urn:li:share:7123456789012345678"
          },
          {
            "name": "commentId",
            "in": "query",
            "description": "Comment ID or URN (can also be provided in request body)",
            "schema": {
              "type": "string"
            },
            "example": "6636062862760562688"
          },
          {
            "name": "platformId",
            "in": "query",
            "description": "LinkedIn connection ID (can also be provided in request body)",
            "schema": {
              "type": "string"
            },
            "example": "linkedin-Tz9W5i6ZYG"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "postedId": {
                    "type": "string"
                  },
                  "commentId": {
                    "type": "string"
                  },
                  "platformId": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Comment deleted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "deleted": {
                      "type": "string",
                      "description": "Comment ID that was deleted"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "missingFields": {
                    "value": {
                      "error": "postedId, commentId, and platformId are required"
                    }
                  },
                  "invalidUrn": {
                    "value": {
                      "error": "postedId must be a valid LinkedIn URN"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "LinkedIn connection not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/workspace/users": {
      "parameters": [
        {
          "$ref": "#/components/parameters/XPubloraClient"
        }
      ],
      "get": {
        "summary": "List managed users",
        "description": "Retrieve all users managed under your workspace.\n\n**Requires Workspace access - contact serge@publora.com to enable.**\n",
        "operationId": "getManagedUsers",
        "tags": [
          "Workspace"
        ],
        "responses": {
          "200": {
            "description": "List of managed users",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "users": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ManagedUser"
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Workspace access not enabled",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Workspace access is not enabled for this key"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create a managed user",
        "description": "Create a new managed user under your workspace.\nThe user can then connect their social accounts via a connection URL.\n\n**Requires Workspace access - contact serge@publora.com to enable.**\n",
        "operationId": "createManagedUser",
        "tags": [
          "Workspace"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "username"
                ],
                "properties": {
                  "username": {
                    "type": "string",
                    "format": "email",
                    "description": "User's email address (must be unique)",
                    "example": "user@example.com"
                  },
                  "displayName": {
                    "type": "string",
                    "description": "Display name (defaults to email if not provided)",
                    "example": "John Doe"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "User created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "user": {
                      "$ref": "#/components/schemas/ManagedUser"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing required fields",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Email is required"
                }
              }
            }
          },
          "403": {
            "description": "Workspace access not enabled",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "An account with this email already exists. The response includes\n`userId` and membership fields (`attachable`, `requiresConsent`,\n`reason`, `alreadyInWorkspace`, `managedByAnotherWorkspace`). When\n`attachable: true`, attach the user via POST /workspace/users/attach.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "User with this email already exists",
                  "code": "email_exists",
                  "userId": "6626a1f5e4b0c91a2d3f4567",
                  "alreadyInWorkspace": false,
                  "managedByAnotherWorkspace": false,
                  "reason": "standalone_user_exists",
                  "attachable": true,
                  "requiresConsent": true
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/workspace/users/attach": {
      "parameters": [
        {
          "$ref": "#/components/parameters/XPubloraClient"
        }
      ],
      "post": {
        "summary": "Attach an existing user",
        "description": "Attach an existing standalone Publora user to your workspace as a managed\nuser. Because the target account already exists, this requires the target\nuser's consent: a valid access token belonging to that user (the\n`accessToken` returned when they sign in), passed as `userAccessToken`.\nRe-attaching a user who is already in your workspace is idempotent and\ndoes not require a consent token.\n\n**Requires Workspace access - contact serge@publora.com to enable.**\n",
        "operationId": "attachManagedUser",
        "tags": [
          "Workspace"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email",
                    "description": "The existing user's email. Provide either email or userId (also accepted as `username`).",
                    "example": "user@example.com"
                  },
                  "userId": {
                    "type": "string",
                    "description": "The existing user's ObjectId. Provide either email or userId.",
                    "example": "6626a1f5e4b0c91a2d3f4567"
                  },
                  "userAccessToken": {
                    "type": "string",
                    "description": "The target user's access token, proving consent. Required unless the user is already in your workspace. Also accepted as `attachToken` in the body or via the `x-publora-user-token` header."
                  }
                },
                "example": {
                  "email": "user@example.com",
                  "userAccessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "User attached (or already in your workspace)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "user": {
                      "$ref": "#/components/schemas/ManagedUser"
                    },
                    "alreadyInWorkspace": {
                      "type": "boolean",
                      "description": "true if the user was already managed by your workspace (idempotent re-attach)",
                      "example": false
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing/invalid identifier, mismatched email vs userId, or the owner's own account",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Email or userId is required",
                  "code": "missing_identifier"
                }
              }
            }
          },
          "403": {
            "description": "Workspace access not enabled, the target user's consent token is missing/invalid/mismatched, or attaching would exceed the plan's connection limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Target user consent token is required",
                  "code": "attach_consent_required"
                }
              }
            }
          },
          "404": {
            "description": "No active user matches the identifier",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "User not found",
                  "code": "user_not_found"
                }
              }
            }
          },
          "409": {
            "description": "The target user is inactive, a workspace owner, in another workspace, or has an active subscription",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Cannot attach a user with an active subscription",
                  "code": "user_has_active_subscription"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/workspace/users/{userId}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/XPubloraClient"
        }
      ],
      "delete": {
        "summary": "Detach a managed user",
        "description": "Remove a user from your workspace. The user account is not deleted,\nbut is no longer managed by your workspace.\n\n**Requires Workspace access - contact serge@publora.com to enable.**\n",
        "operationId": "detachManagedUser",
        "tags": [
          "Workspace"
        ],
        "parameters": [
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "507f1f77bcf86cd799439011"
          }
        ],
        "responses": {
          "200": {
            "description": "User detached",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid userId",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Workspace access not enabled",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "User not found or not managed by this key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/workspace/users/{userId}/api-key": {
      "parameters": [
        {
          "$ref": "#/components/parameters/XPubloraClient"
        }
      ],
      "post": {
        "summary": "Generate API key for managed user",
        "description": "Generate a new API key for a managed user. This key can be used\nto make API calls on behalf of the managed user (e.g., create posts\nto their connected social accounts).\n\n**Requires Workspace access - contact serge@publora.com to enable.**\n",
        "operationId": "generateManagedUserApiKey",
        "tags": [
          "Workspace"
        ],
        "parameters": [
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "API key generated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string",
                      "example": "API key generated successfully"
                    },
                    "apiKey": {
                      "type": "string",
                      "description": "The generated API key (save it securely - shown only once)",
                      "example": "sk_lx5abc.a1b2c3d4e5f6..."
                    },
                    "userId": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid userId",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Workspace or API access not enabled",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "noWorkspace": {
                    "value": {
                      "error": "Workspace access is not enabled for this key"
                    }
                  },
                  "noApiAccess": {
                    "value": {
                      "error": "API access is not enabled for this workspace owner"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "User not found or not managed by this key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/workspace/users/{userId}/connection-url": {
      "parameters": [
        {
          "$ref": "#/components/parameters/XPubloraClient"
        }
      ],
      "post": {
        "summary": "Get connection URL for managed user",
        "description": "Get or generate a URL that allows a managed user to connect their\nsocial media accounts. The URL expires after a configurable period\n(default: 90 days).\n\n**Requires Workspace access - contact serge@publora.com to enable.**\n",
        "operationId": "getConnectionUrl",
        "tags": [
          "Workspace"
        ],
        "parameters": [
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "rotate": {
                    "type": "boolean",
                    "description": "Force generation of a new token even if current is valid",
                    "default": false
                  },
                  "expiresInDays": {
                    "type": "integer",
                    "description": "Number of days until the token expires",
                    "default": 90,
                    "example": 30
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Connection URL",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "url": {
                      "type": "string",
                      "format": "uri",
                      "description": "URL for user to connect their social accounts",
                      "example": "https://app.publora.com/connect/abc123..."
                    },
                    "tokenExpiresAt": {
                      "type": "string",
                      "format": "date-time",
                      "description": "When the connection URL expires"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid userId",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Workspace access not enabled",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "User not found or not managed by this key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  }
}
