{
  "openapi": "3.1.0",
  "info": {
    "title": "PostNitro Embed API",
    "version": "1.0.0",
    "summary": "Design social posts programmatically, then schedule and publish them natively.",
    "description": "PostNitro is a design engine for social content, with a scheduling layer on top.\n\nThe PostNitro Embed API lets applications, AI agents, and automation tools design social media posts and also schedule and publish them natively to LinkedIn, Instagram, TikTok, Threads, Facebook Pages, and Pinterest boards.\n\nDesign is asynchronous: initiate a job (either AI generation from a topic, article, or X post, or import of your own slides), poll the status endpoint, then fetch the finished output, which carries the `designId`.\n\nScheduling is synchronous: `POST /schedule` with that `designId`, a caption, the ids of the connected social accounts to publish to, and a future `scheduledAt`. PostNitro publishes the post at that time; read it back with `GET /schedule/{id}` to see each account's publishing status and live link. X (Twitter) is an AI generation source, not a publishing target.\n\nAuthentication uses a long-lived API key supplied in the `embed-api-key` request header. Generate keys in your PostNitro account settings at https://postnitro.ai/app. GET requests work on any plan; requests that create, change, or delete anything need a plan that includes API access.",
    "termsOfService": "https://postnitro.ai/terms",
    "contact": {
      "name": "PostNitro Support",
      "email": "support@postnitro.ai",
      "url": "https://postnitro.ai"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://postnitro.ai/terms"
    }
  },
  "externalDocs": {
    "description": "Full human-readable API documentation",
    "url": "https://postnitro.ai/docs/embed/api"
  },
  "servers": [
    {
      "url": "https://embed-api.postnitro.ai",
      "description": "Production"
    }
  ],
  "security": [
    {
      "embedApiKey": []
    }
  ],
  "tags": [
    {
      "name": "Post Lifecycle",
      "description": "Create, monitor, and retrieve carousel post generation jobs."
    },
    {
      "name": "Scheduling",
      "description": "Schedule designed posts (or text-only drafts) to connected LinkedIn, Instagram, TikTok, Threads, Facebook Page, and Pinterest board accounts, and track their publishing status."
    },
    {
      "name": "Audio",
      "description": "List and delete the workspace's audio tracks, whose ids are used as `postSettings.audioId` on scheduled reels."
    },
    {
      "name": "Social Accounts",
      "description": "Look up the social accounts connected in the PostNitro app, whose ids are the publishing targets for scheduled posts."
    }
  ],
  "paths": {
    "/post/initiate/generate": {
      "post": {
        "tags": ["Post Lifecycle"],
        "operationId": "initiateGenerate",
        "summary": "Initiate AI carousel generation",
        "description": "Creates a carousel post generation job using AI. Content is generated from a free-form topic, a pasted article, or a reference X (Twitter) post, according to the `aiGeneration.type` field.\n\nThis endpoint returns immediately with an `embedPostId` and status `PENDING`. Poll `/post/status/{embedPostId}` to track progress, then call `/post/output/{embedPostId}` once status is `COMPLETED`.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/GenerateRequest" },
              "example": {
                "postType": "CAROUSEL",
                "requestorId": "user123",
                "templateId": "xtrnxgfgk9a08tmsz2601k1c",
                "brandId": "cmdhb8oe10006v0pmh6a4no6t",
                "presetId": "preset123",
                "responseType": "PNG",
                "aiGeneration": {
                  "type": "article",
                  "context": "Create a carousel about digital marketing tips",
                  "instructions": "Focus on actionable tips for small businesses"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Generation job initiated.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/InitiateResponse" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "402": { "$ref": "#/components/responses/PaymentRequired" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/post/initiate/import": {
      "post": {
        "tags": ["Post Lifecycle"],
        "operationId": "initiateImport",
        "summary": "Initiate carousel creation from imported slides",
        "description": "Creates a carousel post by importing caller-supplied slide content. Use this endpoint when you already have the text and images for each slide.\n\nSlide rules:\n- Exactly one `starting_slide` (required)\n- At least one `body_slide` (required)\n- Exactly one `ending_slide` (required)\n- `layoutType: \"infographic\"` replaces any `image` on that slide with a structured infographic rendered from `layoutConfig`\n\nReturns immediately with `embedPostId` and status `PENDING`. Poll `/post/status/{embedPostId}` and then retrieve the output from `/post/output/{embedPostId}`.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/ImportRequest" },
              "examples": {
                "default": {
                  "summary": "Simple import with images",
                  "value": {
                    "postType": "CAROUSEL",
                    "templateId": "xtrnxgfgk9a08tmsz2601k1c",
                    "brandId": "cmdhb8oe10006v0pmh6a4no6t",
                    "responseType": "PNG",
                    "slides": [
                      {
                        "type": "starting_slide",
                        "sub_heading": "My Awesome Subtitle",
                        "heading": "Welcome to the Carousel!",
                        "description": "This is how you start with a bang.",
                        "cta_button": "Swipe to learn more",
                        "background_image": "https://example.com/background1.jpg"
                      },
                      {
                        "type": "body_slide",
                        "heading": "Section 1: The Core Idea",
                        "description": "Explain your first key point here.",
                        "image": "https://example.com/image1.jpg"
                      },
                      {
                        "type": "ending_slide",
                        "sub_heading": "Ready to Act?",
                        "heading": "Get Started Today!",
                        "description": "A final encouraging message.",
                        "cta_button": "Visit Our Website",
                        "image": "https://example.com/logo.png"
                      }
                    ]
                  }
                },
                "infographic": {
                  "summary": "With infographic body slides",
                  "value": {
                    "postType": "CAROUSEL",
                    "templateId": "xtrnxgfgk9a08tmsz2601k1c",
                    "brandId": "cmdhb8oe10006v0pmh6a4no6t",
                    "responseType": "PNG",
                    "slides": [
                      {
                        "type": "starting_slide",
                        "heading": "PostNitro Infographics",
                        "description": "Create stunning visual carousels with structured data.",
                        "cta_button": "Learn More"
                      },
                      {
                        "type": "body_slide",
                        "heading": "Grid Layout Infographics",
                        "description": "Display multiple data points in an organized grid format.",
                        "layoutType": "infographic",
                        "layoutConfig": {
                          "columnCount": 2,
                          "columnDisplay": "grid",
                          "displayCounterAs": "counter",
                          "hasHeader": true,
                          "columnData": [
                            {
                              "header": "Layout Options",
                              "content": [
                                { "title": "Grid Display", "description": "Organize content in structured columns." },
                                { "title": "Counter Support", "description": "Add numbered counters for sequencing." }
                              ]
                            },
                            {
                              "header": "Customization",
                              "content": [
                                { "title": "Column Headers", "description": "Enable or disable headers per column." },
                                { "title": "Flexible Columns", "description": "Choose 1, 2, or 3 columns." }
                              ]
                            }
                          ]
                        }
                      },
                      {
                        "type": "ending_slide",
                        "heading": "Try PostNitro Infographics",
                        "cta_button": "Get Your API Key"
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Import job initiated.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/InitiateResponse" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "402": { "$ref": "#/components/responses/PaymentRequired" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/post/status/{embedPostId}": {
      "get": {
        "tags": ["Post Lifecycle"],
        "operationId": "getPostStatus",
        "summary": "Retrieve post status and processing logs",
        "description": "Returns the current status of a carousel generation job along with a chronological list of processing logs. Poll this endpoint until `embedPost.status` is `COMPLETED` or `FAILED`.",
        "parameters": [
          { "$ref": "#/components/parameters/EmbedPostIdPath" }
        ],
        "responses": {
          "200": {
            "description": "Current status and logs for the job.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/StatusResponse" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/post/output/{embedPostId}": {
      "get": {
        "tags": ["Post Lifecycle"],
        "operationId": "getPostOutput",
        "summary": "Retrieve the finished carousel output",
        "description": "Fetches the rendered carousel once the job's status is `COMPLETED`. The `result.data` field contains either an array of per-slide PNG references (when `responseType` was `PNG`) or a single PDF reference (when `responseType` was `PDF`).",
        "parameters": [
          { "$ref": "#/components/parameters/EmbedPostIdPath" }
        ],
        "responses": {
          "200": {
            "description": "Rendered output.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/OutputResponse" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "409": {
            "description": "Job is not yet complete. Continue polling `/post/status/{embedPostId}`.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          }
        }
      }
    },
    "/schedule": {
      "get": {
        "tags": ["Scheduling"],
        "operationId": "listScheduledPosts",
        "summary": "List scheduled posts in a date range",
        "description": "Returns the workspace's scheduled posts and drafts whose `scheduledAt` falls between `fromDate` and `toDate` (inclusive), optionally only those targeting given social accounts. Not paginated: narrow the date range to page. Works on any plan.",
        "parameters": [
          {
            "name": "fromDate",
            "in": "query",
            "required": true,
            "schema": { "type": "string", "format": "date-time" },
            "description": "Start of the range, ISO 8601 (e.g. `2026-10-01T00:00:00Z`).",
            "example": "2026-10-01T00:00:00Z"
          },
          {
            "name": "toDate",
            "in": "query",
            "required": true,
            "schema": { "type": "string", "format": "date-time" },
            "description": "End of the range, ISO 8601.",
            "example": "2026-10-31T23:59:59Z"
          },
          {
            "name": "socialAccountIds",
            "in": "query",
            "required": false,
            "schema": { "type": "string" },
            "description": "Comma-separated social account ids. A post is returned when it targets at least one of them; unknown ids match nothing.",
            "example": "cmqi7jyir0003v0m91hxnyyaq,cmqi7k2ab0005v0m9q1w2e3r4"
          }
        ],
        "responses": {
          "200": {
            "description": "Scheduled posts in the range (possibly empty).",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ScheduledPostListResponse" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/ScheduleRuleViolation" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "422": { "$ref": "#/components/responses/ValidationError" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/ScheduleServerError" }
        }
      },
      "post": {
        "tags": ["Scheduling"],
        "operationId": "createScheduledPost",
        "summary": "Schedule a post or save a draft",
        "description": "Schedules a design (and/or a text caption) to publish to one or more connected LinkedIn, Instagram, TikTok, Threads, Facebook Page, or Pinterest board accounts at `scheduledAt`, or saves it as a draft. PostNitro publishes it at that time; poll `GET /schedule/{id}` for per-account status and live links.\n\nTypical flow: design with `/post/initiate/generate` or `/post/initiate/import`, wait for `COMPLETED`, take `data.result.designId` from `/post/output/{embedPostId}`, get account ids from `GET /social-account`, then call this endpoint.\n\nRules enforced by the scheduler (400 when broken): `scheduledAt` must be in the future (drafts included); at least one of `designId` or a non-empty caption; every `designId` and account id must belong to the API key's workspace; platform settings are required for each selected platform as described on its schema; a `SCHEDULED` LinkedIn, Facebook Page, or Pinterest post needs a design.\n\nAlways send `designId` explicitly, as a design id or `null`. Omitting it is not the same as `null`: with it omitted the scheduler still demands Instagram, TikTok, and Threads settings for those accounts, and skips the check that a `SCHEDULED` LinkedIn post has a design.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/ScheduleRequest" },
              "example": {
                "status": "SCHEDULED",
                "scheduledAt": "2026-10-01T09:00:00Z",
                "designId": "gpmg7dl0t73tukqvbnrtoqw6",
                "postContent": {
                  "common": "5 lessons from shipping our API #buildinpublic",
                  "linkedin": "5 lessons from shipping our API. Swipe through for the details. #buildinpublic"
                },
                "selectedAccounts": ["cmqi7jyir0003v0m91hxnyyaq", "cmqi7k2ab0005v0m9q1w2e3r4"],
                "linkedinPostSettings": {
                  "postType": "document",
                  "postTitle": "5 lessons from shipping our API"
                },
                "instagramPostSettings": { "postType": "carousel", "postAsStory": false }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Post scheduled (or draft saved).",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ScheduledPostResponse" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/ScheduleRuleViolation" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "402": { "$ref": "#/components/responses/PaymentRequired" },
          "422": { "$ref": "#/components/responses/ValidationError" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/ScheduleServerError" }
        }
      }
    },
    "/schedule/{id}": {
      "parameters": [
        { "$ref": "#/components/parameters/ScheduledPostIdPath" }
      ],
      "get": {
        "tags": ["Scheduling"],
        "operationId": "getScheduledPost",
        "summary": "Get a scheduled post",
        "description": "Returns one scheduled post or draft, including each selected account's publishing status, error, and live link. Works on any plan.",
        "responses": {
          "200": {
            "description": "The scheduled post.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ScheduledPostResponse" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/ScheduledPostNotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/ScheduleServerError" }
        }
      },
      "put": {
        "tags": ["Scheduling"],
        "operationId": "updateScheduledPost",
        "summary": "Replace a scheduled post",
        "description": "Replaces a scheduled post or draft. This is a full replacement, not a patch: send the complete body, because captions and selected accounts are rebuilt from it and any platform settings left out are cleared. Every selected account's status is reset (`PENDING`, or `DRAFT` for drafts). Use it to reschedule, change captions or accounts, or promote a `DRAFT` to `SCHEDULED`.\n\nRules enforced by the scheduler (400 when broken): `scheduledAt` must be in the future (drafts included); at least one of `designId` or a non-empty caption; every `designId` and account id must belong to the API key's workspace; platform settings are required for each selected platform as described on its schema; a `SCHEDULED` LinkedIn, Facebook Page, or Pinterest post needs a design.\n\nAlways send `designId` explicitly, as a design id or `null`. Omitting it is not the same as `null`: with it omitted the scheduler still demands Instagram, TikTok, and Threads settings for those accounts, and skips the check that a `SCHEDULED` LinkedIn post has a design.\n\nThe current status is not checked: a PUT on a post that is already `IN_PROGRESS`, `PUBLISHED`, `PARTIALLY_FAILED`, or `FAILED` is accepted, resets every account's status, and with `status: SCHEDULED` queues the post again, which can publish it a second time. Read the post first and only update `DRAFT` or `SCHEDULED` posts.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/ScheduleRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Post updated.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ScheduledPostResponse" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/ScheduleRuleViolation" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "402": { "$ref": "#/components/responses/PaymentRequired" },
          "404": { "$ref": "#/components/responses/ScheduledPostNotFound" },
          "422": { "$ref": "#/components/responses/ValidationError" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/ScheduleServerError" }
        }
      },
      "delete": {
        "tags": ["Scheduling"],
        "operationId": "deleteScheduledPost",
        "summary": "Delete a scheduled post",
        "description": "Deletes a scheduled post or draft together with its captions and account rows. It does not remove anything already published on a social platform, and the attached design is kept.",
        "responses": {
          "200": {
            "description": "Post deleted.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ScheduledPostDeleteResponse" }
              }
            }
          },
          "400": {
            "description": "The post could not be deleted (`Error deleting post`).",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ScheduleErrorResponse" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "402": { "$ref": "#/components/responses/PaymentRequired" },
          "404": { "$ref": "#/components/responses/ScheduledPostNotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/ScheduleServerError" }
        }
      }
    },
    "/audio": {
      "get": {
        "tags": ["Audio"],
        "operationId": "listAudio",
        "summary": "List workspace audio files",
        "description": "Lists the audio tracks in the API key's workspace, newest first. Use an entry's `id` as `postSettings.audioId` on a scheduled reel (or `videoSettings.audioId` when generating a video); both take an id, never a URL. Tracks are uploaded in the PostNitro app; this API only lists and deletes. An empty library returns 200 with an empty array. Works on any plan.",
        "parameters": [
          { "$ref": "#/components/parameters/PageQuery" },
          { "$ref": "#/components/parameters/LimitQuery" }
        ],
        "responses": {
          "200": {
            "description": "The workspace's audio tracks.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/AudioListResponse" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "422": { "$ref": "#/components/responses/ValidationError" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          }
        }
      }
    },
    "/audio/{id}": {
      "parameters": [
        { "$ref": "#/components/parameters/AudioIdPath" }
      ],
      "delete": {
        "tags": ["Audio"],
        "operationId": "deleteAudio",
        "summary": "Delete an audio file",
        "description": "Permanently deletes the audio record and its stored file. Refused with a 400 while any scheduled post's `postSettings.audioId` still references it, because a reel resolves its audio at publish time. Videos already rendered to MP4 are unaffected.",
        "responses": {
          "200": {
            "description": "Audio deleted.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/AudioDeleteResponse" }
              }
            }
          },
          "400": {
            "description": "The audio is still used by a scheduled post, or the record could not be deleted.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "402": { "$ref": "#/components/responses/PaymentRequired" },
          "404": {
            "description": "No audio file with this id in the workspace.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": {
            "description": "The stored file or the record could not be deleted.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          }
        }
      }
    },
    "/social-account": {
      "get": {
        "tags": ["Social Accounts"],
        "operationId": "listSocialAccounts",
        "summary": "List connected social accounts",
        "description": "Returns the LinkedIn, Instagram, TikTok, Threads, Facebook Page, and Pinterest board accounts connected to the API key's workspace, grouped by platform. Use an account's `id` in `selectedAccounts` on `POST /schedule`. Accounts are connected in the PostNitro app, not through this API. Not paginated. Works on any plan.",
        "responses": {
          "200": {
            "description": "Connected accounts by platform.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/SocialAccountListResponse" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          }
        }
      }
    },
    "/social-account/{id}": {
      "parameters": [
        { "$ref": "#/components/parameters/SocialAccountIdPath" }
      ],
      "get": {
        "tags": ["Social Accounts"],
        "operationId": "getSocialAccount",
        "summary": "Get a social account",
        "description": "Returns one connected account, including its token status and expiry, plus counts of its scheduled-post targets by status. Works on any plan.",
        "responses": {
          "200": {
            "description": "The social account.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/SocialAccountResponse" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": {
            "description": "No social account with this id in the workspace (`Social account not found`).",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "embedApiKey": {
        "type": "apiKey",
        "in": "header",
        "name": "embed-api-key",
        "description": "PostNitro Embed API key. Generate at https://postnitro.ai/app under account settings."
      }
    },
    "parameters": {
      "EmbedPostIdPath": {
        "name": "embedPostId",
        "in": "path",
        "required": true,
        "description": "Identifier returned by `/post/initiate/generate` or `/post/initiate/import`.",
        "schema": { "type": "string" },
        "example": "post123"
      },
      "ScheduledPostIdPath": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": { "type": "string" },
        "description": "Scheduled post id, from `POST /schedule` or `GET /schedule`.",
        "example": "cmqkvnhk50001v0qusiglvc8h"
      },
      "AudioIdPath": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": { "type": "string" },
        "description": "Audio id, from `GET /audio`."
      },
      "PageQuery": {
        "name": "page",
        "in": "query",
        "required": false,
        "schema": { "type": "integer", "minimum": 1, "default": 1 },
        "description": "Page number."
      },
      "LimitQuery": {
        "name": "limit",
        "in": "query",
        "required": false,
        "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 10 },
        "description": "Results per page."
      },
      "SocialAccountIdPath": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": { "type": "string" },
        "description": "Social account id, from `GET /social-account`."
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Request validation failed.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ErrorResponse" }
          }
        }
      },
      "Unauthorized": {
        "description": "Missing or invalid `embed-api-key`, or the key has no workspace attached.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/GatewayErrorResponse" }
          }
        }
      },
      "PaymentRequired": {
        "description": "The account's plan does not include API access (a Creator+/Team+ plan, or the API Credits add-on on a paid plan). Only state-changing requests are gated; GET requests work on any plan.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/GatewayErrorResponse" }
          }
        }
      },
      "NotFound": {
        "description": "No post exists with the given `embedPostId` for this account.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ErrorResponse" }
          }
        }
      },
      "RateLimited": {
        "description": "Too many requests in the current one-minute window. Production limits per API key per minute: 5 for `/post/initiate/generate`, 5 for `/post/initiate/import`, 120 each for `/post/status` and `/post/output`, and 30 shared across every other endpoint, including scheduling and audio.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/GatewayErrorResponse" }
          }
        },
        "headers": {
          "Retry-After": {
            "description": "Seconds until the window resets.",
            "schema": { "type": "integer" }
          },
          "X-RateLimit-Limit": {
            "schema": { "type": "integer" }
          },
          "X-RateLimit-Remaining": {
            "schema": { "type": "integer" }
          },
          "X-RateLimit-Reset": {
            "description": "When the window resets.",
            "schema": { "type": "string", "format": "date-time" }
          }
        }
      },
      "ValidationError": {
        "description": "The request failed the Embed API's shape validation (missing or mistyped field, unknown body key, or an `audioId` that is not an audio file in this workspace). Nothing was forwarded or saved.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/GatewayErrorResponse" }
          }
        }
      },
      "ScheduleRuleViolation": {
        "description": "PostNitro's scheduler rejected the post: for example `scheduledAt` in the past, neither a design nor a caption, a design or social account not in this workspace, or missing or invalid platform settings for a selected account.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ScheduleErrorResponse" }
          }
        }
      },
      "ScheduledPostNotFound": {
        "description": "No scheduled post with this id in the API key's workspace (`Post details not found.`).",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ScheduleErrorResponse" }
          }
        }
      },
      "ScheduleServerError": {
        "description": "The scheduler could not be reached or failed unexpectedly.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ScheduleErrorResponse" }
          }
        }
      }
    },
    "schemas": {
      "PostType": {
        "type": "string",
        "enum": ["CAROUSEL", "IMAGE", "VIDEO"],
        "description": "Kind of post, uppercase. `CAROUSEL` is a multi-slide carousel, `IMAGE` a single-image post, and `VIDEO` a short video whose slides are its scenes."
      },
      "ResponseType": {
        "type": "string",
        "enum": ["DESIGN", "PDF", "PNG", "MP4"],
        "description": "What to produce. `DESIGN` saves the design without rendering a file (open it via `editorUrl`, or schedule it via `designId`). `PNG` renders one image per slide, `PDF` a single document, `MP4` a single video. `MP4` is valid only for `VIDEO`, and `VIDEO` accepts only `MP4` or `DESIGN`. Required: a request without it is rejected with 422."
      },
      "JobStatus": {
        "type": "string",
        "enum": ["PENDING", "PROCESSING", "COMPLETED", "FAILED"],
        "description": "Lifecycle state of a generation job."
      },
      "AIGeneration": {
        "type": "object",
        "required": ["type", "context"],
        "properties": {
          "type": {
            "type": "string",
            "enum": ["text", "article", "x"],
            "description": "Source of the generation prompt. `text` = free-form topic, `article` = pasted article body, `x` = reference X (Twitter) post."
          },
          "context": {
            "type": "string",
            "description": "Primary input: topic description, article text, or X post content."
          },
          "instructions": {
            "type": "string",
            "description": "Optional additional guidance for the AI (tone, focus, audience)."
          }
        }
      },
      "GenerateRequest": {
        "type": "object",
        "required": [
          "postType",
          "responseType",
          "templateId",
          "brandId",
          "presetId",
          "aiGeneration"
        ],
        "properties": {
          "postType": { "$ref": "#/components/schemas/PostType" },
          "requestorId": {
            "type": "string",
            "description": "Optional caller-supplied identifier for your own tracking."
          },
          "templateId": {
            "type": "string",
            "description": "Template ID configured in your PostNitro account."
          },
          "brandId": {
            "type": "string",
            "description": "Brand configuration ID from your PostNitro account."
          },
          "presetId": {
            "type": "string",
            "description": "AI generation preset ID."
          },
          "responseType": { "$ref": "#/components/schemas/ResponseType" },
          "aiGeneration": { "$ref": "#/components/schemas/AIGeneration" },
          "generateImages": { "$ref": "#/components/schemas/GenerateImages" },
          "videoSettings": { "$ref": "#/components/schemas/VideoSettings" }
        },
        "additionalProperties": false
      },
      "SlideType": {
        "type": "string",
        "enum": ["starting_slide", "body_slide", "ending_slide"]
      },
      "LayoutType": {
        "type": "string",
        "enum": ["default", "infographic"],
        "default": "default"
      },
      "InfographicColumn": {
        "type": "object",
        "required": ["header", "content"],
        "properties": {
          "header": { "type": "string", "description": "Column header label." },
          "content": {
            "type": "array",
            "items": {
              "type": "object",
              "required": ["title", "description"],
              "properties": {
                "title": { "type": "string" },
                "description": { "type": "string" }
              }
            }
          }
        }
      },
      "LayoutConfig": {
        "type": "object",
        "required": ["columnCount", "columnDisplay", "displayCounterAs", "hasHeader"],
        "properties": {
          "columnCount": {
            "type": "integer",
            "enum": [1, 2, 3],
            "description": "Number of columns. Cyclical infographics use column 1 only."
          },
          "columnDisplay": {
            "type": "string",
            "enum": ["cycle", "grid"]
          },
          "displayCounterAs": {
            "type": "string",
            "enum": ["none", "counter"]
          },
          "hasHeader": {
            "type": "boolean",
            "description": "Whether each column displays its `header` value."
          },
          "columnData": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/InfographicColumn" }
          }
        }
      },
      "Slide": {
        "type": "object",
        "required": ["type", "heading"],
        "properties": {
          "type": { "$ref": "#/components/schemas/SlideType" },
          "heading": { "type": "string" },
          "sub_heading": { "type": "string" },
          "description": { "type": "string" },
          "image": { "type": "string", "format": "uri" },
          "background_image": { "type": "string", "format": "uri" },
          "cta_button": { "type": "string" },
          "layoutType": { "$ref": "#/components/schemas/LayoutType" },
          "layoutConfig": { "$ref": "#/components/schemas/LayoutConfig" }
        }
      },
      "ImportRequest": {
        "type": "object",
        "required": ["postType", "responseType", "templateId", "brandId", "slides"],
        "properties": {
          "postType": { "$ref": "#/components/schemas/PostType" },
          "requestorId": {
            "type": "string",
            "description": "Optional caller-supplied identifier."
          },
          "templateId": { "type": "string" },
          "brandId": { "type": "string" },
          "responseType": { "$ref": "#/components/schemas/ResponseType" },
          "slides": {
            "description": "CAROUSEL and VIDEO: an array of slides (for VIDEO each slide is a scene) with exactly one `starting_slide`, at least one `body_slide`, and exactly one `ending_slide`. IMAGE: a single `ImageSlide` object. Sending the wrong shape for the postType is rejected with 422.",
            "oneOf": [
              {
                "type": "array",
                "minItems": 3,
                "items": { "$ref": "#/components/schemas/Slide" }
              },
              { "$ref": "#/components/schemas/ImageSlide" }
            ]
          },
          "generateImages": { "$ref": "#/components/schemas/GenerateImages" },
          "videoSettings": { "$ref": "#/components/schemas/VideoSettings" }
        },
        "additionalProperties": false
      },
      "InitiateResponse": {
        "type": "object",
        "required": ["success", "message", "data"],
        "properties": {
          "success": { "type": "boolean", "const": true },
          "message": { "type": "string", "example": "CAROUSEL generation initiated" },
          "data": {
            "type": "object",
            "required": ["embedPostId", "status"],
            "properties": {
              "embedPostId": { "type": "string" },
              "status": { "$ref": "#/components/schemas/JobStatus" }
            }
          }
        }
      },
      "PostLog": {
        "type": "object",
        "required": ["id", "embedPostId", "step", "status", "message", "timestamp"],
        "properties": {
          "id": { "type": "string" },
          "embedPostId": { "type": "string" },
          "step": {
            "type": "string",
            "description": "Lifecycle step being logged.",
            "examples": ["INITIATED", "PROCESSING", "COMPLETED", "FAILED"]
          },
          "status": {
            "type": "string",
            "enum": ["SUCCESS", "FAILED"]
          },
          "message": { "type": "string" },
          "timestamp": { "type": "string", "format": "date-time" }
        }
      },
      "EmbedPost": {
        "type": "object",
        "required": ["id", "postType", "status", "createdAt", "updatedAt"],
        "properties": {
          "id": { "type": "string" },
          "postType": { "$ref": "#/components/schemas/PostType" },
          "responseType": { "$ref": "#/components/schemas/ResponseType" },
          "status": { "$ref": "#/components/schemas/JobStatus" },
          "credits": {
            "type": "integer",
            "description": "Credits consumed by this job (present on completed posts)."
          },
          "createdAt": { "type": "string", "format": "date-time" },
          "updatedAt": { "type": "string", "format": "date-time" }
        }
      },
      "StatusResponse": {
        "type": "object",
        "required": ["success", "data"],
        "properties": {
          "success": { "type": "boolean", "const": true },
          "data": {
            "type": "object",
            "required": ["embedPostId", "embedPost", "logs"],
            "properties": {
              "embedPostId": { "type": "string" },
              "embedPost": { "$ref": "#/components/schemas/EmbedPost" },
              "logs": {
                "type": "array",
                "items": { "$ref": "#/components/schemas/PostLog" }
              }
            }
          }
        }
      },
      "OutputResult": {
        "type": "object",
        "required": ["designId", "name", "size", "editorUrl"],
        "properties": {
          "name": {
            "type": "string",
            "description": "Design name from the template, or `Untitled`."
          },
          "size": {
            "description": "Design aspect ratio: an id such as `4:5`, or an object with the id and pixel dimensions.",
            "oneOf": [
              {
                "type": "string",
                "examples": ["4:5", "1:1", "9:16", "16:9"]
              },
              {
                "type": "object",
                "properties": {
                  "id": { "type": "string" },
                  "dimensions": {
                    "type": "object",
                    "properties": {
                      "width": { "type": "integer" },
                      "height": { "type": "integer" }
                    }
                  }
                }
              }
            ]
          },
          "type": {
            "type": "string",
            "enum": ["png", "pdf", "mp4"],
            "description": "Rendered file type. Absent for DESIGN."
          },
          "mimeType": {
            "type": "string",
            "enum": ["image/png", "application/pdf", "video/mp4"],
            "description": "Rendered file MIME type. Absent for DESIGN."
          },
          "data": {
            "description": "PNG: array of image URLs (one per slide). PDF: a single document URL. MP4: a single video URL. Absent for DESIGN.",
            "oneOf": [
              { "type": "string" },
              { "type": "array", "items": { "type": "string" } }
            ]
          },
          "designId": {
            "type": "string",
            "description": "Id of the saved design. Pass it as `designId` to `POST /schedule` to publish this post."
          },
          "editorUrl": {
            "type": ["string", "null"],
            "description": "Deep link to open the design in the PostNitro editor (`<domain>/app/<image-maker|carousel-maker|video-maker>/<workspace-identifier>/<designId>`). Null if the workspace or postType can't be resolved."
          }
        },
        "description": "`designId`, `name`, `size`, and `editorUrl` are always present. `type`, `mimeType`, and `data` are present only for PNG, PDF, and MP4; a DESIGN response omits them because nothing is rendered."
      },
      "OutputResponse": {
        "type": "object",
        "required": ["success", "data"],
        "properties": {
          "success": { "type": "boolean", "const": true },
          "data": {
            "type": "object",
            "required": ["embedPost", "result"],
            "properties": {
              "embedPost": { "$ref": "#/components/schemas/EmbedPost" },
              "result": { "$ref": "#/components/schemas/OutputResult" }
            }
          }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "required": ["success"],
        "properties": {
          "success": { "type": "boolean", "const": false },
          "message": { "type": "string" },
          "error": { "type": "string" }
        }
      },
      "GatewayErrorResponse": {
        "type": "object",
        "description": "Error returned by the Embed API's own gateway checks before a request reaches a handler: authentication (401), plan gate (402), request-shape validation (422), and rate limiting (429). Note the flag is `status`, not `success`.",
        "required": ["status", "message"],
        "properties": {
          "status": { "type": "boolean", "const": false },
          "message": {
            "type": "string",
            "description": "Human-readable reason. For 422 this is the first failed validation rule, e.g. `Status must be 'DRAFT' or 'SCHEDULED'`."
          },
          "retryAfter": {
            "type": "integer",
            "description": "429 only: seconds until the rate-limit window resets (also sent as the `Retry-After` header)."
          }
        }
      },
      "ScheduleErrorResponse": {
        "type": "object",
        "description": "Error from the scheduling handlers. Business rules are enforced by PostNitro's scheduler after the Embed API forwards the request; its HTTP status is preserved (400 for rule violations, 404 when the post does not exist in this workspace), and 500 is used when no upstream status is available.",
        "required": ["success", "message"],
        "properties": {
          "success": { "type": "boolean", "const": false },
          "message": {
            "type": "string",
            "description": "The reason, e.g. `Scheduled at is in the past` or `Instagram post settings are required when Instagram accounts are selected`.",
            "example": "Scheduled at is in the past"
          },
          "error": {
            "description": "Upstream details: usually an object such as `{\"error\": \"Scheduled at is in the past\"}`, or the message string when there was no upstream body.",
            "oneOf": [
              { "type": "object", "additionalProperties": true },
              { "type": "string" }
            ]
          }
        }
      },
      "ScheduleStatus": {
        "type": "string",
        "enum": ["DRAFT", "SCHEDULED"],
        "description": "`DRAFT` saves the post without publishing it. `SCHEDULED` queues it for publishing at `scheduledAt`. These are the only values a request may set."
      },
      "ScheduledPostStatus": {
        "type": "string",
        "description": "Post-level status. On create/update it echoes the requested `DRAFT` or `SCHEDULED`; after that PostNitro's publisher moves a scheduled post through `IN_PROGRESS` to a terminal `PUBLISHED`, `PARTIALLY_FAILED` (some accounts failed), or `FAILED`. Treat unknown values as non-terminal.",
        "examples": ["DRAFT", "SCHEDULED", "IN_PROGRESS", "PUBLISHED", "PARTIALLY_FAILED", "FAILED"]
      },
      "SchedulePostContent": {
        "type": "object",
        "description": "Captions keyed by platform. `common` is the default caption; a platform key overrides it for that platform's accounts. At least one key must hold non-empty text, and at least one of `designId` or a caption is required. Hashtags are extracted from the text automatically. Other keys are ignored.",
        "minProperties": 1,
        "properties": {
          "common": {
            "type": "string",
            "description": "Caption used for every selected account without a platform-specific caption."
          },
          "linkedin": { "type": "string", "description": "Caption for LinkedIn accounts." },
          "instagram": { "type": "string", "description": "Caption for Instagram accounts." },
          "tiktok": { "type": "string", "description": "Caption for TikTok accounts." },
          "threads": { "type": "string", "description": "Caption for Threads accounts." },
          "facebook": {
            "type": "string",
            "maxLength": 63206,
            "description": "Caption (post message) for Facebook Pages. Facebook allows up to 63,206 characters."
          },
          "pinterest": {
            "type": "string",
            "maxLength": 800,
            "description": "Caption for Pinterest boards, sent as the Pin description. Up to 800 characters."
          }
        }
      },
      "InstagramPostSettings": {
        "type": "object",
        "description": "Required when a selected account is Instagram and a design is attached (the scheduler also asks for it when `designId` is omitted rather than sent as `null`).",
        "required": ["postType", "postAsStory"],
        "properties": {
          "postType": {
            "type": "string",
            "enum": ["carousel", "image", "reel"],
            "description": "How the design is published. `reel` renders the design as video using `postSettings`."
          },
          "postAsStory": { "type": "boolean", "description": "Also publish as a Story." }
        }
      },
      "TiktokPostSettings": {
        "type": "object",
        "description": "Required when a selected account is TikTok and a design is attached (the scheduler also asks for it when `designId` is omitted rather than sent as `null`). A `carousel` also requires `autoAddMusic`; any other `postType` requires `canDuet` and `canStitch`.",
        "required": [
          "postType",
          "canComment",
          "isBrandedContent",
          "isYourBrand",
          "isThirdPartyBrand",
          "isAIGeneratedContent"
        ],
        "properties": {
          "postType": {
            "type": "string",
            "enum": ["carousel", "reel"],
            "description": "`carousel` publishes a photo carousel; `reel` renders the design as video using `postSettings`."
          },
          "privacyLevel": {
            "type": "string",
            "enum": ["PUBLIC_TO_EVERYONE", "MUTUAL_FOLLOW_FRIENDS", "SELF_ONLY"],
            "description": "Who can see the post. Required when `status` is `SCHEDULED`."
          },
          "canComment": { "type": "boolean" },
          "canDuet": {
            "type": "boolean",
            "description": "Required unless `postType` is `carousel`."
          },
          "canStitch": {
            "type": "boolean",
            "description": "Required unless `postType` is `carousel`."
          },
          "autoAddMusic": {
            "type": "boolean",
            "description": "Required when `postType` is `carousel`."
          },
          "postTitle": {
            "type": ["string", "null"],
            "description": "Optional title."
          },
          "isBrandedContent": {
            "type": "boolean",
            "description": "Content promotes a brand. When `status` is `SCHEDULED` and this is true, `isYourBrand` or `isThirdPartyBrand` must be true. Cannot be true when `privacyLevel` is `SELF_ONLY`."
          },
          "isYourBrand": { "type": "boolean" },
          "isThirdPartyBrand": { "type": "boolean" },
          "isAIGeneratedContent": { "type": "boolean" }
        }
      },
      "LinkedinPostSettings": {
        "type": "object",
        "description": "Required whenever a selected account is LinkedIn, with or without a design. A `SCHEDULED` LinkedIn post also needs a `designId`: LinkedIn text-only posts can only be saved as drafts.",
        "required": ["postType"],
        "properties": {
          "postType": {
            "type": "string",
            "enum": ["carousel", "document", "image", "reel"],
            "description": "`document` publishes the design as a swipeable PDF document post; `reel` renders it as video using `postSettings`."
          },
          "postTitle": {
            "type": ["string", "null"],
            "description": "Document title. Required, 5 to 90 characters, when `postType` is `document` and `status` is `SCHEDULED`."
          }
        }
      },
      "ThreadsPostSettings": {
        "type": "object",
        "description": "Required when a selected account is Threads and a design is attached (the scheduler also asks for it when `designId` is omitted rather than sent as `null`).",
        "required": ["postType"],
        "properties": {
          "postType": {
            "type": "string",
            "enum": ["carousel", "image", "reel"]
          }
        }
      },
      "SchedulePostSettings": {
        "type": "object",
        "additionalProperties": false,
        "required": ["videoDuration"],
        "description": "Video render settings for reels (any platform settings object with `postType: \"reel\"`). Same shape as the `videoSettings` object on the design endpoints. Optional: when a reel is scheduled without it, each missing field is filled from the settings the attached design was generated with, then defaults to 30 seconds with no audio. Ignored when nothing is published as a reel. If sent, `videoDuration` must be present.",
        "properties": {
          "videoDuration": {
            "type": "number",
            "minimum": 5,
            "exclusiveMaximum": 60,
            "description": "Length of the rendered video in seconds: at least 5 and under 60."
          },
          "audioId": {
            "type": ["string", "null"],
            "description": "Id (not a URL) of an audio track from `GET /audio`. Must exist in the API key's workspace and be an audio file, otherwise 422. Omit or null for a silent video."
          }
        }
      },
      "ScheduleRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": ["status", "scheduledAt", "selectedAccounts"],
        "description": "A scheduled post. Unknown top-level keys are rejected with 422. Provide a `designId`, a caption in `postContent`, or both. Each platform settings object is needed only when an account on that platform is selected; see each schema for when.",
        "properties": {
          "status": { "$ref": "#/components/schemas/ScheduleStatus" },
          "scheduledAt": {
            "type": "string",
            "format": "date-time",
            "description": "When to publish, as an ISO 8601 timestamp with a timezone (e.g. `2026-10-01T09:00:00Z`). Must be in the future, for drafts too.",
            "example": "2026-10-01T09:00:00Z"
          },
          "designId": {
            "type": ["string", "null"],
            "description": "Id of a design in the API key's workspace: `data.result.designId` from `GET /post/output/{embedPostId}`, or a design made in the PostNitro editor. Null or omitted for a text-only post."
          },
          "postContent": { "$ref": "#/components/schemas/SchedulePostContent" },
          "selectedAccounts": {
            "type": "array",
            "items": { "type": "string" },
            "description": "Ids of connected social accounts to publish to, from `GET /social-account`. Every id must belong to the API key's workspace. The scheduler requires this array, but it may be empty for a draft.",
            "example": ["cmqi7jyir0003v0m91hxnyyaq"]
          },
          "instagramPostSettings": {
            "oneOf": [
              { "$ref": "#/components/schemas/InstagramPostSettings" },
              { "type": "null" }
            ]
          },
          "tiktokPostSettings": {
            "oneOf": [
              { "$ref": "#/components/schemas/TiktokPostSettings" },
              { "type": "null" }
            ]
          },
          "linkedinPostSettings": {
            "oneOf": [
              { "$ref": "#/components/schemas/LinkedinPostSettings" },
              { "type": "null" }
            ]
          },
          "threadsPostSettings": {
            "oneOf": [
              { "$ref": "#/components/schemas/ThreadsPostSettings" },
              { "type": "null" }
            ]
          },
          "postSettings": {
            "oneOf": [
              { "$ref": "#/components/schemas/SchedulePostSettings" },
              { "type": "null" }
            ]
          },
          "facebookPostSettings": {
            "oneOf": [
              { "$ref": "#/components/schemas/FacebookPostSettings" },
              { "type": "null" }
            ]
          },
          "pinterestPostSettings": {
            "oneOf": [
              { "$ref": "#/components/schemas/PinterestPostSettings" },
              { "type": "null" }
            ]
          }
        }
      },
      "ScheduledPostCaption": {
        "type": "object",
        "description": "A stored caption.",
        "properties": {
          "platform": {
            "type": "string",
            "description": "`common` or a platform key (`linkedin`, `instagram`, `tiktok`, `threads`, `facebook`, `pinterest`)."
          },
          "text": { "type": "string" },
          "hashtags": {
            "type": "array",
            "items": { "type": "string" },
            "description": "Hashtags extracted from `text`, with the `#`."
          }
        }
      },
      "ScheduledPostAccount": {
        "type": "object",
        "description": "Publishing state of the post for one selected social account.",
        "properties": {
          "socialAccountId": { "type": "string" },
          "status": {
            "type": "string",
            "description": "`DRAFT` for drafts and `PENDING` for scheduled posts when saved; the publisher then sets `PROCESSING`, `IN_PROGRESS`, and finally `PUBLISHED` or `FAILED`.",
            "examples": ["DRAFT", "PENDING", "PROCESSING", "IN_PROGRESS", "PUBLISHED", "FAILED"]
          },
          "liveLink": {
            "type": ["string", "null"],
            "description": "Public URL of the published post, once published."
          },
          "errorMessage": {
            "type": ["string", "null"],
            "description": "Why publishing to this account failed, if it did."
          },
          "publishedAt": {
            "type": ["string", "null"],
            "format": "date-time"
          }
        }
      },
      "ScheduledPostDesign": {
        "type": "object",
        "description": "Summary of the attached design.",
        "properties": {
          "id": { "type": "string" },
          "name": { "type": "string" },
          "designType": {
            "type": ["string", "null"],
            "description": "`carousel`, `image`, or `video`."
          },
          "source": {
            "type": "string",
            "description": "Where the design was made: `api` or `editor`."
          }
        }
      },
      "ScheduledPost": {
        "type": "object",
        "description": "A scheduled post or draft. The request's `scheduledAt` comes back as `scheduledFor`, and the request's `postContent` object comes back as the `postContents` array.",
        "properties": {
          "id": { "type": "string", "description": "Use as `{id}` in `/schedule/{id}`." },
          "designId": {
            "type": ["string", "null"]
          },
          "labels": {
            "description": "Labels attached in the PostNitro app, or null.",
            "type": ["array", "null"],
            "items": {}
          },
          "status": { "$ref": "#/components/schemas/ScheduledPostStatus" },
          "scheduledFor": {
            "type": ["string", "null"],
            "format": "date-time"
          },
          "publishedAt": {
            "type": ["string", "null"],
            "format": "date-time"
          },
          "errorMessage": {
            "type": ["string", "null"],
            "description": "Post-level publishing error, if any."
          },
          "postSettings": {
            "oneOf": [
              { "$ref": "#/components/schemas/SchedulePostSettings" },
              { "type": "null" }
            ]
          },
          "instagramPostSettings": {
            "oneOf": [
              { "$ref": "#/components/schemas/InstagramPostSettings" },
              { "type": "null" }
            ]
          },
          "linkedinPostSettings": {
            "oneOf": [
              { "$ref": "#/components/schemas/LinkedinPostSettings" },
              { "type": "null" }
            ]
          },
          "tiktokPostSettings": {
            "oneOf": [
              { "$ref": "#/components/schemas/TiktokPostSettings" },
              { "type": "null" }
            ]
          },
          "threadsPostSettings": {
            "oneOf": [
              { "$ref": "#/components/schemas/ThreadsPostSettings" },
              { "type": "null" }
            ]
          },
          "postContents": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/ScheduledPostCaption" }
          },
          "designDetails": {
            "oneOf": [
              { "$ref": "#/components/schemas/ScheduledPostDesign" },
              { "type": "null" }
            ]
          },
          "socialAccounts": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/ScheduledPostAccount" }
          },
          "createdAt": { "type": "string", "format": "date-time" },
          "updatedAt": { "type": "string", "format": "date-time" },
          "facebookPostSettings": {
            "oneOf": [
              { "$ref": "#/components/schemas/FacebookPostSettings" },
              { "type": "null" }
            ]
          },
          "pinterestPostSettings": {
            "oneOf": [
              { "$ref": "#/components/schemas/PinterestPostSettings" },
              { "type": "null" }
            ]
          }
        }
      },
      "ScheduledPostResponse": {
        "type": "object",
        "required": ["success", "data"],
        "properties": {
          "success": { "type": "boolean", "const": true },
          "message": {
            "type": "string",
            "description": "Present on create and update.",
            "example": "Scheduled post created"
          },
          "data": { "$ref": "#/components/schemas/ScheduledPost" }
        }
      },
      "ScheduledPostListResponse": {
        "type": "object",
        "required": ["success", "data"],
        "properties": {
          "success": { "type": "boolean", "const": true },
          "data": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/ScheduledPost" }
          }
        }
      },
      "ScheduledPostDeleteResponse": {
        "type": "object",
        "required": ["success", "message"],
        "properties": {
          "success": { "type": "boolean", "const": true },
          "message": { "type": "string", "example": "Scheduled post deleted" },
          "data": {
            "type": "object",
            "properties": {
              "message": { "type": "string", "example": "Post deleted successfully" }
            }
          }
        }
      },
      "AudioItem": {
        "type": "object",
        "description": "An audio track in the workspace. Tracks are uploaded in the PostNitro app; the API only lists and deletes them.",
        "properties": {
          "id": {
            "type": "string",
            "description": "Pass this as `postSettings.audioId` on a scheduled reel (and as `videoSettings.audioId` when generating a video)."
          },
          "name": {
            "type": "string",
            "description": "Track title, falling back to the original filename, then `Untitled audio`."
          },
          "url": {
            "type": "string",
            "description": "Public URL of the file, for previewing. The API still expects the id."
          },
          "duration": {
            "type": ["number", "null"],
            "description": "Track length in seconds, or null when the uploader recorded none. A longer track is cut off at the video's length."
          },
          "artistName": {
            "type": ["string", "null"]
          },
          "source": {
            "type": "string",
            "description": "Where the file came from; `upload` when unrecorded."
          },
          "createdAt": { "type": "string", "format": "date-time" }
        }
      },
      "AudioListResponse": {
        "type": "object",
        "required": ["success", "data"],
        "properties": {
          "success": { "type": "boolean", "const": true },
          "data": {
            "type": "object",
            "properties": {
              "audios": {
                "type": "array",
                "items": { "$ref": "#/components/schemas/AudioItem" }
              }
            }
          }
        }
      },
      "AudioDeleteResponse": {
        "type": "object",
        "required": ["success", "message"],
        "properties": {
          "success": { "type": "boolean", "const": true },
          "message": { "type": "string", "example": "Audio deleted successfully" }
        }
      },
      "VideoSettings": {
        "type": "object",
        "additionalProperties": false,
        "required": ["videoDuration"],
        "description": "Render settings for VIDEO posts. Required when postType is VIDEO and responseType is MP4; optional (and omittable) for a DESIGN response; rejected with a 422 for any other postType. If the object is sent at all, videoDuration must be present and valid. Identical in shape to the postSettings object on the scheduling API, so settings carry over from creating a video to scheduling it as a reel",
        "properties": {
          "videoDuration": {
            "type": "number",
            "minimum": 5,
            "exclusiveMaximum": 60,
            "description": "Length of the rendered video in seconds — at least 5 and under 60"
          },
          "audioId": {
            "type": "string",
            "description": "Media id (not a URL) of an audio track to lay over the video — list them with GET /audio. Verified at request time: the media must exist, belong to the API key's workspace, and be an audio file, otherwise the request is rejected with a 422. Omit for a silent video"
          }
        }
      },
      "GenerateImages": {
        "type": "object",
        "additionalProperties": false,
        "description": "Opt-in AI image generation. Presence of this object enables it; omit it entirely for no images. Images are generated after the design is created and before rendering, and baked into the design (observable in the rendered file or via editorUrl) — the output shape is unchanged. Best-effort: if generation fails or isn't permitted (e.g. free plan or over the org's AI-image quota) the post still completes without images, and a GENERATE_IMAGES step is recorded in the status logs. Post credits are unchanged; AI images bill against the org's separate AI-image quota. editorType, image model, and copy config are resolved server-side and must not be sent",
        "properties": {
          "context": {
            "type": "string",
            "description": "Topic/brief that guides the image prompts. On generate, defaults to aiGeneration.context; on import, defaults to an empty string"
          },
          "imagePlacement": {
            "type": "string",
            "enum": ["auto", "background", "in-line"],
            "default": "auto",
            "description": "'auto' lets the AI decide per slide, 'background' generates slide backgrounds, 'in-line' generates images placed inside the slide content"
          },
          "imageStrategy": {
            "type": "string",
            "enum": ["strategic", "all"],
            "default": "strategic",
            "description": "'strategic' lets the AI pick which slides get images (capped at ~50% of slides), 'all' gives every eligible slide an image (capped by credits)"
          }
        }
      },
      "ImageSlide": {
        "type": "object",
        "additionalProperties": false,
        "required": ["heading"],
        "description": "The single slide of an IMAGE post: a plain object (not an array) with no `type` field. Any other field is rejected with 422.",
        "properties": {
          "heading": { "type": "string" },
          "sub_heading": { "type": "string" },
          "description": { "type": "string" },
          "image": { "type": "string", "format": "uri" },
          "background_image": { "type": "string", "format": "uri" },
          "cta_button": { "type": "string" },
          "layoutType": { "$ref": "#/components/schemas/LayoutType" },
          "layoutConfig": { "$ref": "#/components/schemas/LayoutConfig" }
        }
      },
      "SocialAccountSummary": {
        "type": "object",
        "description": "A connected social account. Its `id` is what `selectedAccounts` on `POST /schedule` expects.",
        "properties": {
          "id": {
            "type": "string",
            "description": "Pass this in `selectedAccounts` when scheduling."
          },
          "platformType": {
            "type": "string",
            "enum": ["linkedin", "instagram", "tiktok", "threads", "facebook", "pinterest"]
          },
          "accountHandle": {
            "type": ["string", "null"]
          },
          "accountType": {
            "type": "string",
            "description": "`profile`, `organization` (LinkedIn company page), `page` (Facebook Page), or `board` (Pinterest board).",
            "examples": ["profile", "organization", "page", "board"]
          },
          "accountName": { "type": "string" },
          "accountLogo": {
            "type": ["string", "null"],
            "description": "Avatar URL."
          },
          "accessTokenStatus": {
            "type": "string",
            "description": "`active` when PostNitro can publish to the account; `expired` means it must be reconnected in the PostNitro app before posts to it can publish.",
            "examples": ["active", "expired"]
          }
        }
      },
      "SocialAccountListResponse": {
        "type": "object",
        "required": ["success", "data"],
        "properties": {
          "success": { "type": "boolean", "const": true },
          "data": {
            "type": "object",
            "properties": {
              "socialAccounts": {
                "type": "object",
                "description": "Accounts grouped by platform. All six keys are always present, with an empty array when none are connected. Facebook accounts are Pages (`accountType: page`); Pinterest accounts are boards (`accountType: board`), one per board connected.",
                "required": ["linkedin", "instagram", "tiktok", "threads", "facebook", "pinterest"],
                "properties": {
                  "linkedin": {
                    "type": "array",
                    "items": { "$ref": "#/components/schemas/SocialAccountSummary" }
                  },
                  "instagram": {
                    "type": "array",
                    "items": { "$ref": "#/components/schemas/SocialAccountSummary" }
                  },
                  "tiktok": {
                    "type": "array",
                    "items": { "$ref": "#/components/schemas/SocialAccountSummary" }
                  },
                  "threads": {
                    "type": "array",
                    "items": { "$ref": "#/components/schemas/SocialAccountSummary" }
                  },
                  "facebook": {
                    "type": "array",
                    "items": { "$ref": "#/components/schemas/SocialAccountSummary" }
                  },
                  "pinterest": {
                    "type": "array",
                    "items": { "$ref": "#/components/schemas/SocialAccountSummary" }
                  }
                }
              }
            }
          }
        }
      },
      "SocialAccountDetail": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "organizationId": { "type": "string" },
          "workspaceId": { "type": "string" },
          "platformType": {
            "type": "string",
            "description": "e.g. `linkedin`, `instagram`, `tiktok`, `threads`, `facebook`, `pinterest`."
          },
          "accountHandle": {
            "type": ["string", "null"]
          },
          "accountName": { "type": "string" },
          "accountLogo": {
            "type": ["string", "null"]
          },
          "accessTokenStatus": {
            "type": "string",
            "description": "`active`, or `expired` when the account must be reconnected in the PostNitro app.",
            "examples": ["active", "expired"]
          },
          "tokenExpiresAt": {
            "type": ["string", "null"],
            "format": "date-time"
          },
          "createdAt": { "type": "string", "format": "date-time" },
          "updatedAt": { "type": "string", "format": "date-time" }
        }
      },
      "SocialAccountResponse": {
        "type": "object",
        "required": ["success", "data"],
        "properties": {
          "success": { "type": "boolean", "const": true },
          "data": {
            "type": "object",
            "properties": {
              "socialAccount": { "$ref": "#/components/schemas/SocialAccountDetail" },
              "usage": {
                "type": ["object", "null"],
                "additionalProperties": { "type": "integer" },
                "description": "How many scheduled-post targets this account has, keyed by per-account status (e.g. `{\"PENDING\": 2, \"PUBLISHED\": 8}`). Null when it has none."
              }
            }
          }
        }
      },
      "FacebookPostSettings": {
        "type": "object",
        "description": "Settings for Facebook Pages (personal Facebook profiles are not supported). Required when a selected account is a Facebook Page and a `designId` is attached; ignored when there is no design. A `SCHEDULED` post to a Facebook Page needs a `designId`: text-only Facebook posts can only be saved as drafts. The design must fit the format: photo posts (`carousel`, `link_carousel`, `image`) use a 1:1, 4:5, 16:9, or 9:16 design; a `reel` accepts any aspect ratio from 9:16 (vertical) to 16:9 (landscape).",
        "required": ["postType"],
        "properties": {
          "postType": {
            "type": "string",
            "enum": ["carousel", "link_carousel", "image", "reel"],
            "description": "`carousel` publishes every slide as one multi-photo post; `link_carousel` publishes swipeable cards that each link to `linkUrl` (2 to 10 slides; Facebook shows up to 5 and picks which); `image` publishes a single-slide design; `reel` renders the design as video using `postSettings` and publishes it as a Reel."
          },
          "linkUrl": {
            "type": ["string", "null"],
            "format": "uri",
            "maxLength": 2048,
            "description": "`link_carousel` only: the absolute http(s) URL every card links to, e.g. `https://example.com`. Required when `status` is `SCHEDULED`; a draft may leave it empty, but anything entered must be a valid http(s) URL. Ignored for other post types. Facebook only accepts custom card images for links on a domain the Page's business has verified in Meta Business Settings."
          },
          "callToAction": {
            "type": ["string", "null"],
            "enum": [
              "BOOK_TRAVEL",
              "BUY_NOW",
              "CALL_NOW",
              "DOWNLOAD",
              "GET_DIRECTIONS",
              "GET_QUOTE",
              "INSTALL_APP",
              "INSTALL_MOBILE_APP",
              "LEARN_MORE",
              "LIKE_PAGE",
              "LISTEN_MUSIC",
              "MESSAGE_PAGE",
              "NO_BUTTON",
              "OPEN_LINK",
              "PLAY_GAME",
              "SHOP_NOW",
              "SIGN_UP",
              "SUBSCRIBE",
              "USE_APP",
              "USE_MOBILE_APP",
              "WATCH_MORE",
              "WATCH_VIDEO",
              null
            ],
            "description": "`link_carousel` only: the button shown on the carousel, e.g. `LEARN_MORE` or `SHOP_NOW`. Omit or null for no button. Ignored for other post types. Some types are meant for ads and Facebook may reject them on organic posts."
          },
          "showEndCard": {
            "type": "boolean",
            "default": true,
            "description": "`link_carousel` only: show Facebook's end card linking to the Page. Defaults to true."
          },
          "useSlideTitles": {
            "type": "boolean",
            "default": false,
            "description": "`link_carousel` only: use each slide's heading as its card headline. Defaults to false (Facebook uses the link's own title)."
          },
          "useSlideDescriptions": {
            "type": "boolean",
            "default": false,
            "description": "`link_carousel` only: use each slide's description as its card description. Defaults to false."
          }
        }
      },
      "PinterestPostSettings": {
        "type": "object",
        "description": "Settings for Pinterest boards. Required when a selected account is a Pinterest board and a `designId` is attached; ignored when there is no design. A `SCHEDULED` post to Pinterest needs a `designId`: every Pin needs an image or video, so text-only Pinterest posts can only be saved as drafts. Any design size is accepted except ultra-wide (wider than 21:9); 2:3 (1000 × 1500) is recommended. The caption (`postContent.pinterest`, or `common`) becomes the Pin description, up to 800 characters.",
        "required": ["postType"],
        "properties": {
          "postType": {
            "type": "string",
            "enum": ["carousel", "image", "reel"],
            "description": "`carousel` publishes the slides as one carousel Pin (2 to 5 slides); `image` publishes a single image Pin; `reel` renders the design as video using `postSettings` and publishes it as a video Pin."
          },
          "title": {
            "type": ["string", "null"],
            "maxLength": 100,
            "description": "Optional Pin title, up to 100 characters."
          },
          "link": {
            "type": ["string", "null"],
            "format": "uri",
            "description": "Optional destination link the Pin opens. Anything entered must be a valid absolute http(s) URL, e.g. `https://example.com`."
          }
        }
      }
    }
  }
}
