{
  "openapi": "3.1.0",
  "info": {
    "title": "seenpaid API",
    "version": "1.0.0",
    "description": "seenpaid is a revenue-attributed social scheduler: post once, publish to every connected platform, and trace real Stripe revenue back to the post that earned it. This document describes the REST layer at https://api.seenpaid.com — the same surface the dashboard itself calls. The RECOMMENDED integration path for AI agents is the MCP (Model Context Protocol) server at https://api.seenpaid.com/mcp, which exposes the same capabilities as 50 typed, described tools over a single JSON-RPC endpoint (see the `x-mcp-tools` extension below and https://seenpaid.mintlify.app/agents/mcp). Every authenticated response follows the same envelope: `{\"success\": true, \"data\": ...}` on success, or `{\"success\": false, \"error\": \"...\"}` (validation failures add an `issues` array) on failure — see the shared `Success`/`Error` schemas.",
    "contact": { "name": "seenpaid support", "email": "support@seenpaid.com", "url": "https://seenpaid.com" }
  },
  "servers": [
    { "url": "https://api.seenpaid.com", "description": "Production API (Railway)" }
  ],
  "security": [
    { "ApiKeyAuth": [] },
    { "OAuth2": ["mcp"] }
  ],
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "sp_...",
        "description": "A per-org API key generated in Settings → API (dashboard) or POST /api/api-keys. Send as `Authorization: Bearer sp_...`. Shown in full only once, at creation.",
        "x-scopes": ["accounts:read", "accounts:write", "accounts:delete", "posts:read", "posts:write", "posts:delete", "org:billing", "org:manage_members"]
      },
      "OAuth2": {
        "type": "oauth2",
        "description": "OAuth 2.1 authorization-code flow with PKCE, for clients that can't send a static bearer header (e.g. the claude.ai web MCP connector). Consent happens in the seenpaid dashboard; the minted access token (`spa_...`) resolves to the same org context as an API key. Discovery: GET /.well-known/oauth-protected-resource (RFC 9728) and GET /.well-known/oauth-authorization-server.",
        "flows": {
          "authorizationCode": {
            "authorizationUrl": "https://api.seenpaid.com/authorize",
            "tokenUrl": "https://api.seenpaid.com/token",
            "refreshUrl": "https://api.seenpaid.com/token",
            "scopes": {
              "mcp": "Full access to the org's seenpaid workspace (superset scope — currently the only scope actually enforced at the token layer; the granular scopes below are declared for forward compatibility with per-scope OAuth clients and mirror the REST API's real RBAC permissions).",
              "accounts:read": "List connected social accounts and revenue-connector connections.",
              "accounts:write": "Connect a new social account or revenue connector.",
              "accounts:delete": "Disconnect a social account.",
              "posts:read": "Read posts, analytics, revenue, insights, notifications, autopilot, experiments and recycle state.",
              "posts:write": "Create, edit, or generate posts, media, autopilot config, experiments and recycle rules.",
              "posts:delete": "Permanently delete a post.",
              "org:billing": "Manage subscription, Stripe Connect, and billing portal access.",
              "org:manage_members": "Manage organization membership and roles."
            }
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "success": { "type": "boolean", "enum": [false] },
          "error": { "type": "string", "description": "Human-readable error message." },
          "issues": {
            "type": "array",
            "description": "Present only on a 400 Zod validation failure.",
            "items": {
              "type": "object",
              "properties": {
                "path": { "type": "array", "items": {} },
                "message": { "type": "string" }
              }
            }
          }
        },
        "required": ["success", "error"]
      },
      "Platform": {
        "type": "string",
        "enum": ["x", "bluesky", "linkedin", "instagram", "facebook", "tiktok", "discord", "telegram", "mastodon", "nostr", "devto", "hashnode", "medium", "reddit", "threads", "tumblr", "pinterest", "vk", "slack", "wordpress", "ghost", "lemmy", "webhook", "microblog", "matrix"]
      },
      "Post": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "orgId": { "type": "string", "format": "uuid" },
          "caption": { "type": "string" },
          "status": { "type": "string", "enum": ["draft", "scheduled", "publishing", "published", "failed", "canceled"] },
          "scheduledFor": { "type": "string", "format": "date-time", "nullable": true },
          "mediaIds": { "type": "array", "items": { "type": "string", "format": "uuid" } },
          "createdAt": { "type": "string", "format": "date-time" },
          "targets": {
            "type": "array",
            "description": "One per social account this post targets, with the per-platform publish result once attempted.",
            "items": {
              "type": "object",
              "properties": {
                "socialAccountId": { "type": "string", "format": "uuid" },
                "platform": { "$ref": "#/components/schemas/Platform" },
                "status": { "type": "string", "enum": ["pending", "published", "failed"] },
                "url": { "type": "string", "format": "uri", "nullable": true },
                "error": { "type": "string", "nullable": true }
              }
            }
          }
        }
      },
      "CreatePostRequest": {
        "type": "object",
        "required": ["socialAccountIds"],
        "properties": {
          "caption": { "type": "string", "maxLength": 3000 },
          "mediaIds": { "type": "array", "items": { "type": "string", "format": "uuid" }, "maxItems": 10, "default": [] },
          "socialAccountIds": {
            "type": "array",
            "items": { "type": "string", "format": "uuid" },
            "minItems": 1,
            "description": "Must not contain duplicates."
          },
          "scheduledFor": { "type": "string", "format": "date-time", "description": "Omit to publish immediately. Must be in the future." },
          "captions": {
            "type": "object",
            "additionalProperties": { "type": "string", "maxLength": 3000 },
            "description": "Optional per-account caption overrides, keyed by socialAccountId. Accounts not listed use the shared `caption`."
          }
        }
      },
      "UpdatePostRequest": {
        "type": "object",
        "properties": {
          "caption": { "type": "string", "maxLength": 3000 },
          "scheduledFor": { "type": "string", "format": "date-time", "description": "Must be in the future." }
        }
      },
      "SocialAccount": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "platform": { "$ref": "#/components/schemas/Platform" },
          "handle": { "type": "string", "nullable": true },
          "status": { "type": "string", "enum": ["connected", "needs_reconnect", "disconnected"] },
          "createdAt": { "type": "string", "format": "date-time" }
        }
      },
      "Media": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "url": { "type": "string", "format": "uri" },
          "type": { "type": "string", "enum": ["image", "video"] },
          "createdAt": { "type": "string", "format": "date-time" }
        }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Missing or invalid credential (API key or OAuth token).",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "Forbidden": {
        "description": "Authenticated, but the caller's role/scope lacks the required permission.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "NotFound": {
        "description": "No resource with that id in the caller's org.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "ValidationError": {
        "description": "Request body failed Zod validation.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      }
    }
  },
  "paths": {
    "/api/posts": {
      "get": {
        "operationId": "listPosts",
        "summary": "List posts",
        "description": "List every post in the caller's org, most recent first, with status, schedule time, target platforms and any publish errors.",
        "security": [{ "ApiKeyAuth": ["posts:read"] }, { "OAuth2": ["posts:read"] }],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [true] }, "data": { "type": "array", "items": { "$ref": "#/components/schemas/Post" } } } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" }
        }
      },
      "post": {
        "operationId": "createPost",
        "summary": "Create (schedule or immediately publish) a post",
        "description": "Create a post targeting one or more connected social accounts. Omit `scheduledFor` to publish immediately; otherwise it is queued for the given time.",
        "security": [{ "ApiKeyAuth": ["posts:write"] }, { "OAuth2": ["posts:write"] }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreatePostRequest" } } } },
        "responses": {
          "201": { "description": "Created", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [true] }, "data": { "$ref": "#/components/schemas/Post" } } } } } },
          "400": { "$ref": "#/components/responses/ValidationError" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" }
        }
      }
    },
    "/api/posts/translate": {
      "post": {
        "operationId": "translatePostCaption",
        "summary": "Translate a caption for the multi-language composer",
        "description": "Translate a caption into one target language, respecting an optional platform character limit. Never throws for an unconfigured LLM — returns `{configured:false}` instead.",
        "security": [{ "ApiKeyAuth": ["posts:write"] }, { "OAuth2": ["posts:write"] }],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": {
            "type": "object",
            "required": ["text", "language"],
            "properties": {
              "text": { "type": "string", "maxLength": 3000 },
              "language": { "type": "string", "maxLength": 32 },
              "charLimit": { "type": "integer", "maximum": 300000 }
            }
          } } }
        },
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [true] }, "data": { "type": "object", "properties": { "configured": { "type": "boolean" }, "translated": { "type": "string" } } } } } } } },
          "400": { "$ref": "#/components/responses/ValidationError" },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/api/posts/{id}": {
      "get": {
        "operationId": "getPost",
        "summary": "Get one post",
        "description": "Get a single post in full, including per-platform publish results.",
        "security": [{ "ApiKeyAuth": ["posts:read"] }, { "OAuth2": ["posts:read"] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [true] }, "data": { "$ref": "#/components/schemas/Post" } } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      },
      "patch": {
        "operationId": "updatePost",
        "summary": "Edit a draft or scheduled post",
        "description": "Change a post's caption, its scheduled time, or both. Published posts cannot be edited.",
        "security": [{ "ApiKeyAuth": ["posts:write"] }, { "OAuth2": ["posts:write"] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UpdatePostRequest" } } } },
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [true] }, "data": { "$ref": "#/components/schemas/Post" } } } } } },
          "400": { "$ref": "#/components/responses/ValidationError" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      },
      "delete": {
        "operationId": "deletePost",
        "summary": "Permanently delete a post",
        "description": "Delete a post and cancel any pending publishes. Cannot be undone — prefer POST /api/posts/{id}/cancel unless deletion is explicitly wanted.",
        "security": [{ "ApiKeyAuth": ["posts:delete"] }, { "OAuth2": ["posts:delete"] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }],
        "responses": {
          "204": { "description": "Deleted" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/api/posts/{id}/cancel": {
      "post": {
        "operationId": "cancelPost",
        "summary": "Cancel a scheduled post",
        "description": "Stop every pending publish job for this post before it goes out. Platforms it already published to are untouched; the post row survives (use DELETE to remove it entirely).",
        "security": [{ "ApiKeyAuth": ["posts:write"] }, { "OAuth2": ["posts:write"] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [true] }, "data": { "$ref": "#/components/schemas/Post" } } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/api/accounts": {
      "get": {
        "operationId": "listAccounts",
        "summary": "List connected social accounts",
        "description": "List every social account connected to the caller's org, with platform, handle and health status.",
        "security": [{ "ApiKeyAuth": ["accounts:read"] }, { "OAuth2": ["accounts:read"] }],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [true] }, "data": { "type": "array", "items": { "$ref": "#/components/schemas/SocialAccount" } } } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/api/accounts/connectable": {
      "get": {
        "operationId": "listConnectablePlatforms",
        "summary": "List platforms this deployment can connect",
        "description": "Which platforms have OAuth credentials configured server-side and can actually be connected right now, so a client never offers a Connect button that would just fail.",
        "security": [{ "ApiKeyAuth": ["accounts:read"] }, { "OAuth2": ["accounts:read"] }],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [true] }, "data": { "type": "object", "properties": { "platforms": { "type": "array", "items": { "$ref": "#/components/schemas/Platform" } } } } } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/api/accounts/{platform}/authorize-url": {
      "get": {
        "operationId": "getAccountAuthorizeUrl",
        "summary": "Get the OAuth authorize URL for a platform",
        "description": "For OAuth-based platforms (LinkedIn, X, Instagram, Facebook, TikTok, Mastodon, Reddit, Threads, Tumblr, Pinterest, VK), returns the URL a human must open to grant access. Not for direct-credential platforms (Bluesky, Discord, Telegram, etc.) — those use their own /connect endpoint below.",
        "security": [{ "ApiKeyAuth": ["accounts:write"] }, { "OAuth2": ["accounts:write"] }],
        "parameters": [
          { "name": "platform", "in": "path", "required": true, "schema": { "$ref": "#/components/schemas/Platform" } },
          { "name": "redirectUri", "in": "query", "required": true, "schema": { "type": "string", "format": "uri" } }
        ],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [true] }, "data": { "type": "object", "properties": { "url": { "type": "string", "format": "uri" }, "state": { "type": "string" } } } } } } } },
          "400": { "$ref": "#/components/responses/ValidationError" },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/api/accounts/callback": {
      "post": {
        "operationId": "completeAccountOAuthCallback",
        "summary": "Complete an OAuth connect flow",
        "description": "Exchange the OAuth `code` returned to redirectUri for a connected social account.",
        "security": [{ "ApiKeyAuth": ["accounts:write"] }, { "OAuth2": ["accounts:write"] }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": {
          "type": "object",
          "required": ["platform", "code", "redirectUri"],
          "properties": {
            "platform": { "$ref": "#/components/schemas/Platform" },
            "code": { "type": "string" },
            "redirectUri": { "type": "string", "format": "uri" },
            "state": { "type": "string" }
          }
        } } } },
        "responses": {
          "201": { "description": "Created", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [true] }, "data": { "$ref": "#/components/schemas/SocialAccount" } } } } } },
          "400": { "$ref": "#/components/responses/ValidationError" },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/api/accounts/bluesky/connect": {
      "post": {
        "operationId": "connectBlueskyAccount",
        "summary": "Connect a Bluesky account",
        "description": "Bluesky has no OAuth redirect flow — connect directly with an app password.",
        "security": [{ "ApiKeyAuth": ["accounts:write"] }, { "OAuth2": ["accounts:write"] }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": {
          "type": "object", "required": ["identifier", "appPassword"],
          "properties": { "identifier": { "type": "string", "description": "Handle or email." }, "appPassword": { "type": "string" } }
        } } } },
        "responses": {
          "201": { "description": "Created", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [true] }, "data": { "$ref": "#/components/schemas/SocialAccount" } } } } } },
          "400": { "$ref": "#/components/responses/ValidationError" },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/api/accounts/discord/connect": {
      "post": {
        "operationId": "connectDiscordAccount",
        "summary": "Connect a Discord channel",
        "description": "Discord connects via a channel Incoming Webhook URL — no OAuth.",
        "security": [{ "ApiKeyAuth": ["accounts:write"] }, { "OAuth2": ["accounts:write"] }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": {
          "type": "object", "required": ["webhookUrl"], "properties": { "webhookUrl": { "type": "string", "format": "uri" } }
        } } } },
        "responses": {
          "201": { "description": "Created", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [true] }, "data": { "$ref": "#/components/schemas/SocialAccount" } } } } } },
          "400": { "$ref": "#/components/responses/ValidationError" },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/api/accounts/telegram/connect": {
      "post": {
        "operationId": "connectTelegramAccount",
        "summary": "Connect a Telegram channel",
        "description": "Telegram connects with a bot token (from @BotFather) plus the target chat/channel id — no OAuth.",
        "security": [{ "ApiKeyAuth": ["accounts:write"] }, { "OAuth2": ["accounts:write"] }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": {
          "type": "object", "required": ["botToken", "chatId"],
          "properties": { "botToken": { "type": "string" }, "chatId": { "type": "string", "description": "@channelname or numeric id." } }
        } } } },
        "responses": {
          "201": { "description": "Created", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [true] }, "data": { "$ref": "#/components/schemas/SocialAccount" } } } } } },
          "400": { "$ref": "#/components/responses/ValidationError" },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/api/accounts/x/byok": {
      "post": {
        "operationId": "connectXByokAccount",
        "summary": "Connect X (Twitter) with your own app keys",
        "description": "Bring-your-own-keys: paste your own X app's 4 OAuth 1.0a credentials for reliable posting without depending on seenpaid's shared X app.",
        "security": [{ "ApiKeyAuth": ["accounts:write"] }, { "OAuth2": ["accounts:write"] }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": {
          "type": "object", "required": ["consumerKey", "consumerSecret", "token", "tokenSecret"],
          "properties": {
            "consumerKey": { "type": "string" }, "consumerSecret": { "type": "string" },
            "token": { "type": "string" }, "tokenSecret": { "type": "string" }
          }
        } } } },
        "responses": {
          "201": { "description": "Created", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [true] }, "data": { "$ref": "#/components/schemas/SocialAccount" } } } } } },
          "400": { "$ref": "#/components/responses/ValidationError" },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/api/accounts/{id}": {
      "delete": {
        "operationId": "disconnectAccount",
        "summary": "Disconnect a social account",
        "description": "Disconnect a social account. Scheduled posts targeting only that account will stop publishing.",
        "security": [{ "ApiKeyAuth": ["accounts:delete"] }, { "OAuth2": ["accounts:delete"] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }],
        "responses": {
          "204": { "description": "Disconnected" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/api/media/presign": {
      "post": {
        "operationId": "presignMediaUpload",
        "summary": "Get a presigned upload URL",
        "description": "Get a presigned URL to upload an image or video directly to storage before registering it as media.",
        "security": [{ "ApiKeyAuth": ["posts:write"] }, { "OAuth2": ["posts:write"] }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": {
          "type": "object", "required": ["filename", "contentType"],
          "properties": { "filename": { "type": "string" }, "contentType": { "type": "string" } }
        } } } },
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [true] }, "data": { "type": "object", "properties": { "uploadUrl": { "type": "string", "format": "uri" }, "mediaUrl": { "type": "string", "format": "uri" } } } } } } } },
          "400": { "$ref": "#/components/responses/ValidationError" },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/api/media": {
      "post": {
        "operationId": "registerMedia",
        "summary": "Register an already-uploaded media file",
        "description": "Register a media file (after uploading via the presigned URL) so it can be attached to a post.",
        "security": [{ "ApiKeyAuth": ["posts:write"] }, { "OAuth2": ["posts:write"] }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": {
          "type": "object", "required": ["url", "type"],
          "properties": { "url": { "type": "string", "format": "uri" }, "type": { "type": "string", "enum": ["image", "video"] } }
        } } } },
        "responses": {
          "201": { "description": "Created", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [true] }, "data": { "$ref": "#/components/schemas/Media" } } } } } },
          "400": { "$ref": "#/components/responses/ValidationError" },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/api/media/generate": {
      "post": {
        "operationId": "generateMediaImage",
        "summary": "Generate an image from a text prompt",
        "description": "Generate an image from a prompt (free, no key required) and register it as media in one call. Returns a media id usable directly in POST /api/posts.",
        "security": [{ "ApiKeyAuth": ["posts:write"] }, { "OAuth2": ["posts:write"] }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": {
          "type": "object", "required": ["prompt"], "properties": { "prompt": { "type": "string" } }
        } } } },
        "responses": {
          "201": { "description": "Created", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [true] }, "data": { "$ref": "#/components/schemas/Media" } } } } } },
          "400": { "$ref": "#/components/responses/ValidationError" },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/api/analytics": {
      "get": {
        "operationId": "getOrgAnalytics",
        "summary": "Org-wide revenue attribution analytics",
        "description": "Clicks → sales → revenue for the org, with per-platform, per-country, per-device, per-referrer, per-OS, per-browser, per-page and per-UTM breakdowns, a daily time-series, and the change vs. the previous equal-length period. Requires an active plan (402 if the org is not on an active/trialing plan).",
        "security": [{ "ApiKeyAuth": ["posts:read"] }, { "OAuth2": ["posts:read"] }],
        "parameters": [
          { "name": "days", "in": "query", "schema": { "type": "integer", "enum": [1, 7, 30, 90] }, "description": "Preset window. Default 30." },
          { "name": "to", "in": "query", "schema": { "type": "string", "format": "date" }, "description": "Anchor date (YYYY-MM-DD) for a custom range; pairs with `days` as an arbitrary day-count up to 366." },
          { "name": "platform", "in": "query", "schema": { "$ref": "#/components/schemas/Platform" }, "description": "Filter to a single platform." }
        ],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [true] }, "data": { "type": "object", "description": "Totals, previous-period totals, byPlatform/byCountry/byDevice/byReferrer/byOS/byBrowser/byPage/byUtmSource/byUtmMedium/byUtmCampaign breakdown arrays, liveVisitors, goals, timeseries and funnel." } } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "402": { "description": "Org is not on an active plan.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/api/analytics/events": {
      "get": {
        "operationId": "getRecentAnalyticsEvents",
        "summary": "Recent attribution events",
        "description": "The 25 most recent clicks/sales events for the dashboard live feed.",
        "security": [{ "ApiKeyAuth": ["posts:read"] }, { "OAuth2": ["posts:read"] }],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [true] }, "data": { "type": "object", "properties": { "events": { "type": "array", "items": { "type": "object" } } } } } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/api/analytics/match-health": {
      "get": {
        "operationId": "getMatchHealth",
        "summary": "Attribution match-rate health",
        "description": "Of all real Stripe payments seen, how many were traced back to a post — the trust signal for the attribution numbers.",
        "security": [{ "ApiKeyAuth": ["posts:read"] }, { "OAuth2": ["posts:read"] }],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [true] }, "data": { "type": "object" } } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/api/analytics/tracking-status": {
      "get": {
        "operationId": "getTrackingStatus",
        "summary": "Whether the tracking pixel has recorded a click",
        "description": "Powers the Settings 'your tracking is live' confirmation.",
        "security": [{ "ApiKeyAuth": ["posts:read"] }, { "OAuth2": ["posts:read"] }],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [true] }, "data": { "type": "object" } } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/api/revenue": {
      "get": {
        "operationId": "listRevenueByPost",
        "summary": "Revenue rollup across every post",
        "description": "Every post in the org ranked winners-first by revenue earned. Powers the money dashboard. Requires an active plan.",
        "security": [{ "ApiKeyAuth": ["posts:read"] }, { "OAuth2": ["posts:read"] }],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [true] }, "data": { "type": "array", "items": { "type": "object" } } } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "402": { "description": "Org is not on an active plan.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/api/revenue/by-account": {
      "get": {
        "operationId": "getRevenueBySourceAccount",
        "summary": "Revenue per connected Stripe account",
        "description": "Which of the org's connected Stripe accounts (businesses) earned what. Revenue tagged to a disconnected/untagged source rolls into an 'Other' bucket.",
        "security": [{ "ApiKeyAuth": ["posts:read"] }, { "OAuth2": ["posts:read"] }],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [true] }, "data": { "type": "object", "properties": { "accounts": { "type": "array", "items": { "type": "object", "properties": { "stripeAccountId": { "type": "string" }, "label": { "type": "string", "nullable": true }, "revenueCents": { "type": "integer" }, "payments": { "type": "integer" } } } }, "otherCents": { "type": "integer" }, "otherPayments": { "type": "integer" } } } } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/api/revenue/{postId}": {
      "get": {
        "operationId": "getPostRevenue",
        "summary": "Revenue for one post",
        "description": "Clicks, sales and revenue for a single post. 404s if the post is not in the caller's org.",
        "security": [{ "ApiKeyAuth": ["posts:read"] }, { "OAuth2": ["posts:read"] }],
        "parameters": [{ "name": "postId", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [true] }, "data": { "type": "object" } } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/api/revenue/{postId}/proof": {
      "get": {
        "operationId": "getPostProofStatus",
        "summary": "Get a post's public revenue-proof status",
        "description": "Whether public revenue-proof sharing is enabled for this post, and its share token if so.",
        "security": [{ "ApiKeyAuth": ["posts:read"] }, { "OAuth2": ["posts:read"] }],
        "parameters": [{ "name": "postId", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [true] }, "data": { "type": "object" } } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      },
      "post": {
        "operationId": "enablePostProof",
        "summary": "Enable public revenue-proof sharing for a post",
        "description": "Mint a shareable public revenue-proof page for this post (see GET /api/proof/{token}).",
        "security": [{ "ApiKeyAuth": ["posts:read"] }, { "OAuth2": ["posts:read"] }],
        "parameters": [{ "name": "postId", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [true] }, "data": { "type": "object" } } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      },
      "delete": {
        "operationId": "disablePostProof",
        "summary": "Disable public revenue-proof sharing for a post",
        "security": [{ "ApiKeyAuth": ["posts:read"] }, { "OAuth2": ["posts:read"] }],
        "parameters": [{ "name": "postId", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }],
        "responses": {
          "204": { "description": "Disabled" },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/api/proof/{token}": {
      "get": {
        "operationId": "getPublicRevenueProof",
        "summary": "Public revenue-proof page data (no auth)",
        "description": "PUBLIC — the data source for a shareable revenue-proof page. The token itself is the capability; no authentication required.",
        "security": [],
        "parameters": [{ "name": "token", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [true] }, "data": { "type": "object" } } } } } },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/api/insights/dna": {
      "get": {
        "operationId": "getMoneyDna",
        "summary": "Money DNA — the patterns behind earning posts",
        "description": "Best platform, best caption length, whether links help, best posting time — derived from posts that actually earned. Needs a few earning posts before it reports anything.",
        "security": [{ "ApiKeyAuth": ["posts:read"] }, { "OAuth2": ["posts:read"] }],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [true] }, "data": { "type": "object" } } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/api/insights/forecast": {
      "post": {
        "operationId": "forecastPostRevenue",
        "summary": "Forecast what a draft post would earn",
        "description": "Predict what a draft post would earn based on the org's own revenue history — a low/high range plus the factors driving it. Use before scheduling to compare caption variants.",
        "security": [{ "ApiKeyAuth": ["posts:read"] }, { "OAuth2": ["posts:read"] }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": {
          "type": "object",
          "properties": { "caption": { "type": "string" }, "platform": { "$ref": "#/components/schemas/Platform" }, "scheduledFor": { "type": "string", "format": "date-time" } }
        } } } },
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [true] }, "data": { "type": "object" } } } } } },
          "400": { "$ref": "#/components/responses/ValidationError" },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/api/notifications": {
      "get": {
        "operationId": "listNotifications",
        "summary": "The Money Feed — notifications + unread count",
        "description": "Sale, refund and milestone notifications, newest first, plus the unread count.",
        "security": [{ "ApiKeyAuth": ["posts:read"] }, { "OAuth2": ["posts:read"] }],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [true] }, "data": { "type": "object" } } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/api/notifications/read": {
      "post": {
        "operationId": "markAllNotificationsRead",
        "summary": "Mark all notifications read",
        "security": [{ "ApiKeyAuth": ["posts:read"] }, { "OAuth2": ["posts:read"] }],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [true] } } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/api/notifications/prefs": {
      "get": {
        "operationId": "getNotificationPrefs",
        "summary": "Get notification preferences",
        "description": "Also opts the org into the alert sweep by upserting default prefs if none exist yet.",
        "security": [{ "ApiKeyAuth": ["posts:read"] }, { "OAuth2": ["posts:read"] }],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [true] }, "data": { "type": "object" } } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      },
      "put": {
        "operationId": "updateNotificationPrefs",
        "summary": "Update notification preferences",
        "security": [{ "ApiKeyAuth": ["posts:write"] }, { "OAuth2": ["posts:write"] }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object" } } } },
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [true] }, "data": { "type": "object" } } } } } },
          "400": { "$ref": "#/components/responses/ValidationError" },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/api/autopilot": {
      "get": {
        "operationId": "getAutopilotStatus",
        "summary": "Autopilot config + suggestion queue",
        "description": "Whether Content Autopilot is on, its cadence/batch size/target platforms/topic, and the current AI-drafted suggestion queue.",
        "security": [{ "ApiKeyAuth": ["posts:read"] }, { "OAuth2": ["posts:read"] }],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [true] }, "data": { "type": "object" } } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      },
      "put": {
        "operationId": "updateAutopilotConfig",
        "summary": "Update autopilot config",
        "description": "Turn autopilot on/off and set its cadence, batch size, target platforms and topic. Autopilot drafts posts on a schedule; nothing publishes without explicit approval.",
        "security": [{ "ApiKeyAuth": ["posts:write"] }, { "OAuth2": ["posts:write"] }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object" } } } },
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [true] }, "data": { "type": "object" } } } } } },
          "400": { "$ref": "#/components/responses/ValidationError" },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/api/autopilot/generate": {
      "post": {
        "operationId": "generateAutopilotSuggestionsNow",
        "summary": "Generate a batch of autopilot suggestions immediately",
        "description": "Run autopilot right now instead of waiting for its next scheduled batch.",
        "security": [{ "ApiKeyAuth": ["posts:write"] }, { "OAuth2": ["posts:write"] }],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [true] }, "data": { "type": "object" } } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/api/autopilot/suggestions/{id}/approve": {
      "post": {
        "operationId": "approveAutopilotSuggestion",
        "summary": "Approve an autopilot draft",
        "description": "Returns the caption/platform for the composer to prefill. Approving does NOT publish on its own — follow with POST /api/posts.",
        "security": [{ "ApiKeyAuth": ["posts:write"] }, { "OAuth2": ["posts:write"] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [true] }, "data": { "type": "object" } } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/api/autopilot/suggestions/{id}/dismiss": {
      "post": {
        "operationId": "dismissAutopilotSuggestion",
        "summary": "Reject an autopilot draft",
        "security": [{ "ApiKeyAuth": ["posts:write"] }, { "OAuth2": ["posts:write"] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [true] } } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/api/experiments": {
      "get": {
        "operationId": "listExperiments",
        "summary": "List caption A/B tests",
        "description": "All content experiments with their variants, status and which variant is currently winning on revenue.",
        "security": [{ "ApiKeyAuth": ["posts:read"] }, { "OAuth2": ["posts:read"] }],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [true] }, "data": { "type": "array", "items": { "type": "object" } } } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      },
      "post": {
        "operationId": "createExperiment",
        "summary": "Create a caption A/B test",
        "description": "Give one idea and a platform; seenpaid writes N variants. Nothing goes live until POST /api/experiments/{id}/launch. Returns `{configured, experiment}`.",
        "security": [{ "ApiKeyAuth": ["posts:write"] }, { "OAuth2": ["posts:write"] }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": {
          "type": "object", "properties": { "idea": { "type": "string" }, "platform": { "$ref": "#/components/schemas/Platform" } }
        } } } },
        "responses": {
          "201": { "description": "Created", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [true] }, "data": { "type": "object" } } } } } },
          "400": { "$ref": "#/components/responses/ValidationError" },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/api/experiments/{id}/launch": {
      "post": {
        "operationId": "launchExperiment",
        "summary": "Launch a caption A/B test",
        "description": "Schedule every variant (staggered) as a real post with its own tracked link, and start the experiment.",
        "security": [{ "ApiKeyAuth": ["posts:write"] }, { "OAuth2": ["posts:write"] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [true] }, "data": { "type": "object" } } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/api/experiments/{id}/scale": {
      "post": {
        "operationId": "scaleExperimentWinner",
        "summary": "Get the winning variant's caption",
        "description": "Returns the winning caption of a finished A/B test, ready for the composer to prefill and reuse/scale up.",
        "security": [{ "ApiKeyAuth": ["posts:write"] }, { "OAuth2": ["posts:write"] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [true] }, "data": { "type": "object" } } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/api/recycle": {
      "get": {
        "operationId": "getRecycleStatus",
        "summary": "Auto-recycle rule + eligible top-earners",
        "description": "The current auto-recycle rule and the top-earning posts eligible to be recycled next. The daily sweep that acts on the rule runs server-side.",
        "security": [{ "ApiKeyAuth": ["posts:read"] }, { "OAuth2": ["posts:read"] }],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [true] }, "data": { "type": "object" } } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      },
      "put": {
        "operationId": "upsertRecycleRule",
        "summary": "Set the auto-recycle rule",
        "description": "Re-post anything that earned at least a minimum revenue, no more often than every N days. Turns proven winners into a repeating stream.",
        "security": [{ "ApiKeyAuth": ["posts:write"] }, { "OAuth2": ["posts:write"] }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": {
          "type": "object", "properties": { "minRevenueCents": { "type": "integer" }, "cadenceDays": { "type": "integer" }, "enabled": { "type": "boolean" } }
        } } } },
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [true] }, "data": { "type": "object" } } } } } },
          "400": { "$ref": "#/components/responses/ValidationError" },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/api/revenue-connectors": {
      "get": {
        "operationId": "listRevenueConnections",
        "summary": "List non-Stripe revenue connections",
        "description": "The org's connected non-Stripe revenue sources (Gumroad, Lemon Squeezy).",
        "security": [{ "ApiKeyAuth": ["accounts:read"] }, { "OAuth2": ["accounts:read"] }],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [true] }, "data": { "type": "array", "items": { "type": "object" } } } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      },
      "post": {
        "operationId": "createRevenueConnection",
        "summary": "Connect a non-Stripe revenue source",
        "description": "Connect a revenue provider (Gumroad or Lemon Squeezy) — analogous to connecting a social account.",
        "security": [{ "ApiKeyAuth": ["accounts:write"] }, { "OAuth2": ["accounts:write"] }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": {
          "type": "object", "required": ["provider"], "properties": { "provider": { "type": "string", "enum": ["gumroad", "lemonsqueezy"] } }
        } } } },
        "responses": {
          "201": { "description": "Created", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [true] }, "data": { "type": "object" } } } } } },
          "400": { "$ref": "#/components/responses/ValidationError" },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/api/revenue-connectors/{provider}": {
      "delete": {
        "operationId": "disconnectRevenueConnection",
        "summary": "Disconnect a non-Stripe revenue source",
        "security": [{ "ApiKeyAuth": ["accounts:write"] }, { "OAuth2": ["accounts:write"] }],
        "parameters": [{ "name": "provider", "in": "path", "required": true, "schema": { "type": "string", "enum": ["gumroad", "lemonsqueezy"] } }],
        "responses": {
          "204": { "description": "Disconnected" },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/api/bio": {
      "get": {
        "operationId": "getBioPage",
        "summary": "Get the org's bio-link page settings",
        "description": "The caller's own org's bio page (handle, title, avatar, public URL). Returns `data: null` if not configured yet — not a 404.",
        "security": [{ "ApiKeyAuth": ["posts:read"] }, { "OAuth2": ["posts:read"] }],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [true] }, "data": { "type": "object", "nullable": true } } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      },
      "put": {
        "operationId": "upsertBioPage",
        "summary": "Create or update the org's bio-link page",
        "security": [{ "ApiKeyAuth": ["posts:write"] }, { "OAuth2": ["posts:write"] }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": {
          "type": "object", "required": ["handle", "title"],
          "properties": { "handle": { "type": "string" }, "title": { "type": "string" }, "avatarUrl": { "type": "string", "format": "uri" } }
        } } } },
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [true] }, "data": { "type": "object" } } } } } },
          "400": { "$ref": "#/components/responses/ValidationError" },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/api/bio/{handle}": {
      "get": {
        "operationId": "getBioPageByHandle",
        "summary": "Public bio-link page data (no auth)",
        "description": "PUBLIC — powers the hosted bio-link page, with each link's tracked short URL.",
        "security": [],
        "parameters": [{ "name": "handle", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [true] }, "data": { "type": "object" } } } } } },
          "404": { "description": "No bio page found for this handle.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/api/api-keys": {
      "get": {
        "operationId": "listApiKeys",
        "summary": "List active API keys",
        "description": "List active API keys for the org (secrets are never returned after creation).",
        "security": [{ "ApiKeyAuth": [] }],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [true] }, "data": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "string" }, "name": { "type": "string" }, "createdAt": { "type": "string", "format": "date-time" } } } } } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      },
      "post": {
        "operationId": "createApiKey",
        "summary": "Create an API key",
        "description": "Create a new API key. The plaintext `secret` (sp_...) is returned exactly once, at creation — store it immediately.",
        "security": [{ "ApiKeyAuth": [] }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": {
          "type": "object", "properties": { "name": { "type": "string" } }
        } } } },
        "responses": {
          "201": { "description": "Created", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [true] }, "data": { "type": "object", "properties": { "id": { "type": "string" }, "name": { "type": "string" }, "secret": { "type": "string", "description": "Shown only in this response." } } } } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/api/api-keys/{id}": {
      "delete": {
        "operationId": "revokeApiKey",
        "summary": "Revoke an API key",
        "security": [{ "ApiKeyAuth": [] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": {
          "200": { "description": "Revoked", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [true] } } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/api/billing/plan": {
      "get": {
        "operationId": "getBillingPlanStatus",
        "summary": "Current plan + usage",
        "description": "Read-only; any org member can see it. Powers smart paywalls, usage meters and trial banners.",
        "security": [{ "ApiKeyAuth": ["posts:read"] }, { "OAuth2": ["posts:read"] }],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [true] }, "data": { "type": "object" } } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/api/billing/checkout": {
      "post": {
        "operationId": "createBillingCheckoutSession",
        "summary": "Create a Stripe Checkout session for a plan",
        "security": [{ "ApiKeyAuth": ["org:billing"] }, { "OAuth2": ["org:billing"] }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": {
          "type": "object", "properties": { "plan": { "type": "string" }, "successUrl": { "type": "string", "format": "uri" }, "cancelUrl": { "type": "string", "format": "uri" } }
        } } } },
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [true] }, "data": { "type": "object", "properties": { "url": { "type": "string", "format": "uri" } } } } } } } },
          "400": { "$ref": "#/components/responses/ValidationError" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" }
        }
      }
    },
    "/api/billing/portal": {
      "post": {
        "operationId": "createBillingPortalSession",
        "summary": "Create a Stripe Billing Portal session",
        "security": [{ "ApiKeyAuth": ["org:billing"] }, { "OAuth2": ["org:billing"] }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": {
          "type": "object", "required": ["returnUrl"], "properties": { "returnUrl": { "type": "string", "format": "uri" } }
        } } } },
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [true] }, "data": { "type": "object", "properties": { "url": { "type": "string", "format": "uri" } } } } } } } },
          "400": { "$ref": "#/components/responses/ValidationError" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" }
        }
      }
    },
    "/api/billing/cancel": {
      "post": {
        "operationId": "cancelSubscription",
        "summary": "Cancel the org's subscription",
        "security": [{ "ApiKeyAuth": ["org:billing"] }, { "OAuth2": ["org:billing"] }],
        "requestBody": { "content": { "application/json": { "schema": {
          "type": "object", "properties": { "reason": { "type": "string" }, "comment": { "type": "string" } }
        } } } },
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [true] }, "data": { "type": "object" } } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" }
        }
      }
    },
    "/api/stripe-connect/connect": {
      "get": {
        "operationId": "getStripeConnectAuthorizeUrl",
        "summary": "Get the Stripe Connect OAuth authorize URL",
        "description": "Returns Stripe's Connect OAuth authorize URL as JSON so the client can navigate there. Requires an active plan.",
        "security": [{ "ApiKeyAuth": ["org:billing"] }, { "OAuth2": ["org:billing"] }],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [true] }, "data": { "type": "object", "properties": { "url": { "type": "string", "format": "uri" } } } } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "402": { "description": "Org is not on an active plan.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/api/stripe-connect/status": {
      "get": {
        "operationId": "getStripeConnectStatus",
        "summary": "Get connected Stripe account(s) status",
        "security": [{ "ApiKeyAuth": ["org:billing"] }, { "OAuth2": ["org:billing"] }],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [true] }, "data": { "type": "object", "properties": { "accounts": { "type": "array", "items": { "type": "object", "properties": { "stripeAccountId": { "type": "string" }, "label": { "type": "string", "nullable": true } } } } } } } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/api/stripe-connect/disconnect": {
      "post": {
        "operationId": "disconnectStripeAccount",
        "summary": "Disconnect one or all connected Stripe accounts",
        "description": "Omit `stripeAccountId` to disconnect all of the org's Stripe connections.",
        "security": [{ "ApiKeyAuth": ["org:billing"] }, { "OAuth2": ["org:billing"] }],
        "requestBody": { "content": { "application/json": { "schema": {
          "type": "object", "properties": { "stripeAccountId": { "type": "string" } }
        } } } },
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [true] } } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/api/stripe-connect/label": {
      "post": {
        "operationId": "setStripeAccountLabel",
        "summary": "Set or clear a connected Stripe account's friendly label",
        "security": [{ "ApiKeyAuth": ["org:billing"] }, { "OAuth2": ["org:billing"] }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": {
          "type": "object", "required": ["stripeAccountId", "label"],
          "properties": { "stripeAccountId": { "type": "string" }, "label": { "type": "string" } }
        } } } },
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [true] } } } } } },
          "400": { "description": "stripeAccountId is required.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/api/referrals": {
      "get": {
        "operationId": "getReferralsOverview",
        "summary": "Referral program overview",
        "description": "The caller's referral code + link, stats, reward ledger, milestone progress, and the leaderboard.",
        "security": [{ "ApiKeyAuth": [] }, { "OAuth2": [] }],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [true] }, "data": { "type": "object" } } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/api/stats": {
      "get": {
        "operationId": "getPublicStats",
        "summary": "Public landing milestone counter (no auth)",
        "description": "PUBLIC — a cached aggregate count used by the marketing landing page.",
        "security": [],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [true] }, "data": { "type": "object" } } } } } }
        }
      }
    },
    "/health": {
      "get": {
        "operationId": "getHealth",
        "summary": "Shallow liveness check (no auth)",
        "description": "PUBLIC — answers without touching anything. Safe for platform routing/restart checks.",
        "security": [],
        "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "status": { "type": "string", "enum": ["ok"] } } } } } } }
      }
    },
    "/health/deep": {
      "get": {
        "operationId": "getDeepHealth",
        "summary": "Deep health check — Postgres, Redis, worker heartbeat (no auth)",
        "description": "PUBLIC, rate-limited. Exercises Postgres, Redis and the background worker heartbeat — the check monitors should call.",
        "security": [],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object" } } } },
          "503": { "description": "One or more dependencies unhealthy.", "content": { "application/json": { "schema": { "type": "object" } } } }
        }
      }
    }
  },
  "x-mcp": {
    "description": "The recommended way for an AI agent to drive seenpaid: a single stateless streamable-HTTP MCP endpoint exposing 50 typed, described tools — richer and more agent-native than the raw REST layer above. Authenticate the same way (API key or OAuth Bearer token).",
    "endpoint": "https://api.seenpaid.com/mcp",
    "protocol": "Model Context Protocol (JSON-RPC 2.0 over streamable HTTP)",
    "documentation": "https://seenpaid.mintlify.app/agents/mcp"
  },
  "x-mcp-tools": [
    { "name": "list_posts", "description": "List posts with status, schedule time, target platforms and any publish errors. Filter by status to answer questions like \"what is scheduled this week\" or \"did anything fail\"." },
    { "name": "get_post", "description": "Get one post in full, including per-platform publish results — which platforms succeeded, the live URL of each published copy, and the exact error for any that failed." },
    { "name": "schedule_post", "description": "Schedule or immediately publish a post. Give a caption plus either platforms or account_ids; omit both to post to every active account. Omit schedule_for to publish now. Use per_account_captions to tailor the text per platform (e.g. shorter for X)." },
    { "name": "bulk_schedule", "description": "Schedule many posts in one call — a content calendar, a thread split across days, a week of promos. Each item is scheduled independently; if one fails the rest still go through and the failures are reported per item." },
    { "name": "update_post", "description": "Edit a draft or scheduled post — change its caption, its time, or both. Published posts cannot be edited (the platforms already have them)." },
    { "name": "cancel_post", "description": "Cancel a scheduled post before it goes out. Stops every pending platform job; platforms it already published to are untouched. Reversible in the sense that the post row survives — use delete_post to remove it entirely." },
    { "name": "delete_post", "description": "Permanently delete a post and cancel any pending publishes. This cannot be undone — prefer cancel_post unless the user explicitly asked to delete." },
    { "name": "get_analytics", "description": "The account's revenue attribution over a window — clicks, sales, revenue — with per-platform, per-country and per-device breakdowns, plus the change vs the previous equal-length period." },
    { "name": "get_analytics_breakdown", "description": "Slice attribution by one dimension — where the traffic and money actually came from. Use this to answer \"which referrer converts best\" or \"which UTM campaign earned the most\"." },
    { "name": "get_analytics_timeseries", "description": "Day-by-day clicks, sales and revenue across a window, plus the change from the previous day. Use for trend questions — \"is this growing\", \"which day spiked\" — rather than a single total." },
    { "name": "get_top_posts", "description": "Posts ranked by revenue earned. Answers \"which post made the most money\" and \"what should I do more of\"." },
    { "name": "get_dead_posts", "description": "Posts that got clicks but earned nothing — the content that looks like it worked and didn't. The single most actionable list in seenpaid: it tells you what to stop making." },
    { "name": "get_post_revenue", "description": "Revenue, clicks and sales for one specific post." },
    { "name": "get_money_dna", "description": "The patterns behind this account's earning posts — best platform, best caption length, whether links help, best posting time. Derived from posts that actually earned, so it needs a few earning posts before it reports anything." },
    { "name": "forecast_post", "description": "Predict what a draft would earn, based on this account's own history — returns a low/high range and the factors driving it. Use BEFORE scheduling to compare two versions of a caption." },
    { "name": "get_money_feed", "description": "The live activity stream — recent clicks, sales and refunds newest-first, each with the platform and country it came from. Use to answer \"what just happened\" or \"did that post land\"." },
    { "name": "get_attribution_health", "description": "How much of this account's real Stripe revenue seenpaid can trace back to a post. A low match rate means the tracking setup is incomplete, not that the posts failed — check this before trusting a low revenue number." },
    { "name": "create_tracked_link", "description": "Mint a tracked short link for a post. Clicks on it are attributed to that post, and any Stripe sale that follows is traced back to it. Use this when publishing somewhere seenpaid doesn't post to directly (a newsletter, a video description, a manual post) so the revenue still lands on the right post." },
    { "name": "repost", "description": "Re-publish a post that already worked, as a brand-new post with fresh tracking. The classic use: call get_top_posts, then repost the top earner. Optionally override the caption or the platforms — the original post is left untouched." },
    { "name": "get_next_slot", "description": "Suggest when to schedule the next post. Picks the soonest time on the weekday that has actually earned this account the most money, skipping slots already taken by scheduled posts. Falls back to 'a few hours from now' when there isn't enough revenue history to have an opinion yet." },
    { "name": "get_channel_roi", "description": "Rank connected platforms by what they actually return — revenue, revenue per click, and revenue per post. Answers 'where should I spend my effort' with money instead of follower counts." },
    { "name": "add_media_from_url", "description": "Pull an image or video from a public URL into this account's media library and get back a media id for schedule_post." },
    { "name": "create_media_upload", "description": "Step 1 of 2 for uploading a file you already have (rather than one at a public URL — for that use add_media_from_url). Returns a URL to HTTP PUT the bytes to, then call finish_media_upload with the same r2_key to get a media id. Use this for large videos: the bytes go straight to storage, so there is no size ceiling." },
    { "name": "finish_media_upload", "description": "Step 2 of 2 after create_media_upload: confirms the bytes arrived and returns a media id for schedule_post. Verifies the file against storage rather than trusting what you report, so call it only after the PUT succeeded." },
    { "name": "get_connect_url", "description": "Get the URL a human needs to open to connect a new social account. The agent can't complete OAuth itself — hand this link to the user." },
    { "name": "disconnect_account", "description": "Disconnect a social account. Scheduled posts targeting only that account will stop publishing, so check list_posts first." },
    { "name": "summarize_performance", "description": "A single briefing an agent can read aloud: what went out, what earned, what died, what's broken and what to do next. Use this for 'how are we doing' rather than calling five tools and stitching them together." },
    { "name": "get_platform_requirements", "description": "What each platform will and won't accept: caption character limit, whether media is mandatory, whether links in the post body can be tracked, and any platform-specific catch. Check this before writing captions for several platforms at once." },
    { "name": "validate_post", "description": "Dry-run a caption against the platforms you plan to send it to, WITHOUT publishing. Reports per platform whether it would publish, and exactly why not — too long by N characters, media required and none attached, account disconnected. Call this before schedule_post whenever one caption goes to several platforms." },
    { "name": "list_media", "description": "The account's media library, newest first — images and videos already uploaded or generated. Returns ids you can pass straight to schedule_post instead of regenerating something that already exists." },
    { "name": "list_workspaces", "description": "The workspaces (brands or clients) this account belongs to. Each has its own posts, channels and revenue — an API key is scoped to exactly one, so this is how an agent knows which set of numbers it is looking at." },
    { "name": "get_autopilot_status", "description": "Whether autopilot is on, how often it generates drafts, which platforms it targets, and how many suggestions are waiting for approval." },
    { "name": "configure_autopilot", "description": "Turn autopilot on/off and set its cadence, batch size, target platforms and topic. Autopilot drafts posts for you on a schedule; nothing publishes without approval." },
    { "name": "generate_suggestions", "description": "Run autopilot immediately instead of waiting for its next scheduled batch. Returns how many drafts were created." },
    { "name": "list_suggestions", "description": "The autopilot-generated drafts waiting for a decision. Each has an id to pass to approve_suggestion or dismiss_suggestion." },
    { "name": "approve_suggestion", "description": "Approve an autopilot draft. Returns its caption and intended platform so you can then schedule it with schedule_post — approving does NOT publish on its own." },
    { "name": "dismiss_suggestion", "description": "Reject an autopilot draft so it stops appearing in the queue." },
    { "name": "list_experiments", "description": "All caption A/B tests with their variants, status and which variant is winning on revenue." },
    { "name": "get_experiment", "description": "One A/B test in full — every variant with its clicks, sales and revenue." },
    { "name": "create_experiment", "description": "Create a caption A/B test: give one idea and a platform, and seenpaid writes N variants. Nothing goes live until launch_experiment. The winner is decided on revenue earned, not engagement." },
    { "name": "launch_experiment", "description": "Publish every variant of an A/B test. Each goes out as a real post with its own tracked link, so revenue can be attributed per variant." },
    { "name": "get_experiment_winner", "description": "The winning caption of a finished A/B test, ready to reuse or scale up." },
    { "name": "get_recycle_status", "description": "The rule for automatically re-posting content that already earned money, and which posts currently qualify." },
    { "name": "configure_recycle", "description": "Set the auto-recycle rule: re-post anything that earned at least min_revenue, no more often than every cadence_days. Turns proven winners into a repeating stream instead of one-offs." },
    { "name": "list_accounts", "description": "The connected social accounts — platform, handle, id and status. Use these ids or platforms when scheduling." },
    { "name": "get_account_health", "description": "Which connected accounts are healthy and which stopped working. An account that needs reconnecting will silently fail to publish, so check this when a post didn't go out or before scheduling a big batch." },
    { "name": "get_plan_limits", "description": "This account's plan and its caps: how many workspaces the subscription covers and how many exist. Social accounts inside a workspace are not capped. Check before creating a workspace." },
    { "name": "generate_image", "description": "Generate an image from a text prompt and store it in this account's media library. Returns a media id you can pass to schedule_post — the fastest way to go from an idea to a post with visuals." },
    { "name": "generate_carousel", "description": "Render 2-10 branded carousel slides (1080×1080 PNGs — dark brand background, title slide + numbered content slides) from text YOU write, and get back ordered media ids for schedule_post. Each slide can also take an image_prompt for an AI-generated background, drawn under a scrim so the copy stays readable. This tool only renders; it does not generate copy. Platforms cap attached images: LinkedIn 9, X/Bluesky/Mastodon 4. Instagram is not supported for these." },
    { "name": "list_notifications", "description": "This account's alerts — sales that landed, publish failures, milestones. Use to catch problems the user hasn't seen yet." }
  ]
}
