{
  "openapi": "3.0.3",
  "info": {
    "title": "PostPiper REST API",
    "version": "1.0.0",
    "description": "Manage owned social accounts, drafts, publishing, scheduling, and image assets. Create an API key in https://www.postpiper.ai/dashboard/api-keys. All operations require Bearer authentication. Publishing and scheduling support X, LinkedIn, and Pinterest; other platform drafts can be stored. Account connections are managed in the dashboard."
  },
  "servers": [
    {
      "url": "https://api.postpiper.ai/v1"
    }
  ],
  "externalDocs": {
    "description": "PostPiper API reference",
    "url": "https://www.postpiper.ai/docs/api-reference"
  },
  "security": [
    {
      "ApiKeyBearer": []
    }
  ],
  "tags": [
    {
      "name": "Accounts"
    },
    {
      "name": "Posts"
    },
    {
      "name": "Media"
    }
  ],
  "paths": {
    "/accounts": {
      "get": {
        "operationId": "accounts_list",
        "tags": [
          "Accounts"
        ],
        "summary": "List your active connected social accounts.",
        "description": "Only accounts owned by the API key's user are returned. Connect new accounts in the dashboard.\n\nRequired API key scope: postpiper:read.",
        "externalDocs": {
          "url": "https://www.postpiper.ai/docs/api-reference/accounts/list"
        },
        "x-required-scopes": [
          "postpiper:read"
        ],
        "responses": {
          "200": {
            "description": "200 OK. An empty list includes next_action with a dashboard connection URL.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "accounts": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Account"
                      }
                    },
                    "next_action": {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string"
                        },
                        "label": {
                          "type": "string"
                        },
                        "url": {
                          "type": "string",
                          "format": "uri"
                        }
                      },
                      "required": [
                        "type",
                        "label",
                        "url"
                      ]
                    }
                  },
                  "required": [
                    "accounts"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Invalid platform filter.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Requires postpiper:read scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Accounts cannot be loaded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "platform",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "description": "Lowercase platform identifier, such as x or linkedin."
            }
          }
        ]
      }
    },
    "/posts": {
      "post": {
        "operationId": "posts_create",
        "tags": [
          "Posts"
        ],
        "summary": "Create an unpublished post draft.",
        "description": "Provide text or at least one image attachment. The selected account must be active and owned by you. If platform is omitted, it comes from the account, or defaults to x.\n\nUse either media_urls or asset_ids in one request. Uploaded asset IDs currently work only for X drafts. Draft creation does not publish the post.\n\nRequired API key scope: postpiper:write. Pinterest accepts exactly one JPEG/PNG image via asset_ids or media_urls. content is the Pin description (up to 800 characters). Put board_id, title (1-100 characters), and an optional link in platform_fields.",
        "externalDocs": {
          "url": "https://www.postpiper.ai/docs/api-reference/posts/create"
        },
        "x-required-scopes": [
          "postpiper:write"
        ],
        "responses": {
          "201": {
            "description": "201 Created. Returns the draft post.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "post": {
                      "$ref": "#/components/schemas/Post"
                    }
                  },
                  "required": [
                    "post"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "INVALID_CONTENT, MEDIA_FORMAT_CONFLICT, PLATFORM_MISMATCH, or TOO_MANY_MEDIA.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Requires postpiper:write scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "ACCOUNT_NOT_FOUND or MEDIA_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Required service unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "account_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "Connected account to use later when publishing or scheduling."
                  },
                  "platform": {
                    "type": "string",
                    "description": "Lowercase platform identifier; must match account_id when supplied."
                  },
                  "content": {
                    "type": "string",
                    "maxLength": 25000,
                    "description": "Post text, up to 25,000 characters. Required unless an attachment is supplied."
                  },
                  "media_urls": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uri",
                      "pattern": "^https://"
                    },
                    "maxItems": 20,
                    "description": "Public HTTPS image URLs. Up to 4 for X, 20 for LinkedIn or other draft platforms."
                  },
                  "asset_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "maxItems": 4,
                    "description": "Up to 4 owned image assets from Media endpoints; X drafts only."
                  },
                  "platform_fields": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Platform-specific fields. Pinterest uses board_id, title, and optional link. The Pin description is content. Extra keys are stored for later Pin fields."
                  }
                },
                "required": [],
                "additionalProperties": false,
                "not": {
                  "required": [
                    "media_urls",
                    "asset_ids"
                  ]
                },
                "anyOf": [
                  {
                    "required": [
                      "content"
                    ],
                    "properties": {
                      "content": {
                        "type": "string",
                        "minLength": 1
                      }
                    }
                  },
                  {
                    "required": [
                      "media_urls"
                    ],
                    "properties": {
                      "media_urls": {
                        "type": "array",
                        "minItems": 1
                      }
                    }
                  },
                  {
                    "required": [
                      "asset_ids"
                    ],
                    "properties": {
                      "asset_ids": {
                        "type": "array",
                        "minItems": 1
                      }
                    }
                  }
                ]
              }
            }
          }
        }
      },
      "get": {
        "operationId": "posts_list",
        "tags": [
          "Posts"
        ],
        "summary": "Page through posts by status, platform, account, or time range.",
        "description": "Posts are newest first. With status=scheduled, the soonest scheduled post appears first.\n\nTime ranges require time_field and ISO 8601 timestamps with time zones. from is inclusive; to is exclusive.\n\nFor the next page, send next_cursor with the same filters and sort options.\n\nRequired API key scope: postpiper:read.",
        "externalDocs": {
          "url": "https://www.postpiper.ai/docs/api-reference/posts/list"
        },
        "x-required-scopes": [
          "postpiper:read"
        ],
        "responses": {
          "200": {
            "description": "200 OK. Returns posts and a nullable next_cursor.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "posts": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Post"
                      }
                    },
                    "next_cursor": {
                      "type": "string",
                      "nullable": true
                    }
                  },
                  "required": [
                    "posts",
                    "next_cursor"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "INVALID_CURSOR for a malformed or mismatched cursor; INVALID_TIME_RANGE for invalid time filters.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Requires postpiper:read scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Required service unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "draft",
                "scheduled",
                "published",
                "failed"
              ],
              "description": "draft, scheduled, published, or failed. Omit for all statuses."
            }
          },
          {
            "name": "platform",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "description": "Lowercase platform identifier, such as x or linkedin."
            }
          },
          {
            "name": "account_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "Filter to one owned account."
            }
          },
          {
            "name": "time_field",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "created_at",
                "scheduled_at",
                "published_at"
              ],
              "description": "created_at, scheduled_at, or published_at; required with from or to."
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time",
              "description": "Inclusive start time with a time zone."
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time",
              "description": "Exclusive end time with a time zone."
            }
          },
          {
            "name": "sort_by",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "created_at",
                "scheduled_at",
                "published_at"
              ],
              "description": "created_at (default), scheduled_at (default for scheduled status), or published_at. Posts without that timestamp are excluded."
            }
          },
          {
            "name": "sort_order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "description": "desc for creation and publication time; asc for scheduled time, unless specified."
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50,
              "description": "Page size from 1 to 100; defaults to 50."
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "description": "Opaque next_cursor from a previous response."
            }
          }
        ]
      }
    },
    "/posts/{postId}": {
      "patch": {
        "operationId": "posts_update",
        "tags": [
          "Posts"
        ],
        "summary": "Change the text or attachments of an existing draft.",
        "description": "Only drafts can be edited. Omitted fields keep their values. Supplying asset_ids replaces attached assets in order; an empty array detaches them all. The draft must still contain text or media after editing.\n\nYou cannot change the platform or account with this endpoint.\n\nRequired API key scope: postpiper:write. Pinterest accepts exactly one JPEG/PNG image via asset_ids or media_urls. content is the Pin description (up to 800 characters). Put board_id, title (1-100 characters), and an optional link in platform_fields.",
        "externalDocs": {
          "url": "https://www.postpiper.ai/docs/api-reference/posts/update"
        },
        "x-required-scopes": [
          "postpiper:write"
        ],
        "responses": {
          "200": {
            "description": "200 OK. Returns the updated draft.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "post": {
                      "$ref": "#/components/schemas/Post"
                    }
                  },
                  "required": [
                    "post"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "INVALID_CONTENT or MEDIA_FORMAT_CONFLICT.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Requires postpiper:write scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "POST_NOT_FOUND or MEDIA_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "POST_NOT_DRAFT.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Required service unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "postId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "ID of an owned draft."
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "content": {
                    "type": "string",
                    "maxLength": 25000,
                    "description": "Replacement text, up to 25,000 characters."
                  },
                  "media_urls": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uri",
                      "pattern": "^https://"
                    },
                    "maxItems": 20,
                    "description": "Replacement HTTPS image URLs; cannot be sent with asset_ids."
                  },
                  "asset_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "maxItems": 4,
                    "description": "Replacement owned X image assets; cannot be sent with media_urls."
                  },
                  "platform_fields": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Platform-specific fields. Pinterest uses board_id, title, and optional link. The Pin description is content. Extra keys are stored for later Pin fields."
                  }
                },
                "required": [],
                "additionalProperties": false,
                "not": {
                  "required": [
                    "media_urls",
                    "asset_ids"
                  ]
                },
                "anyOf": [
                  {
                    "required": [
                      "content"
                    ]
                  },
                  {
                    "required": [
                      "media_urls"
                    ]
                  },
                  {
                    "required": [
                      "asset_ids"
                    ]
                  }
                ]
              }
            }
          }
        }
      },
      "get": {
        "operationId": "posts_by_id",
        "tags": [
          "Posts"
        ],
        "summary": "Read a post and its current publishing status.",
        "description": "The response includes the post's attachments, account, timestamps, and platform publication identifiers. Unset scheduling or publication fields are null.\n\nRequired API key scope: postpiper:read.",
        "externalDocs": {
          "url": "https://www.postpiper.ai/docs/api-reference/posts/by-id"
        },
        "x-required-scopes": [
          "postpiper:read"
        ],
        "responses": {
          "200": {
            "description": "200 OK. Returns the post.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "post": {
                      "$ref": "#/components/schemas/Post"
                    }
                  },
                  "required": [
                    "post"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Requires postpiper:read scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "POST_NOT_FOUND for an unknown or unowned post.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Required service unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "postId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "ID of an owned post."
            }
          }
        ]
      }
    },
    "/posts/{postId}/publish": {
      "post": {
        "operationId": "posts_publish",
        "tags": [
          "Posts"
        ],
        "summary": "Publish an X or LinkedIn draft immediately.",
        "description": "The draft needs a connected account for its platform. X supports text or images; LinkedIn requires text of 1–3,000 characters and supports up to 20 JPEG or PNG images from media_urls. The endpoint takes no request body.\n\nAn uncertain upstream failure can mean the post already exists. Check the platform account before retrying.\n\nRequired API key scope: postpiper:write.",
        "externalDocs": {
          "url": "https://www.postpiper.ai/docs/api-reference/posts/publish"
        },
        "x-required-scopes": [
          "postpiper:write"
        ],
        "responses": {
          "200": {
            "description": "200 OK. Returns a published post. The platform ID and URL may be null even after success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "post": {
                      "$ref": "#/components/schemas/Post"
                    }
                  },
                  "required": [
                    "post"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "X_AUTH_EXPIRED or LINKEDIN_AUTH_EXPIRED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Requires postpiper:write scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "POST_NOT_DRAFT.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Platform rate limit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "X_PUBLISH_FAILED or LINKEDIN_PUBLISH_FAILED; the outcome may be uncertain.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Required service unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "postId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "ID of an owned draft."
            }
          }
        ]
      }
    },
    "/posts/{postId}/schedule": {
      "post": {
        "operationId": "posts_schedule",
        "tags": [
          "Posts"
        ],
        "summary": "Schedule an X or LinkedIn draft for a future time.",
        "description": "The post must be an owned draft with a matching connected account. Supply a future ISO 8601 time with an explicit time zone. LinkedIn requires text and authorization that remains valid past the scheduled time. The response normalizes scheduled_at to UTC.\n\nRequired API key scope: postpiper:write.",
        "externalDocs": {
          "url": "https://www.postpiper.ai/docs/api-reference/posts/schedule"
        },
        "x-required-scopes": [
          "postpiper:write"
        ],
        "responses": {
          "200": {
            "description": "200 OK after the scheduling workflow starts.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "post": {
                      "$ref": "#/components/schemas/Post"
                    }
                  },
                  "required": [
                    "post"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "INVALID_SCHEDULED_AT.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Requires postpiper:write scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "POST_NOT_DRAFT.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "SCHEDULING_UNAVAILABLE.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "postId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "ID of an owned draft."
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "scheduled_at": {
                    "type": "string",
                    "format": "date-time",
                    "description": "Future date and time with Z or an explicit offset."
                  }
                },
                "required": [
                  "scheduled_at"
                ],
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/media": {
      "post": {
        "operationId": "media_upload",
        "tags": [
          "Media"
        ],
        "summary": "Upload one image as multipart form data.",
        "description": "Use a file field named file. JPEG, PNG, and WebP images up to 5 MB are accepted. The image is validated and normalized before storage. Preview URLs expire after 15 minutes.\n\nRequired API key scope: postpiper:write.",
        "externalDocs": {
          "url": "https://www.postpiper.ai/docs/api-reference/media/upload"
        },
        "x-required-scopes": [
          "postpiper:write"
        ],
        "responses": {
          "201": {
            "description": "201 Created. Returns the owned image asset.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "asset": {
                      "$ref": "#/components/schemas/Asset"
                    }
                  },
                  "required": [
                    "asset"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "INVALID_MEDIA.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Requires postpiper:write scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "MEDIA_TOO_LARGE.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "MEDIA_RATE_LIMITED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Required service unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "One JPEG, PNG, or WebP image, at most 5 MB."
                  }
                },
                "required": [
                  "file"
                ],
                "additionalProperties": false
              }
            }
          }
        }
      },
      "get": {
        "operationId": "media_list",
        "tags": [
          "Media"
        ],
        "summary": "List your latest ready images.",
        "description": "Only ready images owned by the API key's user are returned, newest first. Each result has a fresh preview URL.\n\nRequired API key scope: postpiper:read.",
        "externalDocs": {
          "url": "https://www.postpiper.ai/docs/api-reference/media/list"
        },
        "x-required-scopes": [
          "postpiper:read"
        ],
        "responses": {
          "200": {
            "description": "200 OK. Returns an assets array.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "assets": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Asset"
                      }
                    }
                  },
                  "required": [
                    "assets"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Requires postpiper:read scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "MEDIA_UNAVAILABLE.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50,
              "default": 20,
              "description": "Number of images from 1 to 50; defaults to 20."
            }
          }
        ]
      }
    },
    "/media/import": {
      "post": {
        "operationId": "media_import",
        "tags": [
          "Media"
        ],
        "summary": "Import one publicly accessible HTTPS image.",
        "description": "The URL must resolve to a public address and return a supported JPEG, PNG, or WebP image up to 5 MB. Private, loopback, link-local, and metadata-service addresses are rejected.\n\nRequired API key scope: postpiper:write.",
        "externalDocs": {
          "url": "https://www.postpiper.ai/docs/api-reference/media/import"
        },
        "x-required-scopes": [
          "postpiper:write"
        ],
        "responses": {
          "201": {
            "description": "201 Created. Returns the imported image asset.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "asset": {
                      "$ref": "#/components/schemas/Asset"
                    }
                  },
                  "required": [
                    "asset"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "INVALID_MEDIA_URL or UNSAFE_MEDIA_URL.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Requires postpiper:write scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "MEDIA_TOO_LARGE.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Upstream image download failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Required service unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri",
                    "pattern": "^https://",
                    "maxLength": 4096,
                    "description": "Public image URL, at most 4,096 characters."
                  }
                },
                "required": [
                  "url"
                ],
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/media/{assetId}": {
      "get": {
        "operationId": "media_get",
        "tags": [
          "Media"
        ],
        "summary": "Read image metadata and get a fresh preview URL.",
        "description": "Preview URLs expire after 15 minutes. Call this endpoint again when a preview URL has expired.\n\nRequired API key scope: postpiper:read.",
        "externalDocs": {
          "url": "https://www.postpiper.ai/docs/api-reference/media/get"
        },
        "x-required-scopes": [
          "postpiper:read"
        ],
        "responses": {
          "200": {
            "description": "200 OK. Returns the image asset.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "asset": {
                      "$ref": "#/components/schemas/Asset"
                    }
                  },
                  "required": [
                    "asset"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Requires postpiper:read scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "MEDIA_NOT_FOUND for an unknown or unowned image.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Required service unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "assetId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "ID of an owned ready image."
            }
          }
        ]
      }
    },
    "/media/prepare": {
      "post": {
        "operationId": "media_prepare",
        "tags": [
          "Media"
        ],
        "summary": "Prepare a direct image upload",
        "description": "Requires postpiper:write. PUT raw image bytes to upload_url using the returned headers, then complete the upload. JPEG, PNG, and WebP up to 5 MB.",
        "x-required-scopes": [
          "postpiper:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "mime_type": {
                    "type": "string",
                    "enum": [
                      "image/jpeg",
                      "image/png",
                      "image/webp"
                    ]
                  },
                  "size_bytes": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 5242880
                  }
                },
                "required": [
                  "mime_type",
                  "size_bytes"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Presigned PUT destination created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "upload": {
                      "type": "object",
                      "properties": {
                        "upload_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "upload_url": {
                          "type": "string",
                          "format": "uri"
                        },
                        "method": {
                          "type": "string",
                          "enum": [
                            "PUT"
                          ]
                        },
                        "headers": {
                          "type": "object",
                          "properties": {
                            "Content-Type": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "Content-Type"
                          ]
                        },
                        "expires_at": {
                          "type": "string",
                          "format": "date-time"
                        }
                      },
                      "required": [
                        "upload_id",
                        "upload_url",
                        "method",
                        "headers",
                        "expires_at"
                      ]
                    }
                  },
                  "required": [
                    "upload"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Request failed; inspect error.code and error.message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Request failed; inspect error.code and error.message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Request failed; inspect error.code and error.message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Request failed; inspect error.code and error.message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Request failed; inspect error.code and error.message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Request failed; inspect error.code and error.message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Request failed; inspect error.code and error.message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Request failed; inspect error.code and error.message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/media/{uploadId}/complete": {
      "post": {
        "operationId": "media_complete",
        "tags": [
          "Media"
        ],
        "summary": "Complete a direct image upload",
        "description": "Requires postpiper:write. Validates and normalizes the staged image. Repeating completion for a ready owned upload returns the same asset.",
        "x-required-scopes": [
          "postpiper:write"
        ],
        "parameters": [
          {
            "name": "uploadId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Owned image asset is ready.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "asset": {
                      "$ref": "#/components/schemas/Asset"
                    }
                  },
                  "required": [
                    "asset"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Request failed; inspect error.code and error.message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Request failed; inspect error.code and error.message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Request failed; inspect error.code and error.message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Request failed; inspect error.code and error.message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Request failed; inspect error.code and error.message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Request failed; inspect error.code and error.message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Request failed; inspect error.code and error.message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Request failed; inspect error.code and error.message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/accounts/{accountId}/boards": {
      "get": {
        "operationId": "pinterest_boards_list",
        "tags": [
          "Accounts"
        ],
        "summary": "List public Pinterest Boards owned by a connected account.",
        "description": "Requires postpiper:read. Only the authenticated user’s active Pinterest account can be used. Pass bookmark for the next page.",
        "x-required-scopes": [
          "postpiper:read"
        ],
        "parameters": [
          {
            "name": "accountId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "bookmark",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 2048
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Public Boards and next-page bookmark.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "boards": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "privacy": {
                            "type": "string",
                            "enum": [
                              "PUBLIC"
                            ]
                          }
                        }
                      }
                    },
                    "bookmark": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Scope or Pinterest app access denied."
          },
          "404": {
            "description": "Owned active Pinterest account not found."
          },
          "429": {
            "description": "Pinterest rate limit reached."
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyBearer": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "PostPiper API key",
        "description": "Use Authorization: Bearer sk_<lookupId>_<secret>. Operations require postpiper:read or postpiper:write as documented."
      }
    },
    "schemas": {
      "Account": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "platform": {
            "type": "string"
          },
          "platform_user_id": {
            "type": "string"
          },
          "platform_username": {
            "type": "string",
            "nullable": true
          },
          "platform_display_name": {
            "type": "string",
            "nullable": true
          },
          "platform_avatar_url": {
            "type": "string",
            "format": "uri",
            "nullable": true
          }
        },
        "required": [
          "id",
          "platform",
          "platform_user_id",
          "platform_username",
          "platform_display_name",
          "platform_avatar_url"
        ]
      },
      "Post": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "content": {
            "type": "string"
          },
          "media_urls": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uri"
            }
          },
          "asset_ids": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "platform": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "scheduled",
              "published",
              "failed"
            ]
          },
          "account_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "scheduled_at": {
            "type": "string",
            "format": "date-time",
            "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
          },
          "platform_fields": {
            "type": "object",
            "additionalProperties": true,
            "description": "Platform-specific fields. Pinterest uses board_id, title, and optional link. The Pin description is content."
          }
        },
        "required": [
          "id",
          "content",
          "media_urls",
          "asset_ids",
          "platform",
          "status",
          "account_id",
          "created_at"
        ]
      },
      "Asset": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "post_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "mime_type": {
            "type": "string"
          },
          "size_bytes": {
            "type": "integer"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "preview_url": {
            "type": "string",
            "format": "uri"
          }
        },
        "required": [
          "id",
          "post_id",
          "mime_type",
          "size_bytes",
          "created_at",
          "preview_url"
        ]
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              },
              "request_id": {
                "type": "string"
              }
            },
            "required": [
              "code",
              "message",
              "request_id"
            ]
          }
        },
        "required": [
          "error"
        ]
      }
    }
  }
}
