{
  "openapi": "3.0.0",
  "paths": {
    "/api/v1/accounts": {
      "get": {
        "description": "Returns all social accounts connected by the user, including platform capabilities (text, media, video support and limits). Use `platform` query to filter by specific platforms.",
        "operationId": "AccountsController_listAccounts",
        "parameters": [
          {
            "name": "platform",
            "required": false,
            "in": "query",
            "description": "Comma-separated platform names to filter by",
            "schema": {
              "example": "x,linkedin",
              "type": "string"
            }
          },
          {
            "name": "status",
            "required": false,
            "in": "query",
            "description": "Filter by account status",
            "schema": {
              "example": "active",
              "type": "string",
              "enum": [
                "active",
                "expired",
                "disabled"
              ]
            }
          },
          {
            "name": "page",
            "required": false,
            "in": "query",
            "schema": {
              "default": 1,
              "example": 1,
              "type": "number"
            }
          },
          {
            "name": "page_size",
            "required": false,
            "in": "query",
            "schema": {
              "maximum": 100,
              "default": 50,
              "example": 50,
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of social accounts with pagination",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AccountListResponseDto"
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "403": {
            "description": "Missing required scope: accounts:read",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "List connected social accounts",
        "tags": [
          "Accounts"
        ]
      }
    },
    "/api/v1/accounts/{id}": {
      "get": {
        "description": "Returns detailed information for a specific social account including platform capabilities.",
        "operationId": "AccountsController_getAccount",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "description": "Account UUID (account_id)",
            "schema": {
              "example": "019cdbe3-473f-733b-b33e-c44e6497f368",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Account details",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AccountSingleResponseDto"
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "404": {
            "description": "Account not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Get a single social account",
        "tags": [
          "Accounts"
        ]
      }
    },
    "/api/v1/publishes": {
      "post": {
        "description": "Creates a new publish request that distributes content to one or more social platforms simultaneously.\n\n**Key behaviors:**\n- Each target maps to a social account. Specify `account_id` for each.\n- `content` at root level is the default; each target can override with its own `content`.\n- `media_ids` work the same way — root default, target override.\n- **Attaching images/videos:** `media_ids` are uploaded media IDs, **not URLs**. Upload each file first via `POST /media/uploads` → PUT to the presigned URL → `POST /media/uploads/:id/confirm`, then pass the returned `media_id` here. See the **Media** endpoints.\n- `publish_mode: \"immediate\"` publishes right away; `\"scheduled\"` requires `scheduled_at` (ISO 8601, must be future).\n- Use `Idempotency-Key` header to prevent duplicate publishes (cached 24h).\n- If dispatch returns `503 PUBLISH_DISPATCH_UNCERTAIN`, retry the same request with the same `Idempotency-Key`. The error includes `details.publish_id`; Solnk redelivers that same publish instead of creating another.\n- Publishing is async — immediate requests return `status: \"publishing\"`. Poll `GET /publishes/:id` for results.\n\n**Rate limit:** 30 requests/minute.",
        "operationId": "PublishesController_createPublish",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Unique key to prevent duplicate publishes (recommended for production). Cached for 24 hours.",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreatePublishDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Publish request created and queued",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublishSingleResponseDto"
                }
              }
            }
          },
          "400": {
            "description": "Validation error (missing targets, invalid account, bad schedule time)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "403": {
            "description": "Missing required scope: posts:write",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "409": {
            "description": "Idempotency key is processing or was reused with a different body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (30/min) or the team publishing quota is exhausted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "503": {
            "description": "Publish created but queue dispatch is uncertain; retry with the same Idempotency-Key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Create a multi-platform publish request",
        "tags": [
          "Publishes"
        ]
      },
      "get": {
        "description": "Returns a paginated list of publish requests. Filter by status, platform, account, or creation date.\n\n**Statuses:** `draft`, `scheduled`, `publishing`, `processing`, `published`, `failed`, or `partial`.\nTeam approval is reported separately in `approval_status`.",
        "operationId": "PublishesController_listPublishes",
        "parameters": [
          {
            "name": "status",
            "required": false,
            "in": "query",
            "description": "Comma-separated statuses: draft, scheduled, publishing, processing, published, failed, partial",
            "schema": {
              "example": "publishing,failed",
              "type": "string"
            }
          },
          {
            "name": "platform",
            "required": false,
            "in": "query",
            "description": "Comma-separated platform names",
            "schema": {
              "example": "x,linkedin",
              "type": "string"
            }
          },
          {
            "name": "account_id",
            "required": false,
            "in": "query",
            "description": "Filter by specific account ID",
            "schema": {
              "example": "019cdbe3-473f-733b-b33e-c44e6497f368",
              "type": "string"
            }
          },
          {
            "name": "created_after",
            "required": false,
            "in": "query",
            "description": "Only return publishes created after this ISO 8601 datetime",
            "schema": {
              "example": "2026-03-01T00:00:00Z",
              "type": "string"
            }
          },
          {
            "name": "page",
            "required": false,
            "in": "query",
            "schema": {
              "default": 1,
              "example": 1,
              "type": "number"
            }
          },
          {
            "name": "page_size",
            "required": false,
            "in": "query",
            "schema": {
              "maximum": 100,
              "default": 20,
              "example": 20,
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of publish requests",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublishListResponseDto"
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "List publish requests",
        "tags": [
          "Publishes"
        ]
      }
    },
    "/api/v1/publishes/{id}": {
      "get": {
        "description": "Returns the full publish request with status of each platform target. Use this to poll for publishing results after creating a publish request.",
        "operationId": "PublishesController_getPublish",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "description": "Publish request ID (UUID v7)",
            "schema": {
              "example": "019cf5a4-b50d-74ed-8d93-fac527aa1c37",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Publish request with target statuses",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublishSingleResponseDto"
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "404": {
            "description": "Publish request not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Get publish request status",
        "tags": [
          "Publishes"
        ]
      },
      "patch": {
        "description": "Updates the same publish record without cancelling it or creating a replacement.\n\nAn unconfirmed `draft` or a not-yet-sent `scheduled` publish is editable. Publishing, processing, published, failed, and partial publishes return `422 NOT_EDITABLE`. Editing a scheduled publish keeps its current schedule and safely replaces its queue job.\n\n**Patch behavior:**\n- Omitted top-level fields are preserved.\n- Omit `targets` to preserve the existing target set.\n- When `targets` is supplied, it is the complete desired target set. Existing targets preserve any omitted per-target fields.\n- Top-level `media_ids` replaces media for retained targets unless a target supplies its own `media_ids`.\n- The publish ID never changes.",
        "operationId": "PublishesController_updatePublish",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "description": "Draft or scheduled publish ID (UUID v7)",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdatePublishDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Publish updated in place",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublishSingleResponseDto"
                }
              }
            }
          },
          "400": {
            "description": "No editable fields, invalid account, or invalid media",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "403": {
            "description": "Missing required scope: posts:write",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "404": {
            "description": "Publish not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "422": {
            "description": "Publish is not editable or media is unsupported",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Update an existing draft or scheduled publish in place",
        "tags": [
          "Publishes"
        ]
      },
      "delete": {
        "description": "Permanently discards an editable draft. Scheduled, publishing, processing, or completed requests cannot be discarded here.",
        "operationId": "PublishesController_cancelPublish",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "description": "Draft publish ID (UUID v7)",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Draft discarded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DiscardPublishResponseDto"
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "403": {
            "description": "Missing required scope: posts:write",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "404": {
            "description": "Draft not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "422": {
            "description": "Only an editable draft can be discarded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Discard a draft publish",
        "tags": [
          "Publishes"
        ]
      }
    },
    "/api/v1/publishes/{id}/retry": {
      "post": {
        "description": "Re-sends **only targets with explicit failure evidence** on a `failed` or `partial` publish. Targets that already succeeded or still have an unknown outcome are never re-sent.\n\n**Editing before resend (recommended for content failures):** pass `targets` to fix the content/media of the failed targets first. A failure usually means the content was rejected (duplicate, banned words, bad media), so resending unchanged will just fail again. Each `targets` entry must reference a target that actually failed — including a successful target returns `400`.\n\n**Resend unchanged:** omit `targets` to re-send the failed targets as-is. Only do this for transient failures (e.g. an expired token that has since reconnected).\n\nPublishing is async — poll `GET /publishes/:id` for the new result.\n\n**Rate limit:** 30 requests/minute.",
        "operationId": "PublishesController_retryPublish",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "description": "Publish request ID (UUID v7)",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RetryPublishDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Failed targets re-queued",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublishSingleResponseDto"
                }
              }
            }
          },
          "400": {
            "description": "Publish is not failed/partial, or an edited target did not fail",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "403": {
            "description": "Missing required scope: posts:write",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "404": {
            "description": "Publish not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "422": {
            "description": "Edited media format not supported by the platform",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Retry a failed or partially-failed publish",
        "tags": [
          "Publishes"
        ]
      }
    },
    "/api/v1/publishes/{id}/confirm": {
      "post": {
        "description": "Releases a draft for publishing. The draft is queued immediately (or scheduled if `scheduled_at` is provided or was set on the draft).\n\nOnly an unconfirmed draft can be confirmed, including drafts created in the Solnk dashboard. Use `DELETE /publishes/:id` to discard a draft instead.\n\nIf the API returns a dispatch-related `503`, retry the same confirm request safely. Solnk redelivers with the same deterministic queue job ID, so the retry repairs an uncertain enqueue without creating another publish.",
        "operationId": "PublishesController_confirmPublish",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "description": "Draft publish ID (UUID v7)",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ConfirmPublishDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Draft confirmed and queued",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublishSingleResponseDto"
                }
              }
            }
          },
          "400": {
            "description": "Invalid scheduled_at",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "403": {
            "description": "Missing required scope: posts:write",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "404": {
            "description": "Draft not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "422": {
            "description": "Publish is not an unconfirmed draft, or its state changed concurrently",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "503": {
            "description": "Queue dispatch uncertain; retry the same confirm request safely",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Confirm a draft publish",
        "tags": [
          "Publishes"
        ]
      }
    },
    "/api/v1/publishes/{id}/reschedule": {
      "post": {
        "description": "Moves a not-yet-sent scheduled publish to a new future time without creating a new publish request.\n\nThe existing publish ID, content, media, and targets are preserved. The old queue job is removed before the same publish is queued at the new time.",
        "operationId": "PublishesController_reschedulePublish",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "description": "Scheduled publish ID (UUID v7)",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReschedulePublishDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Publish rescheduled in place",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublishSingleResponseDto"
                }
              }
            }
          },
          "400": {
            "description": "Invalid or non-future scheduled_at",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "403": {
            "description": "Missing required scope: posts:write",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "404": {
            "description": "Publish not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "422": {
            "description": "Publish is no longer waiting in the schedule queue",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Reschedule a queued publish",
        "tags": [
          "Publishes"
        ]
      }
    },
    "/api/v1/publishes/{id}/unschedule": {
      "post": {
        "description": "Removes a not-yet-sent scheduled publish from the queue and restores the same publish record to an editable draft.\n\nThe publish ID, content, media, and targets are preserved. No replacement publish is created. Processing or completed publishes cannot be restored.",
        "operationId": "PublishesController_unschedulePublish",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "description": "Scheduled publish ID (UUID v7)",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Publish restored to draft",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublishSingleResponseDto"
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "403": {
            "description": "Missing required scope: posts:write",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "404": {
            "description": "Publish not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "422": {
            "description": "Publish is no longer waiting in the schedule queue",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Restore a scheduled publish to draft",
        "tags": [
          "Publishes"
        ]
      }
    },
    "/api/v1/publishes/{id}/approve": {
      "post": {
        "description": "Approves a publish request whose `approval_status` is `pending_approval` and releases it to the publishing queue.\n\n**Who can call this:** only the team **owner or admin** that the API key belongs to. Members get `404`.\nThis is the API/MCP equivalent of approving in the dashboard — `approved_by` and `approved_at` are recorded for audit either way.\n\nIdempotent: approving an already-approved request returns `already_approved: true` (and re-queues only if the earlier dispatch never landed).",
        "operationId": "PublishesController_approvePublish",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "description": "Publish request ID (UUID v7)",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Approved and queued",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublishSingleResponseDto"
                }
              }
            }
          },
          "400": {
            "description": "Publish is not awaiting approval",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "403": {
            "description": "Missing required scope: posts:write",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "404": {
            "description": "Publish not found, or caller is not a team owner/admin",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Approve a publish awaiting team approval",
        "tags": [
          "Publishes"
        ]
      }
    },
    "/api/v1/publishes/{id}/reject": {
      "post": {
        "description": "Rejects a publish request whose `approval_status` is `pending_approval`. The request is **not** published.\n\n**Who can call this:** only the team **owner or admin** that the API key belongs to. Members get `404`.\nA rejected request is terminal — to publish after a rejection, submit a new `POST /publishes`. The optional `reason` is recorded and delivered via the `post.rejected` webhook.",
        "operationId": "PublishesController_rejectPublish",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "description": "Publish request ID (UUID v7)",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RejectPublishDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RejectPublishResponseDto"
                }
              }
            }
          },
          "400": {
            "description": "Publish is not awaiting approval",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "403": {
            "description": "Missing required scope: posts:write",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "404": {
            "description": "Publish not found, or caller is not a team owner/admin",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Reject a publish awaiting team approval",
        "tags": [
          "Publishes"
        ]
      }
    },
    "/api/v1/approvals": {
      "get": {
        "description": "Returns publish requests whose `approval_status` is `pending_approval` (only relevant when the team has approval enabled).\n\nOwners/admins see every pending request in the team; members see only the ones they submitted. Approve or reject via `POST /publishes/:id/approve` and `POST /publishes/:id/reject` (owner/admin only).",
        "operationId": "ApprovalsController_listApprovals",
        "parameters": [
          {
            "name": "page",
            "required": false,
            "in": "query",
            "schema": {
              "default": 1,
              "example": 1,
              "type": "number"
            }
          },
          {
            "name": "page_size",
            "required": false,
            "in": "query",
            "schema": {
              "maximum": 100,
              "default": 20,
              "example": 20,
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of pending-approval publish requests",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublishListResponseDto"
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "List publishes awaiting team approval",
        "tags": [
          "Approvals"
        ]
      }
    },
    "/api/v1/usage": {
      "get": {
        "description": "Returns the team owner's current plan limits and usage across all teams owned by that person during the active subscription period. Use `can_publish` before calling `POST /publishes`; the create endpoint enforces the same quota transactionally.\n\n**Limits:** A `null` value means unlimited (e.g. on a higher-tier plan).",
        "operationId": "UsageController_getUsage",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Current limits and usage",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UsageResponseDto"
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "403": {
            "description": "Missing required scope: billing:read",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Get subscription limits and current usage",
        "tags": [
          "Usage"
        ]
      }
    },
    "/api/v1/media/uploads": {
      "post": {
        "description": "Returns a presigned upload URL. Upload the file via PUT to the returned URL, then call `POST /media/uploads/:id/confirm`.\n\n**Flow:**\n1. Call this endpoint with filename, content_type, size_bytes\n2. PUT the file to the returned `upload_url` with the specified headers\n3. Call `POST /media/uploads/:id/confirm` to finalize\n4. Use the returned `media_id` in `POST /publishes`\n\n**Alternative — ingest by URL:** pass `source_url` (a public image/video URL) instead of uploading bytes yourself. The server fetches and stores it; you still call `confirm`. No presigned PUT needed.\n\n**Limits:**\n- **Max file size:** 500 MB per file.\n- **Allowed types:** `image/png`, `image/jpeg`, `image/webp`, `image/gif`, `video/mp4`, `video/quicktime` (.mov). Any other `content_type` is rejected with 400.\n- **Presigned upload URL expires in 15 minutes.**\n- **Unconfirmed uploads are deleted after 24 hours.**\n- Platform text/title requirements plus media type, count, and file-size limits are checked by `POST /publishes`. A provider may still reject unsupported dimensions, aspect ratio, duration, codec, or account-specific restrictions.",
        "operationId": "MediaController_createUpload",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateUploadDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Upload URL created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateUploadResponseDto"
                }
              }
            }
          },
          "400": {
            "description": "Invalid content type or missing fields",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Create a media upload",
        "tags": [
          "Media"
        ]
      }
    },
    "/api/v1/media/uploads/{id}/confirm": {
      "post": {
        "description": "Call after uploading the file to the presigned URL. Verifies the file exists in storage and marks it as ready for use in publishes.",
        "operationId": "MediaController_confirmUpload",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "description": "Upload/media ID from the create upload response",
            "schema": {
              "example": "019d4300-bbbb-7000-8000-000000000001",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Upload confirmed and ready",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConfirmUploadResponseDto"
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "404": {
            "description": "Upload not found or already confirmed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Confirm a media upload",
        "tags": [
          "Media"
        ]
      }
    },
    "/api/v1/media": {
      "get": {
        "description": "Returns paginated list of confirmed (ready) media files.",
        "operationId": "MediaController_listMedia",
        "parameters": [
          {
            "name": "page",
            "required": false,
            "in": "query",
            "schema": {
              "default": 1,
              "example": 1,
              "type": "number"
            }
          },
          {
            "name": "page_size",
            "required": false,
            "in": "query",
            "schema": {
              "maximum": 100,
              "default": 20,
              "example": 20,
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated media list",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MediaListResponseDto"
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "List uploaded media",
        "tags": [
          "Media"
        ]
      }
    },
    "/api/v1/webhooks": {
      "get": {
        "description": "Returns every webhook subscription owned by the API key team, including its event list, delivery health, and disabled state.",
        "operationId": "WebhooksController_listWebhooks",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Registered webhook subscriptions",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookListResponseDto"
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "List registered webhooks for the current team",
        "tags": [
          "Webhooks"
        ]
      },
      "post": {
        "description": "Subscribe to events by registering a webhook URL.\n\n**Events (whitelist):**\n- `post.published` — Post successfully published to all/some platforms\n- `post.failed` — Post failed to publish to any platform\n- `post.pending_approval` — Post needs approval before publishing (team審批流)\n- `post.approved` — Approver accepted a pending post\n- `post.rejected` — Approver rejected a pending post\n\n**Retry policy:** 10s timeout, exponential backoff (1m, 5m, 30m, 2h, 12h), max 6 attempts.\n\n**Auth:** Webhook subscriptions are team-scoped. Team-scoped API keys subscribe under their team; user-scoped keys use the caller's active_team_id.",
        "operationId": "WebhooksController_createWebhook",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateWebhookDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Webhook registered; save the one-time HMAC secret from this response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookCreateResponseDto"
                }
              }
            }
          },
          "400": {
            "description": "Invalid URL, event, or secret",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Register a webhook endpoint",
        "tags": [
          "Webhooks"
        ]
      }
    },
    "/api/v1/webhooks/{id}": {
      "delete": {
        "description": "Permanently removes one webhook subscription from the current team. Future events are no longer delivered to it.",
        "operationId": "WebhooksController_deleteWebhook",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "example": "019d4400-aaaa-7000-8000-000000000001",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Webhook deleted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookDeleteResponseDto"
                }
              }
            }
          },
          "404": {
            "description": "Webhook not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Delete a webhook",
        "tags": [
          "Webhooks"
        ]
      }
    },
    "/api/v1/analytics/posts": {
      "get": {
        "description": "Returns fully or partially published posts with aggregated metrics from targets that reached `published`.\n\n**Metric fields:** `total_views`, `total_likes`, `total_comments`, `total_shares`\n\n**Sort:** Use `sort=total_views&order=desc` to rank posts by performance.\n\n**Use this to:** identify top-performing content for AI to learn from, compare platforms, or build feedback loops for content generation.",
        "operationId": "AnalyticsController_listPosts",
        "parameters": [
          {
            "name": "page",
            "required": false,
            "in": "query",
            "schema": {
              "default": 1,
              "example": 1,
              "type": "number"
            }
          },
          {
            "name": "page_size",
            "required": false,
            "in": "query",
            "schema": {
              "maximum": 100,
              "default": 20,
              "example": 20,
              "type": "number"
            }
          },
          {
            "name": "platform",
            "required": false,
            "in": "query",
            "description": "Comma-separated platform names to filter by",
            "schema": {
              "example": "x,instagram",
              "type": "string"
            }
          },
          {
            "name": "sort",
            "required": false,
            "in": "query",
            "schema": {
              "default": "published_at",
              "example": "total_views",
              "type": "string",
              "enum": [
                "published_at",
                "total_views",
                "total_likes",
                "total_comments",
                "total_shares"
              ]
            }
          },
          {
            "name": "order",
            "required": false,
            "in": "query",
            "schema": {
              "default": "desc",
              "example": "desc",
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated cross-platform post analytics",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnalyticsListResponseDto"
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "List post analytics",
        "tags": [
          "Analytics"
        ]
      }
    },
    "/api/v1/analytics/posts/{id}": {
      "get": {
        "description": "Returns detailed per-platform metrics for a single published post, including platform-specific fields (e.g. impressions, reach, saves on Instagram).",
        "operationId": "AnalyticsController_getPost",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "description": "Post ID (UUID v7)",
            "schema": {
              "example": "019cf5a4-b50d-74ed-8d93-fac527aa1c37",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Post analytics with per-platform metrics",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnalyticsDetailResponseDto"
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "404": {
            "description": "Post not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Get post analytics",
        "tags": [
          "Analytics"
        ]
      }
    },
    "/api/v1/teams": {
      "get": {
        "description": "Returns owned teams with an explicit is_default marker so callers can identify the protected default team and choose a migration destination before deletion.",
        "operationId": "TeamsController_listTeams",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Owned teams in creation order",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TeamListResponseDto"
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "List teams owned by the API-key user",
        "tags": [
          "Teams"
        ]
      }
    },
    "/api/v1/teams/{team_id}/default": {
      "patch": {
        "description": "The API key must be bound to the selected team. The new default becomes the fallback for shared-team exits and the preselected migration destination when deleting another team.",
        "operationId": "TeamsController_setDefaultTeam",
        "parameters": [
          {
            "name": "team_id",
            "required": true,
            "in": "path",
            "schema": {
              "example": "019d1111-1111-7111-8111-111111111111",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Default team updated or already selected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SetDefaultTeamResponseDto"
                }
              }
            }
          },
          "403": {
            "description": "Owner access required or API key team mismatch",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Make the API-key team the owner default",
        "tags": [
          "Teams"
        ]
      }
    },
    "/api/v1/teams/{team_id}": {
      "delete": {
        "description": "Only the owner can delete a team, and the API key must be bound to that team. The owner's explicitly selected default team is protected. Accounts, groups, posts, media, and folders move to destination_team_id. Source-team API keys, webhooks, invitations, memberships, and incompatible member assignments are removed. The API key used for this request is revoked by the successful deletion, so create a new key in the destination team for later calls.",
        "operationId": "TeamsController_deleteTeam",
        "parameters": [
          {
            "name": "team_id",
            "required": true,
            "in": "path",
            "schema": {
              "example": "019d1111-1111-7111-8111-111111111111",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DeleteTeamDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Team deleted and durable content migrated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeleteTeamResponseDto"
                }
              }
            }
          },
          "400": {
            "description": "Invalid destination or team-name confirmation",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "403": {
            "description": "Owner access required, default team protected, or key scope mismatch",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Delete an extra team and migrate its durable content",
        "tags": [
          "Teams"
        ]
      }
    }
  },
  "info": {
    "title": "Solnk API",
    "description": "Solnk API lets you publish content to **9 social platforms** with a single API call.\n\nSupported platforms: **X (Twitter) · Instagram · TikTok · YouTube · Facebook · LinkedIn · Pinterest · Threads · Bluesky**\n\n### Attaching media (images / videos)\nMedia is uploaded as a **file**, then referenced by ID — you do NOT pass URLs.\n\n1. `POST /media/uploads` with `filename`, `content_type`, `size_bytes` → returns a presigned `upload_url` and a `media_id`.\n2. `PUT` the raw file bytes to `upload_url` (direct to storage, with the given `Content-Type` header).\n3. `POST /media/uploads/{id}/confirm` to finalize.\n4. Pass the `media_id` in the `media_ids` array of `POST /publishes`.\n\nSupported types: `image/png`, `image/jpeg`, `image/webp`, `image/gif`, `video/mp4`, `video/quicktime`.",
    "version": "1.0",
    "contact": {
      "name": "Solnk",
      "url": "https://solnk.com",
      "email": "support@solnk.com"
    }
  },
  "tags": [
    {
      "name": "Accounts",
      "description": "Manage connected social media accounts"
    },
    {
      "name": "Publishes",
      "description": "Create and track multi-platform publish requests"
    },
    {
      "name": "Media",
      "description": "Upload and manage media files for publishing"
    },
    {
      "name": "Analytics",
      "description": "Query post performance metrics across platforms"
    },
    {
      "name": "Webhooks",
      "description": "Register webhook endpoints for event notifications"
    },
    {
      "name": "Usage",
      "description": "Check subscription limits and current usage"
    }
  ],
  "servers": [
    {
      "url": "https://api.solnk.com",
      "description": "Production"
    }
  ],
  "components": {
    "securitySchemes": {
      "api-key": {
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "type": "http",
        "description": "API Key (sk_...)"
      }
    },
    "schemas": {
      "HealthResponseDto": {
        "type": "object",
        "properties": {}
      },
      "AccountCapabilities": {
        "type": "object",
        "properties": {
          "publish_text": {
            "type": "boolean",
            "example": true
          },
          "publish_media": {
            "type": "boolean",
            "example": true
          },
          "publish_video": {
            "type": "boolean",
            "example": true
          },
          "max_media_count": {
            "type": "number",
            "example": 4
          },
          "max_image_count": {
            "type": "number",
            "example": 4,
            "description": "Maximum images accepted in one target."
          },
          "max_video_count": {
            "type": "number",
            "example": 1,
            "description": "Maximum videos accepted in one target."
          },
          "min_image_count": {
            "type": "number",
            "example": 0,
            "description": "Minimum images required when publishing an image post."
          },
          "min_video_count": {
            "type": "number",
            "example": 0,
            "description": "Minimum videos required when publishing a video post."
          },
          "max_image_size_bytes": {
            "type": "object",
            "example": 5242880,
            "nullable": true,
            "description": "Maximum image size in bytes; null means no public limit is declared."
          },
          "max_video_size_bytes": {
            "type": "object",
            "example": 536870912,
            "nullable": true,
            "description": "Maximum video size in bytes; null means no public limit is declared."
          },
          "requires_media": {
            "type": "boolean",
            "example": false
          },
          "requires_title": {
            "type": "boolean",
            "example": false
          },
          "max_text_length": {
            "type": "object",
            "example": 280,
            "nullable": true
          },
          "max_title_length": {
            "type": "object",
            "example": 100,
            "nullable": true
          },
          "supported_image_types": {
            "example": [
              "image/jpeg",
              "image/png"
            ],
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "supported_video_types": {
            "example": [
              "video/mp4"
            ],
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "supports_first_comment": {
            "type": "boolean",
            "example": true
          }
        },
        "required": [
          "publish_text",
          "publish_media",
          "publish_video",
          "max_media_count",
          "max_image_count",
          "max_video_count",
          "min_image_count",
          "min_video_count",
          "requires_media",
          "requires_title",
          "supported_image_types",
          "supported_video_types",
          "supports_first_comment"
        ]
      },
      "AccountItemDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "019cdbe3-473f-733b-b33e-c44e6497f368"
          },
          "platform": {
            "type": "string",
            "example": "x",
            "enum": [
              "x",
              "instagram",
              "tiktok",
              "youtube",
              "facebook",
              "linkedin",
              "pinterest",
              "threads",
              "bluesky"
            ]
          },
          "platform_user_id": {
            "type": "string",
            "example": "2244994945"
          },
          "username": {
            "type": "string",
            "example": "solnkai"
          },
          "display_name": {
            "type": "string",
            "example": "Solnk"
          },
          "avatar_url": {
            "type": "string",
            "example": "https://cdn.solnk.com/avatar/x/123.webp"
          },
          "status": {
            "type": "string",
            "example": "active",
            "enum": [
              "active",
              "expired",
              "disabled"
            ],
            "description": "`disabled` means Solnk intentionally paused the account (for example because of billing or availability); `expired` means authorization is no longer valid."
          },
          "capabilities": {
            "$ref": "#/components/schemas/AccountCapabilities"
          },
          "last_validated_at": {
            "type": "object",
            "example": "2026-03-28T10:12:11Z"
          },
          "created_at": {
            "type": "string",
            "example": "2026-03-01T09:00:00Z"
          }
        },
        "required": [
          "id",
          "platform",
          "platform_user_id",
          "username",
          "display_name",
          "avatar_url",
          "status",
          "capabilities",
          "created_at"
        ]
      },
      "PaginationDto": {
        "type": "object",
        "properties": {
          "page": {
            "type": "number",
            "example": 1
          },
          "page_size": {
            "type": "number",
            "example": 50
          },
          "total": {
            "type": "number",
            "example": 2
          },
          "has_more": {
            "type": "boolean",
            "example": false
          }
        },
        "required": [
          "page",
          "page_size",
          "total",
          "has_more"
        ]
      },
      "AccountListResponseDto": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AccountItemDto"
            }
          },
          "pagination": {
            "$ref": "#/components/schemas/PaginationDto"
          }
        },
        "required": [
          "data",
          "pagination"
        ]
      },
      "ErrorBody": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "example": "invalid_request",
            "enum": [
              "invalid_request",
              "invalid_state",
              "authentication_error",
              "authorization_error",
              "not_found",
              "conflict",
              "rate_limited",
              "quota_exceeded",
              "platform_error",
              "service_unavailable",
              "internal_error"
            ]
          },
          "code": {
            "type": "string",
            "example": "ACCOUNT_NOT_FOUND"
          },
          "message": {
            "type": "string",
            "example": "Account acc_xxx not found"
          },
          "details": {
            "description": "Validation errors use an array of field/reason objects. Recovery errors may use an object with fields such as publish_id and retryable.",
            "oneOf": [
              {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "field": {
                      "type": "string"
                    },
                    "reason": {
                      "type": "string"
                    }
                  }
                }
              },
              {
                "type": "object",
                "additionalProperties": true
              }
            ]
          },
          "request_id": {
            "type": "string",
            "example": "019d4200-1234-7000-8000-000000000001"
          }
        },
        "required": [
          "type",
          "code",
          "message",
          "request_id"
        ]
      },
      "ErrorResponseDto": {
        "type": "object",
        "properties": {
          "error": {
            "$ref": "#/components/schemas/ErrorBody"
          }
        },
        "required": [
          "error"
        ]
      },
      "AccountSingleResponseDto": {
        "type": "object",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/AccountItemDto"
          }
        },
        "required": [
          "data"
        ]
      },
      "BlueskyStrongRefDto": {
        "type": "object",
        "properties": {
          "uri": {
            "type": "string",
            "example": "at://did:plc:example/app.bsky.feed.post/3kxyz",
            "description": "AT Protocol URI of the referenced post."
          },
          "cid": {
            "type": "string",
            "example": "bafyreicidexample",
            "description": "CID of the referenced post."
          }
        },
        "required": [
          "uri",
          "cid"
        ]
      },
      "BlueskyReplyDto": {
        "type": "object",
        "properties": {
          "root": {
            "$ref": "#/components/schemas/BlueskyStrongRefDto"
          },
          "parent": {
            "$ref": "#/components/schemas/BlueskyStrongRefDto"
          }
        },
        "required": [
          "root",
          "parent"
        ]
      },
      "BlueskyExternalEmbedDto": {
        "type": "object",
        "properties": {
          "uri": {
            "type": "string",
            "example": "https://solnk.com",
            "description": "Destination URL for the external card."
          },
          "title": {
            "type": "string",
            "example": "Solnk"
          },
          "description": {
            "type": "string",
            "example": "Plan and publish across social platforms."
          }
        },
        "required": [
          "uri",
          "title",
          "description"
        ]
      },
      "BlueskyEmbedDto": {
        "type": "object",
        "properties": {
          "external": {
            "$ref": "#/components/schemas/BlueskyExternalEmbedDto"
          }
        },
        "required": [
          "external"
        ]
      },
      "PlatformSettingsDto": {
        "type": "object",
        "properties": {
          "reply_to": {
            "type": "string",
            "example": "1900000000000000001",
            "description": "X only. Tweet ID to reply to."
          },
          "quote_tweet": {
            "type": "string",
            "example": "1900000000000000002",
            "description": "X only. Tweet ID to quote."
          },
          "sensitive": {
            "type": "boolean",
            "example": false,
            "description": "X only. Mark attached media as possibly sensitive."
          },
          "for_super_followers_only": {
            "type": "boolean",
            "example": false,
            "description": "X only. Restrict the post to Super Followers."
          },
          "first_comment": {
            "type": "string",
            "example": "What would you add?",
            "description": "X, Facebook, LinkedIn, YouTube, or Bluesky only. Publishes a first comment/self-reply after the main post. Limits: X 280, Facebook 63,206, LinkedIn 3,000, YouTube 5,000, Bluesky 300 characters. Not supported on Instagram, TikTok, Pinterest, or Threads."
          },
          "title": {
            "type": "string",
            "example": "Launch demo",
            "description": "YouTube video title, Pinterest Pin title, LinkedIn media-attachment title, or TikTok post title (TikTok truncates to 90 characters; video MEDIA_UPLOAD cannot prefill it)."
          },
          "visibility": {
            "type": "string",
            "enum": [
              "PUBLIC",
              "CONNECTIONS",
              "LOGGED_IN_MEMBERS"
            ],
            "example": "PUBLIC",
            "description": "LinkedIn only. Post visibility."
          },
          "target_audience": {
            "type": "object",
            "additionalProperties": true,
            "example": {
              "targetedEntities": [
                {
                  "locations": [
                    "urn:li:geo:103644278"
                  ]
                }
              ]
            },
            "description": "LinkedIn only. Target-audience object accepted by LinkedIn."
          },
          "postType": {
            "type": "string",
            "enum": [
              "post",
              "reels"
            ],
            "example": "reels",
            "description": "Facebook only. Publish as a standard post or Reel."
          },
          "link_attachment": {
            "type": "string",
            "example": "https://solnk.com",
            "description": "Threads only. URL attachment for a text-only post."
          },
          "topic_tag": {
            "type": "string",
            "example": "SocialMedia",
            "description": "Threads text-only posts. Topic tag without a leading #."
          },
          "reply": {
            "description": "Bluesky only. Strong references for a reply.",
            "allOf": [
              {
                "$ref": "#/components/schemas/BlueskyReplyDto"
              }
            ]
          },
          "embed": {
            "description": "Bluesky only. External-link card embed.",
            "allOf": [
              {
                "$ref": "#/components/schemas/BlueskyEmbedDto"
              }
            ]
          },
          "boardId": {
            "type": "string",
            "example": "987654321",
            "description": "Pinterest only. Board ID used for this target."
          },
          "link": {
            "type": "string",
            "example": "https://solnk.com",
            "description": "Pinterest only. Destination URL for the Pin."
          },
          "note": {
            "type": "string",
            "example": "Plan and publish across every channel.",
            "description": "Pinterest only. Pin description."
          },
          "alt_text": {
            "type": "string",
            "example": "Solnk publishing calendar",
            "description": "Pinterest only. Accessible media alt text."
          },
          "cover_image_key_frame_time": {
            "type": "number",
            "example": 1,
            "description": "Pinterest video only. Cover-image key-frame time in seconds; defaults to 1.0."
          },
          "per_account": {
            "type": "object",
            "additionalProperties": {
              "type": "object",
              "properties": {
                "boardId": {
                  "type": "string",
                  "example": "987654321"
                }
              },
              "required": [
                "boardId"
              ]
            },
            "example": {
              "019dadeb-ddd5-73d5-8f7a-1238c19e67bf": {
                "boardId": "987654321"
              }
            },
            "description": "Pinterest only. Board override keyed by Solnk account_id. Usually unnecessary because platform_settings already belongs to one target."
          },
          "privacyStatus": {
            "type": "string",
            "enum": [
              "private",
              "public",
              "unlisted"
            ],
            "example": "unlisted",
            "description": "YouTube only. Video privacy; defaults to public."
          },
          "category_id": {
            "type": "string",
            "example": "28",
            "description": "YouTube only. YouTube video category ID."
          },
          "default_language": {
            "type": "string",
            "example": "en",
            "description": "YouTube only. Default metadata language."
          },
          "default_audio_language": {
            "type": "string",
            "example": "en",
            "description": "YouTube only. Default audio language."
          },
          "tags": {
            "example": [
              "solnk",
              "social media"
            ],
            "description": "YouTube only. Video tags.",
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "madeForKids": {
            "type": "boolean",
            "example": false,
            "description": "YouTube only. Made-for-kids declaration."
          },
          "tiktok_post_mode": {
            "type": "string",
            "enum": [
              "DIRECT_POST",
              "MEDIA_UPLOAD"
            ],
            "example": "DIRECT_POST",
            "description": "TikTok only. Publish directly or send the media to the creator inbox."
          },
          "privacy_level": {
            "type": "string",
            "enum": [
              "PUBLIC_TO_EVERYONE",
              "MUTUAL_FOLLOW_FRIENDS",
              "FOLLOWER_OF_CREATOR",
              "SELF_ONLY"
            ],
            "example": "SELF_ONLY",
            "description": "TikTok DIRECT_POST only. Requested privacy level; MEDIA_UPLOAD removes this field before dispatch."
          },
          "allows_comments": {
            "type": "boolean",
            "example": true,
            "description": "TikTok post_info option. Allow comments; video MEDIA_UPLOAD bypasses post_info."
          },
          "allows_duet": {
            "type": "boolean",
            "example": true,
            "description": "TikTok post_info option. Allow Duet; video MEDIA_UPLOAD bypasses post_info."
          },
          "allows_stitch": {
            "type": "boolean",
            "example": true,
            "description": "TikTok post_info option. Allow Stitch; video MEDIA_UPLOAD bypasses post_info."
          },
          "brand_content_toggle": {
            "type": "boolean",
            "example": false,
            "description": "TikTok post_info branded-content disclosure; video MEDIA_UPLOAD bypasses post_info."
          },
          "brand_organic_toggle": {
            "type": "boolean",
            "example": false,
            "description": "TikTok post_info brand-organic disclosure; video MEDIA_UPLOAD bypasses post_info."
          },
          "auto_add_music": {
            "type": "boolean",
            "example": false,
            "description": "TikTok post_info automatic-music flag; video MEDIA_UPLOAD bypasses post_info."
          }
        },
        "x-solnk-platform-groups": [
          {
            "id": "x",
            "label": "X",
            "description": "Replies, quotes, audience, and first comment.",
            "fields": [
              "reply_to",
              "quote_tweet",
              "sensitive",
              "for_super_followers_only",
              "first_comment"
            ]
          },
          {
            "id": "instagram",
            "label": "Instagram",
            "description": "No special settings currently supported.",
            "fields": []
          },
          {
            "id": "tiktok",
            "label": "TikTok",
            "description": "Mode, privacy, interactions, and disclosures.",
            "fields": [
              "tiktok_post_mode",
              "title",
              "privacy_level",
              "allows_comments",
              "allows_duet",
              "allows_stitch",
              "brand_content_toggle",
              "brand_organic_toggle",
              "auto_add_music"
            ]
          },
          {
            "id": "youtube",
            "label": "YouTube",
            "description": "Metadata, visibility, audience, and first comment.",
            "fields": [
              "title",
              "privacyStatus",
              "category_id",
              "default_language",
              "default_audio_language",
              "tags",
              "madeForKids",
              "first_comment"
            ]
          },
          {
            "id": "facebook",
            "label": "Facebook",
            "description": "Post format and first comment.",
            "fields": [
              "postType",
              "first_comment"
            ]
          },
          {
            "id": "linkedin",
            "label": "LinkedIn",
            "description": "Media title, visibility, audience, and first comment.",
            "fields": [
              "title",
              "visibility",
              "target_audience",
              "first_comment"
            ]
          },
          {
            "id": "pinterest",
            "label": "Pinterest",
            "description": "Board, metadata, destination, accessibility, and cover.",
            "fields": [
              "boardId",
              "title",
              "link",
              "note",
              "alt_text",
              "cover_image_key_frame_time",
              "per_account"
            ]
          },
          {
            "id": "threads",
            "label": "Threads",
            "description": "Link attachment and topic tag.",
            "fields": [
              "link_attachment",
              "topic_tag"
            ]
          },
          {
            "id": "bluesky",
            "label": "Bluesky",
            "description": "Replies, embeds, and first comment.",
            "fields": [
              "reply",
              "embed",
              "first_comment"
            ]
          }
        ]
      },
      "CreatePublishTargetDto": {
        "type": "object",
        "properties": {
          "account_id": {
            "type": "string",
            "example": "019dadeb-ddd5-73d5-8f7a-1238c19e67bf",
            "description": "Social account ID (UUID v7)"
          },
          "content": {
            "type": "string",
            "example": "Custom content for this platform",
            "description": "Override default content for this target"
          },
          "media_ids": {
            "example": [
              "019d4300-bbbb-7000-8000-000000000001"
            ],
            "description": "Override media IDs for this target (UUID v7). These are uploaded media IDs — NOT URLs. Obtain them by uploading files first via POST /media/uploads + confirm.",
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "platform_settings": {
            "additionalProperties": true,
            "example": {
              "title": "Launch demo",
              "privacyStatus": "unlisted",
              "tags": [
                "solnk",
                "launch"
              ],
              "madeForKids": false
            },
            "description": "Flat settings for this target platform. Do not nest them under a platform name. Open the nested fields below for the complete platform catalog. Unsupported keys or invalid values are rejected before dispatch.",
            "allOf": [
              {
                "$ref": "#/components/schemas/PlatformSettingsDto"
              }
            ]
          }
        },
        "required": [
          "account_id"
        ]
      },
      "CreatePublishDto": {
        "type": "object",
        "properties": {
          "client_request_id": {
            "type": "string",
            "example": "publish-20260331-001",
            "description": "Caller correlation ID echoed back in metadata. Use the Idempotency-Key header, not this field, to prevent duplicates."
          },
          "content": {
            "type": "string",
            "example": "AI is changing SaaS distribution.",
            "description": "Default content for all targets"
          },
          "media_ids": {
            "example": [
              "019d4300-bbbb-7000-8000-000000000001"
            ],
            "description": "Default media IDs for all targets (UUID v7). These are uploaded media IDs — NOT URLs. To attach images/videos, upload the file first via POST /media/uploads, confirm it, then pass the returned media_id here.",
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "targets": {
            "description": "One or more publish targets (accounts)",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CreatePublishTargetDto"
            }
          },
          "publish_mode": {
            "type": "string",
            "enum": [
              "immediate",
              "scheduled",
              "draft"
            ],
            "example": "scheduled",
            "description": "`draft` saves without publishing — call `POST /publishes/:id/confirm` to release."
          },
          "scheduled_at": {
            "type": "string",
            "example": "2030-01-15T10:00:00Z",
            "description": "Required when publish_mode is scheduled"
          },
          "metadata": {
            "type": "object",
            "description": "Arbitrary metadata (e.g. AI conversation context)"
          }
        },
        "required": [
          "targets",
          "publish_mode"
        ]
      },
      "PublishTargetError": {
        "type": "object",
        "properties": {
          "code": {
            "type": "string",
            "example": "PLATFORM_AUTH_EXPIRED"
          },
          "message": {
            "type": "string",
            "example": "The LinkedIn account authorization has expired."
          },
          "retryable": {
            "type": "boolean",
            "example": false
          }
        },
        "required": [
          "code",
          "message",
          "retryable"
        ]
      },
      "PublishTargetDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "pubtgt_49"
          },
          "platform": {
            "type": "string",
            "example": "x",
            "enum": [
              "x",
              "instagram",
              "tiktok",
              "youtube",
              "facebook",
              "linkedin",
              "pinterest",
              "threads",
              "bluesky"
            ]
          },
          "account_id": {
            "type": "string",
            "example": "019cdbe3-473f-733b-b33e-c44e6497f368"
          },
          "status": {
            "type": "string",
            "example": "published",
            "enum": [
              "draft",
              "scheduled",
              "publishing",
              "processing",
              "published",
              "failed"
            ]
          },
          "content": {
            "type": "string",
            "example": "Per-platform copy override",
            "nullable": true,
            "description": "Null means the target uses the publish-level default content."
          },
          "media_ids": {
            "description": "Media IDs attached to this target",
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "platform_settings": {
            "additionalProperties": true,
            "example": {
              "privacyStatus": "unlisted"
            },
            "nullable": true,
            "description": "Platform-specific publish settings for this target.",
            "allOf": [
              {
                "$ref": "#/components/schemas/PlatformSettingsDto"
              }
            ]
          },
          "published_at": {
            "type": "string",
            "example": "2026-03-30T08:15:44Z",
            "nullable": true
          },
          "platform_post_id": {
            "type": "string",
            "example": "1906234567890123456",
            "nullable": true
          },
          "platform_post_url": {
            "type": "string",
            "example": "https://x.com/solnkai/status/1906234567890123456",
            "nullable": true
          },
          "error": {
            "nullable": true,
            "allOf": [
              {
                "$ref": "#/components/schemas/PublishTargetError"
              }
            ]
          }
        },
        "required": [
          "id",
          "platform",
          "account_id",
          "status",
          "media_ids"
        ]
      },
      "PublishItemDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "019cf5a4-b50d-74ed-8d93-fac527aa1c37"
          },
          "status": {
            "type": "string",
            "example": "published",
            "enum": [
              "draft",
              "scheduled",
              "publishing",
              "processing",
              "published",
              "failed",
              "partial"
            ],
            "description": "Canonical publish lifecycle status"
          },
          "approval_status": {
            "type": "string",
            "example": "pending_approval",
            "enum": [
              "pending_approval",
              "approved",
              "rejected"
            ],
            "nullable": true,
            "description": "Team approval state; null when approval does not apply."
          },
          "rejection_reason": {
            "type": "string",
            "example": "Please revise the opening line.",
            "nullable": true
          },
          "approved_at": {
            "type": "string",
            "example": "2026-03-30T08:14:00Z",
            "nullable": true
          },
          "content": {
            "type": "string",
            "example": "AI is changing SaaS distribution.",
            "nullable": true
          },
          "publish_mode": {
            "type": "string",
            "example": "immediate",
            "enum": [
              "immediate",
              "scheduled",
              "draft"
            ]
          },
          "scheduled_at": {
            "type": "string",
            "example": "2026-04-01T10:00:00Z",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "example": "2026-03-30T08:15:23Z"
          },
          "completed_at": {
            "type": "string",
            "example": "2026-03-30T08:16:01Z",
            "nullable": true
          },
          "targets": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PublishTargetDto"
            }
          },
          "metadata": {
            "type": "object",
            "example": {
              "source": "chatgpt",
              "conversation_id": "conv_abc123"
            },
            "nullable": true
          },
          "already_approved": {
            "type": "boolean",
            "example": true,
            "description": "Present on the approve action. True when the publish had already been approved before this request."
          }
        },
        "required": [
          "id",
          "status",
          "publish_mode",
          "created_at",
          "targets"
        ]
      },
      "PublishSingleResponseDto": {
        "type": "object",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/PublishItemDto"
          }
        },
        "required": [
          "data"
        ]
      },
      "PublishListResponseDto": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PublishItemDto"
            }
          },
          "pagination": {
            "$ref": "#/components/schemas/PaginationDto"
          }
        },
        "required": [
          "data",
          "pagination"
        ]
      },
      "UpdatePublishTargetDto": {
        "type": "object",
        "properties": {
          "account_id": {
            "type": "string",
            "example": "019dadeb-ddd5-73d5-8f7a-1238c19e67bf",
            "description": "Social account ID (UUID v7)"
          },
          "content": {
            "type": "string",
            "example": "Updated copy for this platform",
            "nullable": true,
            "description": "Per-target copy override. Omit to preserve the existing override; use null to clear it and fall back to the top-level content."
          },
          "media_ids": {
            "example": [
              "019d4300-bbbb-7000-8000-000000000001"
            ],
            "description": "Replacement media IDs for this target (UUID v7). Omit to preserve existing media unless top-level media_ids is supplied; use [] to clear.",
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "platform_settings": {
            "additionalProperties": true,
            "nullable": true,
            "description": "Platform-specific settings validated against the selected account platform. Omit to preserve existing settings; use null to clear them.",
            "allOf": [
              {
                "$ref": "#/components/schemas/PlatformSettingsDto"
              }
            ]
          }
        },
        "required": [
          "account_id"
        ]
      },
      "UpdatePublishDto": {
        "type": "object",
        "properties": {
          "content": {
            "type": "string",
            "example": "Updated default copy",
            "nullable": true,
            "description": "Updated default content. Use null or an empty string to clear it."
          },
          "media_ids": {
            "example": [
              "019d4300-bbbb-7000-8000-000000000001"
            ],
            "description": "Replace media on every existing target unless that target supplies its own media_ids. Use [] to clear.",
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "targets": {
            "description": "The complete desired target set. Omit to preserve all existing targets. Retained targets preserve omitted fields.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/UpdatePublishTargetDto"
            }
          },
          "metadata": {
            "type": "object",
            "example": {
              "source": "chatgpt",
              "conversation_id": "conv_abc123"
            },
            "nullable": true,
            "description": "Replacement API metadata. Omit to preserve it; use null to clear it."
          }
        }
      },
      "RetryTargetEditDto": {
        "type": "object",
        "properties": {
          "account_id": {
            "type": "string",
            "example": "019dadeb-ddd5-73d5-8f7a-1238c19e67bf",
            "description": "The failed account to edit (UUID v7). Must be a target that failed on this publish."
          },
          "content": {
            "type": "string",
            "example": "Reworded content that passes platform rules.",
            "description": "New content for this failed target"
          },
          "media_ids": {
            "example": [
              "019d4300-bbbb-7000-8000-000000000001"
            ],
            "description": "Replacement media IDs for this failed target (UUID v7, uploaded media — not URLs)",
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "platform_settings": {
            "additionalProperties": true,
            "description": "Flat platform-specific settings override. Unsupported keys or invalid values are rejected before retry.",
            "allOf": [
              {
                "$ref": "#/components/schemas/PlatformSettingsDto"
              }
            ]
          }
        },
        "required": [
          "account_id"
        ]
      },
      "RetryPublishDto": {
        "type": "object",
        "properties": {
          "targets": {
            "description": "Optional edits applied to the failed targets before resending. Omit to resend the failed targets unchanged (use this only for transient failures like an expired token). Each entry must reference a target that actually failed — successful targets are never re-sent or editable.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RetryTargetEditDto"
            }
          }
        }
      },
      "ConfirmPublishDto": {
        "type": "object",
        "properties": {
          "scheduled_at": {
            "type": "string",
            "example": "2030-01-15T10:00:00Z",
            "description": "Override scheduled time. If omitted, publishes immediately (or uses the original scheduled_at if set on the draft)."
          }
        }
      },
      "ReschedulePublishDto": {
        "type": "object",
        "properties": {
          "scheduled_at": {
            "type": "string",
            "example": "2030-01-15T14:00:00Z",
            "description": "New future publish time in ISO 8601 format."
          }
        },
        "required": [
          "scheduled_at"
        ]
      },
      "RejectPublishDto": {
        "type": "object",
        "properties": {
          "reason": {
            "type": "string",
            "example": "Off-brand wording — please revise the CTA.",
            "description": "Optional reason shown to the submitter and delivered in the post.rejected webhook.",
            "maxLength": 1000
          }
        }
      },
      "RejectedPublishDataDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "019cf5a4-b50d-74ed-8d93-fac527aa1c37"
          },
          "approval_status": {
            "type": "string",
            "example": "rejected",
            "enum": [
              "rejected"
            ]
          },
          "rejection_reason": {
            "type": "object",
            "example": "Off-brand wording",
            "nullable": true
          }
        },
        "required": [
          "id",
          "approval_status"
        ]
      },
      "RejectPublishResponseDto": {
        "type": "object",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/RejectedPublishDataDto"
          }
        },
        "required": [
          "data"
        ]
      },
      "DiscardedPublishDataDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "019cf5a4-b50d-74ed-8d93-fac527aa1c37"
          },
          "discarded": {
            "type": "boolean",
            "example": true
          }
        },
        "required": [
          "id",
          "discarded"
        ]
      },
      "DiscardPublishResponseDto": {
        "type": "object",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/DiscardedPublishDataDto"
          }
        },
        "required": [
          "data"
        ]
      },
      "LimitsDto": {
        "type": "object",
        "properties": {
          "posts_per_month": {
            "type": "object",
            "example": 100,
            "nullable": true,
            "description": "null = unlimited"
          },
          "social_accounts": {
            "type": "object",
            "example": 10,
            "nullable": true,
            "description": "null = unlimited"
          },
          "media_storage_gb": {
            "type": "object",
            "example": 5,
            "nullable": true,
            "description": "null = unlimited (in GB)"
          }
        }
      },
      "CurrentUsageDto": {
        "type": "object",
        "properties": {
          "posts_used_this_month": {
            "type": "number",
            "example": 12,
            "deprecated": true,
            "description": "Compatibility alias for posts_used_this_billing_period. Despite the historical name, it follows the active subscription period."
          },
          "posts_used_this_billing_period": {
            "type": "number",
            "example": 12
          },
          "social_accounts_used": {
            "type": "number",
            "example": 3
          },
          "media_storage_used_gb": {
            "type": "number",
            "example": 0.45
          }
        },
        "required": [
          "posts_used_this_month",
          "posts_used_this_billing_period",
          "social_accounts_used",
          "media_storage_used_gb"
        ]
      },
      "UsageDataDto": {
        "type": "object",
        "properties": {
          "plan": {
            "type": "object",
            "example": "pro_monthly",
            "nullable": true
          },
          "billing_period_start": {
            "type": "object",
            "example": "2026-03-12T10:00:00Z",
            "nullable": true
          },
          "billing_period_end": {
            "type": "object",
            "example": "2026-04-12T10:00:00Z",
            "nullable": true
          },
          "limits": {
            "$ref": "#/components/schemas/LimitsDto"
          },
          "usage": {
            "$ref": "#/components/schemas/CurrentUsageDto"
          },
          "can_publish": {
            "type": "boolean",
            "example": true,
            "description": "Whether the user can create more publishes"
          }
        },
        "required": [
          "limits",
          "usage",
          "can_publish"
        ]
      },
      "UsageResponseDto": {
        "type": "object",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/UsageDataDto"
          }
        },
        "required": [
          "data"
        ]
      },
      "CreateUploadDto": {
        "type": "object",
        "properties": {
          "filename": {
            "type": "string",
            "example": "cover.png",
            "description": "Original filename"
          },
          "content_type": {
            "type": "string",
            "example": "image/png",
            "enum": [
              "image/png",
              "image/jpeg",
              "image/webp",
              "image/gif",
              "video/mp4",
              "video/quicktime"
            ],
            "description": "MIME type"
          },
          "size_bytes": {
            "type": "number",
            "example": 2481921,
            "description": "File size in bytes (max 500MB). Required for presigned uploads; omit when using source_url (the server determines the size)."
          },
          "source_url": {
            "type": "string",
            "example": "https://example.com/image.png",
            "description": "Public URL of an image/video to ingest. When provided, the server fetches and stores it server-side — no presigned PUT needed. Still call confirm afterwards."
          }
        },
        "required": [
          "filename",
          "content_type"
        ]
      },
      "UploadInfo": {
        "type": "object",
        "properties": {
          "upload_id": {
            "type": "string",
            "example": "019d4300-aaaa-7000-8000-000000000001"
          },
          "upload_url": {
            "type": "string",
            "example": "https://r2.example.com/upload/..."
          },
          "method": {
            "type": "string",
            "example": "PUT"
          },
          "headers": {
            "type": "object",
            "example": {
              "Content-Type": "image/png"
            }
          },
          "expires_at": {
            "type": "string",
            "example": "2026-03-31T12:00:00Z"
          }
        },
        "required": [
          "upload_id",
          "upload_url",
          "method",
          "headers",
          "expires_at"
        ]
      },
      "MediaItemData": {
        "type": "object",
        "properties": {
          "media_id": {
            "type": "string",
            "example": "019d4300-bbbb-7000-8000-000000000001"
          },
          "upload": {
            "description": "Presigned PUT instructions. Present for direct uploads; omitted when source_url was ingested server-side.",
            "allOf": [
              {
                "$ref": "#/components/schemas/UploadInfo"
              }
            ]
          },
          "status": {
            "type": "string",
            "example": "pending_upload",
            "enum": [
              "pending_upload",
              "ready"
            ]
          }
        },
        "required": [
          "media_id",
          "status"
        ]
      },
      "CreateUploadResponseDto": {
        "type": "object",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/MediaItemData"
          }
        },
        "required": [
          "data"
        ]
      },
      "ConfirmedMediaData": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "019d4300-bbbb-7000-8000-000000000001"
          },
          "type": {
            "type": "string",
            "example": "image"
          },
          "status": {
            "type": "string",
            "example": "ready"
          },
          "url": {
            "type": "string",
            "example": "https://r2.solnk.com/upload/user/123.png"
          },
          "size_bytes": {
            "type": "number",
            "example": 2481921
          },
          "created_at": {
            "type": "string",
            "example": "2026-03-31T10:00:00Z"
          }
        },
        "required": [
          "id",
          "type",
          "status",
          "url",
          "size_bytes",
          "created_at"
        ]
      },
      "ConfirmUploadResponseDto": {
        "type": "object",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/ConfirmedMediaData"
          }
        },
        "required": [
          "data"
        ]
      },
      "MediaListItem": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "019d4300-bbbb-7000-8000-000000000001"
          },
          "type": {
            "type": "string",
            "example": "image"
          },
          "mime_type": {
            "type": "string",
            "example": "image/png"
          },
          "url": {
            "type": "string",
            "example": "https://r2.solnk.com/upload/user/123.png"
          },
          "size_bytes": {
            "type": "number",
            "example": 2481921
          },
          "ready": {
            "type": "boolean",
            "example": true
          },
          "created_at": {
            "type": "string",
            "example": "2026-03-31T10:00:00Z"
          }
        },
        "required": [
          "id",
          "type",
          "mime_type",
          "url",
          "size_bytes",
          "ready",
          "created_at"
        ]
      },
      "MediaListResponseDto": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MediaListItem"
            }
          },
          "pagination": {
            "$ref": "#/components/schemas/PaginationDto"
          }
        },
        "required": [
          "data",
          "pagination"
        ]
      },
      "WebhookItemDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "019d4400-aaaa-7000-8000-000000000001"
          },
          "url": {
            "type": "string",
            "example": "https://agent.example.com/webhooks/solnk"
          },
          "events": {
            "example": [
              "post.published",
              "post.failed"
            ],
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "enabled": {
            "type": "boolean",
            "example": true
          },
          "consecutive_failures": {
            "type": "number",
            "example": 0
          },
          "disabled_at": {
            "type": "object",
            "example": null,
            "nullable": true
          },
          "disabled_reason": {
            "type": "object",
            "example": null,
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "example": "2026-03-31T10:00:00Z"
          },
          "last_triggered_at": {
            "type": "object",
            "example": "2026-03-31T12:30:00Z"
          }
        },
        "required": [
          "id",
          "url",
          "events",
          "enabled",
          "consecutive_failures",
          "created_at"
        ]
      },
      "WebhookListResponseDto": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WebhookItemDto"
            }
          }
        },
        "required": [
          "data"
        ]
      },
      "CreateWebhookDto": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "example": "https://agent.example.com/webhooks/solnk",
            "description": "URL to receive webhook events (must be HTTPS, no localhost/internal IPs)"
          },
          "events": {
            "type": "array",
            "example": [
              "post.published",
              "post.failed"
            ],
            "description": "Events to subscribe to",
            "items": {
              "type": "string",
              "enum": [
                "post.published",
                "post.failed",
                "post.pending_approval",
                "post.approved",
                "post.rejected"
              ]
            }
          },
          "secret": {
            "type": "string",
            "example": "whsec_abc123",
            "description": "Secret for HMAC-SHA256 signature verification. Auto-generated if omitted."
          }
        },
        "required": [
          "url",
          "events"
        ]
      },
      "CreatedWebhookItemDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "019d4400-aaaa-7000-8000-000000000001"
          },
          "url": {
            "type": "string",
            "example": "https://agent.example.com/webhooks/solnk"
          },
          "events": {
            "example": [
              "post.published",
              "post.failed"
            ],
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "secret": {
            "type": "string",
            "example": "whsec_abc123",
            "description": "HMAC secret. Returned only once when the subscription is created."
          },
          "enabled": {
            "type": "boolean",
            "example": true
          },
          "created_at": {
            "type": "string",
            "example": "2026-03-31T10:00:00Z"
          },
          "last_triggered_at": {
            "type": "object",
            "example": null,
            "nullable": true
          }
        },
        "required": [
          "id",
          "url",
          "events",
          "secret",
          "enabled",
          "created_at"
        ]
      },
      "WebhookCreateResponseDto": {
        "type": "object",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/CreatedWebhookItemDto"
          }
        },
        "required": [
          "data"
        ]
      },
      "DeletedWebhookDataDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "019d4400-aaaa-7000-8000-000000000001"
          },
          "deleted": {
            "type": "boolean",
            "example": true
          }
        },
        "required": [
          "id",
          "deleted"
        ]
      },
      "WebhookDeleteResponseDto": {
        "type": "object",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/DeletedWebhookDataDto"
          }
        },
        "required": [
          "data"
        ]
      },
      "AnalyticsMetrics": {
        "type": "object",
        "properties": {
          "total_views": {
            "type": "number",
            "example": 14200
          },
          "total_likes": {
            "type": "number",
            "example": 540
          },
          "total_comments": {
            "type": "number",
            "example": 32
          },
          "total_shares": {
            "type": "number",
            "example": 18
          },
          "last_sync_at": {
            "type": "object",
            "example": "2026-03-30T09:15:00Z",
            "nullable": true
          }
        },
        "required": [
          "total_views",
          "total_likes",
          "total_comments",
          "total_shares"
        ]
      },
      "AnalyticsPostItem": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "019cf5a4-b50d-74ed-8d93-fac527aa1c37"
          },
          "content": {
            "type": "object",
            "example": "AI is changing SaaS distribution.",
            "nullable": true
          },
          "published_at": {
            "type": "object",
            "example": "2026-03-30T08:16:01Z",
            "nullable": true
          },
          "platform_count": {
            "type": "number",
            "example": 3
          },
          "metrics": {
            "$ref": "#/components/schemas/AnalyticsMetrics"
          }
        },
        "required": [
          "id",
          "platform_count",
          "metrics"
        ]
      },
      "AnalyticsListResponseDto": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AnalyticsPostItem"
            }
          },
          "pagination": {
            "$ref": "#/components/schemas/PaginationDto"
          }
        },
        "required": [
          "data",
          "pagination"
        ]
      },
      "AnalyticsPlatformBreakdown": {
        "type": "object",
        "properties": {
          "platform": {
            "type": "string",
            "example": "x"
          },
          "account_id": {
            "type": "string",
            "example": "019cdbe3-473f-733b-b33e-c44e6497f368"
          },
          "platform_post_id": {
            "type": "object",
            "example": "1906234567890123456",
            "nullable": true
          },
          "platform_post_url": {
            "type": "object",
            "example": "https://x.com/solnkai/status/1906234567890123456",
            "nullable": true
          },
          "published_at": {
            "type": "object",
            "example": "2026-03-30T08:15:44Z",
            "nullable": true
          },
          "metrics": {
            "$ref": "#/components/schemas/AnalyticsMetrics"
          },
          "platform_specific_data": {
            "type": "object",
            "description": "Platform-specific fields (e.g. impressions, reach, saves)",
            "nullable": true
          }
        },
        "required": [
          "platform",
          "account_id",
          "metrics"
        ]
      },
      "AnalyticsPostDetail": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "019cf5a4-b50d-74ed-8d93-fac527aa1c37"
          },
          "content": {
            "type": "object",
            "example": "AI is changing SaaS distribution.",
            "nullable": true
          },
          "published_at": {
            "type": "object",
            "example": "2026-03-30T08:16:01Z",
            "nullable": true
          },
          "metrics": {
            "$ref": "#/components/schemas/AnalyticsMetrics"
          },
          "platforms": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AnalyticsPlatformBreakdown"
            }
          }
        },
        "required": [
          "id",
          "metrics",
          "platforms"
        ]
      },
      "AnalyticsDetailResponseDto": {
        "type": "object",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/AnalyticsPostDetail"
          }
        },
        "required": [
          "data"
        ]
      },
      "TeamItemDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "019d1111-1111-7111-8111-111111111111"
          },
          "name": {
            "type": "string",
            "example": "Default workspace"
          },
          "is_default": {
            "type": "boolean"
          },
          "is_key_team": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string",
            "example": "2026-08-17T12:00:00.000Z"
          }
        },
        "required": [
          "id",
          "name",
          "is_default",
          "is_key_team",
          "created_at"
        ]
      },
      "TeamListResponseDto": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TeamItemDto"
            }
          }
        },
        "required": [
          "data"
        ]
      },
      "SetDefaultTeamResultDto": {
        "type": "object",
        "properties": {
          "default_team_id": {
            "type": "string"
          },
          "changed": {
            "type": "boolean"
          }
        },
        "required": [
          "default_team_id",
          "changed"
        ]
      },
      "SetDefaultTeamResponseDto": {
        "type": "object",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/SetDefaultTeamResultDto"
          }
        },
        "required": [
          "data"
        ]
      },
      "DeleteTeamDto": {
        "type": "object",
        "properties": {
          "destination_team_id": {
            "type": "string",
            "description": "Another team owned by the same user. Durable content moves here.",
            "example": "019d2222-2222-7222-8222-222222222222"
          },
          "confirmation_name": {
            "type": "string",
            "description": "Exact current team name. Required as destructive-action confirmation.",
            "example": "Client campaign"
          }
        },
        "required": [
          "destination_team_id",
          "confirmation_name"
        ]
      },
      "MigratedTeamResourcesDto": {
        "type": "object",
        "properties": {
          "accounts": {
            "type": "number"
          },
          "groups": {
            "type": "number"
          },
          "posts": {
            "type": "number"
          },
          "media": {
            "type": "number"
          },
          "folders": {
            "type": "number"
          }
        },
        "required": [
          "accounts",
          "groups",
          "posts",
          "media",
          "folders"
        ]
      },
      "RemovedTeamResourcesDto": {
        "type": "object",
        "properties": {
          "members": {
            "type": "number"
          },
          "api_keys": {
            "type": "number"
          },
          "webhooks": {
            "type": "number"
          },
          "invitations": {
            "type": "number"
          }
        },
        "required": [
          "members",
          "api_keys",
          "webhooks",
          "invitations"
        ]
      },
      "DeleteTeamResultDto": {
        "type": "object",
        "properties": {
          "deleted_team_id": {
            "type": "string"
          },
          "destination_team_id": {
            "type": "string"
          },
          "deleted": {
            "type": "boolean"
          },
          "migrated": {
            "$ref": "#/components/schemas/MigratedTeamResourcesDto"
          },
          "removed": {
            "$ref": "#/components/schemas/RemovedTeamResourcesDto"
          }
        },
        "required": [
          "deleted_team_id",
          "destination_team_id",
          "deleted",
          "migrated",
          "removed"
        ]
      },
      "DeleteTeamResponseDto": {
        "type": "object",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/DeleteTeamResultDto"
          }
        },
        "required": [
          "data"
        ]
      }
    }
  }
}
