{
  "openapi": "3.0.3",
  "info": {
    "title": "PostEverywhere API",
    "description": "Schedule and publish social media content across multiple platforms from a single API. Manage accounts, create posts, upload media, and monitor publishing results.\n\n## Quick Start\n\n**Node.js SDK:**\n```bash\nnpm install posteverywhere\n```\n\n**Claude Code (MCP):**\n```json\n{\n  \"mcpServers\": {\n    \"posteverywhere\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@posteverywhere/mcp\"],\n      \"env\": { \"POSTEVERYWHERE_API_KEY\": \"pe_live_...\" }\n    }\n  }\n}\n```\n\n**CLI Setup:**\n```bash\nnpx posteverywhere init\n```\n\n## Authentication\n\nAll endpoints require a Bearer token. Generate an API key from **Developers** in the PostEverywhere dashboard.\n\n```\nAuthorization: Bearer pe_live_abc123...\n```\n\n[Sign up for a free 14-day trial](https://app.posteverywhere.ai/signup) (no credit card required).\n\n## Response Format\n\nAll responses follow a consistent envelope:\n\n```json\n{\n  \"data\": { ... },\n  \"error\": null,\n  \"meta\": { \"request_id\": \"a1b2c3d4\", \"timestamp\": \"2026-03-01T12:00:00.000Z\" }\n}\n```\n\nOn error, `data` is `null` and `error` contains `{ \"message\": \"...\", \"code\": \"error_code\", \"details\": ... }`.\n\n## Rate Limits\n\nAPI keys are rate-limited per minute and per hour. When exceeded, the API returns `429 Too Many Requests` with `Retry-After` and `X-RateLimit-*` headers.\n\n## Platform Guides\n\nUse the `platform_content` parameter on `POST /posts` to customise content per platform.\n\n### Instagram\n\nSupports Feed Posts, Reels, Stories, and Trial Reels.\n\n```json\n{\n  \"platform_content\": {\n    \"instagram\": {\n      \"content\": \"Instagram-specific caption\",\n      \"contentType\": \"Story\",\n      \"settings\": {\n        \"altText\": \"Image description for accessibility\",\n        \"firstComment\": \"First comment text\"\n      }\n    }\n  }\n}\n```\n\n- `contentType`: `\"Post\"` (default for images), `\"Reels\"` (default for videos), `\"Story\"`, `\"Trial Reel\"`\n- `altText`: max 100 characters\n- `firstComment`: auto-posted as first comment after publishing\n- Media (image or video) is required\n\n### TikTok\n\n```json\n{\n  \"platform_content\": {\n    \"tiktok\": {\n      \"content\": \"TikTok caption (max 150 chars)\",\n      \"settings\": {\n        \"privacyLevel\": \"PUBLIC_TO_EVERYONE\",\n        \"allowComments\": true,\n        \"allowDuet\": true,\n        \"allowStitch\": true,\n        \"videoCoverTimestamp\": 1000\n      }\n    }\n  }\n}\n```\n\n- `privacyLevel`: `\"PUBLIC_TO_EVERYONE\"` | `\"MUTUAL_FOLLOW_FRIENDS\"` | `\"FOLLOWER_OF_CREATOR\"` | `\"SELF_ONLY\"`\n- `videoCoverTimestamp`: thumbnail frame in milliseconds (default 1000)\n- Video is required for TikTok\n\n### YouTube\n\n```json\n{\n  \"platform_content\": {\n    \"youtube\": {\n      \"content\": \"Video description\",\n      \"settings\": {\n        \"title\": \"My Video Title (required)\",\n        \"privacyStatus\": \"public\",\n        \"tags\": [\"tag1\", \"tag2\"],\n        \"categoryId\": \"22\",\n        \"madeForKids\": false\n      }\n    }\n  }\n}\n```\n\n- `title`: required for YouTube, max 100 characters\n- `privacyStatus`: `\"public\"` | `\"unlisted\"` | `\"private\"`\n- `categoryId`: YouTube category (e.g. `\"22\"` = People & Blogs)\n- Video is required for YouTube\n\n### LinkedIn\n\n```json\n{\n  \"platform_content\": {\n    \"linkedin\": {\n      \"content\": \"LinkedIn-specific text (max 3000 chars)\",\n      \"settings\": {\n        \"visibility\": \"PUBLIC\",\n        \"firstComment\": \"First comment text\"\n      }\n    }\n  }\n}\n```\n\n- `visibility`: `\"PUBLIC\"` | `\"CONNECTIONS\"` | `\"LOGGED_IN_MEMBERS\"`\n- Supports images, videos, and PDF documents\n\n### X (Twitter)\n\n```json\n{\n  \"platform_content\": {\n    \"x\": {\n      \"content\": \"Tweet text (max 280 chars)\",\n      \"settings\": {\n        \"replySettings\": \"everyone\",\n        \"mediaAltText\": \"Image description\",\n        \"firstComment\": \"Reply text\"\n      }\n    }\n  }\n}\n```\n\n- `replySettings`: `\"everyone\"` | `\"following\"` | `\"mentionedUsers\"`\n- `mediaAltText`: max 1000 characters\n- Threads: pass `threadPosts` array at top level for multi-tweet threads\n\n### Facebook\n\n```json\n{\n  \"platform_content\": {\n    \"facebook\": {\n      \"content\": \"Facebook post text\",\n      \"settings\": {\n        \"videoType\": \"reel\",\n        \"link\": \"https://example.com\",\n        \"firstComment\": \"First comment text\"\n      }\n    }\n  }\n}\n```\n\n- `videoType`: `\"reel\"` (9:16, default) | `\"video\"` (any aspect ratio)\n- `link`: URL to share as link preview\n\n### Threads\n\n```json\n{\n  \"platform_content\": {\n    \"threads\": {\n      \"content\": \"Threads post (max 500 chars)\",\n      \"settings\": {\n        \"replyControl\": \"everyone\",\n        \"firstComment\": \"First comment text\"\n      }\n    }\n  }\n}\n```\n\n- `replyControl`: `\"everyone\"` | `\"following\"` | `\"mentioned\"`\n\n## Resources\n\n- [Node.js SDK](https://www.npmjs.com/package/posteverywhere)\n- [MCP Server for Claude Code](https://www.npmjs.com/package/@posteverywhere/mcp)\n- [Help Center](https://posteverywhere.ai/docs)\n- [Pricing](https://posteverywhere.ai/pricing)",
    "version": "1.0.0",
    "contact": {
      "name": "PostEverywhere Support",
      "url": "https://posteverywhere.ai",
      "email": "support@posteverywhere.ai"
    }
  },
  "servers": [
    {
      "url": "https://app.posteverywhere.ai/api/v1",
      "description": "Production"
    }
  ],
  "security": [
    {
      "BearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Accounts",
      "description": "Manage connected social media accounts"
    },
    {
      "name": "Posts",
      "description": "Create, schedule, update, and delete posts"
    },
    {
      "name": "Media",
      "description": "Upload and manage media files"
    },
    {
      "name": "AI",
      "description": "Generate images using AI models. Requires the `ai` scope on your API key. Generated images are stored in your media library and can be attached to posts via `media_ids`."
    }
  ],
  "paths": {
    "/accounts": {
      "get": {
        "operationId": "listAccounts",
        "summary": "List accounts",
        "description": "Returns all connected social media accounts for your organization, including platform, username, and health status.",
        "tags": ["Accounts"],
        "responses": {
          "200": {
            "description": "List of accounts",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    { "$ref": "#/components/schemas/ApiEnvelope" },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "object",
                          "properties": {
                            "accounts": {
                              "type": "array",
                              "items": { "$ref": "#/components/schemas/Account" }
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/accounts/{id}": {
      "get": {
        "operationId": "getAccount",
        "summary": "Get account",
        "description": "Returns detailed information about a specific connected social media account, including its health status and whether it can currently post.",
        "tags": ["Accounts"],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": { "type": "string" },
            "description": "Account ID"
          }
        ],
        "responses": {
          "200": {
            "description": "Account details",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    { "$ref": "#/components/schemas/ApiEnvelope" },
                    {
                      "type": "object",
                      "properties": {
                        "data": { "$ref": "#/components/schemas/Account" }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/posts": {
      "get": {
        "operationId": "listPosts",
        "summary": "List posts",
        "description": "Returns scheduled, published, or draft posts. Supports filtering by status and platform, with pagination.",
        "tags": ["Posts"],
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": ["scheduled", "published", "draft"]
            },
            "description": "Filter by post status"
          },
          {
            "name": "platform",
            "in": "query",
            "schema": { "type": "string" },
            "description": "Filter by destination platform (e.g. instagram, linkedin, x)"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            },
            "description": "Number of posts to return"
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Number of posts to skip"
          }
        ],
        "responses": {
          "200": {
            "description": "List of posts",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    { "$ref": "#/components/schemas/ApiEnvelope" },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "object",
                          "properties": {
                            "posts": {
                              "type": "array",
                              "items": { "$ref": "#/components/schemas/Post" }
                            },
                            "pagination": { "$ref": "#/components/schemas/Pagination" }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      },
      "post": {
        "operationId": "createPost",
        "summary": "Create post",
        "description": "Create and schedule a social media post. Specify content, target accounts, and optionally a schedule time. If no `scheduled_at` is provided, the post is published immediately.\n\nUse `platform_content` to customise content per platform — override captions, set Instagram content type (Story, Reels, Post), configure TikTok privacy, set YouTube titles, and more.\n\n**Instagram Stories example:**\n```json\n{\n  \"content\": \"Check this out!\",\n  \"account_ids\": [123],\n  \"media_ids\": [\"uuid\"],\n  \"platform_content\": {\n    \"instagram\": { \"contentType\": \"Story\" }\n  }\n}\n```",
        "tags": ["Posts"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/CreatePostRequest" }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Post created",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    { "$ref": "#/components/schemas/ApiEnvelope" },
                    {
                      "type": "object",
                      "properties": {
                        "data": { "$ref": "#/components/schemas/CreatePostResponse" }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/posts/{id}": {
      "get": {
        "operationId": "getPost",
        "summary": "Get post",
        "description": "Returns detailed information about a specific post, including content, media, schedule, and per-platform destination statuses.",
        "tags": ["Posts"],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": { "type": "string", "format": "uuid" },
            "description": "Post UUID"
          }
        ],
        "responses": {
          "200": {
            "description": "Post details",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    { "$ref": "#/components/schemas/ApiEnvelope" },
                    {
                      "type": "object",
                      "properties": {
                        "data": { "$ref": "#/components/schemas/Post" }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      },
      "patch": {
        "operationId": "updatePost",
        "summary": "Update post",
        "description": "Update a scheduled or draft post. You can change content, schedule, target accounts, or media. Published posts cannot be modified.",
        "tags": ["Posts"],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": { "type": "string", "format": "uuid" },
            "description": "Post UUID"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/UpdatePostRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated post",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    { "$ref": "#/components/schemas/ApiEnvelope" },
                    {
                      "type": "object",
                      "properties": {
                        "data": { "$ref": "#/components/schemas/Post" }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      },
      "delete": {
        "operationId": "deletePost",
        "summary": "Delete post",
        "description": "Permanently delete a scheduled or draft post. Published posts cannot be deleted. This action cannot be undone.",
        "tags": ["Posts"],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": { "type": "string", "format": "uuid" },
            "description": "Post UUID"
          }
        ],
        "responses": {
          "200": {
            "description": "Post deleted",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    { "$ref": "#/components/schemas/ApiEnvelope" },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "object",
                          "properties": {
                            "deleted": { "type": "boolean", "example": true },
                            "id": { "type": "string", "format": "uuid" },
                            "message": { "type": "string", "example": "Post permanently deleted." }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/posts/{id}/results": {
      "get": {
        "operationId": "getPostResults",
        "summary": "Get post results",
        "description": "Returns per-platform publishing results for a post, including published URLs, error messages, attempt counts, and retry schedules.",
        "tags": ["Posts"],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": { "type": "string", "format": "uuid" },
            "description": "Post UUID"
          }
        ],
        "responses": {
          "200": {
            "description": "Post results",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    { "$ref": "#/components/schemas/ApiEnvelope" },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "object",
                          "properties": {
                            "post_id": { "type": "string", "format": "uuid" },
                            "post_status": { "type": "string" },
                            "results": {
                              "type": "array",
                              "items": { "$ref": "#/components/schemas/PostResult" }
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/posts/{id}/retry": {
      "post": {
        "operationId": "retryFailedPost",
        "summary": "Retry failed post",
        "description": "Retry all failed platform destinations for a post. Resets failed destinations to queued status for re-publishing. Use after resolving temporary issues like rate limits or expired tokens.",
        "tags": ["Posts"],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": { "type": "string", "format": "uuid" },
            "description": "Post UUID"
          }
        ],
        "responses": {
          "200": {
            "description": "Destinations retried",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    { "$ref": "#/components/schemas/ApiEnvelope" },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "object",
                          "properties": {
                            "retried_count": { "type": "integer", "description": "Number of failed destinations being retried" },
                            "message": { "type": "string", "example": "2 failed destinations re-queued for publishing. Check GET /posts/{id}/results for updated statuses." },
                            "destinations": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "id": { "type": "string", "format": "uuid" },
                                  "platform": { "type": "string" },
                                  "new_status": { "type": "string", "example": "queued" }
                                }
                              }
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/posts/bulk-schedule": {
      "post": {
        "operationId": "bulkSchedulePosts",
        "summary": "Bulk schedule posts",
        "description": "Schedule multiple posts at once from a CSV-like structure. Upload up to 100 posts in a single request.\n\nUse `dryRun: true` to validate without creating posts.\n\n**Example:**\n```json\n{\n  \"posts\": [\n    {\"date\": \"2026-04-01\", \"time\": \"09:00\", \"content\": \"Morning post!\", \"platforms\": \"instagram+facebook\"},\n    {\"date\": \"2026-04-01\", \"time\": \"12:00\", \"content\": \"Afternoon update\", \"platforms\": \"2280+2282\", \"media\": \"photo.jpg\"}\n  ],\n  \"timezone\": \"America/New_York\"\n}\n```\n\nThe `platforms` field accepts platform names (`instagram+facebook+x`) or account IDs (`2280+2282`) or a mix of both. Use account IDs when you have multiple accounts on the same platform.\n\nThe `media` field references filenames from your Media Library. Upload media first via `POST /media/upload`, then use the filename here.\n\n**Rate limits:** Max 100 posts per upload. X posting limits still apply per plan (Trial: 10/day, Starter: 50/day, Growth: 150/day, Pro: 500/day).",
        "tags": ["Posts"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/BulkScheduleRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Bulk schedule result",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    { "$ref": "#/components/schemas/ApiEnvelope" },
                    {
                      "type": "object",
                      "properties": {
                        "data": { "$ref": "#/components/schemas/BulkScheduleResponse" }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "422": {
            "description": "Validation failed — batch errors prevent scheduling",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ApiEnvelope" }
              }
            }
          },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/media": {
      "get": {
        "operationId": "listMedia",
        "summary": "List media",
        "description": "Returns media files in your library. Supports filtering by type and pagination.",
        "tags": ["Media"],
        "parameters": [
          {
            "name": "type",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": ["image", "video", "document"]
            },
            "description": "Filter by media type"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            },
            "description": "Number of items to return"
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Number of items to skip"
          }
        ],
        "responses": {
          "200": {
            "description": "List of media",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    { "$ref": "#/components/schemas/ApiEnvelope" },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "object",
                          "properties": {
                            "media": {
                              "type": "array",
                              "items": { "$ref": "#/components/schemas/MediaItem" }
                            },
                            "pagination": { "$ref": "#/components/schemas/Pagination" }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/media/{id}": {
      "get": {
        "operationId": "getMedia",
        "summary": "Get media",
        "description": "Returns detailed information about a specific media file, including type, dimensions, file size, and processing status.",
        "tags": ["Media"],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": { "type": "string", "format": "uuid" },
            "description": "Media UUID"
          }
        ],
        "responses": {
          "200": {
            "description": "Media details",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    { "$ref": "#/components/schemas/ApiEnvelope" },
                    {
                      "type": "object",
                      "properties": {
                        "data": { "$ref": "#/components/schemas/MediaDetail" }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      },
      "delete": {
        "operationId": "deleteMedia",
        "summary": "Delete media",
        "description": "Permanently delete a media file from your library and its backing storage. This action cannot be undone.",
        "tags": ["Media"],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": { "type": "string", "format": "uuid" },
            "description": "Media UUID"
          }
        ],
        "responses": {
          "200": {
            "description": "Media deleted",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    { "$ref": "#/components/schemas/ApiEnvelope" },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "object",
                          "properties": {
                            "deleted": { "type": "boolean", "example": true },
                            "id": { "type": "string", "format": "uuid" },
                            "message": { "type": "string", "example": "Media file permanently deleted from library and storage." }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/media/upload": {
      "post": {
        "operationId": "uploadMedia",
        "summary": "Upload media",
        "description": "Request a presigned upload URL for a media file. This is step 1 of the 3-step upload flow:\n\n1. **POST /media/upload** — Get a presigned upload URL (this endpoint)\n2. **Upload your file** to the returned `upload_url` using the method specified in `upload_method`\n3. **POST /media/{id}/complete** — Signal that the upload is finished\n\n**Upload methods by type:**\n- **Images** — POST multipart-form to Cloudflare Images (field name: `file`)\n- **Videos** — PUT to R2 presigned URL with `Content-Type` header\n- **Documents** — PUT to R2 presigned URL with `Content-Type` header",
        "tags": ["Media"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/UploadMediaRequest" }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Upload URL generated",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    { "$ref": "#/components/schemas/ApiEnvelope" },
                    {
                      "type": "object",
                      "properties": {
                        "data": { "$ref": "#/components/schemas/UploadMediaResponse" }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/media/{id}/complete": {
      "post": {
        "operationId": "completeMediaUpload",
        "summary": "Complete media upload",
        "description": "Signal that a file upload is finished. Call this after uploading your file to the presigned URL returned by `POST /media/upload`.\n\n**Processing by type:**\n- **Images** — Marked as ready immediately (Cloudflare Images handles optimization)\n- **Videos** — File verified in storage, thumbnail generated in background, marked as ready\n- **Documents (PDF)** — File verified in storage, marked as ready\n\nThe response includes the final status and a `message` with next steps.",
        "tags": ["Media"],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": { "type": "string", "format": "uuid" },
            "description": "Media UUID returned by POST /media/upload"
          }
        ],
        "responses": {
          "200": {
            "description": "Media upload completed",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    { "$ref": "#/components/schemas/ApiEnvelope" },
                    {
                      "type": "object",
                      "properties": {
                        "data": { "$ref": "#/components/schemas/CompleteMediaResponse" }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Upload not ready or file verification failed",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    { "$ref": "#/components/schemas/ApiEnvelope" },
                    {
                      "type": "object",
                      "properties": {
                        "data": { "nullable": true },
                        "error": {
                          "type": "object",
                          "properties": {
                            "message": { "type": "string", "example": "File not found in storage. Make sure you uploaded the file to the upload_url returned by POST /media/upload." },
                            "code": { "type": "string", "enum": ["file_not_uploaded", "file_type_mismatch", "upload_failed"] }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/ai/generate-image": {
      "post": {
        "operationId": "generateImage",
        "summary": "Generate image",
        "description": "Generate an AI image from a text prompt. The image is stored in your media library and can be attached to posts via `media_ids`.\n\nRequires the `ai` scope on your API key.\n\n**Available models:**\n| Model | Credits | Speed | Best for |\n|-------|---------|-------|----------|\n| `nano-banana-pro` | 15 | ~5s | Best overall quality |\n| `ideogram-v2` | 8 | ~5s | Text in images |\n| `gemini-3-pro` | 5 | ~8s | Smart/contextual |\n| `flux-schnell` | 1 | ~2s | Fast drafts |",
        "tags": ["AI"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/GenerateImageRequest" }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Image generated successfully",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    { "$ref": "#/components/schemas/ApiEnvelope" },
                    {
                      "type": "object",
                      "properties": {
                        "data": { "$ref": "#/components/schemas/GenerateImageResponse" }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Invalid prompt, aspect ratio, or model",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    { "$ref": "#/components/schemas/ApiEnvelope" },
                    {
                      "type": "object",
                      "properties": {
                        "data": { "nullable": true },
                        "error": {
                          "type": "object",
                          "properties": {
                            "message": { "type": "string" },
                            "code": { "type": "string", "enum": ["validation_error", "prompt_rejected"] }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "402": {
            "description": "Insufficient AI credits",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    { "$ref": "#/components/schemas/ApiEnvelope" },
                    {
                      "type": "object",
                      "properties": {
                        "data": { "nullable": true },
                        "error": {
                          "type": "object",
                          "properties": {
                            "message": { "type": "string", "example": "Not enough credits. This model costs 15 credits but you have 3 available." },
                            "code": { "type": "string", "example": "insufficient_credits" },
                            "details": {
                              "type": "object",
                              "properties": {
                                "credits_required": { "type": "integer" },
                                "credits_available": { "type": "integer" },
                                "model": { "type": "string" }
                              }
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "BearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "API key from Developers. Format: `pe_live_` followed by 32 hex characters."
      }
    },
    "schemas": {
      "ApiEnvelope": {
        "type": "object",
        "properties": {
          "data": {},
          "error": {
            "nullable": true,
            "type": "object",
            "properties": {
              "message": { "type": "string", "description": "Human-readable error description" },
              "code": { "type": "string", "description": "Machine-readable error code (e.g. validation_error, not_found, invalid_api_key)", "example": "validation_error" },
              "details": { "description": "Additional context about the error (varies by error type)" }
            }
          },
          "meta": {
            "type": "object",
            "properties": {
              "request_id": { "type": "string", "example": "a1b2c3d4" },
              "timestamp": { "type": "string", "format": "date-time" }
            }
          }
        }
      },
      "Account": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "platform": {
            "type": "string",
            "enum": ["x", "instagram", "facebook", "linkedin", "youtube", "tiktok", "threads", "pinterest"]
          },
          "account_name": { "type": "string" },
          "avatar_url": { "type": "string", "nullable": true },
          "is_active": { "type": "boolean" },
          "created_at": { "type": "string", "format": "date-time" },
          "health": {
            "type": "object",
            "properties": {
              "status": {
                "type": "string",
                "enum": ["healthy", "expired", "unhealthy"]
              },
              "can_post": { "type": "boolean" }
            }
          }
        }
      },
      "Post": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "content": { "type": "string" },
          "media": {
            "type": "array",
            "nullable": true,
            "items": {}
          },
          "scheduled_for": { "type": "string", "format": "date-time", "nullable": true },
          "timezone": { "type": "string", "example": "UTC" },
          "status": {
            "type": "string",
            "enum": ["draft", "scheduled", "published"]
          },
          "destinations": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/PostDestination" }
          },
          "created_at": { "type": "string", "format": "date-time" },
          "updated_at": { "type": "string", "format": "date-time" }
        }
      },
      "PostDestination": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "platform": { "type": "string" },
          "status": { "type": "string" },
          "account_name": { "type": "string", "nullable": true },
          "account_id": { "type": "integer", "nullable": true },
          "published_at": { "type": "string", "format": "date-time", "nullable": true },
          "platform_post_id": { "type": "string", "nullable": true },
          "error": { "type": "string", "nullable": true },
          "attempts": { "type": "integer" }
        }
      },
      "PostResult": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "platform": { "type": "string" },
          "status": { "type": "string" },
          "account_name": { "type": "string", "nullable": true },
          "account_id": { "type": "integer", "nullable": true },
          "published_at": { "type": "string", "format": "date-time", "nullable": true },
          "platform_post_id": { "type": "string", "nullable": true },
          "platform_post_url": { "type": "string", "nullable": true },
          "error": { "type": "string", "nullable": true },
          "attempts": { "type": "integer" },
          "next_retry_at": { "type": "string", "format": "date-time", "nullable": true }
        }
      },
      "MediaItem": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "type": {
            "type": "string",
            "enum": ["image", "video", "document"]
          },
          "mime_type": { "type": "string", "example": "image/jpeg" },
          "file_size": { "type": "integer", "description": "File size in bytes" },
          "status": { "type": "string" },
          "original_name": { "type": "string", "nullable": true },
          "url": { "type": "string", "nullable": true },
          "thumbnail_url": { "type": "string", "nullable": true },
          "dimensions": {
            "nullable": true,
            "type": "object",
            "properties": {
              "width": { "type": "integer" },
              "height": { "type": "integer" }
            }
          },
          "aspect_ratio": { "type": "number", "nullable": true },
          "orientation": { "type": "string", "nullable": true },
          "created_at": { "type": "string", "format": "date-time" }
        }
      },
      "MediaDetail": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "type": { "type": "string", "enum": ["image", "video", "document"] },
          "mime_type": { "type": "string" },
          "file_size": { "type": "integer", "description": "File size in bytes" },
          "status": { "type": "string", "enum": ["uploading", "ready", "failed"], "description": "Current upload/processing status" },
          "dimensions": {
            "nullable": true,
            "type": "object",
            "properties": {
              "width": { "type": "integer" },
              "height": { "type": "integer" }
            }
          },
          "aspect_ratio": { "type": "number", "nullable": true },
          "orientation": { "type": "string", "nullable": true, "enum": ["portrait", "landscape", "square"] },
          "created_at": { "type": "string", "format": "date-time" },
          "message": { "type": "string", "nullable": true, "description": "Guidance message when status is 'uploading' — tells you to call POST /media/{id}/complete" }
        }
      },
      "Pagination": {
        "type": "object",
        "properties": {
          "limit": { "type": "integer" },
          "offset": { "type": "integer" }
        }
      },
      "CreatePostRequest": {
        "type": "object",
        "required": ["content", "account_ids"],
        "properties": {
          "content": {
            "type": "string",
            "description": "The text content of the post"
          },
          "account_ids": {
            "type": "array",
            "items": { "type": "integer" },
            "minItems": 1,
            "description": "Array of social account IDs to post to (from list accounts)"
          },
          "scheduled_at": {
            "type": "string",
            "format": "date-time",
            "description": "ISO 8601 datetime to schedule the post. Omit to publish immediately."
          },
          "timezone": {
            "type": "string",
            "default": "UTC",
            "description": "IANA timezone (e.g. America/New_York)"
          },
          "media_ids": {
            "type": "array",
            "items": { "type": "string", "format": "uuid" },
            "description": "Array of media UUIDs to attach (from upload media)"
          },
          "platform_content": {
            "type": "object",
            "description": "Platform-specific content overrides and settings. Use the platform key (instagram, tiktok, youtube, linkedin, x, facebook, threads) as the object key.",
            "properties": {
              "instagram": {
                "type": "object",
                "properties": {
                  "content": { "type": "string", "description": "Override caption for Instagram" },
                  "contentType": { "type": "string", "enum": ["Post", "Story", "Reels", "Trial Reel"], "description": "Instagram content type. Videos default to Reels if not specified." },
                  "settings": {
                    "type": "object",
                    "properties": {
                      "altText": { "type": "string", "description": "Accessibility alt text for the image" },
                      "coverPhotoTimestamp": { "type": "number", "description": "Timestamp in seconds for video cover photo" }
                    }
                  }
                }
              },
              "tiktok": {
                "type": "object",
                "properties": {
                  "content": { "type": "string", "description": "Override caption for TikTok" },
                  "settings": {
                    "type": "object",
                    "properties": {
                      "privacyLevel": { "type": "string", "enum": ["PUBLIC_TO_EVERYONE", "MUTUAL_FOLLOW_FRIENDS", "FOLLOWER_OF_CREATOR", "SELF_ONLY"], "description": "TikTok video privacy level" },
                      "allowComments": { "type": "boolean", "default": true },
                      "allowDuet": { "type": "boolean", "default": true },
                      "allowStitch": { "type": "boolean", "default": true }
                    }
                  }
                }
              },
              "youtube": {
                "type": "object",
                "properties": {
                  "content": { "type": "string", "description": "Override description for YouTube" },
                  "settings": {
                    "type": "object",
                    "properties": {
                      "title": { "type": "string", "description": "YouTube video title (required for YouTube)" },
                      "privacyStatus": { "type": "string", "enum": ["public", "unlisted", "private"], "default": "public" },
                      "tags": { "type": "array", "items": { "type": "string" }, "description": "YouTube video tags" }
                    }
                  }
                }
              },
              "linkedin": {
                "type": "object",
                "properties": {
                  "content": { "type": "string", "description": "Override text for LinkedIn" }
                }
              },
              "x": {
                "type": "object",
                "properties": {
                  "content": { "type": "string", "description": "Override text for X (Twitter). Max 280 characters." }
                }
              },
              "facebook": {
                "type": "object",
                "properties": {
                  "content": { "type": "string", "description": "Override text for Facebook" }
                }
              },
              "threads": {
                "type": "object",
                "properties": {
                  "content": { "type": "string", "description": "Override text for Threads" }
                }
              }
            }
          }
        }
      },
      "CreatePostResponse": {
        "type": "object",
        "properties": {
          "post_id": { "type": "string", "format": "uuid" },
          "status": {
            "type": "string",
            "enum": ["publishing", "scheduled"]
          },
          "scheduled_for": { "type": "string", "format": "date-time", "nullable": true },
          "accounts_count": { "type": "integer" },
          "message": { "type": "string" }
        }
      },
      "BulkScheduleRequest": {
        "type": "object",
        "required": ["posts", "timezone"],
        "properties": {
          "posts": {
            "type": "array",
            "maxItems": 100,
            "items": {
              "type": "object",
              "required": ["date", "time", "content", "platforms"],
              "properties": {
                "date": { "type": "string", "description": "YYYY-MM-DD", "example": "2026-04-01" },
                "time": { "type": "string", "description": "HH:MM (24-hour)", "example": "09:00" },
                "content": { "type": "string", "description": "Post text content" },
                "platforms": { "type": "string", "description": "Platform names or account IDs joined with + (e.g. instagram+facebook or 2280+2282)" },
                "media": { "type": "string", "description": "Optional filename from your Media Library" }
              }
            }
          },
          "timezone": { "type": "string", "description": "IANA timezone for all posts (e.g. America/New_York)", "example": "America/New_York" },
          "dryRun": { "type": "boolean", "default": false, "description": "If true, validate only — no posts are created. Use this to preview errors before scheduling." },
          "accountMapping": {
            "type": "object",
            "description": "Optional platform-to-account-ID mapping. Overrides default account selection for platforms where you have multiple accounts.",
            "additionalProperties": { "type": "integer" },
            "example": { "instagram": 2280, "facebook": 2278 }
          }
        }
      },
      "BulkScheduleResponse": {
        "type": "object",
        "properties": {
          "dryRun": { "type": "boolean" },
          "scheduled": { "type": "integer", "description": "Number of posts successfully scheduled" },
          "failed": { "type": "integer", "description": "Number of posts that failed" },
          "results": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "row": { "type": "integer", "description": "Row index (0-based)" },
                "status": { "type": "string", "enum": ["scheduled", "failed"] },
                "postId": { "type": "string", "format": "uuid", "nullable": true },
                "error": { "type": "string", "nullable": true }
              }
            }
          },
          "batchErrors": {
            "type": "array",
            "description": "Batch-level errors that block scheduling (e.g. X daily limit exceeded, subscription limit)",
            "items": {
              "type": "object",
              "properties": {
                "type": { "type": "string" },
                "message": { "type": "string" }
              }
            }
          },
          "batchWarnings": {
            "type": "array",
            "description": "Warnings that don't block scheduling",
            "items": {
              "type": "object",
              "properties": {
                "type": { "type": "string" },
                "message": { "type": "string" }
              }
            }
          },
          "summary": {
            "type": "object",
            "properties": {
              "totalRows": { "type": "integer" },
              "validRows": { "type": "integer" },
              "errorRows": { "type": "integer" },
              "totalDestinations": { "type": "integer" },
              "platformBreakdown": { "type": "object", "additionalProperties": { "type": "integer" } }
            }
          }
        }
      },
      "UpdatePostRequest": {
        "type": "object",
        "properties": {
          "content": {
            "type": "string",
            "description": "New text content"
          },
          "scheduled_at": {
            "type": "string",
            "format": "date-time",
            "description": "New schedule time (ISO 8601)"
          },
          "timezone": {
            "type": "string",
            "description": "New IANA timezone"
          },
          "account_ids": {
            "type": "array",
            "items": { "type": "integer" },
            "description": "New target account IDs (replaces existing)"
          },
          "media_ids": {
            "type": "array",
            "items": { "type": "string", "format": "uuid" },
            "description": "New media attachments (replaces existing)"
          }
        }
      },
      "UploadMediaRequest": {
        "type": "object",
        "required": ["filename", "content_type", "size"],
        "properties": {
          "filename": {
            "type": "string",
            "maxLength": 255,
            "description": "Original filename with extension",
            "example": "photo.jpg"
          },
          "content_type": {
            "type": "string",
            "enum": ["image/jpeg", "image/png", "image/gif", "image/webp", "image/heic", "image/heif", "video/mp4", "application/pdf"],
            "description": "MIME type of the file. Only MP4 is accepted for video."
          },
          "size": {
            "type": "integer",
            "minimum": 1,
            "description": "File size in bytes. Max 20MB for images/documents, 500MB for video."
          },
          "width": {
            "type": "integer",
            "description": "Image/video width in pixels (optional, used for aspect ratio)"
          },
          "height": {
            "type": "integer",
            "description": "Image/video height in pixels (optional, used for aspect ratio)"
          },
          "duration": {
            "type": "number",
            "description": "Video duration in seconds (optional)"
          }
        }
      },
      "UploadMediaResponse": {
        "type": "object",
        "properties": {
          "media_id": { "type": "string", "format": "uuid", "description": "Use this ID when calling POST /media/{id}/complete and when attaching to posts via media_ids" },
          "upload_url": {
            "type": "string",
            "description": "Presigned URL to upload your file to"
          },
          "upload_method": {
            "type": "object",
            "description": "How to upload the file to upload_url",
            "properties": {
              "method": { "type": "string", "enum": ["PUT", "POST"], "description": "HTTP method to use" },
              "content_type": { "type": "string", "description": "Content-Type header to set (for PUT) or multipart/form-data (for POST)" },
              "field_name": { "type": "string", "description": "Form field name (only for POST/multipart uploads)" },
              "headers": {
                "type": "object",
                "additionalProperties": { "type": "string" },
                "description": "Headers to include with the upload request (for PUT uploads)"
              }
            }
          },
          "provider": { "type": "string", "enum": ["cloudflare_images", "r2"], "description": "Storage provider used for this upload" },
          "expires_in": {
            "type": "integer",
            "description": "Seconds until the upload URL expires",
            "example": 3600
          },
          "max_size": {
            "type": "integer",
            "description": "Maximum file size in bytes"
          },
          "next_step": {
            "type": "string",
            "description": "Instructions for what to do after uploading",
            "example": "Upload your file to upload_url, then call POST /media/{media_id}/complete to finalize."
          }
        }
      },
      "CompleteMediaResponse": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "status": { "type": "string", "enum": ["ready"], "description": "Upload status after completion" },
          "type": { "type": "string", "enum": ["image", "video", "document"] },
          "thumbnail_url": { "type": "string", "nullable": true, "description": "Thumbnail URL (images get it immediately, videos get it async)" },
          "message": { "type": "string", "description": "Human-readable next steps", "example": "Image is ready. You can now attach it to a post using media_ids." }
        }
      },
      "GenerateImageRequest": {
        "type": "object",
        "required": ["prompt"],
        "properties": {
          "prompt": {
            "type": "string",
            "maxLength": 2000,
            "description": "Text description of the image to generate",
            "example": "A professional photo of a modern office workspace with natural lighting"
          },
          "aspect_ratio": {
            "type": "string",
            "enum": ["1:1", "16:9", "9:16", "4:3", "3:4", "4:5", "5:4"],
            "default": "1:1",
            "description": "Output image aspect ratio"
          },
          "model": {
            "type": "string",
            "enum": ["nano-banana-pro", "ideogram-v2", "gemini-3-pro", "flux-schnell"],
            "default": "nano-banana-pro",
            "description": "AI model to use. nano-banana-pro (15 credits, best quality), ideogram-v2 (8 credits, best text), gemini-3-pro (5 credits), flux-schnell (1 credit, fast)"
          }
        }
      },
      "GenerateImageResponse": {
        "type": "object",
        "properties": {
          "media_id": { "type": "string", "format": "uuid", "description": "Use this ID to attach the image to a post via media_ids" },
          "model": { "type": "string", "description": "Model that was used", "example": "nano-banana-pro" },
          "aspect_ratio": { "type": "string", "example": "16:9" },
          "credits_used": { "type": "integer", "description": "Credits consumed by this generation", "example": 15 },
          "credits_remaining": { "type": "integer", "description": "Credits remaining after this generation", "example": 485 },
          "message": { "type": "string", "example": "Image generated successfully. Attach it to a post using media_ids." }
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Invalid request parameters",
        "content": {
          "application/json": {
            "schema": {
              "allOf": [
                { "$ref": "#/components/schemas/ApiEnvelope" },
                {
                  "type": "object",
                  "properties": {
                    "data": { "nullable": true },
                    "error": {
                      "type": "object",
                      "properties": {
                        "message": { "type": "string", "example": "Missing required fields: content and account_ids are required." },
                        "code": { "type": "string", "example": "validation_error" },
                        "details": { "description": "Additional context (e.g. which fields are missing, current vs max values)" }
                      }
                    }
                  }
                }
              ]
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Missing or invalid API key",
        "content": {
          "application/json": {
            "schema": {
              "allOf": [
                { "$ref": "#/components/schemas/ApiEnvelope" },
                {
                  "type": "object",
                  "properties": {
                    "data": { "nullable": true },
                    "error": {
                      "type": "object",
                      "properties": {
                        "message": { "type": "string", "example": "Invalid API key. Check that the key is correct and has not been deleted." },
                        "code": { "type": "string", "enum": ["invalid_api_key", "api_key_revoked", "api_key_expired", "insufficient_scope"] }
                      }
                    }
                  }
                }
              ]
            }
          }
        }
      },
      "NotFound": {
        "description": "Resource not found",
        "content": {
          "application/json": {
            "schema": {
              "allOf": [
                { "$ref": "#/components/schemas/ApiEnvelope" },
                {
                  "type": "object",
                  "properties": {
                    "data": { "nullable": true },
                    "error": {
                      "type": "object",
                      "properties": {
                        "message": { "type": "string", "example": "Resource not found. Verify the ID belongs to your workspace." },
                        "code": { "type": "string", "example": "not_found" }
                      }
                    }
                  }
                }
              ]
            }
          }
        }
      },
      "RateLimited": {
        "description": "Rate limit exceeded",
        "headers": {
          "X-RateLimit-Limit": {
            "schema": { "type": "integer" },
            "description": "Request limit per window"
          },
          "X-RateLimit-Remaining": {
            "schema": { "type": "integer" },
            "description": "Remaining requests in current window"
          },
          "X-RateLimit-Reset": {
            "schema": { "type": "integer" },
            "description": "Unix timestamp when the window resets"
          },
          "Retry-After": {
            "schema": { "type": "integer" },
            "description": "Seconds to wait before retrying"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "allOf": [
                { "$ref": "#/components/schemas/ApiEnvelope" },
                {
                  "type": "object",
                  "properties": {
                    "data": { "nullable": true },
                    "error": {
                      "type": "object",
                      "properties": {
                        "message": { "type": "string", "example": "Rate limit exceeded. Check Retry-After header for wait time." },
                        "code": { "type": "string", "example": "rate_limit_exceeded" },
                        "details": {
                          "type": "object",
                          "properties": {
                            "retry_after": { "type": "integer", "description": "Seconds to wait" },
                            "limit": { "type": "integer" },
                            "window": { "type": "string" }
                          }
                        }
                      }
                    }
                  }
                }
              ]
            }
          }
        }
      },
      "InternalError": {
        "description": "Internal server error",
        "content": {
          "application/json": {
            "schema": {
              "allOf": [
                { "$ref": "#/components/schemas/ApiEnvelope" },
                {
                  "type": "object",
                  "properties": {
                    "data": { "nullable": true },
                    "error": {
                      "type": "object",
                      "properties": {
                        "message": { "type": "string", "example": "An internal error occurred. Please try again." },
                        "code": { "type": "string", "example": "internal_error" }
                      }
                    }
                  }
                }
              ]
            }
          }
        }
      }
    }
  }
}
