{
  "openapi": "3.1.0",
  "info": {
    "title": "PostLake API",
    "version": "1.0.0",
    "description": "Unified social media API. One API key, 10 platforms, normalised responses. Built for agents and developers.",
    "contact": {
      "url": "https://postlake.dev"
    }
  },
  "servers": [
    {
      "url": "https://api.postlake.dev"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Account",
      "description": "API key validation + account info"
    },
    {
      "name": "Social Accounts",
      "description": "Connect, list, disconnect social accounts"
    },
    {
      "name": "Profiles",
      "description": "Named groupings of social accounts"
    },
    {
      "name": "Posts",
      "description": "Create, list, edit, cancel, schedule posts"
    },
    {
      "name": "Reading",
      "description": "Notifications, comments and followers, normalised across networks"
    },
    {
      "name": "Engaging",
      "description": "Like, repost, follow, block and mute"
    },
    {
      "name": "Messages",
      "description": "Direct message threads"
    },
    {
      "name": "Analytics",
      "description": "Per-post and cross-platform metrics"
    },
    {
      "name": "Media",
      "description": "Upload media for posts"
    },
    {
      "name": "Webhooks",
      "description": "Register and manage webhook endpoints"
    },
    {
      "name": "Credentials",
      "description": "BYO platform app credentials (BYOK)"
    },
    {
      "name": "Connect",
      "description": "Hosted connect/manage page links"
    },
    {
      "name": "Platforms",
      "description": "Machine-readable platform capabilities (public)"
    },
    {
      "name": "MCP",
      "description": "Model Context Protocol JSON-RPC endpoint"
    }
  ],
  "paths": {
    "/v1/me/limits": {
      "get": {
        "tags": [
          "Account"
        ],
        "summary": "What this key may do, and what it can spend",
        "description": "The account, the channels connected right now, and the credit balance with the plan's monthly allowance. Agents calling over MCP also get their own guardrails (allowed profiles and networks, daily cap, posts left today) through the whoami tool. Read this before planning a batch rather than discovering a limit by being refused.",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "account": {
                      "type": "string"
                    },
                    "email": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "credits": {
                      "type": "object",
                      "properties": {
                        "total": {
                          "type": "integer"
                        },
                        "monthly": {
                          "type": "integer"
                        },
                        "pack": {
                          "type": "integer"
                        },
                        "monthlyAllowance": {
                          "type": "integer"
                        },
                        "plan": {
                          "type": "string"
                        },
                        "blockedPlatforms": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        }
                      }
                    },
                    "connected": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "account": {
                            "type": "string"
                          },
                          "platform": {
                            "type": "string"
                          },
                          "handle": {
                            "type": "string"
                          },
                          "status": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/notifications": {
      "get": {
        "tags": [
          "Reading"
        ],
        "summary": "What happened across every network",
        "description": "Likes, replies, mentions, follows, reposts and quotes, newest first, in one normalised shape. `problems` names any network that could not be read, so an empty `items` never silently means 'we could not look'.",
        "parameters": [
          {
            "name": "account",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "One connected account id (acc_…). Omit for every connected network."
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Opaque cursor from a previous page"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "description": "1-100"
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Notification"
                      }
                    },
                    "cursor": {
                      "type": "string",
                      "nullable": true,
                      "description": "Opaque. Pass it back verbatim for the next page; null means the end. Never parse it: on cross-network reads it encodes a position per network."
                    },
                    "problems": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ReadProblem"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/notifications/seen": {
      "post": {
        "tags": [
          "Reading"
        ],
        "summary": "Mark notifications as seen",
        "description": "On the networks that track it. Networks with no concept of 'seen' are skipped.",
        "parameters": [
          {
            "name": "account",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "One connected account id (acc_…). Omit for every connected network."
          }
        ],
        "responses": {
          "200": {
            "description": "Success"
          }
        }
      }
    },
    "/v1/posts/{id}/comments": {
      "get": {
        "tags": [
          "Reading"
        ],
        "summary": "Replies on a post you published",
        "description": "Across every network the post went to.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "nested",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            },
            "description": "Read the whole thread including replies to replies. Networks that cannot go deeper answer with the top level rather than refusing."
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Opaque cursor from a previous page"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "description": "1-100"
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Comment"
                      }
                    },
                    "cursor": {
                      "type": "string",
                      "nullable": true,
                      "description": "Opaque. Pass it back verbatim for the next page; null means the end. Never parse it: on cross-network reads it encodes a position per network."
                    },
                    "problems": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ReadProblem"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/discover/posts": {
      "get": {
        "tags": [
          "Discovery"
        ],
        "summary": "Search public posts",
        "description": "Search the network itself rather than your own posts, so an agent can see what is already being said before it writes. Runs on every connected network that supports searching; the ones that cannot are named in `problems`.",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "What to search for"
          },
          {
            "name": "mode",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "keyword",
                "tag"
              ]
            },
            "description": "Search the words (default) or a topic tag"
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "top",
                "recent"
              ]
            },
            "description": "Best match (default) or newest first"
          },
          {
            "name": "mediaType",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "text",
                "image",
                "video"
              ]
            }
          },
          {
            "name": "author",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Only this handle's posts"
          },
          {
            "name": "since",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "until",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "account",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Search one connection only. Omit to search them all."
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "description": "1-100"
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/DiscoveredPost"
                      }
                    },
                    "cursor": {
                      "type": "string",
                      "nullable": true,
                      "description": "Opaque. Pass it back verbatim; null means the end."
                    },
                    "problems": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ReadProblem"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/discover/profiles/{handle}": {
      "get": {
        "tags": [
          "Discovery"
        ],
        "summary": "Look someone up",
        "description": "One network only: the same handle on two networks is usually two different people, so merging them would invent someone who does not exist. Counts are null where the network does not publish them, which is not the same as zero.",
        "parameters": [
          {
            "name": "handle",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "With or without the @"
          },
          {
            "name": "account",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Which connection to look on"
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicProfile"
                }
              }
            }
          }
        }
      }
    },
    "/v1/discover/profiles/{handle}/posts": {
      "get": {
        "tags": [
          "Discovery"
        ],
        "summary": "Someone else's public posts",
        "parameters": [
          {
            "name": "handle",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "account",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "description": "1-100"
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/DiscoveredPost"
                      }
                    },
                    "cursor": {
                      "type": "string",
                      "nullable": true
                    },
                    "problems": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ReadProblem"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/discover/creators": {
      "get": {
        "tags": [
          "Discovery"
        ],
        "summary": "Find creators to work with",
        "description": "Search the network's creator marketplace for people a brand could partner with. Instagram needs a connection made through Facebook. IMPORTANT: until Meta grants the app Advanced Access, the results are SIMULATED creators for testing, and every row carries `sample: true` to say so. Acting on a sample means pitching a partnership to somebody who does not exist, so check it before you use a result.",
        "parameters": [
          {
            "name": "account",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Which connected account to search from, so we know which network."
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Free-text search."
          },
          {
            "name": "countries",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated country codes."
          },
          {
            "name": "interests",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated content categories."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Creators",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "handle": {
                            "type": "string"
                          },
                          "displayName": {
                            "type": "string",
                            "nullable": true
                          },
                          "avatarUrl": {
                            "type": "string",
                            "nullable": true
                          },
                          "followers": {
                            "type": "integer",
                            "nullable": true
                          },
                          "bio": {
                            "type": "string",
                            "nullable": true
                          },
                          "categories": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          },
                          "sample": {
                            "type": "boolean",
                            "description": "True when this is simulated test data rather than a real creator."
                          }
                        },
                        "required": [
                          "id",
                          "handle",
                          "sample"
                        ]
                      }
                    },
                    "cursor": {
                      "type": "string",
                      "nullable": true
                    },
                    "platform": {
                      "$ref": "#/components/schemas/Platform"
                    }
                  },
                  "required": [
                    "items",
                    "cursor",
                    "platform"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/discover/places": {
      "get": {
        "tags": [
          "Discovery"
        ],
        "summary": "Find a place to tag on a post",
        "description": "Pass a name, or a latitude and longitude together. The id returned goes in the post's `locationId` option, and only works on the network it came from.",
        "parameters": [
          {
            "name": "account",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "latitude",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number"
            },
            "description": "Use with longitude"
          },
          {
            "name": "longitude",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number"
            },
            "description": "Use with latitude"
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Place"
                      }
                    },
                    "platform": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/comments/{id}/hide": {
      "post": {
        "tags": [
          "Reading"
        ],
        "summary": "Hide or unhide a reply",
        "description": "The other half of moderating a comment section. Without it the only answer to an abusive reply is to reply to it. A failure means the reply is still visible, so treat an error as \"still there\" rather than assuming it worked.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The comment id"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "account"
                ],
                "properties": {
                  "account": {
                    "type": "string",
                    "description": "The connection the post is on"
                  },
                  "hidden": {
                    "type": "boolean",
                    "default": true,
                    "description": "false puts it back"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "account": {
                      "type": "string"
                    },
                    "platform": {
                      "type": "string"
                    },
                    "hidden": {
                      "type": "boolean"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/comments/{id}": {
      "delete": {
        "tags": [
          "Reading"
        ],
        "summary": "Delete a comment",
        "description": "Take a comment down for good. Facebook cannot hide a Page's own comment, so this is how you retract one you posted. A failure means it is still there.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The comment id"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "account"
                ],
                "properties": {
                  "account": {
                    "type": "string",
                    "description": "The connection the post is on"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "account": {
                      "type": "string"
                    },
                    "platform": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/social-accounts/{id}/following": {
      "get": {
        "tags": [
          "Reading"
        ],
        "summary": "Who a connected account follows",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SocialActor"
                      }
                    },
                    "cursor": {
                      "type": "string",
                      "nullable": true
                    },
                    "problems": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ReadProblem"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/social-accounts/{id}/profile": {
      "patch": {
        "tags": [
          "Account"
        ],
        "summary": "Edit a connected account's own profile",
        "description": "Change the display name, bio, avatar or banner. Only the fields you send are changed. Images are given as public URLs and uploaded for you. A network that does not allow this refuses.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "displayName": {
                    "type": "string"
                  },
                  "bio": {
                    "type": "string"
                  },
                  "avatarUrl": {
                    "type": "string"
                  },
                  "bannerUrl": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "account": {
                      "type": "string"
                    },
                    "platform": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/social-accounts/{id}/followers": {
      "get": {
        "tags": [
          "Reading"
        ],
        "summary": "Who follows a connected account",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Opaque cursor from a previous page"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "description": "1-100"
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SocialActor"
                      }
                    },
                    "cursor": {
                      "type": "string",
                      "nullable": true,
                      "description": "Opaque. Pass it back verbatim for the next page; null means the end. Never parse it: on cross-network reads it encodes a position per network."
                    },
                    "problems": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ReadProblem"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/comments/{id}/replies": {
      "post": {
        "tags": [
          "Engaging"
        ],
        "summary": "Reply to a comment",
        "description": "Answer a comment someone left, by that comment's id (from GET /v1/posts/{id}/comments). Distinct from publishing: this acts on someone else's content. Free everywhere except X, where a reply is billed as a post.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The comment id"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "account",
                  "text"
                ],
                "properties": {
                  "account": {
                    "type": "string",
                    "description": "The connected account id (acc_…) replying"
                  },
                  "text": {
                    "type": "string",
                    "description": "The reply"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Posted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "account": {
                      "type": "string"
                    },
                    "platform": {
                      "type": "string"
                    },
                    "id": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/engagements": {
      "post": {
        "tags": [
          "Engaging"
        ],
        "summary": "Like, repost, follow, block or mute",
        "description": "One endpoint for every engagement. Free: engaging never spends credits. A network that does not support the action refuses and names what it does support, so nothing silently does nothing.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "account",
                  "action",
                  "target"
                ],
                "properties": {
                  "account": {
                    "type": "string",
                    "description": "The connected account id (acc_…) acting"
                  },
                  "action": {
                    "type": "string",
                    "enum": [
                      "like",
                      "unlike",
                      "repost",
                      "unrepost",
                      "follow",
                      "unfollow",
                      "block",
                      "unblock",
                      "mute",
                      "unmute"
                    ]
                  },
                  "target": {
                    "type": "string",
                    "description": "A post uri/url for like and repost; a handle for follow, block and mute"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Done",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "account": {
                      "type": "string"
                    },
                    "platform": {
                      "type": "string"
                    },
                    "action": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    },
                    "id": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/conversations": {
      "get": {
        "tags": [
          "Messages"
        ],
        "summary": "Direct message threads",
        "parameters": [
          {
            "name": "account",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "One connected account id (acc_…). Omit for every connected network."
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Opaque cursor from a previous page"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "description": "1-100"
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Conversation"
                      }
                    },
                    "cursor": {
                      "type": "string",
                      "nullable": true,
                      "description": "Opaque. Pass it back verbatim for the next page; null means the end. Never parse it: on cross-network reads it encodes a position per network."
                    },
                    "problems": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ReadProblem"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Messages"
        ],
        "summary": "Open a conversation with someone",
        "description": "Finds or starts the thread with a handle, so a first message does not need an id that does not exist yet. Sends nothing.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "account",
                  "handle"
                ],
                "properties": {
                  "account": {
                    "type": "string"
                  },
                  "handle": {
                    "type": "string",
                    "description": "e.g. alice.bsky.social"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The conversation",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Conversation"
                }
              }
            }
          }
        }
      }
    },
    "/v1/conversations/{id}/messages": {
      "get": {
        "tags": [
          "Messages"
        ],
        "summary": "Messages in one thread",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "account",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Required: a conversation id only means something on one connection"
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Opaque cursor from a previous page"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "description": "1-100"
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Message"
                      }
                    },
                    "cursor": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Messages"
        ],
        "summary": "Send a direct message",
        "description": "Free: messaging never spends credits.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "account",
                  "text"
                ],
                "properties": {
                  "account": {
                    "type": "string"
                  },
                  "text": {
                    "type": "string"
                  },
                  "humanAgent": {
                    "type": "boolean",
                    "description": "Assert that a PERSON wrote this reply. Meta allows a reply within 24 hours of someone's last message; the Human Agent tag extends that to 7 days and is granted only for replies a human composed. Never set it for an automated reply: the account carries the penalty, not the caller."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sent",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Message"
                }
              }
            }
          }
        }
      }
    },
    "/v1/conversations/{id}/read": {
      "post": {
        "tags": [
          "Messages"
        ],
        "summary": "Mark a conversation read",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "account"
                ],
                "properties": {
                  "account": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success"
          }
        }
      }
    },
    "/v1/me": {
      "get": {
        "tags": [
          "Account"
        ],
        "summary": "Get current account",
        "description": "Validate API key and echo account info, including the optional default timezone.",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "email": {
                      "type": "string"
                    },
                    "platforms": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Platform"
                      }
                    },
                    "timezone": {
                      "type": "string",
                      "description": "IANA default for naive scheduledAt values, when set."
                    }
                  },
                  "required": [
                    "id",
                    "email",
                    "platforms"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "patch": {
        "tags": [
          "Account"
        ],
        "summary": "Update current account",
        "description": "Set or clear the account default timezone used for naive scheduledAt values.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "timezone": {
                    "type": "string",
                    "description": "IANA name such as Europe/London. Empty string or null clears the default.",
                    "nullable": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "email": {
                      "type": "string"
                    },
                    "platforms": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Platform"
                      }
                    },
                    "timezone": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "id",
                    "email",
                    "platforms"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Invalid timezone"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/v1/account/export": {
      "get": {
        "tags": [
          "Account"
        ],
        "summary": "Export account data",
        "description": "GDPR data export. All data for the account (no secrets).",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "account": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "email": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "id",
                        "email"
                      ]
                    },
                    "profiles": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Profile"
                      }
                    },
                    "socialAccounts": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SocialAccount"
                      }
                    },
                    "posts": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Post"
                      }
                    },
                    "auditLog": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AuditEvent"
                      }
                    },
                    "exportedAt": {
                      "type": "string",
                      "format": "date-time"
                    }
                  },
                  "required": [
                    "account",
                    "profiles",
                    "socialAccounts",
                    "posts",
                    "auditLog",
                    "exportedAt"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/v1/audit": {
      "get": {
        "tags": [
          "Account"
        ],
        "summary": "Get audit log",
        "description": "Security audit trail (logins, key creation, disconnects). Returns up to 100 events.",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "events": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AuditEvent"
                      }
                    }
                  },
                  "required": [
                    "events"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/v1/social-accounts/connect": {
      "post": {
        "tags": [
          "Social Accounts"
        ],
        "summary": "Connect social account",
        "description": "Connect a social account to PostLake",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "platform": {
                    "$ref": "#/components/schemas/Platform"
                  },
                  "profile": {
                    "type": "string",
                    "description": "Optional profile username to attach to"
                  },
                  "handle": {
                    "type": "string",
                    "description": "Platform-specific handle"
                  },
                  "appPassword": {
                    "type": "string",
                    "description": "Platform-specific password (e.g. for Bluesky)"
                  }
                },
                "required": [
                  "platform"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "platform": {
                      "$ref": "#/components/schemas/Platform"
                    },
                    "handle": {
                      "type": "string"
                    },
                    "profileId": {
                      "oneOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "status": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "id",
                    "platform",
                    "handle",
                    "profileId",
                    "status"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Invalid input"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "Entitlement exceeded"
          },
          "502": {
            "description": "Unsupported platform"
          }
        }
      }
    },
    "/v1/social-accounts": {
      "get": {
        "tags": [
          "Social Accounts"
        ],
        "summary": "List social accounts",
        "description": "List connected social accounts. Cursor-paginated.",
        "parameters": [
          {
            "name": "profile",
            "in": "query",
            "description": "Filter by profile username",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Page size, 1-100 (default 50).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "description": "Opaque cursor from the previous response's `nextCursor`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "accounts": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SocialAccount"
                      }
                    },
                    "nextCursor": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Pass this back as `cursor` to fetch the next page. `null` when there are no more results."
                    }
                  },
                  "required": [
                    "accounts",
                    "nextCursor"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Profile not found"
          }
        }
      }
    },
    "/v1/social-accounts/{id}/targets": {
      "get": {
        "tags": [
          "Social Accounts"
        ],
        "summary": "List social account targets",
        "description": "List postable destinations within an account (Pinterest boards, FB Pages)",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "targets": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "id",
                          "name"
                        ]
                      }
                    }
                  },
                  "required": [
                    "targets"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/v1/profiles/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "patch": {
        "tags": [
          "Profiles"
        ],
        "summary": "Rename profile",
        "description": "Rename a profile. The id is unchanged, so connected channels and posts are untouched, but the slugified identifier moves with the name. The response reports `previousUsername` and `usernameChanged` so anything addressing the profile by its old name can be updated.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  }
                },
                "required": [
                  "name"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Renamed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "profile": {
                      "$ref": "#/components/schemas/Profile"
                    },
                    "previousUsername": {
                      "type": "string"
                    },
                    "usernameChanged": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid name, no such profile, or the name is taken"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "delete": {
        "tags": [
          "Profiles"
        ],
        "summary": "Delete profile",
        "description": "Delete a profile. This ALSO disconnects every channel in it, and reconnecting each one requires its owner to approve access on that network again. It therefore refuses with 400 while channels are attached, naming them, unless `?force=true` is passed.",
        "parameters": [
          {
            "name": "force",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            },
            "description": "Go ahead even though channels will be disconnected."
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "deleted": {
                      "type": "boolean"
                    },
                    "disconnected": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "The channels that went with it."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "No such profile, or channels are attached and force was not set"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/v1/email-preferences": {
      "get": {
        "tags": [
          "Account"
        ],
        "summary": "Read email preferences",
        "description": "How often this account receives the attention summary: the one email PostLake sends on its own schedule, when a channel has stopped working or a scheduled post did not go out. Account and security email (verify, password reset, new sign-in, payment failed) always sends and is not covered here.",
        "responses": {
          "200": {
            "description": "Current preferences",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmailPreferences"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "patch": {
        "tags": [
          "Account"
        ],
        "summary": "Set email frequency",
        "description": "Set how often the attention summary may arrive. `daily` sends at most once a day and only on days something is actually wrong; `weekly` gathers anything still unresolved into at most one email a week; `off` stops it entirely. There is deliberately no MCP tool for this: these emails are how a person learns a post did not go out, and an agent that could switch them off could hide a failure it caused.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "digestFrequency"
                ],
                "properties": {
                  "digestFrequency": {
                    "type": "string",
                    "enum": [
                      "off",
                      "weekly",
                      "daily"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated preferences",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmailPreferences"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/v1/profiles": {
      "post": {
        "tags": [
          "Profiles"
        ],
        "summary": "Create profile",
        "description": "Create a named profile. Send `name` as a person would write it (spaces and capitals are fine); it is slugified into the identifier agents address the profile by, and the original is kept for display. `username` is still accepted as an alias for `name`.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "e.g. \"Otaku Gems News\", stored as otaku-gems-news"
                  },
                  "username": {
                    "type": "string",
                    "description": "Alias for name, kept for compatibility."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Profile"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "get": {
        "tags": [
          "Profiles"
        ],
        "summary": "List profiles",
        "description": "List profiles",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "profiles": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Profile"
                      }
                    }
                  },
                  "required": [
                    "profiles"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/v1/platforms": {
      "get": {
        "tags": [
          "Platforms"
        ],
        "summary": "Platform capabilities (public)",
        "description": "Machine-readable capabilities per platform: character limits, media rules (required / video-only / max images / formats / sizes), whether publishing is asynchronous, and every platformOptions field with its valid values. No authentication required.",
        "security": [],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "platforms": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PlatformCapabilities"
                      }
                    }
                  },
                  "required": [
                    "platforms"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/platforms/{platform}": {
      "get": {
        "tags": [
          "Platforms"
        ],
        "summary": "One platform's capabilities (public)",
        "security": [],
        "parameters": [
          {
            "name": "platform",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlatformCapabilities"
                }
              }
            }
          },
          "404": {
            "description": "Unknown platform"
          }
        }
      }
    },
    "/v1/posts/validate": {
      "post": {
        "tags": [
          "Posts"
        ],
        "summary": "Validate a post (dry run)",
        "description": "Runs the exact validation a publish would run, covering account resolution, media resolution and capability-registry rules, without touching any platform. Returns per-target errors (blocking) and warnings (advisory). Free to call before POST /v1/posts.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "text": {
                    "type": "string"
                  },
                  "profile": {
                    "type": "string",
                    "description": "A profile name. Resolves to every account it owns. The simple way to address accounts; combine with `platforms` to narrow it. Provide `profile`, `accounts`, or both."
                  },
                  "platforms": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/Platform"
                    },
                    "description": "Optional filter. Keeps only these networks from the resolved set."
                  },
                  "accounts": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Connected account ids (acc_…). An alternative (or addition) to `profile`."
                  },
                  "media": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "scheduledAt": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "timezone": {
                    "type": "string",
                    "description": "IANA timezone (e.g. Europe/London). Interprets a naive scheduledAt as wall time in that zone. Stored fire time is always UTC."
                  },
                  "platformOptions": {
                    "type": "object",
                    "additionalProperties": true
                  }
                },
                "required": [
                  "text"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Validation result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "targets": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "account": {
                            "type": "string"
                          },
                          "platform": {
                            "$ref": "#/components/schemas/Platform"
                          },
                          "ok": {
                            "type": "boolean"
                          },
                          "errors": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            },
                            "description": "Blocking problems, as sentences. See `issues` for the actionable form."
                          },
                          "warnings": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            },
                            "description": "Advisory: the post publishes, but something changes (e.g. images dropped)."
                          },
                          "issues": {
                            "type": "array",
                            "description": "The same code / fix / param a real publish would return, so a dry run teaches exactly what the live call teaches.",
                            "items": {
                              "type": "object",
                              "properties": {
                                "code": {
                                  "type": "string"
                                },
                                "message": {
                                  "type": "string"
                                },
                                "fix": {
                                  "type": "string"
                                },
                                "param": {
                                  "type": "string"
                                },
                                "docs": {
                                  "type": "string",
                                  "format": "uri"
                                }
                              },
                              "required": [
                                "code",
                                "message",
                                "fix",
                                "docs"
                              ]
                            }
                          }
                        },
                        "required": [
                          "account",
                          "platform",
                          "ok",
                          "errors",
                          "warnings",
                          "issues"
                        ]
                      }
                    }
                  },
                  "required": [
                    "ok",
                    "targets"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Invalid input"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/v1/social-accounts/{id}/webhook": {
      "post": {
        "tags": [
          "Channels"
        ],
        "summary": "Subscribe this channel to webhooks",
        "description": "Some networks need the subscription on the ACCOUNT as well as the app. Facebook is the case: subscribing the app is not enough, the Page has to be subscribed too or nothing is ever delivered, however well the callback is configured. Defaults to feed, mention and messages.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "fields": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Which events to subscribe to. Defaults to feed, mention, messages."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Subscribed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "subscribed": {
                      "type": "boolean"
                    },
                    "fields": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  },
                  "required": [
                    "subscribed",
                    "fields"
                  ]
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "Channels"
        ],
        "summary": "What this channel is subscribed to",
        "description": "The events this channel currently receives, so \"we are not getting webhooks\" has an answer. A network that does not need a per account subscription returns an empty list.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Subscriptions",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "fields": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  },
                  "required": [
                    "fields"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/social-accounts/{id}/posts": {
      "get": {
        "tags": [
          "Channels"
        ],
        "summary": "This channel's own posts, from the network",
        "description": "The account's own posts read live from the network, including ones published outside PostLake. Different from GET /v1/posts, which lists what PostLake itself published: an account with existing history has posts we never sent, and this is how to reach them.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Posts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/DiscoveredPost"
                      }
                    },
                    "cursor": {
                      "type": "string",
                      "nullable": true
                    },
                    "platform": {
                      "$ref": "#/components/schemas/Platform"
                    }
                  },
                  "required": [
                    "items",
                    "cursor",
                    "platform"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/social-accounts/{id}/tagged": {
      "get": {
        "tags": [
          "Channels"
        ],
        "summary": "Posts other people tagged you in",
        "description": "Media published by SOMEBODY ELSE that tagged this account. Different from a mention, which names you in text, and different from your own posts. This is what other people said about you, which is usually the thing worth watching.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Tagged posts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/DiscoveredPost"
                      }
                    },
                    "cursor": {
                      "type": "string",
                      "nullable": true
                    },
                    "platform": {
                      "$ref": "#/components/schemas/Platform"
                    }
                  },
                  "required": [
                    "items",
                    "cursor",
                    "platform"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/social-accounts/{id}/allowance": {
      "get": {
        "tags": [
          "Channels"
        ],
        "summary": "Publishing headroom left",
        "description": "How many posts this channel has left in the network's own rolling window, read from the network rather than counted locally. Check it before planning a batch: the alternative is discovering the ceiling by being refused partway through one, with some posts already live and some not.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Allowance",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "used": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "remaining": {
                      "type": "integer"
                    },
                    "windowHours": {
                      "type": "integer",
                      "description": "The window the count covers, so \"5 left\" means something."
                    },
                    "platform": {
                      "$ref": "#/components/schemas/Platform"
                    }
                  },
                  "required": [
                    "used",
                    "limit",
                    "remaining",
                    "windowHours",
                    "platform"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/social-accounts/{id}/ad-accounts": {
      "get": {
        "tags": [
          "Channels"
        ],
        "summary": "Ad accounts this channel can use",
        "description": "The ad accounts this social account has authorised, so a promotion runs against the right one instead of a guess. Most accounts have authorised none, which comes back as an empty list rather than an error. Instagram needs a connection made through Facebook.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Ad accounts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "business": {
                      "type": "object",
                      "nullable": true,
                      "description": "The business that owns the Page behind this channel. Named because several things hang off it: creator marketplace requires THIS business to be verified, and ad accounts belong to it. Without it, checking the right business in Business Settings is guesswork.",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string",
                          "nullable": true
                        },
                        "verified": {
                          "type": "boolean",
                          "nullable": true,
                          "description": "Null when the network does not say."
                        }
                      }
                    },
                    "adAccounts": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "currency": {
                            "type": "string",
                            "nullable": true
                          },
                          "status": {
                            "type": "string",
                            "nullable": true,
                            "description": "Only an active account can be spent against."
                          }
                        },
                        "required": [
                          "id",
                          "name"
                        ]
                      }
                    }
                  },
                  "required": [
                    "adAccounts"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/social-accounts/{id}/branded-partners": {
      "get": {
        "tags": [
          "Channels"
        ],
        "summary": "Branded content partners",
        "description": "The creators a brand has approved to tag it in branded content, or the brands a creator has approved. `canPromote` is a SEPARATE grant that lets the partner run the post as an ad; being allowed to tag does not imply it. Instagram needs a connection made through Facebook.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Partners",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "partners": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "handle": {
                            "type": "string"
                          },
                          "canPromote": {
                            "type": "boolean"
                          }
                        },
                        "required": [
                          "id",
                          "handle",
                          "canPromote"
                        ]
                      }
                    }
                  },
                  "required": [
                    "partners"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/social-accounts/{id}/events": {
      "post": {
        "tags": [
          "Channels"
        ],
        "summary": "Create a scheduled event",
        "description": "Create an event on the account, where the network allows it through the API. Instagram calls this a reminder and normally expects it to be added while composing a post in the app; this asks the API directly, and a network that refuses says so in its own words. The returned id goes in the `upcomingEventId` post option.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "title",
                  "startsAt"
                ],
                "properties": {
                  "title": {
                    "type": "string"
                  },
                  "startsAt": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "endsAt": {
                    "type": "string",
                    "format": "date-time"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "title": {
                      "type": "string"
                    },
                    "startsAt": {
                      "type": "string",
                      "nullable": true
                    },
                    "endsAt": {
                      "type": "string",
                      "nullable": true
                    }
                  },
                  "required": [
                    "id",
                    "title"
                  ]
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "Channels"
        ],
        "summary": "Scheduled events on this channel",
        "description": "Events scheduled on the account (a launch, a drop, a live), whose id goes in the `upcomingEventId` post option to put a reminder button on the post. Events are created in the network's own app; this reads them. Instagram needs a connection made through Facebook. A network without events returns an empty list.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Events",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "description": "Pass this in the upcomingEventId post option."
                          },
                          "title": {
                            "type": "string"
                          },
                          "startsAt": {
                            "type": "string",
                            "nullable": true
                          },
                          "endsAt": {
                            "type": "string",
                            "nullable": true
                          }
                        },
                        "required": [
                          "id",
                          "title"
                        ]
                      }
                    },
                    "cursor": {
                      "type": "string",
                      "nullable": true
                    }
                  },
                  "required": [
                    "items",
                    "cursor"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/social-accounts/{id}/products": {
      "get": {
        "tags": [
          "Channels"
        ],
        "summary": "Shoppable products on this channel",
        "description": "The account's own shop catalogue, so you can find the product ids the `productIds` post option takes. Only networks with a shop answer; anything else returns an empty list rather than an error, because \"nothing to tag\" is a true answer to the question. Instagram needs an approved Shop and a catalogue on the same business.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Filter by product name."
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Products",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "description": "Pass this in the productIds post option."
                          },
                          "name": {
                            "type": "string"
                          },
                          "imageUrl": {
                            "type": "string",
                            "nullable": true
                          },
                          "price": {
                            "type": "string",
                            "nullable": true
                          },
                          "status": {
                            "type": "string",
                            "nullable": true,
                            "description": "Only approved products can be tagged on a post."
                          }
                        },
                        "required": [
                          "id",
                          "name"
                        ]
                      }
                    },
                    "cursor": {
                      "type": "string",
                      "nullable": true
                    }
                  },
                  "required": [
                    "items",
                    "cursor"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/social-accounts/{id}/publish-info": {
      "get": {
        "tags": [
          "Social Accounts"
        ],
        "summary": "Creator-level publish constraints",
        "description": "Live constraints for one connected account (TikTok creator_info): available privacy options, whether comments/duet/stitch are allowed, and this creator's max video duration. Render post UIs from this. 404 for platforms without creator-level constraints.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "platform": {
                      "$ref": "#/components/schemas/Platform"
                    },
                    "username": {
                      "type": "string"
                    },
                    "nickname": {
                      "type": "string"
                    },
                    "avatarUrl": {
                      "type": "string"
                    },
                    "privacyOptions": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "commentDisabled": {
                      "type": "boolean"
                    },
                    "duetDisabled": {
                      "type": "boolean"
                    },
                    "stitchDisabled": {
                      "type": "boolean"
                    },
                    "maxVideoDurationSec": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "platform"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Unknown account or no creator-level constraints"
          }
        }
      }
    },
    "/v1/posts": {
      "post": {
        "tags": [
          "Posts"
        ],
        "summary": "Publish or schedule post",
        "description": "Publish or schedule a post",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Idempotency Key",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "text": {
                    "type": "string"
                  },
                  "profile": {
                    "type": "string",
                    "description": "A profile name. Resolves to every account it owns. The simple way to address accounts; combine with `platforms` to narrow it. Provide `profile`, `accounts`, or both."
                  },
                  "platforms": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/Platform"
                    },
                    "description": "Optional filter. Keeps only these networks from the resolved set (e.g. profile \"my-brand\" + platforms [\"bluesky\"])."
                  },
                  "accounts": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Connected account ids (acc_…). An alternative (or addition) to `profile`."
                  },
                  "media": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "scheduledAt": {
                    "type": "string",
                    "format": "date-time",
                    "description": "UTC with a trailing Z, or a naive local time together with timezone."
                  },
                  "timezone": {
                    "type": "string",
                    "description": "IANA timezone (e.g. Europe/London). Interprets a naive scheduledAt as wall time in that zone."
                  },
                  "platformOptions": {
                    "type": "object",
                    "properties": {
                      "pinterest": {
                        "type": "object",
                        "properties": {
                          "boardId": {
                            "type": "string"
                          },
                          "link": {
                            "type": "string"
                          },
                          "altText": {
                            "type": "string"
                          }
                        }
                      },
                      "tiktok": {
                        "type": "object",
                        "description": "See GET /v1/platforms/tiktok for the full option set and valid values.",
                        "properties": {
                          "mode": {
                            "type": "string",
                            "enum": [
                              "direct",
                              "inbox"
                            ],
                            "description": "direct = publish now; inbox = send to the creator's TikTok inbox as a draft (finished inside the TikTok app)."
                          },
                          "privacyLevel": {
                            "type": "string",
                            "enum": [
                              "PUBLIC_TO_EVERYONE",
                              "MUTUAL_FOLLOW_FRIENDS",
                              "FOLLOWER_OF_CREATOR",
                              "SELF_ONLY"
                            ]
                          },
                          "title": {
                            "type": "string",
                            "maxLength": 90,
                            "description": "Photo-post title (video posts use `text` as the caption)."
                          },
                          "allowComment": {
                            "type": "boolean"
                          },
                          "allowDuet": {
                            "type": "boolean"
                          },
                          "allowStitch": {
                            "type": "boolean"
                          },
                          "coverTimestampMs": {
                            "type": "integer"
                          },
                          "photoCoverIndex": {
                            "type": "integer"
                          },
                          "autoAddMusic": {
                            "type": "boolean",
                            "default": true
                          },
                          "brandContent": {
                            "type": "boolean"
                          },
                          "brandOrganic": {
                            "type": "boolean"
                          },
                          "isAigc": {
                            "type": "boolean"
                          }
                        }
                      }
                    }
                  }
                },
                "required": [
                  "text"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Post"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "Entitlement exceeded"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "get": {
        "tags": [
          "Posts"
        ],
        "summary": "List posts",
        "description": "List posts (most recent first)",
        "parameters": [
          {
            "name": "state",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/PostState"
            }
          },
          {
            "name": "account",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Filter to posts that targeted this connected account id (acc_…)."
          },
          {
            "name": "profile",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Filter to posts that targeted a channel in this profile (username)."
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "posts": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Post"
                      }
                    },
                    "nextCursor": {
                      "oneOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    }
                  },
                  "required": [
                    "posts",
                    "nextCursor"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/v1/posts/{id}": {
      "get": {
        "tags": [
          "Posts"
        ],
        "summary": "Get post",
        "description": "Get a single post by id",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Post"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Post not found"
          }
        }
      },
      "patch": {
        "tags": [
          "Posts"
        ],
        "summary": "Edit scheduled post",
        "description": "Edit a scheduled post's text, media, platformOptions and/or fire time. Destinations stay fixed. Media and options are re-validated.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "text": {
                    "type": "string"
                  },
                  "textOverrides": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "string"
                    }
                  },
                  "scheduledAt": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "timezone": {
                    "type": "string",
                    "description": "IANA timezone (e.g. Europe/London)."
                  },
                  "media": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "mediaAlt": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "mediaOverrides": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  },
                  "mediaAltOverrides": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  },
                  "platformOptions": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Merged per platform, so a TikTok title patch does not wipe other TikTok fields."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Post"
                }
              }
            }
          },
          "400": {
            "description": "Only scheduled posts can be edited or invalid input"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Post not found"
          }
        }
      },
      "delete": {
        "tags": [
          "Posts"
        ],
        "summary": "Cancel scheduled post",
        "description": "Cancel a scheduled post",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "cancelled": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "id",
                    "cancelled"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Only scheduled posts can be cancelled"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Post not found"
          }
        }
      }
    },
    "/v1/posts/{id}/analytics": {
      "get": {
        "tags": [
          "Analytics"
        ],
        "summary": "Get post analytics",
        "description": "Per-post normalised analytics",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "postId": {
                      "type": "string"
                    },
                    "createdAt": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "totals": {
                      "$ref": "#/components/schemas/Metrics"
                    },
                    "targets": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "account": {
                            "type": "string"
                          },
                          "platform": {
                            "$ref": "#/components/schemas/Platform"
                          },
                          "remoteId": {
                            "oneOf": [
                              {
                                "type": "string"
                              },
                              {
                                "type": "null"
                              }
                            ]
                          },
                          "url": {
                            "oneOf": [
                              {
                                "type": "string"
                              },
                              {
                                "type": "null"
                              }
                            ]
                          },
                          "metrics": {
                            "oneOf": [
                              {
                                "$ref": "#/components/schemas/Metrics"
                              },
                              {
                                "type": "null"
                              }
                            ]
                          }
                        },
                        "required": [
                          "account",
                          "platform",
                          "remoteId",
                          "url",
                          "metrics"
                        ]
                      }
                    }
                  },
                  "required": [
                    "postId",
                    "createdAt",
                    "totals",
                    "targets"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Post not found"
          }
        }
      }
    },
    "/v1/analytics": {
      "get": {
        "tags": [
          "Analytics"
        ],
        "summary": "Get cross-platform analytics",
        "description": "Cross-platform analytics over a period",
        "parameters": [
          {
            "name": "period",
            "in": "query",
            "schema": {
              "type": "string",
              "default": "30d",
              "pattern": "^[0-9]+d$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "period": {
                      "type": "string"
                    },
                    "since": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "posts": {
                      "type": "integer"
                    },
                    "totals": {
                      "$ref": "#/components/schemas/Metrics"
                    },
                    "byPlatform": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "platform": {
                            "$ref": "#/components/schemas/Platform"
                          },
                          "posts": {
                            "type": "integer"
                          },
                          "available": {
                            "type": "boolean"
                          },
                          "metrics": {
                            "$ref": "#/components/schemas/Metrics"
                          }
                        },
                        "required": [
                          "platform",
                          "posts",
                          "available",
                          "metrics"
                        ]
                      }
                    }
                  },
                  "required": [
                    "period",
                    "since",
                    "posts",
                    "totals",
                    "byPlatform"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/media/upload": {
      "put": {
        "tags": [
          "Media"
        ],
        "summary": "Complete an MCP local-file upload",
        "description": "Upload raw file bytes to the short-lived target returned by the MCP upload_media tool when called without a URL. Send the returned upload token as `Authorization: Bearer …` and the exact returned Content-Type. This endpoint does not accept a PostLake API key and is not called until upload_media has prepared a media id and target.",
        "requestBody": {
          "required": true,
          "content": {
            "image/jpeg": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            },
            "image/png": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            },
            "image/webp": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            },
            "image/gif": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            },
            "video/mp4": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            },
            "video/quicktime": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            },
            "video/webm": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Uploaded; returns the MediaAsset whose id is ready for create_post",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MediaAsset"
                }
              }
            }
          },
          "400": {
            "description": "Content type, byte signature, exact size, or media limits did not match the prepared target"
          },
          "401": {
            "description": "Upload token is missing, invalid, or expired"
          },
          "409": {
            "description": "The upload target has already been used"
          }
        }
      }
    },
    "/v1/media": {
      "post": {
        "tags": [
          "Media"
        ],
        "summary": "Upload media",
        "description": "Upload media, either as raw binary with Content-Type set, or as multipart/form-data with a file part. For images the response includes the pixel dimensions, which are checked against each network's limits at publish time.",
        "requestBody": {
          "required": true,
          "content": {
            "application/octet-stream": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            },
            "image/jpeg": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            },
            "image/png": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            },
            "image/gif": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            },
            "video/mp4": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            },
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary"
                  }
                },
                "required": [
                  "file"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MediaAsset"
                }
              }
            }
          },
          "400": {
            "description": "Empty body, missing Content-Type, no file part, or an unsupported media type"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/v1/media/batch": {
      "post": {
        "tags": [
          "Media"
        ],
        "summary": "Upload or prepare several media items",
        "description": "Carousel helper. JSON `{ items: [{ contentType, sizeBytes }] }` returns signed five-minute PUT targets (same as MCP local upload). Multipart with multiple file parts uploads the bytes and returns MediaAsset records. Max 12 items.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "items"
                ],
                "properties": {
                  "items": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 12,
                    "items": {
                      "type": "object",
                      "required": [
                        "contentType"
                      ],
                      "properties": {
                        "contentType": {
                          "type": "string"
                        },
                        "sizeBytes": {
                          "type": "integer",
                          "minimum": 1
                        }
                      }
                    }
                  }
                }
              }
            },
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "additionalProperties": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "oneOf": [
                          {
                            "$ref": "#/components/schemas/MediaAsset"
                          },
                          {
                            "type": "object"
                          }
                        ]
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Empty items, too many files, or an unsupported media type"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/v1/webhooks": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Register webhook",
        "description": "Register a webhook endpoint",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri"
                  },
                  "events": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/WebhookEventType"
                    }
                  }
                },
                "required": [
                  "url"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookEndpoint"
                }
              }
            }
          },
          "400": {
            "description": "Invalid URL or events"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "get": {
        "tags": [
          "Webhooks"
        ],
        "summary": "List webhooks",
        "description": "List webhook endpoints",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "webhooks": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/WebhookEndpoint"
                      }
                    }
                  },
                  "required": [
                    "webhooks"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/v1/webhooks/{id}": {
      "delete": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Delete webhook",
        "description": "Delete a webhook endpoint",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "deleted": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "id",
                    "deleted"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Webhook not found"
          }
        }
      }
    },
    "/v1/credentials": {
      "post": {
        "tags": [
          "Credentials"
        ],
        "summary": "Save credentials",
        "description": "Save BYO platform app credentials (BYOK)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "platform": {
                    "$ref": "#/components/schemas/Platform"
                  },
                  "clientId": {
                    "type": "string"
                  },
                  "clientSecret": {
                    "type": "string"
                  }
                },
                "required": [
                  "platform",
                  "clientId",
                  "clientSecret"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "credential": {
                      "$ref": "#/components/schemas/Credential"
                    }
                  },
                  "required": [
                    "credential"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Invalid platform or missing credentials"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "get": {
        "tags": [
          "Credentials"
        ],
        "summary": "List credentials",
        "description": "List BYO credentials",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "credentials": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Credential"
                      }
                    }
                  },
                  "required": [
                    "credentials"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/v1/credentials/{platform}": {
      "delete": {
        "tags": [
          "Credentials"
        ],
        "summary": "Delete credentials",
        "description": "Delete BYO credentials for a platform",
        "parameters": [
          {
            "name": "platform",
            "in": "path",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/Platform"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Credentials not found"
          }
        }
      }
    },
    "/v1/connect-link": {
      "post": {
        "tags": [
          "Connect"
        ],
        "summary": "Mint connect link",
        "description": "Mint a short-lived link to the hosted connect/manage page",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "profile": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "url": {
                      "type": "string",
                      "format": "uri"
                    },
                    "expiresInSeconds": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "url",
                    "expiresInSeconds"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/v1/app-link": {
      "post": {
        "tags": [
          "Connect"
        ],
        "summary": "Mint app link",
        "description": "Mint a session link to the full app dashboard",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "url": {
                      "type": "string",
                      "format": "uri"
                    },
                    "expiresInSeconds": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "url",
                    "expiresInSeconds"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/v1/mcp": {
      "post": {
        "tags": [
          "MCP"
        ],
        "summary": "MCP Endpoint",
        "description": "Model Context Protocol JSON-RPC endpoint for API-key clients. The same tool registry as the OAuth /mcp endpoint covers publishing, scheduling, validation, media, notifications, comments, direct messages, engagement, profile and channel management, and analytics. See https://docs.postlake.dev/mcp for the full toolset and authentication options.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "jsonrpc": {
                    "type": "string",
                    "const": "2.0"
                  },
                  "method": {
                    "type": "string"
                  },
                  "params": {
                    "type": "object"
                  },
                  "id": {
                    "oneOf": [
                      {
                        "type": "string"
                      },
                      {
                        "type": "number"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  }
                },
                "required": [
                  "jsonrpc",
                  "method"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "jsonrpc": {
                      "type": "string",
                      "const": "2.0"
                    },
                    "result": {
                      "type": "object"
                    },
                    "error": {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "integer"
                        },
                        "message": {
                          "type": "string"
                        },
                        "data": {
                          "type": "object"
                        }
                      },
                      "required": [
                        "code",
                        "message"
                      ]
                    },
                    "id": {
                      "oneOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "number"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    }
                  },
                  "required": [
                    "jsonrpc"
                  ]
                }
              }
            }
          },
          "204": {
            "description": "Notification success"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/openapi.json": {
      "get": {
        "summary": "OpenAPI Specification",
        "description": "Returns this OpenAPI specification",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "SocialActor": {
        "type": "object",
        "properties": {
          "handle": {
            "type": "string"
          },
          "displayName": {
            "type": "string",
            "nullable": true
          },
          "avatarUrl": {
            "type": "string",
            "nullable": true
          },
          "id": {
            "type": "string",
            "nullable": true,
            "description": "The network's own id, for follow/block/mute"
          }
        }
      },
      "ReadProblem": {
        "type": "object",
        "description": "A network that could not be read for this request. Its presence is what lets you tell 'nothing happened' from 'we could not look'.",
        "properties": {
          "account": {
            "type": "string"
          },
          "platform": {
            "type": "string"
          },
          "reason": {
            "type": "string"
          }
        }
      },
      "Notification": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "platform": {
            "type": "string"
          },
          "account": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": [
              "like",
              "reply",
              "mention",
              "follow",
              "repost",
              "quote",
              "other"
            ],
            "description": "Normalised kind. Unknown kinds arrive as 'other' so a new one cannot break a parser."
          },
          "platformType": {
            "type": "string",
            "description": "The network's own word for it, unmapped"
          },
          "actor": {
            "$ref": "#/components/schemas/SocialActor"
          },
          "post": {
            "type": "object",
            "nullable": true,
            "properties": {
              "uri": {
                "type": "string"
              },
              "url": {
                "type": "string",
                "nullable": true
              },
              "text": {
                "type": "string",
                "nullable": true
              }
            }
          },
          "text": {
            "type": "string",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "read": {
            "type": "boolean"
          }
        }
      },
      "Comment": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "platform": {
            "type": "string"
          },
          "account": {
            "type": "string",
            "description": "The connection (acc_…) this comment was read through. Pass it back when you reply, hide, like or delete."
          },
          "author": {
            "$ref": "#/components/schemas/SocialActor"
          },
          "text": {
            "type": "string"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "url": {
            "type": "string",
            "nullable": true
          },
          "replyCount": {
            "type": "integer"
          },
          "likeCount": {
            "type": "integer"
          },
          "hidden": {
            "type": "boolean",
            "nullable": true,
            "description": "Whether this reply is currently hidden. null where the network does not say, which is not the same as false: an agent that reads null as 'not hidden' will keep trying to hide the same reply."
          },
          "replies": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Comment"
            }
          }
        }
      },
      "DiscoveredPost": {
        "type": "object",
        "description": "A post found by searching or browsing, rather than one you published. Deliberately not the Post shape: far less is known about someone else's post, and a shape full of nulls invites you to assume they are readable.",
        "properties": {
          "id": {
            "type": "string"
          },
          "platform": {
            "type": "string"
          },
          "author": {
            "$ref": "#/components/schemas/SocialActor"
          },
          "text": {
            "type": "string"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "url": {
            "type": "string",
            "nullable": true
          },
          "mediaType": {
            "type": "string",
            "enum": [
              "text",
              "image",
              "video",
              "other"
            ],
            "description": "'other' keeps a new network media type from breaking a parser that switches on this."
          },
          "isReply": {
            "type": "boolean"
          },
          "isQuote": {
            "type": "boolean"
          }
        }
      },
      "PublicProfile": {
        "type": "object",
        "description": "Someone else's public profile.",
        "properties": {
          "platform": {
            "type": "string"
          },
          "handle": {
            "type": "string"
          },
          "displayName": {
            "type": "string",
            "nullable": true
          },
          "avatarUrl": {
            "type": "string",
            "nullable": true
          },
          "bio": {
            "type": "string",
            "nullable": true
          },
          "verified": {
            "type": "boolean"
          },
          "followerCount": {
            "type": "integer",
            "nullable": true,
            "description": "null where the network does not publish it, which is not the same as zero."
          },
          "recent": {
            "type": "object",
            "nullable": true,
            "description": "The last 7 days, where the network reports it. Each figure is null when unpublished, never 0.",
            "properties": {
              "likes": {
                "type": "integer",
                "nullable": true
              },
              "quotes": {
                "type": "integer",
                "nullable": true
              },
              "reposts": {
                "type": "integer",
                "nullable": true
              },
              "views": {
                "type": "integer",
                "nullable": true
              }
            }
          },
          "id": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "Place": {
        "type": "object",
        "description": "A place that can be tagged on a post. Ids are per-network and only mean anything to the network they came from, so the platform travels with them.",
        "properties": {
          "id": {
            "type": "string"
          },
          "platform": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "address": {
            "type": "string",
            "nullable": true
          },
          "city": {
            "type": "string",
            "nullable": true
          },
          "country": {
            "type": "string",
            "nullable": true
          },
          "latitude": {
            "type": "number",
            "nullable": true
          },
          "longitude": {
            "type": "number",
            "nullable": true
          }
        }
      },
      "Conversation": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "platform": {
            "type": "string"
          },
          "account": {
            "type": "string"
          },
          "participants": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SocialActor"
            }
          },
          "lastMessage": {
            "type": "object",
            "nullable": true,
            "properties": {
              "text": {
                "type": "string"
              },
              "createdAt": {
                "type": "string",
                "format": "date-time"
              },
              "fromMe": {
                "type": "boolean"
              }
            }
          },
          "unreadCount": {
            "type": "integer"
          }
        }
      },
      "Message": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "text": {
            "type": "string"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "from": {
            "$ref": "#/components/schemas/SocialActor"
          },
          "fromMe": {
            "type": "boolean",
            "description": "True when you sent it, so callers never compare handles"
          }
        }
      },
      "Platform": {
        "type": "string",
        "enum": [
          "bluesky",
          "threads",
          "x",
          "linkedin",
          "instagram",
          "tiktok",
          "facebook",
          "youtube",
          "pinterest"
        ]
      },
      "PlatformCapabilities": {
        "type": "object",
        "description": "What one platform supports: limits, media rules, and every platformOptions field with its valid values.",
        "properties": {
          "platform": {
            "$ref": "#/components/schemas/Platform"
          },
          "displayName": {
            "type": "string"
          },
          "maxChars": {
            "type": "integer"
          },
          "charsByPostType": {
            "type": "object",
            "properties": {
              "video": {
                "type": "integer"
              },
              "image": {
                "type": "integer"
              }
            }
          },
          "title": {
            "type": "object",
            "properties": {
              "maxChars": {
                "type": "integer"
              },
              "appliesTo": {
                "type": "string"
              }
            }
          },
          "media": {
            "type": "object",
            "properties": {
              "required": {
                "type": "boolean"
              },
              "videoRequired": {
                "type": "boolean"
              },
              "maxImages": {
                "type": "integer"
              },
              "maxVideos": {
                "type": "integer"
              },
              "imageTypes": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "videoTypes": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "maxImageBytes": {
                "type": "integer"
              },
              "maxVideoBytes": {
                "type": "integer"
              },
              "maxVideoSeconds": {
                "type": "integer"
              }
            }
          },
          "postsPerDay": {
            "type": "integer"
          },
          "asyncPublish": {
            "type": "boolean"
          },
          "options": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "type": {
                  "type": "string",
                  "enum": [
                    "string",
                    "boolean",
                    "enum",
                    "number"
                  ]
                },
                "label": {
                  "type": "string"
                },
                "values": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                },
                "default": {},
                "appliesTo": {
                  "type": "string"
                },
                "maxLength": {
                  "type": "integer"
                },
                "description": {
                  "type": "string"
                }
              },
              "required": [
                "id",
                "type",
                "label"
              ]
            }
          },
          "notes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "firstComment": {
            "type": "boolean",
            "description": "A comment can be posted under the post in the same call."
          },
          "deletePost": {
            "type": "boolean",
            "description": "A published post can be retracted through the API."
          },
          "thread": {
            "type": "boolean",
            "description": "A chain of posts can be published as one."
          },
          "replyToComment": {
            "type": "boolean"
          },
          "hideComments": {
            "type": "boolean",
            "description": "A reply on your own post can be hidden and unhidden."
          },
          "editProfile": {
            "type": "boolean"
          },
          "messages": {
            "type": "boolean",
            "description": "Direct messages can be read and sent."
          },
          "engages": {
            "type": "array",
            "description": "Which actions POST /v1/engagements accepts on this network.",
            "items": {
              "type": "string",
              "enum": [
                "like",
                "unlike",
                "repost",
                "unrepost",
                "follow",
                "unfollow",
                "block",
                "unblock",
                "mute",
                "unmute"
              ]
            }
          },
          "reads": {
            "type": "object",
            "description": "Which of your own surfaces can be read.",
            "properties": {
              "notifications": {
                "type": "boolean"
              },
              "comments": {
                "type": "boolean"
              },
              "followers": {
                "type": "boolean"
              },
              "following": {
                "type": "boolean"
              }
            }
          },
          "discovers": {
            "type": "object",
            "description": "Which parts of the public network can be searched, so an agent can look before it speaks.",
            "properties": {
              "posts": {
                "type": "boolean"
              },
              "profiles": {
                "type": "boolean"
              },
              "profilePosts": {
                "type": "boolean"
              },
              "places": {
                "type": "boolean"
              },
              "creators": {
                "type": "boolean",
                "description": "Find creators through the network's creator marketplace."
              }
            }
          },
          "variants": {
            "type": "array",
            "description": "Where a network can be connected in more than one way, the ways it offers. What a connection can do depends on HOW it was made, not only on which network it is: an Instagram account connected through Facebook can search, look people up and read insights, while the same account connected directly cannot. Absent when a network has only one way in.",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "description": "Pass as `variant` when creating a connect link."
                },
                "label": {
                  "type": "string",
                  "description": "What to call this choice to a person."
                },
                "requires": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "description": "What the person must already have for this way to work."
                },
                "then": {
                  "type": "string",
                  "description": "What happens next when they choose it."
                },
                "summary": {
                  "type": "string",
                  "description": "What this way of connecting can do, in one line."
                },
                "only": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "description": "Capabilities this way unlocks that the others do not."
                },
                "note": {
                  "type": "string"
                }
              },
              "required": [
                "id",
                "label"
              ]
            }
          }
        },
        "required": [
          "platform",
          "displayName",
          "maxChars",
          "media",
          "options"
        ]
      },
      "PostState": {
        "type": "string",
        "enum": [
          "queued",
          "scheduled",
          "processing",
          "partial",
          "published",
          "failed"
        ]
      },
      "TargetState": {
        "type": "string",
        "enum": [
          "queued",
          "scheduled",
          "processing",
          "published",
          "failed"
        ]
      },
      "WebhookEventType": {
        "type": "string",
        "enum": [
          "post.published",
          "post.failed",
          "post.partial",
          "post.processing",
          "account.connected",
          "message.received",
          "comment.received",
          "mention.received"
        ],
        "description": "Which events an endpoint wants. `message.received` fires when someone messages a connected channel: the network pushes it to us and we pass it on, which is how you learn about a message in time to answer it. Its payload carries `replyBy`, the moment the network stops accepting a reply."
      },
      "Metrics": {
        "type": "object",
        "properties": {
          "impressions": {
            "type": "integer"
          },
          "reach": {
            "type": "integer"
          },
          "likes": {
            "type": "integer"
          },
          "comments": {
            "type": "integer"
          },
          "shares": {
            "type": "integer"
          },
          "saves": {
            "type": "integer"
          },
          "clicks": {
            "type": "integer"
          },
          "followers": {
            "type": "integer"
          }
        },
        "required": [
          "impressions",
          "reach",
          "likes",
          "comments",
          "shares",
          "saves",
          "clicks",
          "followers"
        ]
      },
      "NormalisedError": {
        "type": "object",
        "description": "`type` is the coarse category you route on and is deliberately small, so it cannot say WHICH input was wrong. `code`, `fix`, `docs` and `param` are what turn a rejection into something a caller can act on rather than guess at. They are optional only in the sense that not every error has all four.",
        "properties": {
          "type": {
            "type": "string",
            "description": "Coarse category to route on, e.g. rate_limited, auth_expired, platform_rejected."
          },
          "message": {
            "type": "string"
          },
          "platform": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Platform"
              },
              {
                "type": "null"
              }
            ]
          },
          "retryable": {
            "type": "boolean"
          },
          "code": {
            "type": "string",
            "description": "Stable, granular handle to branch on, e.g. text_too_long. Also the anchor in the error reference."
          },
          "fix": {
            "type": "string",
            "description": "The next action, in plain words. Safe to show a person as-is."
          },
          "docs": {
            "type": "string",
            "format": "uri",
            "description": "Where the rule is written down, so it is learned once rather than rediscovered."
          },
          "param": {
            "type": "string",
            "description": "The request field at fault, so a form can mark the right input."
          }
        },
        "required": [
          "type",
          "message",
          "retryable"
        ]
      },
      "Profile": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "username": {
            "type": "string",
            "description": "The identifier agents put in `profile:`. Slugified."
          },
          "name": {
            "type": "string",
            "description": "What the person typed, for display. Absent on profiles created before this existed."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "username",
          "createdAt"
        ]
      },
      "EmailPreferences": {
        "type": "object",
        "properties": {
          "digest": {
            "type": "boolean",
            "description": "False when digestFrequency is off. Kept so older clients still work."
          },
          "digestFrequency": {
            "type": "string",
            "enum": [
              "off",
              "weekly",
              "daily"
            ]
          }
        },
        "required": [
          "digest",
          "digestFrequency"
        ]
      },
      "SocialAccount": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "platform": {
            "$ref": "#/components/schemas/Platform"
          },
          "handle": {
            "type": "string"
          },
          "profileId": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "status": {
            "type": "string"
          },
          "requires": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "What this network needs before it will publish, in one sentence (media requirements, title and caption limits). Read it before composing: TikTok, Instagram, YouTube and Pinterest all refuse text-only posts. Null when the network accepts anything."
          }
        },
        "required": [
          "id",
          "platform",
          "handle",
          "profileId",
          "status"
        ]
      },
      "PostTarget": {
        "type": "object",
        "properties": {
          "account": {
            "type": "string"
          },
          "platform": {
            "$ref": "#/components/schemas/Platform"
          },
          "state": {
            "$ref": "#/components/schemas/TargetState"
          },
          "profile": {
            "type": "string",
            "description": "Username of the profile this channel belongs to. Resolved when the post is read, so it follows the channel if it moves. Absent once the channel is disconnected."
          },
          "channelName": {
            "type": "string",
            "description": "The channel's handle at the time of reading, not the one frozen at publish. Absent once the channel is disconnected."
          },
          "remoteId": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "url": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "publishedAt": {
            "oneOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ]
          },
          "permalinkPending": {
            "type": "boolean",
            "description": "True when publication succeeded but an exact public permalink is still being resolved."
          },
          "error": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/NormalisedError"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "account",
          "platform",
          "state",
          "remoteId",
          "url",
          "publishedAt",
          "error"
        ]
      },
      "Post": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "state": {
            "$ref": "#/components/schemas/PostState"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "scheduledAt": {
            "oneOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ]
          },
          "timezone": {
            "type": "string",
            "description": "IANA timezone the caller scheduled in, when they named one."
          },
          "scheduledAtLocal": {
            "type": "string",
            "description": "Wall-clock time in timezone, computed on read. Not stored."
          },
          "text": {
            "type": "string"
          },
          "media": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Advisory notes from create or edit, for example PNG converted to JPEG for TikTok."
          },
          "platformOptions": {
            "oneOf": [
              {
                "type": "object",
                "properties": {
                  "pinterest": {
                    "type": "object",
                    "properties": {
                      "boardId": {
                        "type": "string"
                      },
                      "link": {
                        "type": "string"
                      },
                      "altText": {
                        "type": "string"
                      }
                    }
                  }
                }
              },
              {
                "type": "null"
              }
            ]
          },
          "targets": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PostTarget"
            }
          }
        },
        "required": [
          "id",
          "state",
          "createdAt",
          "scheduledAt",
          "text",
          "media",
          "platformOptions",
          "targets"
        ]
      },
      "MediaAsset": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "contentType": {
            "type": "string"
          },
          "size": {
            "type": "integer"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "width": {
            "type": "integer",
            "description": "Pixel width. Images only, and absent when the header could not be read."
          },
          "height": {
            "type": "integer",
            "description": "Pixel height. Images only, and absent when the header could not be read."
          }
        },
        "required": [
          "id",
          "url",
          "contentType",
          "size",
          "createdAt"
        ]
      },
      "WebhookEndpoint": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "secret": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WebhookEventType"
            }
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "url",
          "secret",
          "events",
          "createdAt"
        ]
      },
      "AuditEvent": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "action": {
            "type": "string"
          },
          "ip": {
            "type": "string"
          },
          "userAgent": {
            "type": "string"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "action",
          "ip",
          "userAgent",
          "createdAt"
        ]
      },
      "Credential": {
        "type": "object",
        "properties": {
          "platform": {
            "$ref": "#/components/schemas/Platform"
          },
          "clientId": {
            "type": "string"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "platform",
          "clientId",
          "createdAt"
        ]
      },
      "ErrorResponse": {
        "type": "object",
        "properties": {
          "error": {
            "$ref": "#/components/schemas/NormalisedError"
          }
        },
        "required": [
          "error"
        ]
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Unauthorized",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "error": {
                "type": "unauthorized",
                "message": "missing or invalid api key",
                "retryable": false
              }
            }
          }
        }
      },
      "RateLimited": {
        "description": "Rate Limited",
        "headers": {
          "Retry-After": {
            "schema": {
              "type": "integer"
            },
            "description": "Seconds to wait before retrying"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "error": {
                "type": "rate_limited",
                "message": "Rate limit exceeded",
                "retryable": true
              }
            }
          }
        }
      }
    }
  }
}
