{
  "openapi": "3.0.3",
  "info": {
    "title": "PostWire API",
    "version": "0.1.0",
    "description": "The social media posting API + MCP server for AI agents. Connect an account once (OAuth or credentials), then post and cross-post to TikTok, Instagram, YouTube, X, LinkedIn, Reddit, Bluesky, Telegram, Mastodon and Discord from one REST call.",
    "contact": {
      "email": "support@postwire.io"
    }
  },
  "servers": [
    {
      "url": "https://postwire.io"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "paths": {
    "/api/platforms": {
      "get": {
        "summary": "List supported platforms",
        "security": [],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/plans": {
      "get": {
        "summary": "List plans and limits",
        "security": [],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/signup": {
      "post": {
        "summary": "Create an account (the API key is emailed as a confirmation link, not returned here)",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Account created; pending_verification=true, emailed=true|false"
          }
        },
        "description": "Signup never returns credentials. We create the account and email a one-time confirmation link; opening it proves you control the mailbox and issues your API key. This prevents anyone from registering an address they do not own and receiving a working key."
      }
    },
    "/api/me": {
      "get": {
        "summary": "Account info, usage and connections",
        "responses": {
          "200": {
            "description": "OK"
          },
          "401": {
            "description": "Missing/invalid API key"
          }
        }
      }
    },
    "/api/connect": {
      "post": {
        "summary": "Connect a credential-based platform (telegram, bluesky, mastodon, discord)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "platform",
                  "credentials"
                ],
                "properties": {
                  "platform": {
                    "type": "string"
                  },
                  "credentials": {
                    "type": "object"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Connected"
          }
        }
      }
    },
    "/api/oauth/{platform}/url": {
      "post": {
        "summary": "Get an OAuth authorize URL for an OAuth platform (tiktok, youtube, reddit, linkedin, facebook, instagram, x)",
        "parameters": [
          {
            "name": "platform",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{ url }"
          }
        }
      }
    },
    "/api/post": {
      "post": {
        "summary": "Post (or cross-post) to one or more platforms",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PostRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{ results: [...] }"
          },
          "401": {
            "description": "Missing/invalid API key"
          }
        }
      }
    },
    "/api/post/status": {
      "get": {
        "summary": "Check the status of a created post",
        "parameters": [
          {
            "name": "platform",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{ platform, id, status }"
          }
        }
      }
    },
    "/api/connect-link": {
      "post": {
        "summary": "Generate a hosted connect link for an end-user (multi-tenant)",
        "responses": {
          "200": {
            "description": "{ url, token, expires_in }"
          }
        }
      }
    },
    "/api/billing/checkout": {
      "post": {
        "summary": "Start a Stripe checkout for a paid plan",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "plan"
                ],
                "properties": {
                  "plan": {
                    "type": "string",
                    "enum": [
                      "starter",
                      "pro",
                      "agency",
                      "scale"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{ url }"
          }
        }
      }
    },
    "/api/keys": {
      "get": {
        "summary": "List your API keys (prefixes only — the secret is never returned)",
        "responses": {
          "200": {
            "description": "keys[]"
          }
        }
      },
      "post": {
        "summary": "Create a new API key, optionally with an expiry",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "expires_in_days": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 3650
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "api_key shown once"
          }
        }
      }
    },
    "/api/keys/{id}": {
      "delete": {
        "summary": "Revoke an API key immediately",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "revoked"
          },
          "404": {
            "description": "not found or already revoked"
          }
        }
      }
    },
    "/api/media/upload-url": {
      "post": {
        "summary": "Get a signed URL to upload a photo or video (max 50MB)",
        "description": "Upload your own file instead of hosting it yourself. The bytes go straight from your client to storage — they never pass through this API. 1) POST here. 2) PUT the raw file bytes to upload_url. 3) GET /api/media/finalize?path=<path> to receive media_url. 4) pass media_url as video_url or photo_url to /api/post. Media is deleted after 30 days.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "content_type"
                ],
                "properties": {
                  "content_type": {
                    "type": "string",
                    "enum": [
                      "image/jpeg",
                      "image/png",
                      "image/webp",
                      "image/gif",
                      "video/mp4",
                      "video/quicktime",
                      "video/webm"
                    ]
                  },
                  "size_bytes": {
                    "type": "integer"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "upload_url + path"
          },
          "413": {
            "description": "file too large"
          }
        }
      }
    },
    "/api/media/finalize": {
      "get": {
        "summary": "Confirm the upload landed and get the media_url to publish with",
        "parameters": [
          {
            "name": "path",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "media_url + bytes"
          },
          "404": {
            "description": "not uploaded yet"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "pw_live_ key"
      }
    },
    "schemas": {
      "PostRequest": {
        "type": "object",
        "properties": {
          "platform": {
            "type": "string",
            "description": "Single platform; or use 'platforms'."
          },
          "platforms": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Cross-post to many at once, in parallel.",
            "example": [
              "tiktok",
              "youtube"
            ]
          },
          "text": {
            "type": "string",
            "description": "Caption / description."
          },
          "title": {
            "type": "string",
            "description": "Title (used by YouTube)."
          },
          "video_url": {
            "type": "string",
            "description": "URL of the video to post (required for TikTok/YouTube)."
          },
          "photo_url": {
            "type": "string"
          },
          "privacy": {
            "type": "string",
            "description": "e.g. private | public | unlisted (YouTube)."
          },
          "subreddit": {
            "type": "string",
            "description": "Target subreddit (required for Reddit; omit the r/ prefix)."
          }
        }
      }
    }
  }
}