{
  "openapi": "3.0.3",
  "info": {
    "title": "Mallary API",
    "version": "1.6.0",
    "description": "Public API for multi-platform publishing, analytics, media upload, and webhooks.\n\nAuthentication:\n- Use header `Authorization: Bearer {api_key}`.\n- API key identity is authoritative. Do not use `user_id` to identify a caller.\n\nConnection profiles:\n- Omit `profile_id` to use the authenticated user's default profile.\n- Send the profile's random public `profile_id` when you want to publish, list posts, read post analytics or audience counts, list platforms, update settings, or disconnect a platform for a non-default profile.\n- Use `GET /api/v1/profiles` to list profile IDs.\n- Total social media accounts: Starter 5 ($15/month), Pro 15 ($29/month), Business 100 ($95/month). Posts are unlimited. Scheduling, analytics, and AI auto-replies are included. X publishing and comments use credits at cost; analytics and account lookups use no credits. Existing customers keep their current terms; use the authenticated workspace limits.\n\nRate limits (per user, per minute) depend on subscription plan:\n- plan_id 1: 75 req/min\n- plan_id 2: 150 req/min\n- plan_id 3: 750 req/min\n- plan_id 4: 1500 req/min\n\nSaved drafts:\n- Use /api/v1/drafts to save unfinished content without a date or posting job.\n- Read the current revision before editing, deleting or submitting.\n- Only /api/v1/drafts/{id}/submit publishes or schedules a saved draft.\n"
  },
  "servers": [
    {
      "url": "https://mallary.ai",
      "description": "Production"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "paths": {
    "/api/v1/upload": {
      "post": {
        "operationId": "createUploadUrl",
        "summary": "Create presigned upload URL",
        "description": "Returns a temporary PUT URL and a public media URL for use in `POST /api/v1/post`.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateUploadRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Upload metadata created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateUploadResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request (missing filename, invalid size when provided, unsupported MIME type, or size exceeded)"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "403": {
            "description": "Supplied `user_id` (if any) does not match API key owner"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        }
      }
    },
    "/api/v1/post": {
      "post": {
        "operationId": "createPost",
        "summary": "Create or schedule a post across platforms",
        "description": "Enqueues one job per selected platform and returns job IDs plus `batch_id`. Platform post IDs and URLs are `null` in the queued response until each platform finishes publishing; read `GET /api/v1/jobs/{id}` or `GET /api/v1/posts` for populated `platform_post_id` and `platform_post_url` values after completion.",
        "parameters": [
          {
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "description": "Use one new random key per approved post. The same account, key, and exact request return the original batch and job IDs, including after a lost reply. Changed content with the same key returns 409. A request still committing returns 409 post_submission_in_progress; check status and retry the same request and key. Keys stay reserved while publishing history is retained. Requests without a key have no retry protection."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreatePostRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Jobs enqueued",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreatePostResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation error (platform/media constraints, unsupported platform, etc.)"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "402": {
            "description": "Posting limit reached for current plan"
          },
          "403": {
            "description": "Feature not available on current plan, or supplied `user_id` does not match API key owner"
          },
          "409": {
            "description": "Idempotency key conflict or submission still committing"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        }
      }
    },
    "/api/v1/jobs/{id}": {
      "get": {
        "operationId": "getJob",
        "summary": "Get job status",
        "description": "Returns queue/database job status, result, and user warnings.",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Job ID returned by `POST /api/v1/post`."
          }
        ],
        "responses": {
          "200": {
            "description": "Job found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetJobResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "403": {
            "description": "Supplied `user_id` (if any) does not match API key owner"
          },
          "404": {
            "description": "Job not found"
          }
        }
      }
    },
    "/api/v1/jobs/{id}/cancel": {
      "post": {
        "operationId": "cancelJob",
        "summary": "Cancel one platform job",
        "description": "Cancels only the selected queued or retrying platform job. Other jobs in the same multi-platform batch, including completed posts, are preserved. Failed jobs are already terminal and remain failed for accurate history.",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "Job ID returned by `POST /api/v1/post`."
          },
          {
            "in": "query",
            "name": "profile_id",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/ProfileId"
            },
            "description": "Optional current profile. A different or unavailable profile returns 404."
          }
        ],
        "responses": {
          "200": {
            "description": "The selected job is cancelled and remains available through `GET /api/v1/jobs/{id}`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CancelJobResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid job ID"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "404": {
            "description": "Job not found"
          },
          "409": {
            "description": "Revision changed, or the selected platform job is processing, completed, failed, or otherwise no longer cancelable"
          }
        },
        "requestBody": {
          "required": false,
          "description": "Optional concurrency guard. Read the current group revision from GET /api/v1/posts/{id}; stale revisions return 409. Omit to retain the existing unguarded behavior.",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "expected_revision": {
                    "type": "integer",
                    "minimum": 1
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/jobs/{id}/tiktok/post-url": {
      "post": {
        "operationId": "attachTikTokPostUrl",
        "summary": "Attach the final TikTok post URL",
        "description": "Saves the final TikTok URL after a manual TikTok upload is complete. This endpoint only accepts TikTok jobs that are waiting for the final URL.",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "TikTok job ID returned by `POST /api/v1/post`."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "post_url"
                ],
                "properties": {
                  "post_url": {
                    "type": "string",
                    "format": "uri",
                    "description": "Final public TikTok post URL."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "TikTok post URL saved",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AttachTikTokPostUrlResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid job ID, missing URL, or non-TikTok job"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "404": {
            "description": "Job not found"
          },
          "409": {
            "description": "The job is not waiting for a final TikTok post URL"
          }
        }
      }
    },
    "/api/v1/posts": {
      "get": {
        "operationId": "listPosts",
        "summary": "List grouped posts",
        "description": "Returns grouped multi-platform posts with per-platform outcomes, retries, warnings, and capabilities.",
        "parameters": [
          {
            "in": "query",
            "name": "page",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "in": "query",
            "name": "per_page",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 10
            }
          },
          {
            "in": "query",
            "name": "profile_id",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/ProfileId"
            },
            "description": "Filter grouped posts to one connection profile. Omit to use the default profile."
          }
        ],
        "responses": {
          "200": {
            "description": "Posts retrieved",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListPostsResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "403": {
            "description": "Supplied `user_id` (if any) does not match API key owner"
          }
        }
      }
    },
    "/api/v1/comments": {
      "get": {
        "operationId": "listPostComments",
        "summary": "List comments on a published post",
        "description": "Returns comments/replies for one Mallary-published platform post so an external agent can draft responses in the user's voice. Available on all plans.",
        "parameters": [
          {
            "in": "query",
            "name": "post_id",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "Mallary job/post ID returned by `POST /api/v1/post` or `GET /api/v1/posts`."
          },
          {
            "in": "query",
            "name": "platform",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/CommentPlatform"
            },
            "description": "Optional platform hint. Required only when a stored post cannot be disambiguated from the job record."
          },
          {
            "in": "query",
            "name": "profile_id",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/ProfileId"
            },
            "description": "Optional connection profile ID. Omit to use the profile that created the post."
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Comments retrieved",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListCommentsResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid post ID or unsupported platform"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "404": {
            "description": "Post not found"
          },
          "409": {
            "description": "Post not published yet or platform connection unavailable"
          }
        }
      }
    },
    "/api/v1/comments/reply": {
      "post": {
        "operationId": "replyToComment",
        "summary": "Reply to a comment",
        "description": "Posts the exact supplied `message` as a reply/comment through the connected platform account. Mallary does not generate the text.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReplyToCommentRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Reply posted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReplyToCommentResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "404": {
            "description": "Post not found"
          },
          "409": {
            "description": "Post not published yet or platform connection unavailable"
          },
          "429": {
            "description": "Platform or API rate limit exceeded"
          }
        }
      }
    },
    "/api/v1/posts/{id}/comments": {
      "get": {
        "operationId": "listPostCommentsByPath",
        "summary": "List comments on a published post",
        "description": "Path-style alias for `GET /api/v1/comments?post_id={id}`.",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "platform",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/CommentPlatform"
            }
          },
          {
            "in": "query",
            "name": "profile_id",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/ProfileId"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Comments retrieved",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListCommentsResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "404": {
            "description": "Post not found"
          }
        }
      }
    },
    "/api/v1/posts/{id}": {
      "get": {
        "operationId": "getScheduledPost",
        "summary": "Get a post and its edit details",
        "description": "Returns the full grouped post, including its saved destinations, connection profile, editable fields, and revision. The ID may be any Mallary job ID from the group, including one retired by an earlier edit.",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "profile_id",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/ProfileId"
            },
            "description": "Optional current connection profile for the post. This does not change its destination profile."
          }
        ],
        "responses": {
          "200": {
            "description": "Post retrieved",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetScheduledPostResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid post id"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "404": {
            "description": "Post not found for this user or current profile"
          }
        }
      },
      "patch": {
        "operationId": "updateScheduledPost",
        "summary": "Edit a scheduled post before publishing starts",
        "description": "Updates the entire grouped post. Omitted fields keep their current values. To change platforms or the connection profile, send `destinations` with both the target public `profile_id` and the complete final `platforms` list. The scheduled time must be in the future, and no platform in the group may have started publishing. A stale revision returns HTTP 409.",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "profile_id",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/ProfileId"
            },
            "description": "Optional current profile of the post. The target profile belongs in `destinations.profile_id`."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateScheduledPostRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Scheduled post updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetScheduledPostResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid content, time, media, platform options, destination list, or target connection"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "402": {
            "description": "Posting allowance reached when adding destinations"
          },
          "403": {
            "description": "Scheduling or auto-reply not available on the current plan, posting paused, or content policy blocked"
          },
          "404": {
            "description": "Post or target connection profile not found"
          },
          "409": {
            "description": "Revision changed, post no longer editable, or upload not complete"
          },
          "429": {
            "description": "Rate limit or post safety limit exceeded"
          }
        }
      },
      "delete": {
        "operationId": "deletePendingPost",
        "summary": "Delete pending/scheduled post",
        "description": "Deletes queued/scheduled jobs for a grouped post that has not started publishing. With expected_revision, the group must still be a future, unattempted schedule.",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "profile_id",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/ProfileId"
            },
            "description": "Optional current profile. A different or unavailable profile returns 404."
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeletePostResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid post id"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "404": {
            "description": "Post not found"
          },
          "409": {
            "description": "Revision changed, or post started publishing or is no longer a future schedule"
          }
        },
        "requestBody": {
          "required": false,
          "description": "Optional concurrency guard. Read the current group revision from GET /api/v1/posts/{id}; stale revisions return 409. Omit to retain the existing unguarded behavior.",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "expected_revision": {
                    "type": "integer",
                    "minimum": 1
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/posts/{id}/comments/{comment_id}/reply": {
      "post": {
        "operationId": "replyToCommentByPath",
        "summary": "Reply to a comment",
        "description": "Path-style alias for `POST /api/v1/comments/reply`.",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "in": "path",
            "name": "comment_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "message"
                ],
                "properties": {
                  "message": {
                    "type": "string",
                    "minLength": 1
                  },
                  "platform": {
                    "$ref": "#/components/schemas/CommentPlatform"
                  },
                  "profile_id": {
                    "$ref": "#/components/schemas/ProfileId"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Reply posted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReplyToCommentResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "404": {
            "description": "Post not found"
          },
          "409": {
            "description": "Post not published yet or platform connection unavailable"
          }
        }
      }
    },
    "/api/v1/analytics": {
      "get": {
        "operationId": "listAnalytics",
        "summary": "Get latest post metrics and audience counts",
        "description": "Returns the latest per-platform post metrics in `data.analytics` and the latest saved follower or subscriber counts in `data.audience`. Audience counts use the same fields and statuses as GET /api/v1/audience and cover every connected account in the selected profile. Mallary refreshes supported audience counts once every 24 hours. TikTok follower counts become available after a connection grants `user.info.stats` and Mallary saves its first count. Existing connections without that permission return `permission_required` for their audience count and keep working as before.",
        "parameters": [
          {
            "in": "query",
            "name": "post_id",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "description": "Filter post metrics to a single post/job ID owned by the authenticated user. Audience counts still cover the whole selected profile."
          },
          {
            "in": "query",
            "name": "profile_id",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/ProfileId"
            },
            "description": "Select the connection profile for both post metrics and audience counts. Omit to use the default profile."
          }
        ],
        "responses": {
          "200": {
            "description": "Analytics retrieved",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "ok"
                      ]
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "profile_id": {
                          "type": "string",
                          "pattern": "^[A-Za-z0-9]{6,32}$",
                          "nullable": true
                        },
                        "analytics": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/AnalyticsItem"
                          }
                        },
                        "audience": {
                          "type": "array",
                          "description": "Latest saved audience count and collection status for each connected account in the selected profile. This list is not filtered by `post_id`.",
                          "items": {
                            "$ref": "#/components/schemas/AudienceItem"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "403": {
            "description": "Analytics are not available for current plan, or supplied `user_id` does not match API key owner"
          },
          "404": {
            "description": "Post not found"
          }
        }
      }
    },
    "/api/v1/analytics/posts": {
      "get": {
        "operationId": "listAnalyticsPosts",
        "summary": "Browse analytics for all posts",
        "description": "Returns paginated post groups across the selected profile's full history. Metric values are the latest saved totals for each post, not activity earned inside the selected date range. Date filters use the post group's creation date (UTC). Unavailable metrics are null; a reported zero remains zero. Pass the next cursor with the same filters and sort. Metric ranking uses snapshots available when the first page was requested; post status can still change between pages. Requires a paid plan with analytics access.",
        "parameters": [
          {
            "in": "query",
            "name": "profile_id",
            "schema": {
              "$ref": "#/components/schemas/ProfileId"
            },
            "description": "Connection profile to inspect. Omit to use the default profile."
          },
          {
            "in": "query",
            "name": "start_date",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Include post groups created on or after this UTC date."
          },
          {
            "in": "query",
            "name": "end_date",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Include post groups created on or before this UTC date."
          },
          {
            "in": "query",
            "name": "platform",
            "schema": {
              "$ref": "#/components/schemas/Platform"
            },
            "description": "Include groups with a post on this platform."
          },
          {
            "in": "query",
            "name": "status",
            "schema": {
              "type": "string",
              "enum": [
                "successful",
                "scheduled",
                "failed",
                "processing",
                "partial"
              ]
            },
            "description": "Filter by the post group's status."
          },
          {
            "in": "query",
            "name": "media_type",
            "schema": {
              "type": "string",
              "enum": [
                "video",
                "photo",
                "mixed_media",
                "text_or_link"
              ]
            },
            "description": "Include groups with this media type."
          },
          {
            "in": "query",
            "name": "search",
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Search post text and batch IDs."
          },
          {
            "in": "query",
            "name": "sort",
            "schema": {
              "type": "string",
              "enum": [
                "newest",
                "oldest",
                "views",
                "display_views",
                "engagement",
                "engagement_rate",
                "clicks",
                "impressions"
              ],
              "default": "newest"
            },
            "description": "Sort post groups. `display_views` uses a platform's views when available, otherwise its impressions. Posts without the selected metric appear after posts with it."
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            },
            "description": "Maximum number of post groups in one page."
          },
          {
            "in": "query",
            "name": "cursor",
            "schema": {
              "type": "string"
            },
            "description": "Opaque `next_cursor` from the previous page. Keep filters and sort the same."
          }
        ],
        "responses": {
          "200": {
            "description": "Post analytics page retrieved",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnalyticsPostsResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid filter or cursor"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "403": {
            "description": "Analytics are not available for the current plan"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        }
      }
    },
    "/api/v1/audience": {
      "get": {
        "operationId": "listAudience",
        "summary": "Get current audience counts",
        "description": "Returns the latest follower or subscriber count for every connected account in one Mallary profile. Mallary refreshes supported counts once every 24 hours.",
        "parameters": [
          {
            "in": "query",
            "name": "profile_id",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/ProfileId"
            },
            "description": "Connection profile to inspect. Omit to use the default profile."
          }
        ],
        "responses": {
          "200": {
            "description": "Audience counts retrieved",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AudienceResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "403": {
            "description": "Audience analytics are not available for the current plan, or supplied `user_id` does not match the API key owner"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        }
      }
    },
    "/api/v1/platforms": {
      "get": {
        "operationId": "listPlatforms",
        "summary": "List connected platforms",
        "description": "Returns Mallary's supported public platform set, marks which platforms are currently connected for the selected connection profile, and lists exact selectable post types where the API supports an explicit type.",
        "parameters": [
          {
            "in": "query",
            "name": "profile_id",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/ProfileId"
            },
            "description": "Connection profile to inspect. Omit to use the default profile."
          }
        ],
        "responses": {
          "200": {
            "description": "Platform connection summary retrieved",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectedPlatformsResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "403": {
            "description": "Supplied `user_id` (if any) does not match API key owner"
          }
        }
      }
    },
    "/api/v1/profiles": {
      "get": {
        "operationId": "listConnectionProfiles",
        "summary": "List connection profiles",
        "description": "Returns the authenticated user's connection profiles, connected platforms per profile, and current account limits. For account-based plans, max_accounts_total is shared across platforms and profiles; account_limit_scope is total. Existing contracts can return platform-based limits.",
        "responses": {
          "200": {
            "description": "Connection profiles retrieved",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileListResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "403": {
            "description": "Supplied `user_id` (if any) does not match API key owner"
          }
        }
      },
      "post": {
        "operationId": "createConnectionProfile",
        "summary": "Create connection profile",
        "description": "Creates a named non-default connection profile for grouping platform accounts.",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateConnectionProfileRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Connection profile created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileMutationResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid profile name"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "403": {
            "description": "Supplied `user_id` (if any) does not match API key owner"
          }
        }
      }
    },
    "/api/v1/profiles/{id}": {
      "post": {
        "operationId": "renameConnectionProfile",
        "summary": "Rename connection profile",
        "description": "Renames one of the authenticated user's connection profiles.",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/ProfileId"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RenameConnectionProfileRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Connection profile renamed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileMutationResponse"
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid profile id/name"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "404": {
            "description": "Profile not found"
          }
        }
      }
    },
    "/api/v1/settings": {
      "get": {
        "operationId": "getSettings",
        "summary": "Get profile settings",
        "description": "Returns brand and AI auto-reply settings for the selected connection profile.",
        "parameters": [
          {
            "in": "query",
            "name": "profile_id",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/ProfileId"
            },
            "description": "Connection profile settings to read. Omit to use the default profile."
          }
        ],
        "responses": {
          "200": {
            "description": "Settings retrieved",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SettingsResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "403": {
            "description": "Supplied `user_id` (if any) does not match API key owner"
          }
        }
      },
      "post": {
        "operationId": "updateSettings",
        "summary": "Update profile settings",
        "description": "Updates brand and AI auto-reply settings for the selected connection profile.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SettingsUpdateRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Settings updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SettingsResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid settings or incomplete auto-reply profile"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "403": {
            "description": "AI auto-replies are unavailable for the current plan, or supplied `user_id` does not match API key owner"
          }
        }
      }
    },
    "/api/v1/disconnect": {
      "post": {
        "operationId": "disconnectPlatform",
        "summary": "Disconnect a platform",
        "description": "Removes the authenticated user's connection row for a platform from the selected connection profile.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DisconnectRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Platform disconnected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DisconnectResponse"
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid platform"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "403": {
            "description": "Supplied `user_id` (if any) does not match API key owner"
          }
        }
      }
    },
    "/api/v1/webhooks": {
      "get": {
        "operationId": "listWebhooks",
        "summary": "List webhooks",
        "responses": {
          "200": {
            "description": "Webhooks for authenticated user",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Webhook"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key"
          }
        }
      },
      "post": {
        "operationId": "createWebhook",
        "summary": "Create webhook",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url"
                ],
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri"
                  },
                  "events": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/WebhookEvent"
                    }
                  },
                  "secret": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "integer"
                    },
                    "url": {
                      "type": "string",
                      "format": "uri"
                    },
                    "events": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/WebhookEvent"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request"
          },
          "401": {
            "description": "Missing or invalid API key"
          }
        }
      }
    },
    "/api/v1/webhooks/{id}": {
      "delete": {
        "operationId": "deleteWebhook",
        "summary": "Delete webhook",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "ok"
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid id"
          },
          "401": {
            "description": "Missing or invalid API key"
          }
        }
      }
    },
    "/api/v1/drafts": {
      "post": {
        "tags": [
          "Drafts"
        ],
        "operationId": "createDraft",
        "summary": "Save an unpublished draft",
        "description": "Save incomplete content without publishing, scheduling or using posting quota. Empty content is allowed. No schedule fields are accepted. Available to all users. Requires mallary.publish for OAuth.",
        "responses": {
          "201": {
            "description": "Draft saved",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DraftResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid fields or publishing validation failed. The draft stays saved."
          },
          "401": {
            "description": "Authentication required"
          },
          "404": {
            "description": "Draft or profile not found for this user"
          },
          "409": {
            "description": "Draft changed or was submitted. Read it again before editing, deleting or submitting."
          },
          "403": {
            "description": "Required OAuth scope missing, or submission blocked by normal publishing or scheduling rules. Draft management is available to all users."
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DraftContent"
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "Drafts"
        ],
        "operationId": "listDrafts",
        "summary": "List unpublished drafts",
        "description": "Lists only unpublished drafts in the selected or default profile, most recently edited first. Available to all users. Requires mallary.read for OAuth.",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListDraftsResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid fields or publishing validation failed. The draft stays saved."
          },
          "401": {
            "description": "Authentication required"
          },
          "404": {
            "description": "Draft or profile not found for this user"
          },
          "409": {
            "description": "Draft changed or was submitted. Read it again before editing, deleting or submitting."
          },
          "403": {
            "description": "Required OAuth scope missing, or submission blocked by normal publishing or scheduling rules. Draft management is available to all users."
          }
        },
        "parameters": [
          {
            "name": "profile_id",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/ProfileId"
            },
            "description": "Omit to use the default profile on lists. On reads, deletes and submissions, optionally verify the draft belongs to this profile."
          },
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100000,
              "default": 1
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          }
        ]
      }
    },
    "/api/v1/drafts/{id}": {
      "get": {
        "tags": [
          "Drafts"
        ],
        "operationId": "getDraft",
        "summary": "Read a saved draft",
        "description": "Returns complete content and the revision. Submitted drafts include their original publishing result for recovery after a lost response. Available to all users. Requires mallary.read for OAuth.",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DraftResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid fields or publishing validation failed. The draft stays saved."
          },
          "401": {
            "description": "Authentication required"
          },
          "404": {
            "description": "Draft or profile not found for this user"
          },
          "409": {
            "description": "Draft changed or was submitted. Read it again before editing, deleting or submitting."
          },
          "403": {
            "description": "Required OAuth scope missing, or submission blocked by normal publishing or scheduling rules. Draft management is available to all users."
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "profile_id",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/ProfileId"
            },
            "description": "Omit to use the default profile on lists. On reads, deletes and submissions, optionally verify the draft belongs to this profile."
          }
        ]
      },
      "patch": {
        "tags": [
          "Drafts"
        ],
        "operationId": "updateDraft",
        "summary": "Edit an unpublished draft",
        "description": "Requires the current revision and at least one changed field. Omitted fields are kept. Supplied lists and platform_options replace their complete saved values. One connection profile per draft. No schedule fields accepted. Available to all users. Requires mallary.publish for OAuth.",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DraftResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid fields or publishing validation failed. The draft stays saved."
          },
          "401": {
            "description": "Authentication required"
          },
          "404": {
            "description": "Draft or profile not found for this user"
          },
          "409": {
            "description": "Draft changed or was submitted. Read it again before editing, deleting or submitting."
          },
          "403": {
            "description": "Required OAuth scope missing, or submission blocked by normal publishing or scheduling rules. Draft management is available to all users."
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateDraftRequest"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "profile_id",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/ProfileId"
            },
            "description": "Omit to use the default profile on lists. On reads, deletes and submissions, optionally verify the draft belongs to this profile."
          }
        ]
      },
      "delete": {
        "tags": [
          "Drafts"
        ],
        "operationId": "deleteDraft",
        "summary": "Delete an unpublished draft",
        "description": "Requires its current revision. Submitted drafts cannot be deleted here. Does not delete uploaded media or any social post. Available to all users. Requires mallary.publish for OAuth.",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeleteDraftResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid fields or publishing validation failed. The draft stays saved."
          },
          "401": {
            "description": "Authentication required"
          },
          "404": {
            "description": "Draft or profile not found for this user"
          },
          "409": {
            "description": "Draft changed or was submitted. Read it again before editing, deleting or submitting."
          },
          "403": {
            "description": "Required OAuth scope missing, or submission blocked by normal publishing or scheduling rules. Draft management is available to all users."
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DraftRevisionRequest"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "profile_id",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/ProfileId"
            },
            "description": "Omit to use the default profile on lists. On reads, deletes and submissions, optionally verify the draft belongs to this profile."
          }
        ]
      }
    },
    "/api/v1/drafts/{id}/submit": {
      "post": {
        "tags": [
          "Drafts"
        ],
        "operationId": "submitDraft",
        "summary": "Publish or schedule a saved draft",
        "description": "Requires an explicit publishing or scheduling action and its current revision. Uses the normal publishing checks and quotas. Omit scheduled_at to publish now. The saved draft and all jobs commit together. Repeating the exact revision and schedule returns the same jobs; a different revision or schedule conflicts. Validation failures leave the draft unpublished. Available to all users. Requires mallary.publish for OAuth.",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreatePostResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid fields or publishing validation failed. The draft stays saved."
          },
          "401": {
            "description": "Authentication required"
          },
          "404": {
            "description": "Draft or profile not found for this user"
          },
          "409": {
            "description": "Draft changed or was submitted. Read it again before editing, deleting or submitting."
          },
          "403": {
            "description": "Required OAuth scope missing, or submission blocked by normal publishing or scheduling rules. Draft management is available to all users."
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SubmitDraftRequest"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "profile_id",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/ProfileId"
            },
            "description": "Omit to use the default profile on lists. On reads, deletes and submissions, optionally verify the draft belongs to this profile."
          }
        ]
      }
    },
    "/api/v1/webhooks/{id}/deliveries": {
      "get": {
        "operationId": "listWebhookDeliveries",
        "summary": "List webhook delivery history",
        "description": "Returns up to 50 saved deliveries for this account's webhook, newest first. Includes attempt history without event bodies or signing secrets.",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          },
          {
            "in": "query",
            "name": "before",
            "description": "Use next_before from the last page to read older deliveries.",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Delivery and attempt history",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookDeliveryHistory"
                }
              }
            }
          },
          "400": {
            "description": "Invalid id or cursor"
          },
          "401": {
            "description": "Missing or invalid API key"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Use `Authorization: Bearer {api_key}`"
      }
    },
    "schemas": {
      "PublicPlatform": {
        "type": "string",
        "description": "Canonical public platform identifier used in connected-platform listings.",
        "enum": [
          "x",
          "facebook",
          "instagram",
          "linkedin",
          "youtube",
          "tiktok",
          "pinterest",
          "reddit",
          "threads",
          "bluesky"
        ]
      },
      "ProfileId": {
        "type": "string",
        "pattern": "^[A-Za-z0-9]{6,32}$",
        "example": "AbC123xYz90",
        "description": "Random public connection profile ID returned by `GET /api/v1/profiles`."
      },
      "Platform": {
        "type": "string",
        "description": "Supported publishing platform identifier.",
        "enum": [
          "twitter",
          "x",
          "facebook",
          "instagram",
          "linkedin",
          "youtube",
          "tiktok",
          "pinterest",
          "reddit",
          "threads",
          "bluesky"
        ]
      },
      "CommentPlatform": {
        "type": "string",
        "description": "Platforms that currently support comment listing and reply posting through Mallary.",
        "enum": [
          "facebook",
          "instagram",
          "threads",
          "linkedin",
          "x",
          "twitter",
          "youtube",
          "bluesky"
        ]
      },
      "Comment": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "platform": {
            "$ref": "#/components/schemas/CommentPlatform"
          },
          "post_id": {
            "type": "integer"
          },
          "remote_post_id": {
            "type": "string"
          },
          "text": {
            "type": "string"
          },
          "author_name": {
            "type": "string",
            "nullable": true
          },
          "author_id": {
            "type": "string",
            "nullable": true
          },
          "author_username": {
            "type": "string",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "nullable": true
          },
          "can_reply": {
            "type": "boolean",
            "description": "False when Mallary can identify the comment as authored by the connected account."
          },
          "is_own": {
            "type": "boolean",
            "description": "True when Mallary can identify the comment as authored by the connected account."
          }
        }
      },
      "CommentPlatformError": {
        "type": "object",
        "properties": {
          "post_id": {
            "type": "integer"
          },
          "platform": {
            "$ref": "#/components/schemas/CommentPlatform"
          },
          "http_status": {
            "type": "integer"
          },
          "code": {
            "type": "string"
          },
          "message": {
            "type": "string"
          }
        }
      },
      "ListCommentsResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "ok"
            ]
          },
          "data": {
            "type": "object",
            "properties": {
              "post_id": {
                "type": "integer"
              },
              "platform": {
                "type": "string",
                "description": "A concrete platform for platform-specific requests, or `multiple` when comments were aggregated across a grouped multi-platform post.",
                "enum": [
                  "facebook",
                  "instagram",
                  "threads",
                  "linkedin",
                  "x",
                  "twitter",
                  "youtube",
                  "multiple"
                ]
              },
              "remote_post_id": {
                "type": "string",
                "nullable": true
              },
              "comments": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Comment"
                }
              },
              "supported_platforms": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/CommentPlatform"
                }
              },
              "platforms": {
                "type": "array",
                "description": "Platforms checked when a grouped multi-platform post is queried without a platform filter.",
                "items": {
                  "$ref": "#/components/schemas/CommentPlatform"
                }
              },
              "unsupported_platforms": {
                "type": "array",
                "description": "Platforms in the grouped post that do not currently support comment listing through Mallary.",
                "items": {
                  "type": "string"
                }
              },
              "platform_errors": {
                "type": "array",
                "description": "Per-platform read errors for aggregate grouped-post requests. Other platform comments may still be returned.",
                "items": {
                  "$ref": "#/components/schemas/CommentPlatformError"
                }
              }
            }
          }
        }
      },
      "ReplyToCommentRequest": {
        "type": "object",
        "required": [
          "post_id",
          "comment_id",
          "message"
        ],
        "properties": {
          "post_id": {
            "type": "integer"
          },
          "comment_id": {
            "type": "string"
          },
          "message": {
            "type": "string",
            "minLength": 1,
            "description": "Exact reply text to post. Mallary does not generate or rewrite this text."
          }
        }
      },
      "ReplyToCommentResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "ok"
            ]
          },
          "data": {
            "type": "object",
            "properties": {
              "post_id": {
                "type": "integer"
              },
              "platform": {
                "$ref": "#/components/schemas/CommentPlatform"
              },
              "remote_post_id": {
                "type": "string"
              },
              "comment_id": {
                "type": "string"
              },
              "reply_id": {
                "type": "string",
                "nullable": true
              },
              "message": {
                "type": "string"
              }
            }
          }
        }
      },
      "CapabilityTier": {
        "type": "string",
        "enum": [
          "free",
          "starter",
          "pro",
          "business"
        ]
      },
      "Capabilities": {
        "type": "object",
        "properties": {
          "can_schedule": {
            "type": "boolean"
          },
          "can_analytics": {
            "type": "boolean"
          },
          "can_auto_reply": {
            "type": "boolean"
          },
          "tier": {
            "$ref": "#/components/schemas/CapabilityTier"
          },
          "max_accounts_per_platform": {
            "type": "integer"
          }
        }
      },
      "MediaItem": {
        "type": "object",
        "required": [
          "url"
        ],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Media URL (`mediaUrl` must be obtained from `/api/v1/upload`)."
          },
          "type": {
            "type": "string",
            "description": "Optional MIME type hint."
          },
          "thumbnail_url": {
            "type": "string",
            "format": "uri",
            "description": "Optional Mallary-hosted image URL to use as the custom thumbnail or cover for a video post when supported by the target platform."
          },
          "alt_text": {
            "type": "string",
            "maxLength": 2000,
            "description": "Optional media description for accessibility. Bluesky adds it to the matching image or video."
          }
        }
      },
      "CreateUploadRequest": {
        "type": "object",
        "required": [
          "filename"
        ],
        "properties": {
          "filename": {
            "type": "string",
            "example": "launch-video.mp4"
          },
          "size": {
            "type": "integer",
            "minimum": 1,
            "description": "Optional file size in bytes for early validation against the 5GB limit."
          },
          "type": {
            "type": "string",
            "example": "video/mp4",
            "description": "File MIME type. Send it when known, such as `video/mp4` for an MP4 video. If omitted, Mallary infers it from a supported filename extension. Files with unknown extensions need an explicit supported type."
          }
        }
      },
      "CreateUploadResponse": {
        "type": "object",
        "required": [
          "uploadUrl",
          "mediaUrl",
          "contentType"
        ],
        "properties": {
          "uploadUrl": {
            "type": "string",
            "format": "uri"
          },
          "mediaUrl": {
            "type": "string",
            "format": "uri"
          },
          "storageKey": {
            "type": "string"
          },
          "contentType": {
            "type": "string"
          },
          "size": {
            "type": "integer",
            "description": "Echoed back when a `size` value was supplied in the request."
          },
          "headers": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          }
        }
      },
      "PlatformOptions": {
        "type": "object",
        "description": "Per-platform options keyed by platform id.",
        "additionalProperties": true,
        "properties": {
          "facebook": {
            "type": "object",
            "properties": {
              "message": {
                "type": "string",
                "description": "Facebook-specific message/caption. Defaults to the top-level `message`."
              },
              "post_type": {
                "type": "string",
                "enum": [
                  "feed",
                  "story",
                  "reel"
                ],
                "default": "feed",
                "description": "Defaults to `feed` when omitted. `story` publishes one image/video as a Facebook Story without caption/follow-up comments. `reel` publishes one 9:16 MP4/MOV video that is 3 to 90 seconds long."
              },
              "link": {
                "type": "string",
                "format": "uri",
                "description": "Optional link for a feed post without media."
              },
              "pageId": {
                "type": "string",
                "description": "Optional advanced override for a connected Facebook Page."
              },
              "title": {
                "type": "string",
                "maxLength": 255,
                "description": "Optional title for a Facebook Reel."
              },
              "is_ai_generated": {
                "type": "boolean",
                "description": "For Facebook Reels only. Set true to disclose AI-generated media at publish time. Accepts true or false; omitted by default."
              }
            }
          },
          "instagram": {
            "type": "object",
            "properties": {
              "message": {
                "type": "string",
                "description": "Instagram-specific caption. Defaults to the top-level `message`."
              },
              "post_type": {
                "type": "string",
                "enum": [
                  "feed",
                  "story",
                  "reel",
                  "carousel"
                ],
                "default": "feed",
                "description": "Defaults to `feed` when omitted. `story` publishes one image/video as an Instagram Story with an optional caption and no follow-up comments, `reel` requires one video, and `carousel` publishes 2 to 10 image/video items as one carousel post."
              },
              "shareToFeed": {
                "type": "boolean",
                "default": true,
                "description": "For Reels only. When true, the Reel appears in the profile feed and Reels tab. When false, it appears only in the Reels tab."
              },
              "trialParams": {
                "type": "object",
                "description": "For Reels only. Add this object to publish the video as a Trial Reel that Instagram shows to non-followers first. Do not use `shareToFeed` with a Trial Reel.",
                "required": [
                  "graduationStrategy"
                ],
                "additionalProperties": false,
                "properties": {
                  "graduationStrategy": {
                    "type": "string",
                    "enum": [
                      "MANUAL",
                      "SS_PERFORMANCE"
                    ],
                    "description": "Use `MANUAL` to decide later in Instagram, or `SS_PERFORMANCE` to let Instagram share the Reel with followers if it performs well."
                  }
                }
              },
              "isPaidPartnership": {
                "type": "boolean",
                "description": "Show Instagram's Paid partnership label. Available for feed posts, Reels, and carousels connected through Facebook Login. Sponsors also turn this on."
              },
              "brandedContentSponsors": {
                "type": "array",
                "maxItems": 2,
                "description": "Up to two sponsor Instagram usernames or numeric user IDs. Each sponsor must be a public Business or Creator account. Available only through Facebook Login and not for Stories.",
                "items": {
                  "type": "string",
                  "pattern": "^@?(?:[A-Za-z0-9._]{1,30}|\\d{1,64})$"
                }
              },
              "is_ai_generated": {
                "type": "boolean",
                "description": "Set true to disclose AI-generated media when creating the Instagram post. Accepts true or false; omitted by default."
              }
            }
          },
          "threads": {
            "type": "object",
            "properties": {
              "message": {
                "type": "string",
                "description": "Threads-specific message. Defaults to the top-level `message`."
              },
              "post_type": {
                "type": "string",
                "enum": [
                  "text",
                  "image",
                  "video",
                  "carousel"
                ],
                "description": "Defaults dynamically. Mallary uses `text` when no media is supplied, `carousel` when more than one supported media item is supplied, `video` for a single MP4/MOV item, and `image` for a single JPG/JPEG/PNG/WEBP item."
              }
            }
          },
          "twitter": {
            "type": "object",
            "properties": {
              "message": {
                "type": "string",
                "description": "X-specific message. Defaults to the top-level `message`."
              },
              "made_with_ai": {
                "type": "boolean",
                "description": "Alias for platform_options.x.made_with_ai. Applies to the main X post only. Accepts true or false; omitted by default."
              }
            }
          },
          "x": {
            "type": "object",
            "properties": {
              "message": {
                "type": "string",
                "description": "X-specific message. Defaults to the top-level `message`."
              },
              "made_with_ai": {
                "type": "boolean",
                "description": "Set true to mark the main X post as made with AI. Does not label follow-up replies. Accepts true or false; omitted by default."
              }
            }
          },
          "linkedin": {
            "type": "object",
            "properties": {
              "message": {
                "type": "string",
                "description": "LinkedIn-specific message. Defaults to the top-level `message`."
              },
              "author_urn": {
                "type": "string",
                "description": "Optional author URN override."
              }
            }
          },
          "tiktok": {
            "type": "object",
            "properties": {
              "message": {
                "type": "string",
                "description": "TikTok-specific message/caption. Defaults to the top-level `message`."
              },
              "post_type": {
                "type": "string",
                "enum": [
                  "video",
                  "photo"
                ],
                "description": "Defaults dynamically. Mallary picks `photo` when the media list contains only images and no videos; otherwise it uses `video`."
              },
              "media_type": {
                "type": "string",
                "enum": [
                  "PHOTO"
                ],
                "description": "Optional alias for selecting the TikTok photo variant. No default; use this only when you want to force photo mode via the alias."
              },
              "post_mode": {
                "type": "string",
                "enum": [
                  "DIRECT_POST",
                  "MEDIA_UPLOAD"
                ],
                "default": "DIRECT_POST",
                "description": "Defaults to `DIRECT_POST`, which publishes through Mallary. Use `MEDIA_UPLOAD` only when you want to send the upload to the creator inbox and finish publishing it in TikTok."
              },
              "source": {
                "type": "string",
                "enum": [
                  "FILE_UPLOAD",
                  "PULL_FROM_URL"
                ],
                "description": "Defaults by variant. Video posts default to `FILE_UPLOAD`. Photo posts always use `PULL_FROM_URL`. Mallary only accepts `PULL_FROM_URL` media from its own CDN."
              },
              "title": {
                "type": "string",
                "maxLength": 2200,
                "description": "TikTok caption/title override. Defaults to `message` (trimmed to TikTok's limit). For photo posts, TikTok applies a shorter platform limit."
              },
              "description": {
                "type": "string",
                "maxLength": 4000,
                "description": "TikTok photo description override. No default; omitted unless supplied."
              },
              "privacy_level": {
                "type": "string",
                "enum": [
                  "PUBLIC_TO_EVERYONE",
                  "MUTUAL_FOLLOW_FRIENDS",
                  "FOLLOWER_OF_CREATOR",
                  "SELF_ONLY"
                ],
                "description": "Applies to `DIRECT_POST`. Defaults to the first allowed privacy level returned by TikTok creator info, preferring `PUBLIC_TO_EVERYONE`, then `MUTUAL_FOLLOW_FRIENDS`, then `FOLLOWER_OF_CREATOR`, then `SELF_ONLY`. If TikTok returns the private-account-only direct-post restriction, Mallary retries once with the most private allowed level."
              },
              "disable_comment": {
                "type": "boolean",
                "description": "Controls whether comments are disabled on the TikTok post. Applies to direct posts. When omitted, Mallary falls back to the creator's current TikTok comment setting."
              },
              "disable_duet": {
                "type": "boolean",
                "description": "Controls whether other TikTok users can create Duets from the video. Applies to direct-post video only. When omitted, Mallary falls back to the creator's current TikTok duet setting."
              },
              "disable_stitch": {
                "type": "boolean",
                "description": "Controls whether other TikTok users can create Stitches from the video. Applies to direct-post video only. When omitted, Mallary falls back to the creator's current TikTok stitch setting."
              },
              "video_cover_timestamp_ms": {
                "type": "integer",
                "minimum": 0,
                "description": "Millisecond timestamp used to choose the video cover frame for a direct-post TikTok video. Omitted unless supplied."
              },
              "auto_add_music": {
                "type": "boolean",
                "description": "For direct-post photo posts, asks TikTok to automatically add music to the photo carousel. Omitted unless supplied."
              },
              "brand_content_toggle": {
                "type": "boolean",
                "description": "TikTok branded-content disclosure flag. When true, marks the post as branded content. Omitted unless supplied."
              },
              "brand_organic_toggle": {
                "type": "boolean",
                "description": "TikTok brand-organic disclosure flag. Omitted unless supplied."
              },
              "is_aigc": {
                "type": "boolean",
                "description": "TikTok AI-generated-content disclosure flag for direct-post video. Omitted unless supplied."
              },
              "photo_cover_index": {
                "type": "integer",
                "minimum": 0,
                "default": 0,
                "description": "Zero-based index of the image TikTok should use as the photo carousel cover. Values beyond the image count are clamped to the last image. Defaults to `0` when omitted."
              }
            }
          },
          "youtube": {
            "type": "object",
            "properties": {
              "message": {
                "type": "string",
                "description": "YouTube-specific description/default-title source. Defaults to the top-level `message`."
              },
              "description": {
                "type": "string",
                "description": "Independent YouTube description. When supplied, this takes precedence over both message fields. Use an empty string to clear inherited message text. Omit to keep the existing message fallback."
              },
              "post_type": {
                "type": "string",
                "enum": [
                  "regular",
                  "shorts"
                ],
                "description": "Defaults dynamically. Mallary uses `shorts` when the video is short-form (<= 180s) and vertical, or when dimensions are unavailable for a short candidate; otherwise it uses `regular`."
              },
              "title": {
                "type": "string",
                "maxLength": 100,
                "description": "Defaults to the first non-empty value of `platform_options.youtube.title`, the first line of `message`, or `Untitled Video`."
              },
              "tags": {
                "type": "array",
                "description": "Optional video tags. Each tag can use up to 100 characters. The complete tag list can use up to 500 characters after YouTube counts commas and quote characters around tags with spaces. Exact duplicates are removed before upload.",
                "items": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 100,
                  "pattern": "^[^<>]*$"
                }
              },
              "visibility": {
                "type": "string",
                "enum": [
                  "public",
                  "unlisted",
                  "private"
                ],
                "default": "public",
                "description": "Defaults to `public`."
              },
              "categoryId": {
                "type": "string",
                "default": "22",
                "description": "Defaults to YouTube category id `22`."
              },
              "madeForKids": {
                "type": "boolean",
                "default": false,
                "description": "Defaults to `false`."
              },
              "containsSyntheticMedia": {
                "type": "boolean",
                "description": "Optional AI-content disclosure. Set to `true` when the video contains realistic AI-generated or altered content that could be mistaken for real people, places, or events. YouTube may show a disclosure label. Omitted unless supplied."
              },
              "playlist_id": {
                "type": "string",
                "pattern": "^[A-Za-z0-9_-]{2,150}$",
                "description": "Existing playlist owned by the connected YouTube channel. Mallary checks access before upload, then adds the new video to this playlist."
              }
            }
          },
          "pinterest": {
            "type": "object",
            "properties": {
              "message": {
                "type": "string",
                "description": "Pinterest-specific description/default title source. Defaults to the top-level `message`. Mallary shortens descriptions longer than Pinterest's 800-character limit."
              },
              "post_type": {
                "type": "string",
                "enum": [
                  "image",
                  "video"
                ],
                "description": "Defaults dynamically. Mallary uses `video` when the media list contains a video; otherwise it uses `image`."
              },
              "title": {
                "type": "string",
                "maxLength": 100
              },
              "boardId": {
                "type": "string",
                "description": "Board to post into. Defaults to the connected Pinterest board id from the saved token when omitted."
              },
              "link": {
                "type": "string",
                "format": "uri"
              },
              "alt_text": {
                "type": "string",
                "description": "Short image description for people who use screen readers."
              }
            }
          },
          "reddit": {
            "type": "object",
            "properties": {
              "message": {
                "type": "string",
                "description": "Reddit-specific title/text source. Defaults to the top-level `message`."
              },
              "post_type": {
                "type": "string",
                "enum": [
                  "text",
                  "link",
                  "image"
                ],
                "description": "Defaults dynamically. Mallary uses `image` when the media list contains an image; otherwise it uses `text`."
              },
              "subreddit": {
                "type": "string",
                "description": "Community that receives the post."
              },
              "subredditName": {
                "type": "string",
                "description": "Alias for `subreddit`."
              }
            }
          },
          "bluesky": {
            "type": "object",
            "properties": {
              "message": {
                "type": "string",
                "maxLength": 300,
                "description": "Bluesky-specific message. Defaults to the top-level `message`."
              },
              "languages": {
                "type": "array",
                "maxItems": 3,
                "description": "Optional BCP 47 language codes for the post.",
                "items": {
                  "type": "string"
                }
              },
              "langs": {
                "type": "array",
                "maxItems": 3,
                "description": "Alias for `languages`.",
                "items": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "CreatePostRequest": {
        "type": "object",
        "required": [
          "message",
          "platforms"
        ],
        "properties": {
          "message": {
            "type": "string",
            "minLength": 1
          },
          "platforms": {
            "type": "array",
            "minItems": 1,
            "items": {
              "$ref": "#/components/schemas/Platform"
            }
          },
          "profile_id": {
            "$ref": "#/components/schemas/ProfileId",
            "description": "Connection profile to publish from. Omit to use the default profile."
          },
          "media": {
            "type": "array",
            "description": "Media URLs obtained from using the /api/v1/upload endpoint.",
            "items": {
              "$ref": "#/components/schemas/MediaItem"
            }
          },
          "comments_under_post": {
            "type": "array",
            "maxItems": 3,
            "items": {
              "type": "string"
            },
            "description": "Up to 3 follow-up comments/replies under the main post when supported by the target platform/format."
          },
          "scheduled_at": {
            "type": "string",
            "description": "Optional scheduled publish timestamp. Send an absolute timestamp like `2026-04-06T18:30:00Z`, or send a local datetime like `2026-04-06T14:30` together with `scheduled_timezone`."
          },
          "scheduled_timezone": {
            "type": "string",
            "description": "Optional IANA timezone such as `America/New_York`. When provided with a local `scheduled_at` that omits an offset, Mallary resolves the wall-clock time in this timezone and stores the resulting UTC publish time."
          },
          "auto_reply_enabled": {
            "type": "boolean",
            "description": "Optional per-post override for AI auto-replies."
          },
          "platform_options": {
            "$ref": "#/components/schemas/PlatformOptions",
            "description": "Optional per-platform overrides."
          },
          "webhook_url": {
            "type": "string",
            "format": "uri",
            "description": "Optional callback URL for completion notifications."
          }
        }
      },
      "CreatePostResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "queued"
            ]
          },
          "batch_id": {
            "type": "string"
          },
          "profile_id": {
            "type": "string",
            "pattern": "^[A-Za-z0-9]{6,32}$",
            "nullable": true
          },
          "jobs": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "platform": {
                  "$ref": "#/components/schemas/Platform"
                },
                "jobId": {
                  "type": "string"
                },
                "platform_post_id": {
                  "type": "string",
                  "nullable": true,
                  "description": "Platform-returned post ID. `null` until the queued job publishes successfully."
                },
                "platform_post_url": {
                  "type": "string",
                  "nullable": true,
                  "description": "Public platform post URL when available. `null` until the queued job publishes successfully or when the platform does not expose a URL."
                }
              }
            }
          },
          "warnings": {
            "type": "array",
            "description": "Non-blocking warnings about provider behavior. A queued post may still publish.",
            "items": {
              "type": "object",
              "properties": {
                "platform": {
                  "$ref": "#/components/schemas/Platform"
                },
                "code": {
                  "type": "string"
                },
                "severity": {
                  "type": "string",
                  "enum": [
                    "warning"
                  ]
                },
                "message": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "AutoReplyWarning": {
        "type": "object",
        "properties": {
          "scope": {
            "type": "string"
          },
          "platform": {
            "type": "string"
          },
          "code": {
            "type": "string"
          },
          "severity": {
            "type": "string",
            "enum": [
              "warning",
              "error",
              "info"
            ]
          },
          "message": {
            "type": "string"
          },
          "details": {
            "nullable": true
          },
          "retry_at": {
            "type": "string",
            "nullable": true
          },
          "expires_at": {
            "type": "string",
            "nullable": true
          },
          "updated_at": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "JobData": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "platform": {
            "$ref": "#/components/schemas/Platform"
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "waiting",
              "delayed",
              "active",
              "processing",
              "action_required",
              "completed",
              "failed",
              "cancelled",
              "unknown"
            ]
          },
          "attemptsMade": {
            "type": "integer",
            "nullable": true
          },
          "attemptsAllowed": {
            "type": "integer",
            "nullable": true
          },
          "next_retry_at": {
            "type": "string",
            "nullable": true
          },
          "error_code": {
            "type": "string",
            "nullable": true
          },
          "quota_reset_at": {
            "type": "string",
            "nullable": true,
            "description": "Expected YouTube project upload-quota reset time in ISO 8601 format when `error_code` is `youtube_project_upload_quota_exhausted`."
          },
          "retryable": {
            "type": "boolean",
            "nullable": true
          },
          "failedReason": {
            "type": "string",
            "nullable": true
          },
          "platform_post_id": {
            "type": "string",
            "nullable": true,
            "description": "Platform-returned post ID when the job has published successfully."
          },
          "platform_post_url": {
            "type": "string",
            "nullable": true,
            "description": "Public platform post URL when available."
          },
          "result": {
            "type": "object",
            "nullable": true,
            "additionalProperties": true,
            "description": "Successful Instagram publishes may include config_issue and warnings when the caption or user tags were not added. The post is published; do not retry or republish it because of this warning.",
            "properties": {
              "config_issue": {
                "type": "string",
                "description": "Instagram's non-blocking issue code, such as CAPTION_NOT_ATTACHED or USER_TAGGING_FAILURE. Omitted when no issue is reported."
              },
              "warnings": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/PublishWarning"
                }
              }
            }
          },
          "error": {
            "type": "string",
            "nullable": true
          },
          "action_required": {
            "type": "object",
            "nullable": true,
            "additionalProperties": true
          },
          "created_at": {
            "type": "string",
            "nullable": true
          },
          "updated_at": {
            "type": "string",
            "nullable": true
          },
          "started_at": {
            "type": "string",
            "nullable": true
          },
          "finished_at": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "GetJobResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "ok"
            ]
          },
          "data": {
            "type": "object",
            "properties": {
              "job": {
                "$ref": "#/components/schemas/JobData"
              },
              "warnings": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/AutoReplyWarning"
                }
              }
            }
          }
        }
      },
      "CancelJobResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "ok"
            ]
          },
          "data": {
            "type": "object",
            "properties": {
              "job_id": {
                "type": "integer"
              },
              "batch_id": {
                "type": "string",
                "nullable": true
              },
              "platform": {
                "$ref": "#/components/schemas/Platform"
              },
              "previous_status": {
                "type": "string"
              },
              "status": {
                "type": "string",
                "enum": [
                  "cancelled"
                ]
              },
              "completed_job_ids_preserved": {
                "type": "array",
                "items": {
                  "type": "integer"
                }
              }
            }
          }
        }
      },
      "AttachTikTokPostUrlResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "ok"
            ]
          },
          "data": {
            "type": "object",
            "properties": {
              "job_id": {
                "type": "integer"
              },
              "platform": {
                "type": "string",
                "enum": [
                  "tiktok"
                ]
              },
              "platform_post_id": {
                "type": "string"
              },
              "platform_post_url": {
                "type": "string",
                "format": "uri"
              },
              "analytics_refresh": {
                "type": "string",
                "enum": [
                  "queued"
                ]
              }
            }
          }
        }
      },
      "PublishWarning": {
        "type": "object",
        "required": [
          "code",
          "message"
        ],
        "properties": {
          "code": {
            "type": "string",
            "description": "Provider issue code. Future Instagram issue codes may also be returned."
          },
          "message": {
            "type": "string",
            "description": "What was missing from the published post and what to check."
          }
        }
      },
      "PostResult": {
        "type": "object",
        "properties": {
          "job_id": {
            "type": "integer"
          },
          "platform": {
            "$ref": "#/components/schemas/Platform"
          },
          "success": {
            "type": "boolean",
            "nullable": true
          },
          "platform_post_id": {
            "type": "string",
            "nullable": true
          },
          "platform_post_url": {
            "type": "string",
            "nullable": true
          },
          "shortcode": {
            "type": "string",
            "nullable": true
          },
          "platform_username": {
            "type": "string",
            "nullable": true
          },
          "post_type": {
            "type": "string",
            "nullable": true
          },
          "error_message": {
            "type": "string",
            "nullable": true
          },
          "error_code": {
            "type": "string",
            "nullable": true
          },
          "quota_reset_at": {
            "type": "string",
            "nullable": true,
            "description": "Expected YouTube project upload-quota reset time in ISO 8601 format when `error_code` is `youtube_project_upload_quota_exhausted`."
          },
          "retryable": {
            "type": "boolean",
            "nullable": true
          },
          "config_issue": {
            "type": "string",
            "nullable": true,
            "description": "Instagram's non-blocking publish issue, such as CAPTION_NOT_ATTACHED or USER_TAGGING_FAILURE. Null when none is reported. Success stays true; do not retry or republish the post because of this warning."
          },
          "warnings": {
            "type": "array",
            "description": "Warnings about missing content on an already published post. Empty when none are reported.",
            "items": {
              "$ref": "#/components/schemas/PublishWarning"
            }
          },
          "action_required": {
            "type": "object",
            "nullable": true,
            "additionalProperties": true
          },
          "retry_exhausted": {
            "type": "boolean",
            "nullable": true
          },
          "next_retry_at": {
            "type": "string",
            "nullable": true
          },
          "attempt": {
            "type": "integer",
            "nullable": true
          },
          "attempts_allowed": {
            "type": "integer",
            "nullable": true
          }
        }
      },
      "RetryInfo": {
        "type": "object",
        "properties": {
          "exhausted": {
            "type": "boolean"
          },
          "next_retry_at": {
            "type": "string",
            "nullable": true
          },
          "attempt": {
            "type": "integer"
          },
          "attempts_allowed": {
            "type": "integer"
          }
        }
      },
      "AnalyticsMetrics": {
        "type": "object",
        "properties": {
          "captured_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "impressions": {
            "type": "integer",
            "nullable": true
          },
          "reach": {
            "type": "integer",
            "nullable": true
          },
          "likes": {
            "type": "integer",
            "nullable": true
          },
          "comments": {
            "type": "integer",
            "nullable": true
          },
          "shares": {
            "type": "integer",
            "nullable": true
          },
          "clicks": {
            "type": "integer",
            "nullable": true
          },
          "views": {
            "type": "integer",
            "nullable": true
          },
          "saves": {
            "type": "integer",
            "nullable": true
          },
          "bookmarks": {
            "type": "integer",
            "nullable": true
          },
          "outbound_clicks": {
            "type": "integer",
            "nullable": true
          },
          "pin_clicks": {
            "type": "integer",
            "nullable": true
          },
          "url_clicks": {
            "type": "integer",
            "nullable": true
          },
          "profile_clicks": {
            "type": "integer",
            "nullable": true
          },
          "engaged_users": {
            "type": "integer",
            "nullable": true
          },
          "engagementRate": {
            "type": "number",
            "nullable": true,
            "description": "Interaction rate as a ratio, retained for API compatibility. Use the denominator field to interpret it."
          },
          "engagementRatePercent": {
            "type": "number",
            "nullable": true,
            "description": "The same interaction rate as a percentage."
          },
          "engagementRateDenominator": {
            "type": "string",
            "nullable": true,
            "enum": [
              "views",
              "impressions",
              "reach"
            ]
          },
          "metric_window": {
            "type": "string",
            "nullable": true,
            "enum": [
              "lifetime",
              "rolling_30d",
              "rolling_90d"
            ]
          },
          "metric_semantics_version": {
            "type": "integer",
            "nullable": true
          }
        }
      },
      "AnalyticsPostsResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "ok"
            ]
          },
          "data": {
            "type": "object",
            "properties": {
              "profile_id": {
                "$ref": "#/components/schemas/ProfileId",
                "nullable": true
              },
              "items": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/AnalyticsPostGroup"
                }
              },
              "next_cursor": {
                "type": "string",
                "nullable": true
              },
              "total": {
                "type": "integer",
                "description": "Count of matching post groups, independent of the current page."
              },
              "generated_at": {
                "type": "string",
                "format": "date-time"
              }
            }
          }
        }
      },
      "AnalyticsPostGroup": {
        "type": "object",
        "description": "A post group and its latest saved per-platform metrics. Flat totals may cover only some platforms; check each `*_available` and `*_partial` flag. A null metric means unavailable, not zero. `display_views` uses one count per platform, preferring views and otherwise using impressions.",
        "properties": {
          "id": {
            "type": "string"
          },
          "batch_id": {
            "type": "string",
            "nullable": true
          },
          "post_id": {
            "type": "integer",
            "nullable": true
          },
          "job_ids": {
            "type": "array",
            "items": {
              "type": "integer"
            }
          },
          "title": {
            "type": "string"
          },
          "message": {
            "type": "string"
          },
          "thumbnail_url": {
            "type": "string",
            "nullable": true
          },
          "media_type": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "successful",
              "scheduled",
              "failed",
              "processing",
              "partial"
            ]
          },
          "created_at": {
            "type": "string",
            "nullable": true
          },
          "posted_at": {
            "type": "string",
            "nullable": true
          },
          "platforms": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "platform_count": {
            "type": "integer"
          },
          "platform_metrics": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AnalyticsPostPlatformMetrics"
            }
          },
          "views": {
            "type": "integer",
            "nullable": true
          },
          "display_views": {
            "type": "integer",
            "nullable": true,
            "description": "Dashboard Views count. Uses views when reported, otherwise impressions, once per platform post."
          },
          "display_views_available": {
            "type": "boolean"
          },
          "display_views_partial": {
            "type": "boolean"
          },
          "display_view_sample_count": {
            "type": "integer",
            "description": "Number of platform posts with a reported view or impression count."
          },
          "average_display_views": {
            "type": "number",
            "nullable": true
          },
          "impressions": {
            "type": "integer",
            "nullable": true
          },
          "reach": {
            "type": "integer",
            "nullable": true
          },
          "likes": {
            "type": "integer",
            "nullable": true
          },
          "comments": {
            "type": "integer",
            "nullable": true
          },
          "shares": {
            "type": "integer",
            "nullable": true
          },
          "clicks": {
            "type": "integer",
            "nullable": true
          },
          "saves": {
            "type": "integer",
            "nullable": true
          },
          "bookmarks": {
            "type": "integer",
            "nullable": true
          },
          "outbound_clicks": {
            "type": "integer",
            "nullable": true
          },
          "pin_clicks": {
            "type": "integer",
            "nullable": true
          },
          "url_clicks": {
            "type": "integer",
            "nullable": true
          },
          "profile_clicks": {
            "type": "integer",
            "nullable": true
          },
          "engaged_users": {
            "type": "integer",
            "nullable": true
          },
          "engagement": {
            "type": "integer",
            "nullable": true
          },
          "engagement_rate": {
            "type": "number",
            "nullable": true,
            "description": "Percentage reported or derived for a comparable platform and reporting window. Check the denominator field."
          },
          "engagement_rate_denominator": {
            "type": "string",
            "nullable": true,
            "enum": [
              "views",
              "impressions",
              "reach"
            ]
          },
          "outbound_click_rate": {
            "type": "number",
            "nullable": true,
            "description": "Pinterest outbound clicks divided by impressions, as a percentage, when available for one platform."
          },
          "outbound_click_rate_denominator": {
            "type": "string",
            "nullable": true,
            "enum": [
              "impressions"
            ]
          },
          "url_click_rate": {
            "type": "number",
            "nullable": true,
            "description": "X URL clicks divided by impressions, as a percentage, when available for one platform."
          },
          "url_click_rate_denominator": {
            "type": "string",
            "nullable": true,
            "enum": [
              "impressions"
            ]
          },
          "metric_window": {
            "type": "string",
            "nullable": true,
            "enum": [
              "lifetime",
              "rolling_30d",
              "rolling_90d",
              "mixed"
            ]
          },
          "captured_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "analytics_available": {
            "type": "boolean"
          },
          "analytics_partial": {
            "type": "boolean"
          },
          "analytics_collecting": {
            "type": "boolean"
          },
          "analytics_reconnect_required": {
            "type": "boolean"
          }
        },
        "additionalProperties": true
      },
      "AnalyticsPostPlatformMetrics": {
        "type": "object",
        "description": "One platform's latest saved metric snapshot. Each numeric metric has a matching `*_available` flag; unsupported or uncollected values are null. `display_views` uses views when reported, otherwise impressions, without adding the two.",
        "properties": {
          "job_id": {
            "type": "integer"
          },
          "platform": {
            "$ref": "#/components/schemas/Platform"
          },
          "label": {
            "type": "string"
          },
          "platform_post_url": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string"
          },
          "analytics_status": {
            "type": "string",
            "enum": [
              "available",
              "collecting",
              "reconnect_required",
              "unavailable"
            ]
          },
          "captured_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "metric_window": {
            "type": "string",
            "nullable": true
          },
          "metric_semantics_version": {
            "type": "integer",
            "nullable": true
          },
          "display_views": {
            "type": "integer",
            "nullable": true
          },
          "display_views_available": {
            "type": "boolean"
          },
          "engagement_rate": {
            "type": "number",
            "nullable": true
          },
          "engagement_rate_denominator": {
            "type": "string",
            "nullable": true,
            "enum": [
              "views",
              "impressions",
              "reach"
            ]
          },
          "outbound_click_rate": {
            "type": "number",
            "nullable": true
          },
          "outbound_click_rate_denominator": {
            "type": "string",
            "nullable": true,
            "enum": [
              "impressions"
            ]
          },
          "url_click_rate": {
            "type": "number",
            "nullable": true
          },
          "url_click_rate_denominator": {
            "type": "string",
            "nullable": true,
            "enum": [
              "impressions"
            ]
          }
        },
        "additionalProperties": true
      },
      "AnalyticsItem": {
        "allOf": [
          {
            "type": "object",
            "properties": {
              "post_id": {
                "type": "integer"
              },
              "platform": {
                "$ref": "#/components/schemas/Platform"
              },
              "status": {
                "type": "string",
                "enum": [
                  "available",
                  "reconnect_required"
                ]
              },
              "status_message": {
                "type": "string",
                "nullable": true
              },
              "reconnect_required": {
                "type": "boolean"
              }
            }
          },
          {
            "$ref": "#/components/schemas/AnalyticsMetrics"
          }
        ]
      },
      "AudienceStatus": {
        "type": "string",
        "enum": [
          "available",
          "collecting",
          "permission_required",
          "reconnect_required",
          "unsupported",
          "error"
        ]
      },
      "AudienceMetric": {
        "type": "string",
        "enum": [
          "followers",
          "subscribers",
          "page_likes"
        ]
      },
      "AudienceAccount": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "nullable": true
          },
          "username": {
            "type": "string",
            "nullable": true
          },
          "avatar_url": {
            "type": "string",
            "format": "uri",
            "nullable": true
          }
        }
      },
      "AudienceItem": {
        "type": "object",
        "required": [
          "platform",
          "label",
          "account",
          "metric",
          "count",
          "status"
        ],
        "properties": {
          "platform": {
            "$ref": "#/components/schemas/PublicPlatform"
          },
          "label": {
            "type": "string"
          },
          "account": {
            "$ref": "#/components/schemas/AudienceAccount"
          },
          "metric": {
            "$ref": "#/components/schemas/AudienceMetric"
          },
          "count": {
            "type": "integer",
            "format": "int64",
            "minimum": 0,
            "nullable": true
          },
          "status": {
            "$ref": "#/components/schemas/AudienceStatus"
          },
          "status_message": {
            "type": "string",
            "nullable": true
          },
          "captured_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "last_attempt_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "next_update_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        }
      },
      "AudienceResponse": {
        "type": "object",
        "required": [
          "status",
          "data"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "ok"
            ]
          },
          "data": {
            "type": "object",
            "required": [
              "profile_id",
              "generated_at",
              "audience"
            ],
            "properties": {
              "profile_id": {
                "type": "string",
                "pattern": "^[A-Za-z0-9]{6,32}$",
                "nullable": true
              },
              "generated_at": {
                "type": "string",
                "format": "date-time"
              },
              "audience": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/AudienceItem"
                }
              }
            }
          }
        }
      },
      "Post": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "batch_id": {
            "type": "string",
            "nullable": true
          },
          "revision": {
            "type": "integer",
            "minimum": 0,
            "description": "Current edit revision when available."
          },
          "editable": {
            "type": "boolean",
            "description": "Whether the grouped post can still be edited."
          },
          "profile_id": {
            "type": "string",
            "pattern": "^[A-Za-z0-9]{6,32}$",
            "nullable": true
          },
          "job_ids": {
            "type": "array",
            "items": {
              "type": "integer"
            }
          },
          "auto_reply_enabled": {
            "type": "boolean"
          },
          "message": {
            "type": "string"
          },
          "media": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MediaItem"
            }
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "scheduled",
              "processing",
              "action_required",
              "partial",
              "completed",
              "failed",
              "cancelled"
            ]
          },
          "source": {
            "type": "string",
            "enum": [
              "api",
              "dashboard",
              "cli",
              "mcp",
              "make"
            ]
          },
          "created_at": {
            "type": "string"
          },
          "posted_at": {
            "type": "string",
            "nullable": true
          },
          "scheduled_at": {
            "type": "string",
            "nullable": true
          },
          "scheduled_timezone": {
            "type": "string",
            "nullable": true
          },
          "action_required": {
            "type": "object",
            "nullable": true,
            "additionalProperties": true
          },
          "platforms": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Platform"
            }
          },
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PostResult"
            }
          },
          "retry": {
            "$ref": "#/components/schemas/RetryInfo"
          },
          "analytics": {
            "type": "object",
            "additionalProperties": {
              "$ref": "#/components/schemas/AnalyticsMetrics"
            }
          }
        }
      },
      "ListPostsResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "ok"
            ]
          },
          "data": {
            "type": "object",
            "properties": {
              "posts": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Post"
                }
              },
              "warnings": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/AutoReplyWarning"
                }
              },
              "capabilities": {
                "$ref": "#/components/schemas/Capabilities"
              },
              "profile_id": {
                "type": "string",
                "pattern": "^[A-Za-z0-9]{6,32}$",
                "nullable": true
              },
              "meta": {
                "type": "object",
                "properties": {
                  "page": {
                    "type": "integer"
                  },
                  "per_page": {
                    "type": "integer"
                  },
                  "total": {
                    "type": "integer"
                  }
                }
              }
            }
          }
        }
      },
      "ScheduledPostDestination": {
        "type": "object",
        "properties": {
          "job_id": {
            "type": "integer"
          },
          "platform": {
            "$ref": "#/components/schemas/Platform"
          },
          "profile_id": {
            "type": "string",
            "nullable": true,
            "description": "Public connection profile ID, or null if that profile no longer exists."
          },
          "account_name": {
            "type": "string",
            "nullable": true
          },
          "account_handle": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "ScheduledPostDetail": {
        "type": "object",
        "required": [
          "id",
          "revision",
          "editable",
          "profile_id",
          "platforms",
          "message",
          "media",
          "scheduled_at"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "description": "Stable numeric ID for this grouped post, including after destinations change."
          },
          "batch_id": {
            "type": "string",
            "nullable": true
          },
          "job_ids": {
            "type": "array",
            "description": "Active Mallary job IDs for the current destinations.",
            "items": {
              "type": "integer"
            }
          },
          "revision": {
            "type": "integer",
            "minimum": 0
          },
          "editable": {
            "type": "boolean"
          },
          "edit_block_reason": {
            "type": "string",
            "nullable": true
          },
          "profile_id": {
            "type": "string",
            "nullable": true,
            "description": "Public connection profile ID, or null if that profile no longer exists."
          },
          "platforms": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Platform"
            }
          },
          "destinations": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ScheduledPostDestination"
            }
          },
          "message": {
            "type": "string"
          },
          "media": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MediaItem"
            }
          },
          "comments_under_post": {
            "type": "array",
            "maxItems": 3,
            "items": {
              "type": "object",
              "properties": {
                "content": {
                  "type": "string"
                }
              }
            }
          },
          "scheduled_at": {
            "type": "string",
            "nullable": true,
            "description": "Saved scheduled publish time."
          },
          "scheduled_timezone": {
            "type": "string",
            "nullable": true
          },
          "auto_reply_enabled": {
            "type": "boolean"
          },
          "platform_options": {
            "$ref": "#/components/schemas/PlatformOptions"
          }
        }
      },
      "GetScheduledPostResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "ok"
            ]
          },
          "data": {
            "$ref": "#/components/schemas/ScheduledPostDetail"
          }
        }
      },
      "UpdateScheduledPostDestinations": {
        "type": "object",
        "required": [
          "profile_id",
          "platforms"
        ],
        "properties": {
          "profile_id": {
            "$ref": "#/components/schemas/ProfileId",
            "description": "The target connection profile for the whole grouped post."
          },
          "platforms": {
            "type": "array",
            "minItems": 1,
            "uniqueItems": true,
            "description": "Complete final platform list. Platforms omitted from this list are removed from the scheduled post.",
            "items": {
              "$ref": "#/components/schemas/Platform"
            }
          }
        }
      },
      "UpdateScheduledPostRequest": {
        "type": "object",
        "required": [
          "expected_revision"
        ],
        "properties": {
          "expected_revision": {
            "type": "integer",
            "minimum": 1,
            "description": "Revision returned by `GET /api/v1/posts/{id}`. A stale value returns HTTP 409."
          },
          "message": {
            "type": "string",
            "minLength": 1
          },
          "media": {
            "type": "array",
            "description": "Replaces all saved media. Send an empty array to remove media.",
            "items": {
              "$ref": "#/components/schemas/MediaItem"
            }
          },
          "comments_under_post": {
            "type": "array",
            "maxItems": 3,
            "description": "Replaces all saved comments. Send an empty array to remove comments.",
            "items": {
              "oneOf": [
                {
                  "type": "string"
                },
                {
                  "type": "object",
                  "required": [
                    "content"
                  ],
                  "properties": {
                    "content": {
                      "type": "string"
                    }
                  }
                }
              ]
            }
          },
          "scheduled_at": {
            "type": "string",
            "description": "New future publish time. Send an absolute time or a local time with `scheduled_timezone`."
          },
          "scheduled_timezone": {
            "type": "string",
            "nullable": true,
            "description": "IANA timezone. Send null to clear it when `scheduled_at` is absolute."
          },
          "auto_reply_enabled": {
            "type": "boolean"
          },
          "platform_options": {
            "type": "object",
            "nullable": true,
            "description": "Complete replacement map of per-platform options. Send an empty object or null to clear all overrides.",
            "additionalProperties": {
              "type": "object"
            }
          },
          "destinations": {
            "$ref": "#/components/schemas/UpdateScheduledPostDestinations"
          }
        }
      },
      "DeletePostResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "ok"
            ]
          },
          "data": {
            "type": "object",
            "properties": {
              "deleted_count": {
                "type": "integer"
              },
              "deleted_job_ids": {
                "type": "array",
                "items": {
                  "type": "integer"
                }
              },
              "batch_id": {
                "type": "string",
                "nullable": true
              }
            }
          }
        }
      },
      "DisconnectRequest": {
        "type": "object",
        "required": [
          "platform"
        ],
        "properties": {
          "platform": {
            "type": "string"
          },
          "profile_id": {
            "$ref": "#/components/schemas/ProfileId",
            "description": "Connection profile to disconnect from. Omit to use the default profile."
          }
        }
      },
      "PlatformProfile": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "nullable": true
          },
          "username": {
            "type": "string",
            "nullable": true
          },
          "avatar": {
            "type": "string",
            "nullable": true
          },
          "scope": {
            "type": "string",
            "nullable": true
          },
          "updated_at": {
            "type": "string",
            "nullable": true
          },
          "expires_at": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "ConnectionProfile": {
        "type": "object",
        "properties": {
          "id": {
            "$ref": "#/components/schemas/ProfileId"
          },
          "name": {
            "type": "string"
          },
          "is_default": {
            "type": "boolean"
          },
          "connected_platforms": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PublicPlatform"
            }
          },
          "platform_profiles": {
            "type": "object",
            "additionalProperties": {
              "$ref": "#/components/schemas/PlatformProfile"
            }
          },
          "created_at": {
            "type": "string",
            "nullable": true
          },
          "updated_at": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "ProfileLimits": {
        "type": "object",
        "properties": {
          "max_accounts_per_platform": {
            "type": "integer",
            "description": "Compatibility field for per-platform limits. Use max_accounts_total when account_limit_scope is total."
          },
          "max_accounts_total": {
            "type": "integer",
            "nullable": true,
            "description": "Total connected social accounts allowed across platforms and profiles. Null for a platform-based contract."
          },
          "account_limit_scope": {
            "type": "string",
            "enum": [
              "total",
              "platform"
            ],
            "description": "Whether the account allowance is pooled or counted separately per platform."
          }
        }
      },
      "ProfileListResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "ok"
            ]
          },
          "data": {
            "type": "object",
            "properties": {
              "profiles": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/ConnectionProfile"
                }
              },
              "default_profile_id": {
                "type": "string",
                "pattern": "^[A-Za-z0-9]{6,32}$",
                "nullable": true
              },
              "limits": {
                "$ref": "#/components/schemas/ProfileLimits"
              }
            }
          }
        }
      },
      "CreateConnectionProfileRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "description": "Profile display name. Defaults to `New profile`."
          }
        }
      },
      "RenameConnectionProfileRequest": {
        "type": "object",
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string"
          }
        }
      },
      "ProfileMutationResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "ok"
            ]
          },
          "data": {
            "type": "object",
            "properties": {
              "profile_id": {
                "type": "string",
                "pattern": "^[A-Za-z0-9]{6,32}$",
                "nullable": true
              },
              "profiles": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/ConnectionProfile"
                }
              }
            }
          }
        }
      },
      "AutoReplyProfileState": {
        "type": "object",
        "properties": {
          "ready": {
            "type": "boolean"
          },
          "missing_fields": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "SettingsUpdateRequest": {
        "type": "object",
        "properties": {
          "profile_id": {
            "$ref": "#/components/schemas/ProfileId",
            "description": "Connection profile settings to update. Omit to use the default profile."
          },
          "auto_reply_enabled": {
            "type": "boolean",
            "description": "Enables AI auto-replies for this profile when the plan and profile fields allow it."
          },
          "brand_profile": {
            "type": "string"
          },
          "business_name": {
            "type": "string"
          },
          "business_description": {
            "type": "string"
          },
          "website_url": {
            "type": "string"
          },
          "services": {
            "type": "string"
          },
          "features": {
            "type": "string"
          },
          "contact_info": {
            "type": "string"
          },
          "pricing": {
            "type": "string"
          },
          "faq": {
            "type": "string"
          }
        }
      },
      "SettingsResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "ok"
            ]
          },
          "data": {
            "type": "object",
            "properties": {
              "profile_id": {
                "type": "string",
                "pattern": "^[A-Za-z0-9]{6,32}$",
                "nullable": true
              },
              "auto_reply_enabled": {
                "type": "boolean"
              },
              "capabilities": {
                "$ref": "#/components/schemas/Capabilities"
              },
              "auto_reply_profile": {
                "$ref": "#/components/schemas/AutoReplyProfileState"
              },
              "warnings": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/AutoReplyWarning"
                }
              },
              "brand_profile": {
                "type": "string"
              },
              "business_name": {
                "type": "string"
              },
              "business_description": {
                "type": "string"
              },
              "website_url": {
                "type": "string"
              },
              "services": {
                "type": "string"
              },
              "features": {
                "type": "string"
              },
              "contact_info": {
                "type": "string"
              },
              "pricing": {
                "type": "string"
              },
              "faq": {
                "type": "string"
              },
              "business_profile_prefill_at": {
                "type": "string",
                "nullable": true
              },
              "business_profile_prefill_next_allowed_at": {
                "type": "string",
                "nullable": true
              },
              "business_profile_prefill_retry_after_seconds": {
                "type": "integer"
              }
            }
          }
        }
      },
      "ConnectedPlatformItem": {
        "type": "object",
        "properties": {
          "platform": {
            "$ref": "#/components/schemas/PublicPlatform"
          },
          "connected": {
            "type": "boolean"
          },
          "post_types": {
            "type": "array",
            "description": "Exact values accepted as `platform_options.<platform>.post_type`. Empty when Mallary selects the format from the content and media.",
            "items": {
              "type": "string"
            }
          },
          "post_type_selection": {
            "type": "string",
            "enum": [
              "explicit",
              "automatic"
            ],
            "description": "Whether callers may select a listed post type or Mallary determines the format from content and media."
          }
        }
      },
      "ConnectedPlatformsCounts": {
        "type": "object",
        "properties": {
          "connected": {
            "type": "integer"
          },
          "supported": {
            "type": "integer"
          }
        }
      },
      "ConnectedPlatformsResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "ok"
            ]
          },
          "data": {
            "type": "object",
            "properties": {
              "platforms": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/ConnectedPlatformItem"
                }
              },
              "profile_id": {
                "type": "string",
                "pattern": "^[A-Za-z0-9]{6,32}$",
                "nullable": true
              },
              "default_profile_id": {
                "type": "string",
                "pattern": "^[A-Za-z0-9]{6,32}$",
                "nullable": true
              },
              "profiles": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/ConnectionProfile"
                }
              },
              "connected": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/PublicPlatform"
                }
              },
              "disconnected": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/PublicPlatform"
                }
              },
              "counts": {
                "$ref": "#/components/schemas/ConnectedPlatformsCounts"
              },
              "limits": {
                "$ref": "#/components/schemas/ProfileLimits"
              }
            }
          }
        }
      },
      "DisconnectResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "ok"
            ]
          },
          "platform": {
            "type": "string"
          },
          "profile_id": {
            "type": "string",
            "pattern": "^[A-Za-z0-9]{6,32}$",
            "nullable": true
          }
        }
      },
      "Webhook": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WebhookEvent"
            }
          },
          "active": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "WebhookEvent": {
        "type": "string",
        "description": "Use '*' to receive all events.",
        "enum": [
          "*",
          "post.scheduled",
          "post.published",
          "post.failed",
          "post.partial",
          "post.delayed",
          "post.job_failed",
          "auto_reply.posted"
        ]
      },
      "DraftContent": {
        "type": "object",
        "additionalProperties": false,
        "description": "Unpublished content. All fields are optional; no scheduled date or connected destination is needed to save. Supplied arrays and platform_options replace the saved value when editing. Media must already be uploaded to files.mallary.ai.",
        "properties": {
          "profile_id": {
            "$ref": "#/components/schemas/ProfileId"
          },
          "message": {
            "type": "string",
            "maxLength": 63206
          },
          "platforms": {
            "type": "array",
            "maxItems": 10,
            "items": {
              "type": "string",
              "enum": [
                "facebook",
                "meta",
                "instagram",
                "threads",
                "twitter",
                "x",
                "linkedin",
                "tiktok",
                "youtube",
                "pinterest",
                "reddit",
                "bluesky"
              ]
            }
          },
          "media": {
            "type": "array",
            "maxItems": 20,
            "items": {
              "type": "object",
              "required": [
                "url"
              ],
              "properties": {
                "url": {
                  "type": "string",
                  "format": "uri",
                  "pattern": "^https://files\\.mallary\\.ai/"
                }
              },
              "additionalProperties": true
            }
          },
          "comments_under_post": {
            "type": "array",
            "maxItems": 3,
            "items": {
              "oneOf": [
                {
                  "type": "string",
                  "maxLength": 10000
                },
                {
                  "type": "object",
                  "required": [
                    "content"
                  ],
                  "additionalProperties": false,
                  "properties": {
                    "content": {
                      "type": "string",
                      "maxLength": 10000
                    }
                  }
                }
              ]
            }
          },
          "platform_options": {
            "type": "object",
            "nullable": true,
            "additionalProperties": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "auto_reply_enabled": {
            "type": "boolean"
          }
        }
      },
      "Draft": {
        "type": "object",
        "required": [
          "id",
          "revision",
          "status",
          "profile_id"
        ],
        "properties": {
          "profile_id": {
            "$ref": "#/components/schemas/ProfileId"
          },
          "message": {
            "type": "string",
            "maxLength": 63206
          },
          "platforms": {
            "type": "array",
            "maxItems": 10,
            "items": {
              "type": "string",
              "enum": [
                "facebook",
                "meta",
                "instagram",
                "threads",
                "twitter",
                "x",
                "linkedin",
                "tiktok",
                "youtube",
                "pinterest",
                "reddit",
                "bluesky"
              ]
            }
          },
          "media": {
            "type": "array",
            "maxItems": 20,
            "items": {
              "type": "object",
              "required": [
                "url"
              ],
              "properties": {
                "url": {
                  "type": "string",
                  "format": "uri",
                  "pattern": "^https://files\\.mallary\\.ai/"
                }
              },
              "additionalProperties": true
            }
          },
          "comments_under_post": {
            "type": "array",
            "maxItems": 3,
            "items": {
              "oneOf": [
                {
                  "type": "string",
                  "maxLength": 10000
                },
                {
                  "type": "object",
                  "required": [
                    "content"
                  ],
                  "additionalProperties": false,
                  "properties": {
                    "content": {
                      "type": "string",
                      "maxLength": 10000
                    }
                  }
                }
              ]
            }
          },
          "platform_options": {
            "type": "object",
            "nullable": true,
            "additionalProperties": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "auto_reply_enabled": {
            "type": "boolean"
          },
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "revision": {
            "type": "integer",
            "minimum": 1
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "submitted"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "submission": {
            "$ref": "#/components/schemas/CreatePostResponse"
          }
        }
      },
      "DraftResponse": {
        "type": "object",
        "required": [
          "status",
          "data"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "ok"
            ]
          },
          "data": {
            "$ref": "#/components/schemas/Draft"
          }
        }
      },
      "ListDraftsResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "ok"
            ]
          },
          "data": {
            "type": "object",
            "properties": {
              "drafts": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Draft"
                }
              },
              "meta": {
                "type": "object",
                "properties": {
                  "page": {
                    "type": "integer"
                  },
                  "per_page": {
                    "type": "integer"
                  },
                  "total": {
                    "type": "integer"
                  }
                }
              }
            }
          }
        }
      },
      "UpdateDraftRequest": {
        "type": "object",
        "additionalProperties": false,
        "description": "Unpublished content. All fields are optional; no scheduled date or connected destination is needed to save. Supplied arrays and platform_options replace the saved value when editing. Media must already be uploaded to files.mallary.ai.",
        "properties": {
          "profile_id": {
            "$ref": "#/components/schemas/ProfileId"
          },
          "message": {
            "type": "string",
            "maxLength": 63206
          },
          "platforms": {
            "type": "array",
            "maxItems": 10,
            "items": {
              "type": "string",
              "enum": [
                "facebook",
                "meta",
                "instagram",
                "threads",
                "twitter",
                "x",
                "linkedin",
                "tiktok",
                "youtube",
                "pinterest",
                "reddit",
                "bluesky"
              ]
            }
          },
          "media": {
            "type": "array",
            "maxItems": 20,
            "items": {
              "type": "object",
              "required": [
                "url"
              ],
              "properties": {
                "url": {
                  "type": "string",
                  "format": "uri",
                  "pattern": "^https://files\\.mallary\\.ai/"
                }
              },
              "additionalProperties": true
            }
          },
          "comments_under_post": {
            "type": "array",
            "maxItems": 3,
            "items": {
              "oneOf": [
                {
                  "type": "string",
                  "maxLength": 10000
                },
                {
                  "type": "object",
                  "required": [
                    "content"
                  ],
                  "additionalProperties": false,
                  "properties": {
                    "content": {
                      "type": "string",
                      "maxLength": 10000
                    }
                  }
                }
              ]
            }
          },
          "platform_options": {
            "type": "object",
            "nullable": true,
            "additionalProperties": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "auto_reply_enabled": {
            "type": "boolean"
          },
          "expected_revision": {
            "type": "integer",
            "minimum": 1,
            "description": "Current revision from a draft read or save."
          }
        },
        "required": [
          "expected_revision"
        ]
      },
      "DraftRevisionRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "expected_revision"
        ],
        "properties": {
          "expected_revision": {
            "type": "integer",
            "minimum": 1,
            "description": "Current revision from a draft read or save."
          }
        }
      },
      "SubmitDraftRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "expected_revision"
        ],
        "properties": {
          "expected_revision": {
            "type": "integer",
            "minimum": 1,
            "description": "Current revision from a draft read or save."
          },
          "scheduled_at": {
            "type": "string",
            "minLength": 1,
            "maxLength": 80,
            "description": "Omit to publish now. Absolute time or local time paired with scheduled_timezone."
          },
          "scheduled_timezone": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100,
            "description": "IANA time zone for a local scheduled_at; paid scheduling rules apply."
          }
        }
      },
      "DeleteDraftResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "ok"
            ]
          },
          "data": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "deleted": {
                "type": "boolean"
              }
            }
          }
        }
      },
      "WebhookDeliveryHistory": {
        "type": "object",
        "properties": {
          "deliveries": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WebhookDelivery"
            }
          },
          "next_before": {
            "type": "integer",
            "nullable": true
          }
        }
      },
      "WebhookDelivery": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "event_id": {
            "type": "string"
          },
          "event": {
            "$ref": "#/components/schemas/WebhookEvent"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "delivering",
              "retrying",
              "delivered",
              "failed",
              "cancelled"
            ]
          },
          "attempts": {
            "type": "integer"
          },
          "next_attempt_at": {
            "type": "string",
            "nullable": true
          },
          "last_status_code": {
            "type": "integer",
            "nullable": true
          },
          "last_error_code": {
            "type": "string",
            "nullable": true
          },
          "created_at": {
            "type": "string"
          },
          "delivered_at": {
            "type": "string",
            "nullable": true
          },
          "attempt_history": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "attempt_number": {
                  "type": "integer"
                },
                "started_at": {
                  "type": "string"
                },
                "finished_at": {
                  "type": "string",
                  "nullable": true
                },
                "status_code": {
                  "type": "integer",
                  "nullable": true
                },
                "error_code": {
                  "type": "string",
                  "nullable": true
                }
              }
            }
          }
        }
      }
    }
  },
  "externalDocs": {
    "description": "Full product docs",
    "url": "https://docs.mallary.ai/"
  }
}
