{
  "openapi": "3.1.0",
  "info": {
    "title": "Werepost API",
    "version": "2026-08-21",
    "summary": "Profile-scoped social publishing API",
    "description": "Create immediate or scheduled social posts, inspect their status, retry failures, delete posts, and manage connected destinations. API keys are created in Werepost Settings and must be sent as Bearer credentials. Every API-key operation is restricted to a profile owned by the key's Werepost account.",
    "termsOfService": "https://werepost.ai/terms-of-service",
    "contact": {
      "name": "Werepost support",
      "email": "info@sofuto.uk",
      "url": "https://werepost.ai/docs"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://werepost.ai/terms-of-service"
    }
  },
  "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
  "servers": [
    {
      "url": "https://werepost.ai",
      "description": "Production"
    }
  ],
  "externalDocs": {
    "description": "Werepost API guide",
    "url": "https://werepost.ai/docs"
  },
  "tags": [
    {
      "name": "Service",
      "description": "Public API metadata and health."
    },
    {
      "name": "Connections",
      "description": "Signed-in web-session endpoints for profile provisioning and OAuth connection management. API keys are not accepted for these operations."
    },
    {
      "name": "Posts",
      "description": "Create, list, inspect, retry, and delete profile-scoped posts."
    }
  ],
  "paths": {
    "/api/v1": {
      "get": {
        "operationId": "getApiIndex",
        "summary": "API index",
        "tags": ["Service"],
        "security": [],
        "responses": {
          "200": {
            "description": "API metadata and the implemented endpoint list.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiIndex"
                }
              }
            },
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          }
        }
      }
    },
    "/api/v1/health": {
      "get": {
        "operationId": "getHealth",
        "summary": "Health check",
        "tags": ["Service"],
        "security": [],
        "responses": {
          "200": {
            "description": "The API is configured.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Health"
                },
                "example": {
                  "ok": true,
                  "version": "2026-08-21"
                }
              }
            },
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "503": {
            "description": "The API is deployed but not fully configured.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Health"
                },
                "example": {
                  "ok": false,
                  "version": "2026-08-21"
                }
              }
            },
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          }
        }
      }
    },
    "/api/v1/platforms": {
      "get": {
        "operationId": "listPlatforms",
        "summary": "List supported platforms",
        "tags": ["Service"],
        "security": [],
        "responses": {
          "200": {
            "description": "Current platform definitions.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["data"],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PlatformDefinition"
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          }
        }
      }
    },
    "/api/v1/profiles/{profile_id}/provision": {
      "post": {
        "operationId": "provisionProfile",
        "summary": "Provision a profile publishing workspace",
        "description": "Creates or resolves the isolated publishing workspace for an owned profile. This endpoint requires a signed-in user access token; API keys are rejected.",
        "tags": ["Connections"],
        "security": [
          {
            "UserSessionBearer": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ProfilePath"
          }
        ],
        "responses": {
          "200": {
            "description": "The profile is ready for connections and publishing.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["ready", "profile_id"],
                  "properties": {
                    "ready": {
                      "type": "boolean",
                      "const": true
                    },
                    "profile_id": {
                      "$ref": "#/components/schemas/Uuid"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "502": {
            "$ref": "#/components/responses/BadGateway"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/api/v1/connections": {
      "get": {
        "operationId": "listConnections",
        "summary": "List a profile's connections",
        "description": "Requires a signed-in user access token. API keys are rejected.",
        "tags": ["Connections"],
        "security": [
          {
            "UserSessionBearer": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ProfileQuery"
          }
        ],
        "responses": {
          "200": {
            "description": "Connected accounts and any channel-selection state.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["data", "profile_id"],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Connection"
                      }
                    },
                    "profile_id": {
                      "$ref": "#/components/schemas/Uuid"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "502": {
            "$ref": "#/components/responses/BadGateway"
          }
        }
      }
    },
    "/api/v1/connect/{platform}": {
      "get": {
        "operationId": "createConnectionUrl",
        "summary": "Create a direct platform authorization URL",
        "description": "Returns a fresh direct OAuth URL for the selected platform. Navigate the browser to the returned URL. After the provider returns to Werepost, list connections again and complete channel selection when `needs_channel_selection` is true. Requires a signed-in user access token; API keys are rejected.",
        "tags": ["Connections"],
        "security": [
          {
            "UserSessionBearer": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PlatformPath"
          },
          {
            "$ref": "#/components/parameters/ProfileQuery"
          },
          {
            "name": "choose_account",
            "in": "query",
            "required": false,
            "description": "Defaults to true. Set to false to allow the provider's existing login session where supported.",
            "schema": {
              "type": "boolean",
              "default": true
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A fresh direct authorization URL.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["url", "platform"],
                  "properties": {
                    "url": {
                      "type": "string",
                      "format": "uri",
                      "description": "Short-lived authorization URL on the selected platform. Treat it as sensitive and do not persist it."
                    },
                    "platform": {
                      "$ref": "#/components/schemas/Platform"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "502": {
            "$ref": "#/components/responses/BadGateway"
          }
        }
      }
    },
    "/api/v1/connections/channel": {
      "post": {
        "operationId": "selectConnectionChannel",
        "summary": "Select a page, channel, or location",
        "description": "Completes connection setup for platforms whose account contains selectable destinations. Requires a signed-in user access token; API keys are rejected.",
        "tags": ["Connections"],
        "security": [
          {
            "UserSessionBearer": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ChannelSelectionRequest"
              },
              "example": {
                "profile_id": "4a40c92d-68af-4f10-b1cc-881591b575fd",
                "platform": "youtube",
                "channel_id": "UC123456789"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The selected destination is active.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["data"],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Connection"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "502": {
            "$ref": "#/components/responses/BadGateway"
          }
        }
      }
    },
    "/api/v1/connections/{platform}": {
      "delete": {
        "operationId": "disconnectPlatform",
        "summary": "Disconnect a platform",
        "description": "Revokes the Werepost connection and prevents future publishing through it. Already-published social posts are not deleted. Requires a signed-in user access token; API keys are rejected.",
        "tags": ["Connections"],
        "security": [
          {
            "UserSessionBearer": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PlatformPath"
          },
          {
            "$ref": "#/components/parameters/ProfileQuery"
          }
        ],
        "responses": {
          "200": {
            "description": "The platform was disconnected.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["disconnected", "platform"],
                  "properties": {
                    "disconnected": {
                      "type": "boolean",
                      "const": true
                    },
                    "platform": {
                      "$ref": "#/components/schemas/Platform"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "502": {
            "$ref": "#/components/responses/BadGateway"
          }
        }
      }
    },
    "/api/v1/posts": {
      "post": {
        "operationId": "createPost",
        "summary": "Create an immediate or scheduled post",
        "description": "Omit `scheduled_at` for immediate publication; Werepost schedules it shortly ahead so media can be prepared safely. Supply a future ISO-8601 timestamp for scheduled publication. The request transfers the media from `media_url`, creates one post for all selected connected platforms, and returns after the publication job is accepted. `Idempotency-Key` is required and is scoped to the profile.",
        "tags": ["Posts"],
        "security": [
          {
            "ApiKeyBearer": []
          },
          {
            "UserSessionBearer": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PostCreateRequest"
              },
              "examples": {
                "immediate": {
                  "summary": "Publish immediately",
                  "value": {
                    "profile_id": "4a40c92d-68af-4f10-b1cc-881591b575fd",
                    "title": "Launch clip",
                    "caption": "A first look at what we built.",
                    "media_url": "https://cdn.example.com/videos/launch.mp4",
                    "platforms": ["tiktok", "instagram", "youtube"],
                    "platform_options": {
                      "youtube": {
                        "title": "A first look",
                        "made_for_kids": false
                      },
                      "instagram": {
                        "share_to_feed": true
                      }
                    }
                  }
                },
                "scheduled": {
                  "summary": "Publish at a future time",
                  "value": {
                    "profile_id": "4a40c92d-68af-4f10-b1cc-881591b575fd",
                    "caption": "Scheduled from the Werepost API.",
                    "media_url": "https://cdn.example.com/videos/scheduled.mp4",
                    "platforms": ["facebook", "linkedin"],
                    "scheduled_at": "2026-08-25T14:30:00Z"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "A new post was accepted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PostEnvelope"
                },
                "example": {
                  "data": {
                    "id": "4cf807c9-5e93-4c98-9077-586b5ce0e20e",
                    "profile_id": "4a40c92d-68af-4f10-b1cc-881591b575fd",
                    "title": "Launch clip",
                    "caption": "A first look at what we built.",
                    "media_url": "https://cdn.example.com/videos/launch.mp4",
                    "source": "api",
                    "platforms": ["tiktok", "instagram", "youtube"],
                    "status": "pending",
                    "provider_status": "SCHEDULED",
                    "error": null,
                    "scheduled_at": "2026-08-21T12:00:15.000Z",
                    "posted_at": null,
                    "created_at": "2026-08-21T12:00:00.000Z",
                    "updated_at": "2026-08-21T12:00:01.000Z",
                    "deliveries": [
                      {
                        "platform": "tiktok",
                        "status": "pending",
                        "external_id": null,
                        "external_url": null,
                        "error": null,
                        "delivered_at": null
                      }
                    ]
                  },
                  "idempotent": false
                }
              }
            },
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "200": {
            "description": "The same idempotency key already created this post. The existing representation is returned with `idempotent: true`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PostEnvelope"
                }
              }
            },
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "502": {
            "$ref": "#/components/responses/BadGateway"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      },
      "get": {
        "operationId": "listPosts",
        "summary": "List posts",
        "description": "API keys must supply `profile_id`. A signed-in user session may omit it to list posts visible across that user's profiles.",
        "tags": ["Posts"],
        "security": [
          {
            "ApiKeyBearer": []
          },
          {
            "UserSessionBearer": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OptionalProfileQuery"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of posts. Values are clamped to 1–200.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Posts ordered by `scheduled_at` descending.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["data"],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Post"
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/posts/{post_id}": {
      "get": {
        "operationId": "getPost",
        "summary": "Get a post",
        "description": "Returns the Werepost record. Use `refresh=true` to fetch the latest publishing state before returning it.",
        "tags": ["Posts"],
        "security": [
          {
            "ApiKeyBearer": []
          },
          {
            "UserSessionBearer": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PostPath"
          },
          {
            "name": "refresh",
            "in": "query",
            "required": false,
            "description": "When true, synchronise provider status before responding.",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/PostResponse"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "502": {
            "$ref": "#/components/responses/BadGateway"
          }
        }
      },
      "delete": {
        "operationId": "deletePost",
        "summary": "Cancel and delete a post",
        "description": "Attempts to cancel the publishing job, removes associated private Werepost media when applicable, then removes the Werepost record. A public CDN object is not deleted. Content already delivered to a platform may remain there and must be managed on that platform.",
        "tags": ["Posts"],
        "security": [
          {
            "ApiKeyBearer": []
          },
          {
            "UserSessionBearer": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PostPath"
          }
        ],
        "responses": {
          "200": {
            "description": "The Werepost record was deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["deleted", "id"],
                  "properties": {
                    "deleted": {
                      "type": "boolean",
                      "const": true
                    },
                    "id": {
                      "$ref": "#/components/schemas/Uuid"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "502": {
            "$ref": "#/components/responses/BadGateway"
          }
        }
      }
    },
    "/api/v1/posts/{post_id}/retry": {
      "post": {
        "operationId": "retryPost",
        "summary": "Retry a failed post",
        "description": "Retries an existing publishing job and returns its synchronised state. If initial submission failed before a publishing job was created, the API returns `409 resubmit_required`; resubmit the original create request with the same Idempotency-Key instead.",
        "tags": ["Posts"],
        "security": [
          {
            "ApiKeyBearer": []
          },
          {
            "UserSessionBearer": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PostPath"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/PostResponse"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "502": {
            "$ref": "#/components/responses/BadGateway"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyBearer": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "wrp_live_…",
        "description": "Werepost API key created in Settings → API keys. Keep it server-side and send `Authorization: Bearer wrp_live_…`."
      },
      "UserSessionBearer": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "Signed-in Werepost user access token. Connection-management operations require this credential and reject API keys."
      }
    },
    "headers": {
      "RequestId": {
        "description": "Unique request identifier. Include it when contacting support.",
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      }
    },
    "parameters": {
      "ProfileQuery": {
        "name": "profile_id",
        "in": "query",
        "required": true,
        "description": "Werepost profile UUID. API keys are accepted only when this profile belongs to the key owner.",
        "schema": {
          "$ref": "#/components/schemas/Uuid"
        }
      },
      "OptionalProfileQuery": {
        "name": "profile_id",
        "in": "query",
        "required": false,
        "description": "Werepost profile UUID. Required for API keys. A signed-in user session may omit it to list posts across profiles visible to that user.",
        "schema": {
          "$ref": "#/components/schemas/Uuid"
        }
      },
      "ProfilePath": {
        "name": "profile_id",
        "in": "path",
        "required": true,
        "description": "Owned Werepost profile UUID.",
        "schema": {
          "$ref": "#/components/schemas/Uuid"
        }
      },
      "PostPath": {
        "name": "post_id",
        "in": "path",
        "required": true,
        "description": "Werepost post UUID.",
        "schema": {
          "$ref": "#/components/schemas/Uuid"
        }
      },
      "PlatformPath": {
        "name": "platform",
        "in": "path",
        "required": true,
        "description": "Supported Werepost platform key.",
        "schema": {
          "$ref": "#/components/schemas/Platform"
        }
      },
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": true,
        "description": "Unique key for one intended post within this profile. Retrying the same request with the same key returns the original post when it was already created. Use 8–128 ASCII letters, digits, `.`, `_`, `:`, or `-`.",
        "schema": {
          "type": "string",
          "minLength": 8,
          "maxLength": 128,
          "pattern": "^[A-Za-z0-9._:-]{8,128}$",
          "example": "launch-video-2026-08-21"
        }
      }
    },
    "responses": {
      "PostResponse": {
        "description": "Current post representation.",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "required": ["data"],
              "properties": {
                "data": {
                  "$ref": "#/components/schemas/Post"
                }
              }
            }
          }
        },
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/RequestId"
          }
        }
      },
      "BadRequest": {
        "description": "Malformed JSON, missing fields, unsupported platform, invalid media URL, schedule, channel, or idempotency key.",
        "$ref": "#/components/responses/ErrorResponse"
      },
      "Unauthorized": {
        "description": "Missing, invalid, revoked, wrong credential type, or profile-mismatched Bearer credential.",
        "$ref": "#/components/responses/ErrorResponse"
      },
      "NotFound": {
        "description": "The endpoint or an authorised resource was not found.",
        "$ref": "#/components/responses/ErrorResponse"
      },
      "Conflict": {
        "description": "A required connection or channel is missing, an Idempotency-Key was reused with a different request, or the original request must be resubmitted with its Idempotency-Key.",
        "$ref": "#/components/responses/ErrorResponse"
      },
      "PayloadTooLarge": {
        "description": "The JSON request body exceeds 1,000,000 bytes. Media must be supplied by URL rather than in the JSON body.",
        "$ref": "#/components/responses/ErrorResponse"
      },
      "RateLimited": {
        "description": "Publishing infrastructure temporarily rate-limited the operation. Retry with exponential backoff and the same Idempotency-Key for create requests.",
        "$ref": "#/components/responses/ErrorResponse"
      },
      "InternalError": {
        "description": "An unexpected Werepost error occurred. The public message is intentionally generic; retain `request_id` for support.",
        "$ref": "#/components/responses/ErrorResponse"
      },
      "BadGateway": {
        "description": "A database, media, connection, or social publishing dependency could not complete the request.",
        "$ref": "#/components/responses/ErrorResponse"
      },
      "Unavailable": {
        "description": "The service or a required publishing dependency is not configured or temporarily unavailable.",
        "$ref": "#/components/responses/ErrorResponse"
      },
      "ErrorResponse": {
        "description": "Werepost error.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "examples": {
              "validation": {
                "value": {
                  "error": "platforms_required",
                  "message": "platforms must contain at least one supported platform.",
                  "request_id": "b4805362-5580-4eb0-b4e3-d95b30235b2b"
                }
              },
              "connection": {
                "value": {
                  "error": "platform_not_connected",
                  "message": "Connect every selected platform before publishing.",
                  "details": {
                    "missing": ["tiktok"],
                    "needs_channel": []
                  },
                  "request_id": "b4805362-5580-4eb0-b4e3-d95b30235b2b"
                }
              }
            }
          }
        },
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/RequestId"
          }
        }
      }
    },
    "schemas": {
      "Uuid": {
        "type": "string",
        "format": "uuid",
        "example": "4a40c92d-68af-4f10-b1cc-881591b575fd"
      },
      "ApiIndex": {
        "type": "object",
        "required": ["name", "version", "documentation", "openapi", "endpoints"],
        "properties": {
          "name": {
            "type": "string",
            "const": "Werepost API"
          },
          "version": {
            "type": "string",
            "example": "2026-08-21"
          },
          "documentation": {
            "type": "string",
            "format": "uri",
            "const": "https://werepost.ai/docs"
          },
          "openapi": {
            "type": "string",
            "format": "uri",
            "const": "https://werepost.ai/openapi.json"
          },
          "endpoints": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "Health": {
        "type": "object",
        "required": ["ok", "version"],
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "version": {
            "type": "string"
          }
        }
      },
      "Platform": {
        "type": "string",
        "enum": [
          "tiktok",
          "instagram",
          "facebook",
          "youtube",
          "x",
          "linkedin",
          "pinterest",
          "reddit",
          "bluesky",
          "threads",
          "googlebusiness",
          "discord"
        ]
      },
      "PlatformDefinition": {
        "type": "object",
        "required": ["id", "label", "category", "icon", "connection", "calendar", "channel_selection"],
        "properties": {
          "id": {
            "$ref": "#/components/schemas/Platform"
          },
          "label": {
            "type": "string"
          },
          "category": {
            "type": "string"
          },
          "icon": {
            "type": "string",
            "pattern": "^/"
          },
          "connection": {
            "type": "string",
            "const": "oauth"
          },
          "calendar": {
            "type": "boolean"
          },
          "channel_selection": {
            "type": "boolean"
          }
        }
      },
      "Channel": {
        "type": "object",
        "required": ["id", "name", "username", "avatar_url"],
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "username": {
            "type": ["string", "null"]
          },
          "avatar_url": {
            "type": ["string", "null"],
            "format": "uri"
          }
        }
      },
      "Connection": {
        "type": "object",
        "required": [
          "id",
          "profile_id",
          "platform",
          "handle",
          "display_name",
          "avatar_url",
          "external_id",
          "connected_at",
          "status",
          "needs_channel_selection",
          "channels"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "profile_id": {
            "$ref": "#/components/schemas/Uuid"
          },
          "platform": {
            "$ref": "#/components/schemas/Platform"
          },
          "handle": {
            "type": ["string", "null"]
          },
          "display_name": {
            "type": ["string", "null"]
          },
          "avatar_url": {
            "type": ["string", "null"],
            "format": "uri"
          },
          "external_id": {
            "type": ["string", "null"]
          },
          "connected_at": {
            "type": ["string", "null"],
            "format": "date-time"
          },
          "status": {
            "type": "string",
            "enum": ["connected", "action_required", "disconnected"]
          },
          "needs_channel_selection": {
            "type": "boolean"
          },
          "channels": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Channel"
            }
          }
        }
      },
      "ChannelSelectionRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": ["profile_id", "platform", "channel_id"],
        "properties": {
          "profile_id": {
            "$ref": "#/components/schemas/Uuid"
          },
          "platform": {
            "$ref": "#/components/schemas/Platform"
          },
          "channel_id": {
            "type": "string",
            "minLength": 1
          }
        }
      },
      "PostCreateRequest": {
        "type": "object",
        "additionalProperties": true,
        "required": ["profile_id", "media_url", "platforms"],
        "properties": {
          "profile_id": {
            "$ref": "#/components/schemas/Uuid"
          },
          "title": {
            "type": "string",
            "description": "Internal post title. Values longer than 180 characters are truncated. If omitted, Werepost derives it from the caption or media URL."
          },
          "caption": {
            "type": "string",
            "maxLength": 20000,
            "description": "Caption or description adapted to each selected platform. `text` and `description` are accepted aliases."
          },
          "media_url": {
            "type": "string",
            "description": "Public HTTPS CDN URL without embedded credentials. Localhost and literal private IP addresses are rejected. The URL may be at most 4,096 characters and must remain fetchable while the create request is processed. Private `storage://scheduled-videos/...` references are reserved for media uploaded through the Werepost web application.",
            "example": "https://cdn.example.com/videos/launch.mp4"
          },
          "platforms": {
            "type": "array",
            "minItems": 1,
            "uniqueItems": true,
            "items": {
              "$ref": "#/components/schemas/Platform"
            }
          },
          "scheduled_at": {
            "type": "string",
            "format": "date-time",
            "description": "Future ISO-8601 time. Omit for immediate publishing. `publish_at` and `post_date` are accepted aliases."
          },
          "platform_options": {
            "$ref": "#/components/schemas/PlatformOptions"
          },
          "idempotency_key": {
            "type": "string",
            "minLength": 8,
            "maxLength": 128,
            "pattern": "^[A-Za-z0-9._:-]{8,128}$",
            "deprecated": true,
            "description": "Body fallback for clients that cannot set headers. Prefer the required `Idempotency-Key` header."
          }
        }
      },
      "PlatformOptions": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "tiktok": {
            "$ref": "#/components/schemas/TikTokOptions"
          },
          "instagram": {
            "$ref": "#/components/schemas/InstagramOptions"
          },
          "facebook": {
            "$ref": "#/components/schemas/FacebookOptions"
          },
          "youtube": {
            "$ref": "#/components/schemas/YouTubeOptions"
          },
          "x": {
            "$ref": "#/components/schemas/XOptions"
          },
          "linkedin": {
            "$ref": "#/components/schemas/LinkedInOptions"
          },
          "pinterest": {
            "$ref": "#/components/schemas/PinterestOptions"
          },
          "reddit": {
            "$ref": "#/components/schemas/RedditOptions"
          },
          "discord": {
            "$ref": "#/components/schemas/DiscordOptions"
          },
          "googlebusiness": {
            "$ref": "#/components/schemas/GoogleBusinessOptions"
          },
          "threads": {
            "$ref": "#/components/schemas/ThreadsOptions"
          },
          "bluesky": {
            "$ref": "#/components/schemas/BlueskyOptions"
          }
        }
      },
      "TikTokOptions": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "privacy": {
            "type": "string",
            "enum": ["SELF_ONLY", "PUBLIC_TO_EVERYONE", "MUTUAL_FOLLOW_FRIENDS", "FOLLOWER_OF_CREATOR"],
            "default": "PUBLIC_TO_EVERYONE"
          },
          "disable_comments": { "type": "boolean", "default": false },
          "disable_duet": { "type": "boolean", "default": false },
          "disable_stitch": { "type": "boolean", "default": false },
          "is_brand_content": { "type": "boolean", "default": false },
          "is_organic_brand_content": { "type": "boolean", "default": false },
          "is_ai_generated": { "type": "boolean", "default": false }
        }
      },
      "InstagramOptions": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "type": { "type": "string", "default": "REEL" },
          "share_to_feed": { "type": "boolean", "default": true },
          "is_paid_partnership": { "type": "boolean", "default": false },
          "is_ai_generated": { "type": "boolean", "default": false }
        }
      },
      "FacebookOptions": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "type": { "type": "string", "default": "REEL" }
        }
      },
      "YouTubeOptions": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "type": { "type": "string", "default": "SHORT" },
          "title": { "type": "string", "maxLength": 100 },
          "privacy": { "type": "string", "enum": ["PRIVATE", "PUBLIC", "UNLISTED"], "default": "PUBLIC" },
          "made_for_kids": { "type": "boolean", "default": false },
          "contains_synthetic_media": { "type": "boolean", "default": false },
          "has_paid_product_placement": { "type": "boolean", "default": false }
        }
      },
      "XOptions": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "is_ai_generated": { "type": "boolean", "default": false }
        }
      },
      "LinkedInOptions": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "disable_reshare": { "type": "boolean", "default": false }
        }
      },
      "PinterestOptions": {
        "type": "object",
        "additionalProperties": false,
        "required": ["board_name"],
        "properties": {
          "board_name": { "type": "string", "minLength": 1 },
          "title": { "type": "string", "maxLength": 100 },
          "link": { "type": "string", "format": "uri" },
          "alt_text": { "type": "string" },
          "is_ai_generated": { "type": "boolean", "default": false }
        }
      },
      "RedditOptions": {
        "type": "object",
        "additionalProperties": false,
        "required": ["subreddit"],
        "properties": {
          "subreddit": { "type": "string", "minLength": 1 },
          "title": { "type": "string", "maxLength": 300 },
          "nsfw": { "type": "boolean", "default": false },
          "flair_id": { "type": "string" }
        }
      },
      "DiscordOptions": {
        "type": "object",
        "additionalProperties": false,
        "required": ["channel_id"],
        "properties": {
          "channel_id": { "type": "string", "minLength": 1 }
        }
      },
      "GoogleBusinessOptions": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "topic_type": { "type": "string", "default": "STANDARD" }
        }
      },
      "ThreadsOptions": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "topic_tag": { "type": "string" }
        }
      },
      "BlueskyOptions": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "tags": {
            "type": "array",
            "items": { "type": "string" }
          }
        }
      },
      "Post": {
        "type": "object",
        "required": [
          "id",
          "profile_id",
          "title",
          "caption",
          "media_url",
          "source",
          "platforms",
          "status",
          "provider_status",
          "error",
          "scheduled_at",
          "posted_at",
          "created_at",
          "updated_at",
          "deliveries"
        ],
        "properties": {
          "id": {
            "$ref": "#/components/schemas/Uuid"
          },
          "profile_id": {
            "$ref": "#/components/schemas/Uuid"
          },
          "title": {
            "type": ["string", "null"]
          },
          "caption": {
            "type": "string"
          },
          "media_url": {
            "type": ["string", "null"]
          },
          "source": {
            "type": ["string", "null"],
            "enum": ["api", "app", "calendar", null]
          },
          "platforms": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Platform"
            }
          },
          "status": {
            "type": "string",
            "enum": ["pending", "processing", "delivered", "failed"],
            "description": "Normalised Werepost state. `delivered` means the publishing job reported success; destination platforms may continue processing or reviewing content."
          },
          "provider_status": {
            "type": ["string", "null"],
            "description": "Detailed publishing state such as `UPLOADING`, `SCHEDULED`, `PROCESSING`, `RETRYING`, `REVIEW`, `POSTED`, `ERROR`, or `DELETED`."
          },
          "error": {
            "type": ["string", "null"],
            "description": "Sanitised publishing error when available."
          },
          "scheduled_at": {
            "type": "string",
            "format": "date-time"
          },
          "posted_at": {
            "type": ["string", "null"],
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "deliveries": {
            "type": "array",
            "description": "Per-platform delivery state. This array can be empty until delivery rows have been initialised.",
            "items": {
              "$ref": "#/components/schemas/Delivery"
            }
          }
        }
      },
      "Delivery": {
        "type": "object",
        "required": ["platform", "status", "external_id", "external_url", "error", "delivered_at"],
        "properties": {
          "platform": {
            "$ref": "#/components/schemas/Platform"
          },
          "status": {
            "type": "string",
            "enum": ["pending", "uploading", "delivered", "failed"]
          },
          "external_id": {
            "type": ["string", "null"],
            "description": "Destination post identifier when the platform returns one."
          },
          "external_url": {
            "type": ["string", "null"],
            "format": "uri",
            "description": "Sanitised HTTPS permalink when the platform returns one."
          },
          "error": {
            "type": ["string", "null"],
            "description": "Sanitised platform-specific delivery error."
          },
          "delivered_at": {
            "type": ["string", "null"],
            "format": "date-time"
          }
        }
      },
      "PostEnvelope": {
        "type": "object",
        "required": ["data", "idempotent"],
        "properties": {
          "data": {
            "$ref": "#/components/schemas/Post"
          },
          "idempotent": {
            "type": "boolean",
            "description": "True when the same profile-scoped Idempotency-Key already created the returned post."
          }
        }
      },
      "Error": {
        "type": "object",
        "required": ["error", "message", "request_id"],
        "properties": {
          "error": {
            "type": "string",
            "description": "Stable machine-readable code."
          },
          "message": {
            "type": "string",
            "description": "Safe human-readable message. Messages for server errors are intentionally generic."
          },
          "details": {
            "description": "Optional structured validation or connection details.",
            "type": ["object", "array", "string", "null"]
          },
          "request_id": {
            "type": "string",
            "format": "uuid",
            "description": "Matches the `X-Request-Id` response header."
          }
        }
      }
    }
  }
}
