addrbookFor publishers
← The evidence ledger

Evidence row 1226289

Observed , by probe.mcp/1.

Kind
mcp.tools
About
endpoint b7b10d45-f6fb-49e6-abb9-091c4a879468
Recorded
Previous row
None: the first of its kind about this subject
Body SHA-256
b715ce9da6d51d9b61a6a9a8ac702912c6582cabec21462eafa36c0de4cfdc24
Leaf
0xa3df3afc50f77b0ae35f09dde3a06829c2fff638f6682d710bb58cb3149ee45e

What was observed

{
  "count": 57,
  "tools": [
    {
      "description": "[PUBLISHES to social accounts · `publish` scope] approve lets a rule's reshare / comment go out at its time; retry re-sends a failed one (same idempotency key); reject / cancel stop it. Confirm with the user before approving. Needs the `publish` scope (or admin) on the Agency plan or higher.\n(Wraps POST /api/v2/automations/actions/{id}.)",
      "inputSchema": {
        "additionalProperties": false,
        "properties": {
          "id": {
            "type": "string"
          },
          "op": {
            "enum": [
              "approve",
              "reject",
              "retry",
              "cancel"
            ],
            "type": "string"
          }
        },
        "required": [
          "id",
          "op"
        ],
        "type": "object"
      },
      "name": "act_on_publish_action"
    },
    {
      "description": "[PUBLISHES to social accounts · `publish` scope] Schedules comments on a post, sent once after it is live on that platform and its tokens resolve ({{postUrl}}, {{parentPostUrl:<post id>}}, {{youtubeUrl:<post id>}}), optionally at sendAt. Confirm with the user first. Needs the `publish` scope (or admin) on the Agency plan or higher.\n(Wraps POST /api/v2/posts/{id}/comments.)",
      "inputSchema": {
        "additionalProperties": false,
        "properties": {
          "comments": {
            "items": {
              "properties": {
                "accountId": {
                  "description": "Default: the account the post went out through on that platform.",
                  "type": "string"
                },
                "delayMinutes": {
                  "minimum": 0,
                  "type": "integer"
                },
                "platform": {
                  "type": "string"
                },
                "sendAt": {
                  "description": "Not before this time.",
                  "format": "date-time",
                  "type": "string"
                },
                "text": {
                  "type": "string"
                }
              },
              "required": [
                "platform",
                "text"
              ],
              "type": "object"
            },
            "maxItems": 20,
            "type": "array"
          },
          "id": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "comments"
        ],
        "type": "object"
      },
      "name": "add_post_comments"
    },
    {
      "description": "[PUBLISHES to social accounts · `publish` scope] Appends drafts (content ids) to the end of a queue — they will be BOOKED AND POSTED on the queue's schedule. Use `contentIds`, or `items` for a per-item YouTube title or platforms. Drafts of another brand, duplicates, archived drafts and drafts without a video for a video platform come back in `rejected`. Needs the `publish` scope (or admin) on the Agency plan or higher.\n(Wraps POST /api/v2/queues/{id}/items.)",
      "inputSchema": {
        "additionalProperties": false,
        "properties": {
          "brandId": {
            "type": "string"
          },
          "contentIds": {
            "items": {
              "type": "string"
            },
            "maxItems": 200,
            "type": "array"
          },
          "id": {
            "type": "string"
          },
          "items": {
            "items": {
              "properties": {
                "contentId": {
                  "type": "string"
                },
                "platforms": {
                  "description": "Post this item to these platforms instead of the queue's.",
                  "items": {
                    "type": "string"
                  },
                  "minItems": 1,
                  "type": "array"
                },
                "youtubeTitle": {
                  "description": "The video's YouTube title. Omitted → the draft's stored title, else derived from its caption.",
                  "maxLength": 100,
                  "type": "string"
                }
              },
              "required": [
                "contentId"
              ],
              "type": "object"
            },
            "maxItems": 200,
            "type": "array"
          }
        },
        "required": [
          "id"
        ],
        "type": "object"
      },
      "name": "add_queue_items"
    },
    {
      "description": "[writes flayr data · `write` scope] Draws a frame over an image and returns a new image URL (the original is untouched). Use the URL in create_content, or pass contentId + platform so that post publishes the framed image on that platform. Free by default; the same inputs again are free.\n(Wraps POST /api/v2/frames/apply.)",
      "inputSchema": {
        "additionalProperties": false,
        "properties": {
          "aspect": {
            "description": "Default: nearest to the source image.",
            "enum": [
              "1:1",
              "4:5",
              "9:16",
              "16:9"
            ],
            "type": "string"
          },
          "contentId": {
            "description": "A content id: its publish swaps this framed image in for `platform`.",
            "type": "string"
          },
          "fit": {
            "enum": [
              "cover",
              "contain"
            ],
            "type": "string"
          },
          "frameRef": {
            "description": "`default:<key>` or `brand:<id>` from GET /api/v2/frames.",
            "type": "string"
          },
          "offsetX": {
            "maximum": 1,
            "minimum": -1,
            "type": "number"
          },
          "offsetY": {
            "maximum": 1,
            "minimum": -1,
            "type": "number"
          },
          "paletteOverride": {
            "description": "Colors that win over the brand kit.",
            "properties": {
              "accent": {
                "pattern": "^#[0-9a-fA-F]{6}$",
                "type": "string"
              },
              "ink": {
                "pattern": "^#[0-9a-fA-F]{6}$",
                "type": "string"
              },
              "onPrimary": {
                "pattern": "^#[0-9a-fA-F]{6}$",
                "type": "string"
              },
              "paper": {
                "pattern": "^#[0-9a-fA-F]{6}$",
                "type": "string"
              },
              "primary": {
                "pattern": "^#[0-9a-fA-F]{6}$",
                "type": "string"
              },
              "secondary": {
                "pattern": "^#[0-9a-fA-F]{6}$",
                "type": "string"
              }
            },
            "type": "object"
          },
          "platform": {
            "description": "Platform this framed image is for (\"all\" by default).",
            "type": "string"
          },
          "slideIndex": {
            "description": "Image position in the post.",
            "minimum": 0,
            "type": "integer"
          },
          "sourceUrl": {
            "description": "https image URL (flayr/Convex storage or a stock host).",
            "type": "string"
          },
          "texts": {
            "description": "Texts some frames print (ribbon/tag label, lower-third title/subtitle).",
            "properties": {
              "label": {
                "maxLength": 60,
                "type": "string"
              },
              "subtitle": {
                "maxLength": 60,
                "type": "string"
              },
              "title": {
                "maxLength": 60,
                "type": "string"
              }
            },
            "type": "object"
          },
          "zoom": {
            "maximum": 2,
            "minimum": 1,
            "type": "number"
          }
        },
        "required": [
          "sourceUrl",
          "frameRef"
        ],
        "type": "object"
      },
      "name": "apply_frame"
    },
    {
      "description": "[PUBLISHES to social accounts · `publish` scope] Takes a SCHEDULED post off the platform schedule and marks it cancelled (the draft stays, so it can be published again). Safe to repeat. Refused within 5 minutes of the slot, and when the platform will not remove it (some YouTube uploads). Needs the `publish` scope (or admin) and the Pro plan or higher; each publish counts against the plan's direct posts for the month. Confirm with the user first.\n(Wraps DELETE /api/v2/posts/{id}.)",
      "inputSchema": {
        "additionalProperties": false,
        "properties": {
          "id": {
            "description": "The post: `convexPostId` from POST /api/v2/publish, or `posts[].id` from GET /api/v2/content/{id}.",
            "type": "string"
          }
        },
        "required": [
          "id"
        ],
        "type": "object"
      },
      "name": "cancel_scheduled_post"
    },
    {
      "description": "[writes flayr data · `write` scope] Extends a video draft (`id`) with more clips and image cards into ONE finished video, rendered by flayr and saved as a NEW linked draft (same brand, caption copied, credits merged, labelled variant:combined). Nothing is posted and the original is unchanged. Use it to show a finished print with painting/assembly clips before or after it. `segments` (2-8, in order) must include the video itself as { kind: \"clip\", contentId: id }; clips are Library videos (contentId) or uploads (storageId from upload_media) with inMs/outMs (or durationMs = the clip's full length); cards are images (contentId of a Library image or video thumbnail, or imageStorageId) with durationMs and optional text. `transition` cut|crossfade, `audio` keep|mute_extras|main_across. Max 60 min by default (long-form: cold open + trimmed recording + bumpers). Costs 25 Embers per started minute of output for the first 3 min, then 5 per further started minute by default (refunded on failure). Returns a jobId — poll get_job_status.\n(Wraps POST /api/v2/content/{id}/combine.)",
      "inputSchema": {
        "additionalProperties": false,
        "properties": {
          "audio": {
            "default": "keep",
            "enum": [
              "keep",
              "mute_extras",
              "main_across"
            ],
            "type": "string"
          },
          "crossfadeMs": {
            "default": 400,
            "description": "Crossfade overlap per join.",
            "maximum": 500,
            "minimum": 300,
            "type": "integer"
          },
          "id": {
            "description": "The video to extend.",
            "type": "string"
          },
          "segments": {
            "description": "In playback order. Must include this video ({ \"kind\": \"clip\", \"contentId\": \"<id>\" }).",
            "items": {
              "additionalProperties": false,
              "properties": {
                "contentId": {
                  "description": "clip: a Library video. card: a Library image, or a video (its thumbnail).",
                  "type": "string"
                },
                "durationMs": {
                  "description": "clip: the source clip's full length (needed when outMs is omitted). card: how long it shows (1000-10000).",
                  "minimum": 1,
                  "type": "integer"
                },
                "imageStorageId": {
                  "description": "card only: an uploaded image.",
                  "type": "string"
                },
                "inMs": {
                  "description": "clip only: where the clip starts (default 0).",
                  "minimum": 0,
                  "type": "integer"
                },
                "kind": {
                  "enum": [
                    "clip",
                    "card"
                  ],
                  "type": "string"
                },
                "outMs": {
                  "description": "clip only: where the clip ends. Defaults to durationMs.",
                  "minimum": 0,
                  "type": "integer"
                },
                "storageId": {
                  "description": "clip only: an uploaded video (POST /api/v2/media/upload-url).",
                  "type": "string"
                },
                "text": {
                  "description": "card only: one short line over the image.",
                  "maxLength": 80,
                  "type": "string"
                }
              },
              "required": [
                "kind"
              ],
              "type": "object"
            },
            "maxItems": 8,
            "minItems": 2,
            "type": "array"
          },
          "transition": {
            "default": "cut",
            "enum": [
              "cut",
              "crossfade"
            ],
            "type": "string"
          }
        },
        "required": [
          "id",
          "segments"
        ],
        "type": "object"
      },
      "name": "combine_clips"
    },
    {
      "description": "[read-only] Splits every labelled post by the values of one label key (e.g. key \"cut\" → cut:opus vs cut:raw) and reports medians with n per group at the 48h / 7d snapshot. Descriptive, not a significance test. Pro plan or higher.\n(Wraps GET /api/v2/analytics/experiments.)",
      "inputSchema": {
        "additionalProperties": false,
        "properties": {
          "brandId": {
            "description": "A brand id, or \"all\" (default: every profile you can see). A brand-bound key is always its own brand.",
            "type": "string"
          },
          "include": {
            "enum": [
              "posts"
            ],
            "type": "string"
          },
          "key": {
            "default": "cut",
            "type": "string"
          },
          "metric": {
            "default": "impressions",
            "description": "Which metric \"leading\" ranks by.",
            "enum": [
              "impressions",
              "views",
              "engagement",
              "engagementRate",
              "clicks"
            ],
            "type": "string"
          },
          "window": {
            "default": "48h",
            "enum": [
              "48h",
              "7d",
              "latest"
            ],
            "type": "string"
          }
        },
        "type": "object"
      },
      "name": "compare_labels"
    },
    {
      "description": "[writes flayr data · `write` scope] Creates a DRAFT content item. Nothing is posted. Give `text` plus optional media (`videoUrl` / `imageUrl`, or `videoStorageId` / `imageStorageId` from upload_media), target `platforms`, `brandId` (from list_brands; omitted → the brand last active in the dashboard), per-platform captions (`platformText`), creator `credits`, `labels` and YouTube options. Returns the new `id` — pass it as `contentId` to publish or add_queue_items.\n(Wraps POST /api/v2/content.)",
      "inputSchema": {
        "additionalProperties": false,
        "properties": {
          "brandId": {
            "description": "An id from GET /api/v2/brands, or \"personal\".",
            "type": "string"
          },
          "contentType": {
            "type": "string"
          },
          "copyFromContentId": {
            "description": "COPY TO BRAND (staff and the Agency plan): the id of an existing item (GET /api/v2/content) to copy into `brandId` as a new draft. Images, video, slides, documents and thumbnails are copied into new storage objects, so deleting the source never breaks the copy. Combine only with `brandId` (required, a brand id — not \"personal\") and optionally `text` (replaces the caption; omitted = the source caption). Labels, mentions, campaign/experiment links and post history are not copied. Needs an account-wide key. Errors: 403 below the Agency plan or with a brand-bound key; 404 unknown item or brand; 409 the item changed mid-copy; 413 the brand's storage is full; 422 the item is already in that brand or its media is missing.",
            "type": "string"
          },
          "credits": {
            "description": "Creator credits for work this post uses. Added to the published caption above the flayr footer, one line each: model → \"Print your own: <url> (model by <author>)\"; other kinds → \"Credit: <title> by <author> <url>\". Never added twice (a URL already in the caption is skipped); on X a shorter line (or no URL) is used, or the credits are left out, before the caption text is ever cut. YouTube: description only, never the title.",
            "items": {
              "additionalProperties": false,
              "properties": {
                "author": {
                  "description": "Designer / artist / photographer.",
                  "maxLength": 200,
                  "type": "string"
                },
                "kind": {
                  "enum": [
                    "model",
                    "music",
                    "footage",
                    "image",
                    "other"
                  ],
                  "type": "string"
                },
                "site": {
                  "description": "Detected from the URL when omitted (e.g. MakerWorld, Printables, Thingiverse).",
                  "maxLength": 200,
                  "type": "string"
                },
                "title": {
                  "maxLength": 200,
                  "type": "string"
                },
                "url": {
                  "description": "Absolute http(s) link to the credited work.",
                  "format": "uri",
                  "maxLength": 2048,
                  "type": "string"
                }
              },
              "required": [
                "kind",
                "url"
              ],
              "type": "object"
            },
            "maxItems": 10,
            "type": "array"
          },
          "imageStorageId": {
            "type": "string"
          },
          "imageUrl": {
            "type": "string"
          },
          "labels": {
            "description": "Normalized \"key:value\" labels (keys and values are lower-cased, spaces become \"-\"). Linked keys: campaign:<media campaign id>, experiment:<experiment id> (+ variant:<name> or the experiment's compareKey), type:<post type> (short, long-form, carousel, image, text, reel, story, poem, timelapse…). Free keys work too: cut:opus, lane:tech, parent:pantheon-part-1, ab:<pair id> (an A/B pair). Labels are copied onto every post made from the draft, and compared in GET /api/v2/analytics/experiments.",
            "items": {
              "pattern": "^[a-z0-9][a-z0-9_-]{0,31}:[a-z0-9][a-z0-9._\\-/+]{0,79}$",
              "type": "string"
            },
            "maxItems": 30,
            "type": "array"
          },
          "platformText": {
            "additionalProperties": {
              "maxLength": 10000,
              "type": [
                "string",
                "null"
              ]
            },
            "description": "Per-platform captions: platform id → the caption that platform gets instead of `text`, e.g. { \"twitter\": \"short version\" }. Each must fit its platform: X 280 (a link counts 23, an emoji 2), Bluesky 300, Threads 500, Instagram 2200, LinkedIn 3000. Platforms without one get `text` when it fits, else an automatic short version: the first line, the credits in short form, as many hashtags as fit and the short flayr link (hashtags go first, then the credit link becomes \"Model by <author>\"; words are never cut). PATCH merges: a null (or \"\") value clears that platform, platformText: null clears all. At publish every platform is validated on its own caption: one failing platform no longer fails the others — the post publishes to the rest and reports the failures in platformErrors.",
            "type": "object"
          },
          "platforms": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "referralFooter": {
            "description": "Caption watermark: appends \"Posted with flayr.social — try it: flayr.social/r/<code>/<ch>\" to each platform's text (<code> is your referral code — your custom one if you claimed it; <ch> names the platform: yt, x, li, ig, tt, fb, th, bs). (YouTube: description only, never the title; X: the shorter \"via flayr.social/r/<code>/x\" or nothing when it would not fit — your text is never cut). On by default. false removes it only on plans that can remove the Flayr watermark (Pro and up); omitted follows your watermark setting.",
            "type": "boolean"
          },
          "sourceLabel": {
            "maxLength": 80,
            "type": "string"
          },
          "text": {
            "maxLength": 10000,
            "minLength": 1,
            "type": "string"
          },
          "videoStorageId": {
            "type": "string"
          },
          "videoUrl": {
            "type": "string"
          },
          "youtube": {
            "description": "Stored on the draft and used when it is published (POST /api/v2/publish with contentId). YouTube upload options, applied to YouTube targets only. madeForKids is always sent to YouTube (default false). A missing title is derived from the first sentence of the caption, cut to 100 characters at a word boundary. SCHEDULED UPLOADS: the video is uploaded ahead of scheduledAt. \"public\" is uploaded as private and released by YouTube at the scheduled time; \"unlisted\" is watchable by anyone with the link from upload time; \"private\" stays private.",
            "properties": {
              "categoryId": {
                "description": "YouTube category id, e.g. \"28\" (Science & Technology). YouTube default: 22.",
                "pattern": "^\\d{1,3}$",
                "type": "string"
              },
              "containsSyntheticMedia": {
                "description": "AI-generated content disclosure.",
                "type": "boolean"
              },
              "firstComment": {
                "description": "For scheduled videos, posted at the scheduled time.",
                "maxLength": 10000,
                "type": "string"
              },
              "madeForKids": {
                "default": false,
                "type": "boolean"
              },
              "playlistId": {
                "description": "From GET /api/v2/youtube/playlists for the same brand.",
                "type": "string"
              },
              "tags": {
                "description": "At most 500 characters combined.",
                "items": {
                  "maxLength": 100,
                  "type": "string"
                },
                "type": "array"
              },
              "title": {
                "description": "No < or >.",
                "maxLength": 100,
                "minLength": 1,
                "type": "string"
              },
              "visibility": {
                "default": "public",
                "enum": [
                  "public",
                  "private",
                  "unlisted"
                ],
                "type": "string"
              }
            },
            "type": "object"
          }
        },
        "type": "object"
      },
      "name": "create_content"
    },
    {
      "description": "[writes flayr data · `write` scope] Creates an A/B experiment. Then tag drafts with its `label` (experiment:<id>) and `<compareKey>:<variant>` via update_content `labels`, publish them, and read get_experiment_results.\n(Wraps POST /api/v2/experiments.)",
      "inputSchema": {
        "additionalProperties": false,
        "properties": {
          "brandId": {
            "description": "A brand id, or \"personal\" (default).",
            "type": "string"
          },
          "compareKey": {
            "type": "string"
          },
          "endAt": {
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "hypothesis": {
            "maxLength": 2000,
            "type": "string"
          },
          "name": {
            "maxLength": 120,
            "type": "string"
          },
          "notes": {
            "maxLength": 2000,
            "type": "string"
          },
          "primaryMetric": {
            "enum": [
              "impressions",
              "views",
              "engagement",
              "engagementRate",
              "clicks"
            ],
            "type": "string"
          },
          "startAt": {
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "enum": [
              "draft",
              "running",
              "done"
            ],
            "type": "string"
          },
          "variants": {
            "items": {
              "type": "string"
            },
            "maxItems": 12,
            "type": "array"
          }
        },
        "required": [
          "name"
        ],
        "type": "object"
      },
      "name": "create_experiment"
    },
    {
      "description": "[writes flayr data · `write` scope] Copy a flayr frame into the brand library (`defaultKey`, optional colors/texts), or add uploaded PNG/SVG art (upload_media first; `variants` of storageIds, the content area is detected from transparency).\n(Wraps POST /api/v2/frames.)",
      "inputSchema": {
        "additionalProperties": false,
        "properties": {
          "defaultKey": {
            "description": "A defaults[].ref without \"default:\".",
            "type": "string"
          },
          "kind": {
            "description": "Default: guessed from the name.",
            "enum": [
              "border",
              "overlay",
              "lower_third",
              "corner_bug",
              "quote_card",
              "ribbon"
            ],
            "type": "string"
          },
          "name": {
            "maxLength": 80,
            "type": "string"
          },
          "paletteOverride": {
            "description": "Colors that win over the brand kit.",
            "properties": {
              "accent": {
                "pattern": "^#[0-9a-fA-F]{6}$",
                "type": "string"
              },
              "ink": {
                "pattern": "^#[0-9a-fA-F]{6}$",
                "type": "string"
              },
              "onPrimary": {
                "pattern": "^#[0-9a-fA-F]{6}$",
                "type": "string"
              },
              "paper": {
                "pattern": "^#[0-9a-fA-F]{6}$",
                "type": "string"
              },
              "primary": {
                "pattern": "^#[0-9a-fA-F]{6}$",
                "type": "string"
              },
              "secondary": {
                "pattern": "^#[0-9a-fA-F]{6}$",
                "type": "string"
              }
            },
            "type": "object"
          },
          "rights": {
            "description": "Who holds the rights to the art. Optional; a frame without it shows a reminder.",
            "enum": [
              "original",
              "owned",
              "licensed"
            ],
            "type": "string"
          },
          "tags": {
            "items": {
              "type": "string"
            },
            "maxItems": 20,
            "type": "array"
          },
          "texts": {
            "description": "Texts some frames print (ribbon/tag label, lower-third title/subtitle).",
            "properties": {
              "label": {
                "maxLength": 60,
                "type": "string"
              },
              "subtitle": {
                "maxLength": 60,
                "type": "string"
              },
              "title": {
                "maxLength": 60,
                "type": "string"
              }
            },
            "type": "object"
          },
          "variants": {
            "description": "One per aspect ratio.",
            "items": {
              "properties": {
                "aspect": {
                  "description": "Default: nearest to the image size.",
                  "enum": [
                    "1:1",
                    "4:5",
                    "9:16",
                    "16:9"
                  ],
                  "type": "string"
                },
                "contentBox": {
                  "description": "Default: detected from the transparent area. Where the post sits inside the frame, as fractions (0–1) of the frame canvas.",
                  "properties": {
                    "h": {
                      "type": "number"
                    },
                    "w": {
                      "type": "number"
                    },
                    "x": {
                      "type": "number"
                    },
                    "y": {
                      "type": "number"
                    }
                  },
                  "required": [
                    "x",
                    "y",
                    "w",
                    "h"
                  ],
                  "type": "object"
                },
                "fit": {
                  "enum": [
                    "cover",
                    "contain"
                  ],
                  "type": "string"
                },
                "storageId": {
                  "description": "From POST /api/v2/media/upload-url, then POST the PNG (with transparency) or SVG bytes.",
                  "type": "string"
                }
              },
              "required": [
                "storageId"
              ],
              "type": "object"
            },
            "maxItems": 4,
            "minItems": 1,
            "type": "array"
          }
        },
        "type": "object"
      },
      "name": "create_frame"
    },
    {
      "description": "[PUBLISHES to social accounts · `publish` scope] Creates a posting queue. Once items are added it BOOKS REAL POSTS on the given platforms on its cadence (default daily 19:00 America/Chicago, 2 booked ahead). Use `startsAt` to hold everything until a date. Needs the `publish` scope (or admin) on the Agency plan or higher.\n(Wraps POST /api/v2/queues.)",
      "inputSchema": {
        "additionalProperties": false,
        "properties": {
          "brandId": {
            "type": "string"
          },
          "cadenceHours": {
            "default": 24,
            "description": "1, 2, 3, 4, 6, 8, 12, or whole days (24, 48, …, 720).",
            "type": "integer"
          },
          "lookahead": {
            "default": 2,
            "description": "How many upcoming items are scheduled at the platform at once.",
            "maximum": 10,
            "minimum": 1,
            "type": "integer"
          },
          "name": {
            "maxLength": 80,
            "type": "string"
          },
          "platforms": {
            "items": {
              "type": "string"
            },
            "minItems": 1,
            "type": "array"
          },
          "startsAt": {
            "description": "ISO 8601 with an offset (or Z). Nothing is booked before it; the first slot is the first timeOfDay (in the queue's timezone) at or after it, so a startsAt that is itself a slot is the first slot. A past date holds nothing back.",
            "format": "date-time",
            "type": "string"
          },
          "timeOfDay": {
            "default": "19:00",
            "description": "24-hour \"HH:MM\" local time.",
            "type": "string"
          },
          "timezone": {
            "default": "America/Chicago",
            "description": "IANA timezone. Slots keep their local time across DST.",
            "type": "string"
          },
          "youtube": {
            "description": "Queue-wide YouTube defaults (a draft's own options win; titles are per item). YouTube upload options, applied to YouTube targets only. madeForKids is always sent to YouTube (default false). A missing title is derived from the first sentence of the caption, cut to 100 characters at a word boundary. SCHEDULED UPLOADS: the video is uploaded ahead of scheduledAt. \"public\" is uploaded as private and released by YouTube at the scheduled time; \"unlisted\" is watchable by anyone with the link from upload time; \"private\" stays private.",
            "properties": {
              "categoryId": {
                "description": "YouTube category id, e.g. \"28\" (Science & Technology). YouTube default: 22.",
                "pattern": "^\\d{1,3}$",
                "type": "string"
              },
              "containsSyntheticMedia": {
                "description": "AI-generated content disclosure.",
                "type": "boolean"
              },
              "firstComment": {
                "description": "For scheduled videos, posted at the scheduled time.",
                "maxLength": 10000,
                "type": "string"
              },
              "madeForKids": {
                "default": false,
                "type": "boolean"
              },
              "playlistId": {
                "description": "From GET /api/v2/youtube/playlists for the same brand.",
                "type": "string"
              },
              "tags": {
                "description": "At most 500 characters combined.",
                "items": {
                  "maxLength": 100,
                  "type": "string"
                },
                "type": "array"
              },
              "title": {
                "description": "No < or >.",
                "maxLength": 100,
                "minLength": 1,
                "type": "string"
              },
              "visibility": {
                "default": "public",
                "enum": [
                  "public",
                  "private",
                  "unlisted"
                ],
                "type": "string"
              }
            },
            "type": "object"
          }
        },
        "required": [
          "name",
          "platforms"
        ],
        "type": "object"
      },
      "name": "create_queue"
    },
    {
      "description": "[PUBLISHES to social accounts · `publish` scope] Creates a rule that reshares (or comments on) every new post of the source accounts. Defaults to needs_approval: each fire waits for approve in act_on_publish_action. Only LinkedIn (native reshare) and X (quote own / retweet) really repost; other platforms get a link share. Confirm with the user first. Needs the `publish` scope (or admin) on the Agency plan or higher.\n(Wraps POST /api/v2/automations/reshare-rules.)",
      "inputSchema": {
        "additionalProperties": false,
        "properties": {
          "approval": {
            "default": "needs_approval",
            "description": "`needs_approval` (default): each fire waits for a person (POST …/actions/{id} `approve`). `auto`: runs on its own.",
            "enum": [
              "needs_approval",
              "auto"
            ],
            "type": "string"
          },
          "brandId": {
            "type": "string"
          },
          "delayMinutes": {
            "default": 0,
            "minimum": 0,
            "type": "integer"
          },
          "enabled": {
            "default": true,
            "type": "boolean"
          },
          "kind": {
            "default": "reshare",
            "description": "`reshare`: share the post to `targets`. `comment`: comment on the post itself through the source account.",
            "enum": [
              "reshare",
              "comment"
            ],
            "type": "string"
          },
          "name": {
            "maxLength": 120,
            "type": "string"
          },
          "sourceAccountIds": {
            "description": "Fires when a post goes live through any of these connected accounts.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "sourceLabel": {
            "description": "Only posts carrying this label.",
            "type": [
              "string",
              "null"
            ]
          },
          "targets": {
            "items": {
              "properties": {
                "accountId": {
                  "type": "string"
                },
                "platform": {
                  "type": "string"
                }
              },
              "required": [
                "accountId",
                "platform"
              ],
              "type": "object"
            },
            "type": "array"
          },
          "template": {
            "description": "The reshare commentary or comment text. Tokens, resolved only once the URL exists: `{{postUrl}}` (this post, on the comment's platform), `{{postUrl:<platform>}}`, `{{parentPostUrl:<post or draft id>}}` (another flayr post: same platform, else YouTube, else any), `{{youtubeUrl:<post or draft id>}}`.",
            "type": "string"
          }
        },
        "required": [
          "name",
          "sourceAccountIds"
        ],
        "type": "object"
      },
      "name": "create_share_rule"
    },
    {
      "description": "[writes flayr data · `write` scope] Creates a reusable format (text template) in a brand.\n(Wraps POST /api/v2/text-templates.)",
      "inputSchema": {
        "additionalProperties": false,
        "properties": {
          "brandId": {
            "type": "string"
          },
          "ctaSlots": {
            "items": {
              "properties": {
                "label": {
                  "type": "string"
                },
                "placement": {
                  "default": "body",
                  "enum": [
                    "body",
                    "first_comment",
                    "none"
                  ],
                  "type": "string"
                },
                "text": {
                  "type": "string"
                }
              },
              "required": [
                "label"
              ],
              "type": "object"
            },
            "maxItems": 10,
            "type": "array"
          },
          "defaultLabels": {
            "description": "\"key:value\" labels every draft started from this format gets (plus the format's own `label`).",
            "items": {
              "type": "string"
            },
            "maxItems": 30,
            "type": "array"
          },
          "example": {
            "maxLength": 20000,
            "type": [
              "string",
              "null"
            ]
          },
          "format": {
            "enum": [
              "post",
              "article",
              "carousel_script",
              "meme",
              "thread"
            ],
            "type": "string"
          },
          "guardrails": {
            "items": {
              "maxLength": 500,
              "type": "string"
            },
            "maxItems": 30,
            "type": "array"
          },
          "hookPatterns": {
            "description": "\"[slot]\" placeholders are kept as written.",
            "items": {
              "maxLength": 300,
              "type": "string"
            },
            "maxItems": 20,
            "type": "array"
          },
          "length": {
            "properties": {
              "captionWords": {
                "description": "carousel_script: the caption that goes with the slides.",
                "properties": {
                  "max": {
                    "type": "integer"
                  },
                  "min": {
                    "type": "integer"
                  }
                },
                "type": "object"
              },
              "max": {
                "minimum": 1,
                "type": "integer"
              },
              "min": {
                "minimum": 0,
                "type": "integer"
              },
              "perSlideMaxWords": {
                "description": "carousel_script / thread: words allowed per slide or post.",
                "type": "integer"
              },
              "titleMaxWords": {
                "description": "carousel_script: words allowed in the title (first line of slide 1).",
                "type": "integer"
              },
              "unit": {
                "enum": [
                  "words",
                  "chars",
                  "slides"
                ],
                "type": "string"
              }
            },
            "required": [
              "unit",
              "min",
              "max"
            ],
            "type": "object"
          },
          "maxHashtags": {
            "maximum": 30,
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "name": {
            "maxLength": 120,
            "type": "string"
          },
          "platform": {
            "description": "e.g. \"linkedin\"; null = any platform.",
            "type": [
              "string",
              "null"
            ]
          },
          "sections": {
            "description": "In order. \"Start from a format\" writes one `[you fill: Label — guidance]` placeholder per section.",
            "items": {
              "properties": {
                "guidance": {
                  "maxLength": 1000,
                  "type": "string"
                },
                "key": {
                  "description": "Derived from the label when omitted.",
                  "type": "string"
                },
                "label": {
                  "maxLength": 80,
                  "type": "string"
                },
                "maxLines": {
                  "type": "integer"
                },
                "minLines": {
                  "type": "integer"
                },
                "required": {
                  "default": true,
                  "type": "boolean"
                }
              },
              "required": [
                "label"
              ],
              "type": "object"
            },
            "maxItems": 20,
            "type": "array"
          },
          "source": {
            "description": "Where the shape came from, e.g. an import path.",
            "maxLength": 200,
            "type": [
              "string",
              "null"
            ]
          },
          "useFor": {
            "description": "One line: when to pick this format.",
            "maxLength": 300,
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "name",
          "format",
          "length"
        ],
        "type": "object"
      },
      "name": "create_text_template"
    },
    {
      "description": "[PUBLISHES to social accounts · `publish` scope] Same rules as the dashboard: only the assigned approver may decide. On a \"workflow\" approval, decision \"approve\" resumes the parked run, whose next steps may publish (GOES LIVE); \"reject\" cancels it. On a \"manual\" approval, \"approve\" only marks the content approved — nothing is posted until you call publish with its contentId — and \"reject\" returns it to draft. Needs the explicit `approve` scope (admin does not include it). Agency plan. Repeating the same decision is safe (alreadyDecided: true). Confirm with the user before approving a workflow approval.\n(Wraps POST /api/v2/approvals/{id}.)",
      "inputSchema": {
        "additionalProperties": false,
        "properties": {
          "decision": {
            "enum": [
              "approve",
              "reject"
            ],
            "type": "string"
          },
          "id": {
            "description": "An approval id.",
            "type": "string"
          },
          "note": {
            "maxLength": 1000,
            "type": "string"
          }
        },
        "required": [
          "id",
          "decision"
        ],
        "type": "object"
      },
      "name": "decide_approval"
    },
    {
      "description": "[PUBLISHES to social accounts · `publish` scope] decision \"approve\" sends a held post to the platform for its slot (GOES LIVE) — needs the explicit `review` scope (admin does not include it) and never works on a post the same connection created. decision \"hold\" takes a scheduled post off the platform schedule — needs `review` or `publish`. Agency plan. `id` is a reviewId or a convexPostId.\n(Wraps POST /api/v2/reviews/{id}.)",
      "inputSchema": {
        "additionalProperties": false,
        "properties": {
          "collabSide": {
            "description": "FFE-501 collab posts only: which brand this decision is for (\"owner\" = the shared character's brand, \"grantee\" = the brand that made the post). Needed only when the key's owner approves for both brands; a brand-bound key always decides for its own brand. A collab post publishes only after BOTH sides approved; \"hold\" rejects it for good.",
            "enum": [
              "owner",
              "grantee"
            ],
            "type": "string"
          },
          "decision": {
            "enum": [
              "approve",
              "hold"
            ],
            "type": "string"
          },
          "id": {
            "description": "A convexPostId or a review id.",
            "type": "string"
          },
          "note": {
            "maxLength": 1000,
            "type": "string"
          },
          "pastSlot": {
            "description": "Only for a held post whose slot passed: \"now\", or { at: ISO-8601 future time }.",
            "oneOf": [
              {
                "enum": [
                  "now"
                ],
                "type": "string"
              },
              {
                "properties": {
                  "at": {
                    "format": "date-time",
                    "type": "string"
                  }
                },
                "required": [
                  "at"
                ],
                "type": "object"
              }
            ]
          }
        },
        "required": [
          "id",
          "decision"
        ],
        "type": "object"
      },
      "name": "decide_review"
    },
    {
      "description": "[DESTRUCTIVE] DESTRUCTIVE: permanently deletes a content item from the library. Posts already live on a platform are not removed. Needs the `write` scope. Ask the user before calling.\n(Wraps DELETE /api/v2/content/{id}.)",
      "inputSchema": {
        "additionalProperties": false,
        "properties": {
          "id": {
            "type": "string"
          }
        },
        "required": [
          "id"
        ],
        "type": "object"
      },
      "name": "delete_content"
    },
    {
      "description": "[DESTRUCTIVE] DESTRUCTIVE: deletes the experiment record (content keeps its labels). Ask the user before calling.\n(Wraps DELETE /api/v2/experiments/{id}.)",
      "inputSchema": {
        "additionalProperties": false,
        "properties": {
          "id": {
            "type": "string"
          }
        },
        "required": [
          "id"
        ],
        "type": "object"
      },
      "name": "delete_experiment"
    },
    {
      "description": "[DESTRUCTIVE] DESTRUCTIVE: deletes the queue (its drafts stay in the library). With posts booked at the platform it is refused (409) unless `unschedule: true`, which first deletes those platform posts. Needs the `publish` scope (or admin) on the Agency plan or higher. Ask the user before calling.\n(Wraps DELETE /api/v2/queues/{id}.)",
      "inputSchema": {
        "additionalProperties": false,
        "properties": {
          "brandId": {
            "description": "Account-wide keys: \"personal\" or an id from GET /api/v2/brands (omitted → the brand last active in the dashboard). A brand-bound key always uses its own brand.",
            "type": "string"
          },
          "id": {
            "type": "string"
          },
          "unschedule": {
            "default": false,
            "description": "true: delete every booked post at the platform first (the dashboard's \"Unschedule\"), then delete. Without it a booked post is a 409 — nothing is ever left booked at the platform without its queue item.",
            "type": "boolean"
          }
        },
        "required": [
          "id"
        ],
        "type": "object"
      },
      "name": "delete_queue"
    },
    {
      "description": "[DESTRUCTIVE] DESTRUCTIVE: deletes a format (drafts started from it keep their text). Ask the user before calling.\n(Wraps DELETE /api/v2/text-templates/{id}.)",
      "inputSchema": {
        "additionalProperties": false,
        "properties": {
          "id": {
            "type": "string"
          }
        },
        "required": [
          "id"
        ],
        "type": "object"
      },
      "name": "delete_text_template"
    },
    {
      "description": "[read-only] Engagement overview for the last 7, 30 or 90 days. Pro plan or higher.\n(Wraps GET /api/v2/analytics.)",
      "inputSchema": {
        "additionalProperties": false,
        "properties": {
          "range": {
            "default": "30d",
            "enum": [
              "7d",
              "30d",
              "90d"
            ],
            "type": "string"
          }
        },
        "type": "object"
      },
      "name": "get_analytics"
    },
    {
      "description": "[read-only] One content item plus its live posting status: `postState` and `posts[]` (up to 20, newest first) with each post's status (scheduled · publishing · published · partial · failed · review_held · held), scheduledAt, and per-platform status, live URL and error. The item's own `status` stays \"draft\" after publishing — read `posts` to know whether it is live. Use this to confirm a publish landed.\n(Wraps GET /api/v2/content/{id}.)",
      "inputSchema": {
        "additionalProperties": false,
        "properties": {
          "id": {
            "type": "string"
          }
        },
        "required": [
          "id"
        ],
        "type": "object"
      },
      "name": "get_content"
    },
    {
      "description": "[read-only] One experiment: hypothesis, variants, compareKey, window and status.\n(Wraps GET /api/v2/experiments/{id}.)",
      "inputSchema": {
        "additionalProperties": false,
        "properties": {
          "id": {
            "type": "string"
          }
        },
        "required": [
          "id"
        ],
        "type": "object"
      },
      "name": "get_experiment"
    },
    {
      "description": "[read-only] The experiment's posts split by its compareKey, with the leading variant by its primary metric and n per arm. Small samples: read medians with their n.\n(Wraps GET /api/v2/experiments/{id}/results.)",
      "inputSchema": {
        "additionalProperties": false,
        "properties": {
          "id": {
            "type": "string"
          },
          "include": {
            "enum": [
              "posts"
            ],
            "type": "string"
          },
          "window": {
            "default": "48h",
            "enum": [
              "48h",
              "7d",
              "latest"
            ],
            "type": "string"
          }
        },
        "required": [
          "id"
        ],
        "type": "object"
      },
      "name": "get_experiment_results"
    },
    {
      "description": "[read-only] One library frame with its sizes and content areas.\n(Wraps GET /api/v2/frames/{id}.)",
      "inputSchema": {
        "additionalProperties": false,
        "properties": {
          "id": {
            "type": "string"
          }
        },
        "required": [
          "id"
        ],
        "type": "object"
      },
      "name": "get_frame"
    },
    {
      "description": "[read-only] Which frame each platform's images get automatically when a post is published or scheduled.\n(Wraps GET /api/v2/frames/settings.)",
      "inputSchema": {
        "additionalProperties": false,
        "properties": {},
        "type": "object"
      },
      "name": "get_frame_settings"
    },
    {
      "description": "[read-only] Status of an async job (combine_clips, or an API video generation): pending, processing, completed or failed. For a combine, `resultContentId` is the new draft once completed; a failed combine is refunded automatically. Poll every 10-30 s — renders take about a minute.\n(Wraps GET /api/v2/generate/status/{jobId}.)",
      "inputSchema": {
        "additionalProperties": false,
        "properties": {
          "jobId": {
            "type": "string"
          }
        },
        "required": [
          "jobId"
        ],
        "type": "object"
      },
      "name": "get_job_status"
    },
    {
      "description": "[read-only] A post (`id` = convexPostId from publish, or posts[].id from get_content): status, slot, platforms, and while it is scheduled the first comment each platform will publish and whether it can be changed now.\n(Wraps GET /api/v2/posts/{id}.)",
      "inputSchema": {
        "additionalProperties": false,
        "properties": {
          "id": {
            "description": "The post: `convexPostId` from POST /api/v2/publish, or `posts[].id` from GET /api/v2/content/{id}.",
            "type": "string"
          }
        },
        "required": [
          "id"
        ],
        "type": "object"
      },
      "name": "get_post"
    },
    {
      "description": "[read-only] A queue and its items in order, with each item's state (queued · publishing · scheduled · posted · failed · skipped), booked or projected slot, and attribution status.\n(Wraps GET /api/v2/queues/{id}.)",
      "inputSchema": {
        "additionalProperties": false,
        "properties": {
          "brandId": {
            "description": "Account-wide keys: \"personal\" or an id from GET /api/v2/brands (omitted → the brand last active in the dashboard). A brand-bound key always uses its own brand.",
            "type": "string"
          },
          "id": {
            "type": "string"
          }
        },
        "required": [
          "id"
        ],
        "type": "object"
      },
      "name": "get_queue"
    },
    {
      "description": "[read-only] One format (text template) with its full structure and example.\n(Wraps GET /api/v2/text-templates/{id}.)",
      "inputSchema": {
        "additionalProperties": false,
        "properties": {
          "id": {
            "type": "string"
          }
        },
        "required": [
          "id"
        ],
        "type": "object"
      },
      "name": "get_text_template"
    },
    {
      "description": "[read-only] Content held for approval that waits on the account owner's decision, newest first: posts filed with publish `requiresApproval: true`, drafts submitted for review, and workflow runs parked at an approval gate. `kind` is \"manual\" or \"workflow\". Needs the explicit `approve` scope (admin does not include it). Agency plan.\n(Wraps GET /api/v2/approvals.)",
      "inputSchema": {
        "additionalProperties": false,
        "properties": {
          "brandId": {
            "type": "string"
          },
          "status": {
            "default": "pending",
            "enum": [
              "pending"
            ],
            "type": "string"
          }
        },
        "type": "object"
      },
      "name": "list_approvals"
    },
    {
      "description": "[read-only] The brand's \"posts like this must carry a credit\" rules and its mode (flag / push_down). In push_down mode publish refuses or holds a post missing a required credit — add `credits` with update_content first.\n(Wraps GET /api/v2/attribution-rules.)",
      "inputSchema": {
        "additionalProperties": false,
        "properties": {
          "brandId": {
            "description": "Account-wide keys: \"personal\" or an id from GET /api/v2/brands (omitted → the brand last active in the dashboard). A brand-bound key always uses its own brand.",
            "type": "string"
          }
        },
        "type": "object"
      },
      "name": "list_attribution_rules"
    },
    {
      "description": "[read-only] Lists the brands this connection can file content into and publish through. Use an `id` as `brandId` on create_content, publish, queues and analytics tools; \"personal\" means the account owner's personal accounts. A connection bound to one brand sees only that brand (and personal is null).\n(Wraps GET /api/v2/brands.)",
      "inputSchema": {
        "additionalProperties": false,
        "properties": {},
        "type": "object"
      },
      "name": "list_brands"
    },
    {
      "description": "[read-only] The social accounts a brand can post to — the same set publish would use for that `brandId`: platform, handle, display name, health (healthy, needs_reauth, disconnected) and whether flayr can post there right now (`postable`; Reddit is paused because Reddit closed its API to third-party apps). Call this before publish to pick platforms, and tell the user to reconnect any account that is not healthy at flayr.social/connect.\n(Wraps GET /api/v2/accounts.)",
      "inputSchema": {
        "additionalProperties": false,
        "properties": {
          "brandId": {
            "description": "An id from GET /api/v2/brands, or \"personal\". Omitted → the key's bound brand, else the owner's personal accounts.",
            "type": "string"
          }
        },
        "type": "object"
      },
      "name": "list_connected_accounts"
    },
    {
      "description": "[read-only] Lists content items (drafts), newest first. Each carries `postState` (\"posted\" / \"scheduled\" / null) and `posts[]` (up to 5): where it went out, per platform, with status, live URL and failure reason.\n(Wraps GET /api/v2/content.)",
      "inputSchema": {
        "additionalProperties": false,
        "properties": {
          "brandId": {
            "description": "A brand id from GET /api/v2/brands. Omitted: the key's brand (brand-bound key) or the owner's active brand.",
            "type": "string"
          },
          "includeArchived": {
            "default": false,
            "type": "boolean"
          },
          "limit": {
            "default": 20,
            "maximum": 200,
            "minimum": 1,
            "type": "integer"
          }
        },
        "type": "object"
      },
      "name": "list_content"
    },
    {
      "description": "[read-only] A/B experiments. Content joins one by carrying the label `experiment:<id>` plus `<compareKey>:<variant>`.\n(Wraps GET /api/v2/experiments.)",
      "inputSchema": {
        "additionalProperties": false,
        "properties": {
          "allBrands": {
            "description": "true: every brand of the organizations you own or administer, plus your personal brands. Refused (403) for a plain member and for a brand-bound key.",
            "type": "boolean"
          },
          "brandId": {
            "description": "A brand id, or \"all\" (default: every profile you can see). A brand-bound key is always its own brand.",
            "type": "string"
          }
        },
        "type": "object"
      },
      "name": "list_experiments"
    },
    {
      "description": "[read-only] The brand's frames (borders, overlays, lower thirds, corner tags, quote-card frames, ribbons) plus the built-in flayr frames in the brand colors. Each has a `ref` to pass to apply_frame.\n(Wraps GET /api/v2/frames.)",
      "inputSchema": {
        "additionalProperties": false,
        "properties": {
          "aspect": {
            "enum": [
              "1:1",
              "4:5",
              "9:16",
              "16:9"
            ],
            "type": "string"
          },
          "includeArchived": {
            "default": false,
            "type": "boolean"
          },
          "kind": {
            "enum": [
              "border",
              "overlay",
              "lower_third",
              "corner_bug",
              "quote_card",
              "ribbon"
            ],
            "type": "string"
          }
        },
        "type": "object"
      },
      "name": "list_frames"
    },
    {
      "description": "[read-only] Boards of the Pinterest account(s) a brand publishes to. Use a board id as `pinterest.boardId` on publish. A board is required; the one you pick is remembered as the brand's default for next time, and a publish that uses the default says so (defaultBoardApplied).\n(Wraps GET /api/v2/pinterest/boards.)",
      "inputSchema": {
        "additionalProperties": false,
        "properties": {
          "brandId": {
            "description": "An id from GET /api/v2/brands, or \"personal\". Omitted → the key's bound brand, else the owner's personal accounts.",
            "type": "string"
          }
        },
        "type": "object"
      },
      "name": "list_pinterest_boards"
    },
    {
      "description": "[read-only] A post's comments-after-publish and reshares, with status (waiting, needs_approval, sent, failed) and the resolved text.\n(Wraps GET /api/v2/posts/{id}/comments.)",
      "inputSchema": {
        "additionalProperties": false,
        "properties": {
          "id": {
            "type": "string"
          }
        },
        "required": [
          "id"
        ],
        "type": "object"
      },
      "name": "list_post_comments"
    },
    {
      "description": "[read-only] Pending, sent and failed comments-after-publish and reshares of the brand (filter by `status`, e.g. needs_approval or failed), with what each waits on.\n(Wraps GET /api/v2/automations/actions.)",
      "inputSchema": {
        "additionalProperties": false,
        "properties": {
          "brandId": {
            "description": "Account-wide keys: \"personal\" or an id from GET /api/v2/brands (omitted → the brand last active in the dashboard). A brand-bound key always uses its own brand.",
            "type": "string"
          },
          "status": {
            "enum": [
              "waiting",
              "needs_approval",
              "sending",
              "sent",
              "failed",
              "cancelled",
              "rejected"
            ],
            "type": "string"
          }
        },
        "type": "object"
      },
      "name": "list_publish_actions"
    },
    {
      "description": "[read-only] Posting (drip) queues of one brand. A queue posts one item every cadenceHours at timeOfDay in its timezone, keeping `lookahead` items booked at the platform.\n(Wraps GET /api/v2/queues.)",
      "inputSchema": {
        "additionalProperties": false,
        "properties": {
          "brandId": {
            "description": "Account-wide keys: \"personal\" or an id from GET /api/v2/brands (omitted → the brand last active in the dashboard). A brand-bound key always uses its own brand.",
            "type": "string"
          }
        },
        "type": "object"
      },
      "name": "list_queues"
    },
    {
      "description": "[read-only] Scheduled posts waiting for (or given) a human sign-off, oldest slot first. `canApprove` tells whether THIS connection may approve each one.\n(Wraps GET /api/v2/reviews.)",
      "inputSchema": {
        "additionalProperties": false,
        "properties": {
          "brandId": {
            "type": "string"
          },
          "status": {
            "default": "pending",
            "enum": [
              "pending",
              "approved",
              "held",
              "missed",
              "all"
            ],
            "type": "string"
          },
          "window": {
            "default": "all",
            "enum": [
              "in",
              "upcoming",
              "all"
            ],
            "type": "string"
          }
        },
        "type": "object"
      },
      "name": "list_reviews"
    },
    {
      "description": "[read-only] The brand's share rules: \"when <account> publishes, reshare to <accounts> (or comment on it) after N minutes with text T\", each with its approval mode.\n(Wraps GET /api/v2/automations/reshare-rules.)",
      "inputSchema": {
        "additionalProperties": false,
        "properties": {
          "brandId": {
            "description": "Account-wide keys: \"personal\" or an id from GET /api/v2/brands (omitted → the brand last active in the dashboard). A brand-bound key always uses its own brand.",
            "type": "string"
          }
        },
        "type": "object"
      },
      "name": "list_share_rules"
    },
    {
      "description": "[read-only] Reusable text shapes per brand: sections, length targets, hook patterns, CTA slots and guardrails. Use one to shape a draft's text before create_content.\n(Wraps GET /api/v2/text-templates.)",
      "inputSchema": {
        "additionalProperties": false,
        "properties": {
          "brandId": {
            "description": "A brand id (GET /api/v2/brands), \"personal\", or \"all\" (default: everything the key owner can see). A brand-bound key is always pinned to its brand; any other value is 403.",
            "type": "string"
          },
          "includeArchived": {
            "default": false,
            "type": "boolean"
          }
        },
        "type": "object"
      },
      "name": "list_text_templates"
    },
    {
      "description": "[read-only] Playlists of the YouTube channel(s) a brand publishes to. Use a playlist id as `youtube.playlistId` on create_content or publish.\n(Wraps GET /api/v2/youtube/playlists.)",
      "inputSchema": {
        "additionalProperties": false,
        "properties": {
          "brandId": {
            "description": "An id from GET /api/v2/brands, or \"personal\". Omitted → the key's bound brand, else the owner's personal accounts.",
            "type": "string"
          }
        },
        "type": "object"
      },
      "name": "list_youtube_playlists"
    },
    {
      "description": "[PUBLISHES to social accounts · `publish` scope] PUBLISHES TO REAL SOCIAL ACCOUNTS. Posts now, or at `scheduledAt` (ISO-8601, future). Prefer `contentId` (a draft from create_content: its text and media become the post) over inline `content`. The post goes out through the target brand's own connected accounts. Always pass an `idempotencyKey` so a retry never double-posts. The same safety gates as the dashboard apply: platform readiness/media rules are checked up front (422); if the brand requires scheduled-post review nothing is sent and you get { status: \"pending-review\", reviewId }; a missing required creator credit in a push-down brand is refused (422 attribution_missing) or held; the \"Posted with flayr.social\" caption watermark and video watermark follow the plan (409 watermark_pending means the watermarked video is still rendering — retry after retryAfterSeconds). `facebook: { contentType: \"reel\" }` posts a Facebook Reel (exactly one video), `\"story\"` a Story, `\"feed\"` a regular post; `instagram.contentType` works the same; omitted → the draft's saved post type. `pinterest: { boardId, title, link }` picks the board (ids from list_pinterest_boards; required: omitted → the brand's last-picked board for that account, flagged as defaultBoardApplied, else 422 pinterest_board_required), pin title and destination link. `requiresApproval: true` files it for human approval instead of posting. A draft that is already scheduled on one of the platforms is refused (409 already_booked): change the booked post instead, or pass `allowDuplicateBooking: true` only when the user asked to post it again. Needs the `publish` scope (or admin) and the Pro plan or higher; each publish counts against the plan's direct posts for the month. Confirm with the user before posting now. Then call get_content to see the live URLs.\n(Wraps POST /api/v2/publish.)",
      "inputSchema": {
        "additionalProperties": false,
        "properties": {
          "aiGenerated": {
            "description": "Label the post as AI-generated / synthetic media: YouTube containsSyntheticMedia, Instagram isAiGenerated, TikTok videoMadeWithAi, X madeWithAi, and the disclosure line (default \"#AIgenerated\") on platforms with no label field. Omitted → the content item's own setting, else on when its media was made with flayr AI (a character, a cloned voice, generated video or images). false on flayr-generated media posts without a label and adds a warning. A character post always carries the label.",
            "type": "boolean"
          },
          "allowDuplicateBooking": {
            "default": false,
            "description": "A contentId that is already scheduled (a live booking still ahead) on one of these platforms is refused with 409 { code: \"already_booked\", bookedAt, bookedPlatforms }. Pass true only to book the same draft again on purpose.",
            "type": "boolean"
          },
          "autoFirstComment": {
            "default": true,
            "description": "When the target brand has \"Auto first comment\" on (dashboard: Brand → First comment), LinkedIn, Instagram, Facebook, Threads and YouTube targets that have no first comment of their own get the brand's comment, with a tracked link per platform. false posts this one without it. No effect when the brand has it off.",
            "type": "boolean"
          },
          "brandId": {
            "description": "An id from GET /api/v2/brands, or \"personal\". With contentId it must match the item's brand.",
            "type": "string"
          },
          "content": {
            "description": "Post text. Required unless contentId is given.",
            "maxLength": 10000,
            "minLength": 1,
            "type": "string"
          },
          "contentId": {
            "description": "An existing content item id (GET /api/v2/content/{id} access rules). Cannot be combined with imageUrl / videoUrl.",
            "type": "string"
          },
          "facebook": {
            "additionalProperties": false,
            "description": "Facebook post type. \"reel\" posts a Reel (exactly one video, no images; anything else is a 422 with code content_type_invalid), \"story\" a Page Story, \"feed\" a regular post. Omitted → the content item's stored platformSpecificData.facebook.contentType (what the composer saved), else a regular post.",
            "properties": {
              "contentType": {
                "enum": [
                  "reel",
                  "story",
                  "feed"
                ],
                "type": "string"
              }
            },
            "type": "object"
          },
          "firstComment": {
            "additionalProperties": false,
            "description": "Explicit first comments per platform, posted right after the post goes out. Always wins over the brand's auto first comment. Threads: posted as a reply (it counts toward Threads' 250 posts/24h). Facebook and Instagram: not on Stories. YouTube: use youtube.firstComment. Not supported on TikTok, X, Bluesky or Pinterest.",
            "properties": {
              "facebook": {
                "maxLength": 8000,
                "type": "string"
              },
              "instagram": {
                "maxLength": 2200,
                "type": "string"
              },
              "linkedin": {
                "maxLength": 1250,
                "type": "string"
              },
              "threads": {
                "maxLength": 500,
                "type": "string"
              }
            },
            "type": "object"
          },
          "followUpComments": {
            "description": "Comments after publish, sent once each (Idempotency-Key) after the post is live on that platform and its tokens resolve: {{postUrl}}, {{postUrl:<platform>}}, {{parentPostUrl:<post or draft id>}}, {{youtubeUrl:<post or draft id>}}. TikTok: TikTok for Business accounts only (unverified); not on Pinterest.",
            "items": {
              "properties": {
                "delayMinutes": {
                  "minimum": 0,
                  "type": "integer"
                },
                "platform": {
                  "type": "string"
                },
                "sendAt": {
                  "format": "date-time",
                  "type": "string"
                },
                "text": {
                  "type": "string"
                }
              },
              "required": [
                "platform",
                "text"
              ],
              "type": "object"
            },
            "maxItems": 20,
            "type": "array"
          },
          "idempotencyKey": {
            "description": "Unique per API key. A retry with the same key never posts twice.",
            "maxLength": 200,
            "type": "string"
          },
          "imageUrl": {
            "type": "string"
          },
          "instagram": {
            "additionalProperties": false,
            "description": "Instagram post type, same rules: \"reel\" (exactly one video — a single video is posted as a Reel), \"story\", or \"feed\". Omitted → the content item's stored platformSpecificData.instagram.contentType.",
            "properties": {
              "contentType": {
                "enum": [
                  "reel",
                  "story",
                  "feed"
                ],
                "type": "string"
              }
            },
            "type": "object"
          },
          "labels": {
            "description": "Labels for this post on top of the draft's own (the draft wins for type/cut/variant). Normalized \"key:value\" labels (keys and values are lower-cased, spaces become \"-\"). Linked keys: campaign:<media campaign id>, experiment:<experiment id> (+ variant:<name> or the experiment's compareKey), type:<post type> (short, long-form, carousel, image, text, reel, story, poem, timelapse…). Free keys work too: cut:opus, lane:tech, parent:pantheon-part-1, ab:<pair id> (an A/B pair). Labels are copied onto every post made from the draft, and compared in GET /api/v2/analytics/experiments.",
            "items": {
              "pattern": "^[a-z0-9][a-z0-9_-]{0,31}:[a-z0-9][a-z0-9._\\-/+]{0,79}$",
              "type": "string"
            },
            "maxItems": 30,
            "type": "array"
          },
          "mediaCampaignId": {
            "description": "Link to a media campaign; with a brand target it must belong to that brand.",
            "type": "string"
          },
          "memo": {
            "description": "\"When it posts\" memo: the people-steps after the post goes live (reply to a comment, share to a personal account, pin a link, add a first comment flayr can't). Saved on the draft (else the post). When the post goes live, the brand's recipients (Brand → When a post goes live; default owners + admins) get ONE notice — in-app, email and push per their own settings — linking /publish/live/{convexPostId}: live links, the shared checklist, Copy buttons and a share link.",
            "properties": {
              "notes": {
                "maxLength": 4000,
                "type": "string"
              },
              "tasks": {
                "items": {
                  "oneOf": [
                    {
                      "description": "The to-do, e.g. \"Share to personal LinkedIn\".",
                      "maxLength": 500,
                      "type": "string"
                    },
                    {
                      "properties": {
                        "copyText": {
                          "description": "Text to paste (a reply, a first comment). The live page shows a Copy button.",
                          "maxLength": 5000,
                          "type": "string"
                        },
                        "id": {
                          "description": "PUT only: keep an existing to-do (and its tick) by id.",
                          "type": "string"
                        },
                        "platform": {
                          "description": "The platform the step is for, e.g. \"youtube\".",
                          "type": "string"
                        },
                        "text": {
                          "maxLength": 500,
                          "type": "string"
                        },
                        "url": {
                          "description": "Where to do it (http/https).",
                          "format": "uri",
                          "type": "string"
                        }
                      },
                      "required": [
                        "text"
                      ],
                      "type": "object"
                    }
                  ]
                },
                "maxItems": 30,
                "type": "array"
              }
            },
            "type": "object"
          },
          "onPublishTasks": {
            "description": "Shorthand for memo.tasks (appended after them). Response: `memoId`; a memo that could not be saved is a `warnings` entry, never a failed publish.",
            "items": {
              "oneOf": [
                {
                  "description": "The to-do, e.g. \"Share to personal LinkedIn\".",
                  "maxLength": 500,
                  "type": "string"
                },
                {
                  "properties": {
                    "copyText": {
                      "description": "Text to paste (a reply, a first comment). The live page shows a Copy button.",
                      "maxLength": 5000,
                      "type": "string"
                    },
                    "id": {
                      "description": "PUT only: keep an existing to-do (and its tick) by id.",
                      "type": "string"
                    },
                    "platform": {
                      "description": "The platform the step is for, e.g. \"youtube\".",
                      "type": "string"
                    },
                    "text": {
                      "maxLength": 500,
                      "type": "string"
                    },
                    "url": {
                      "description": "Where to do it (http/https).",
                      "format": "uri",
                      "type": "string"
                    }
                  },
                  "required": [
                    "text"
                  ],
                  "type": "object"
                }
              ]
            },
            "maxItems": 30,
            "type": "array"
          },
          "pinterest": {
            "additionalProperties": false,
            "description": "FFE-428: Pinterest pin options for every Pinterest target. Overrides the content item's stored platformSpecificData.pinterest field by field. A board is required. boardId omitted → the brand's last-picked board for that Pinterest account, flagged in the response (defaultBoardApplied, defaultBoardName); with no default the publish is refused (422 pinterest_board_required). The board picked is remembered as the default. title omitted → the first line of the caption.",
            "properties": {
              "boardId": {
                "description": "From GET /api/v2/pinterest/boards for the same brand.",
                "maxLength": 200,
                "type": "string"
              },
              "link": {
                "description": "Destination link (http or https).",
                "format": "uri",
                "maxLength": 2048,
                "type": "string"
              },
              "title": {
                "maxLength": 100,
                "minLength": 1,
                "type": "string"
              }
            },
            "type": "object"
          },
          "platforms": {
            "items": {
              "enum": [
                "instagram",
                "tiktok",
                "youtube",
                "youtube_shorts",
                "linkedin",
                "pinterest",
                "twitter",
                "facebook",
                "threads",
                "bluesky",
                "snapchat",
                "googlebusiness",
                "google_business",
                "reddit",
                "telegram",
                "discord"
              ],
              "type": "string"
            },
            "maxItems": 10,
            "minItems": 1,
            "type": "array"
          },
          "poll": {
            "additionalProperties": false,
            "description": "A native poll. Only on twitter (X), linkedin and discord, and with no media: any other platform or media is a 422 (code poll_invalid), never a plain post. X: 2-4 choices of at most 25 characters, 5 minutes to 7 days, and the post text is the question. LinkedIn: question (at most 140) required, 2-4 choices of at most 30 characters, 1, 3, 7 or 14 days. Discord: question (at most 300) required, 2-10 choices, whole hours up to 768. Cannot be combined with requiresApproval, or with a brand whose scheduled-post review is required (409 poll_not_holdable). Vote counts are not available through the API.",
            "properties": {
              "durationMinutes": {
                "default": 1440,
                "minimum": 5,
                "type": "integer"
              },
              "options": {
                "items": {
                  "minLength": 1,
                  "type": "string"
                },
                "maxItems": 10,
                "minItems": 2,
                "type": "array"
              },
              "question": {
                "maxLength": 300,
                "type": "string"
              }
            },
            "required": [
              "options"
            ],
            "type": "object"
          },
          "referralFooter": {
            "description": "Caption watermark: appends \"Posted with flayr.social — try it: flayr.social/r/<code>/<ch>\" to each platform's text (<code> is your referral code — your custom one if you claimed it; <ch> names the platform: yt, x, li, ig, tt, fb, th, bs). (YouTube: description only, never the title; X: the shorter \"via flayr.social/r/<code>/x\" or nothing when it would not fit — your text is never cut). On by default. false removes it only on plans that can remove the Flayr watermark (Pro and up); omitted follows your watermark setting. Beats the content item's stored value.",
            "type": "boolean"
          },
          "requiresApproval": {
            "default": false,
            "type": "boolean"
          },
          "reshare": {
            "additionalProperties": false,
            "description": "Share/repost another post: a flayr post id (`postId`) or a pasted link (`url`). Per platform: linkedin a native reshare with your text on top (not with media or a poll); twitter a quote of your own X post, otherwise a retweet of it fired once this post publishes (no text); every other platform a link share — a new post carrying the original's link, because it has no repost API. X alone that can only retweet is a 422. Cannot be combined with requiresApproval.",
            "properties": {
              "postId": {
                "type": "string"
              },
              "url": {
                "type": "string"
              }
            },
            "type": "object"
          },
          "scheduledAt": {
            "description": "Must be in the future. Omitted → publish now.",
            "format": "date-time",
            "type": "string"
          },
          "videoUrl": {
            "type": "string"
          },
          "youtube": {
            "description": "Overrides the content item's stored YouTube options field by field. YouTube upload options, applied to YouTube targets only. madeForKids is always sent to YouTube (default false). A missing title is derived from the first sentence of the caption, cut to 100 characters at a word boundary. SCHEDULED UPLOADS: the video is uploaded ahead of scheduledAt. \"public\" is uploaded as private and released by YouTube at the scheduled time; \"unlisted\" is watchable by anyone with the link from upload time; \"private\" stays private.",
            "properties": {
              "categoryId": {
                "description": "YouTube category id, e.g. \"28\" (Science & Technology). YouTube default: 22.",
                "pattern": "^\\d{1,3}$",
                "type": "string"
              },
              "containsSyntheticMedia": {
                "description": "AI-generated content disclosure.",
                "type": "boolean"
              },
              "firstComment": {
                "description": "For scheduled videos, posted at the scheduled time.",
                "maxLength": 10000,
                "type": "string"
              },
              "madeForKids": {
                "default": false,
                "type": "boolean"
              },
              "playlistId": {
                "description": "From GET /api/v2/youtube/playlists for the same brand.",
                "type": "string"
              },
              "tags": {
                "description": "At most 500 characters combined.",
                "items": {
                  "maxLength": 100,
                  "type": "string"
                },
                "type": "array"
              },
              "title": {
                "description": "No < or >.",
                "maxLength": 100,
                "minLength": 1,
                "type": "string"
              },
              "visibility": {
                "default": "public",
                "enum": [
                  "public",
                  "private",
                  "unlisted"
                ],
                "type": "string"
              }
            },
            "type": "object"
          }
        },
        "required": [
          "platforms"
        ],
        "type": "object"
      },
      "name": "publish"
    },
    {
      "description": "[read-only] Read-only analytics query: one `metric` aggregated (median by default) over posts, broken down by up to a few dimensions (platform, weekday, hour, brand, campaign, postType, label:<key>, arm), at a fixed post age `window` (48h / 7d / 30d / lifetime) so posts are compared fairly. Every row carries its sample size n; platforms that do not report the metric are listed in `availability`, never counted as 0. Pro plan or higher.\n(Wraps POST /api/v2/insights/query.)",
      "inputSchema": {
        "additionalProperties": false,
        "properties": {
          "aggregation": {
            "default": "median",
            "description": "Default median; pooled for the rates engagementRate, ctr and completionRate. A rate is pooled (Σ numerator ÷ Σ denominator), mean, median or count, never sum or p90. Pooled applies to rates only.",
            "enum": [
              "sum",
              "mean",
              "median",
              "p90",
              "count",
              "pooled"
            ],
            "type": "string"
          },
          "brandId": {
            "description": "A brand id, or \"all\" (default: every profile you can see). A brand-bound key is always its own brand.",
            "type": "string"
          },
          "breakdown": {
            "description": "Up to 2 of: platform, brand, campaign, experiment, arm, weekday, hour, postType, or label:<key> (e.g. label:cut, label:line, label:source, label:hook, label:series). The multi-valued format key also splits per group: label:format@length, label:format@style, label:format@non_video (a post counts once in each, never twice in one). A post with no source: label (or no length) falls back to how it was made; those rows have `derived` > 0 and a \"derived\" label, and no label is written. \"arm\" = the experiment's compareKey and needs filters.experimentId.",
            "items": {
              "type": "string"
            },
            "maxItems": 2,
            "type": "array"
          },
          "dateFrom": {
            "description": "ISO date or epoch ms (publish time). Default: 90 days ago; ranges are capped at 400 days.",
            "type": "string"
          },
          "dateTo": {
            "description": "ISO date or epoch ms. Default: now.",
            "type": "string"
          },
          "filters": {
            "properties": {
              "campaignId": {
                "type": "string"
              },
              "excludeLabels": {
                "description": "Posts with any of these labels are left out (the tag templates exclude line:test).",
                "items": {
                  "type": "string"
                },
                "maxItems": 5,
                "type": "array"
              },
              "experimentId": {
                "description": "Reads the experiment over its own start–end window.",
                "type": "string"
              },
              "labels": {
                "description": "Every label must be on the post (key:value).",
                "items": {
                  "type": "string"
                },
                "maxItems": 5,
                "type": "array"
              },
              "platforms": {
                "items": {
                  "type": "string"
                },
                "type": "array"
              }
            },
            "type": "object"
          },
          "format": {
            "default": "json",
            "description": "csv = the result as a spreadsheet-safe CSV attachment (UTF-8 BOM, CRLF, cells starting with = + - @ are prefixed with '). Without `format`, `Accept: text/csv` also selects CSV. Errors are always JSON.",
            "enum": [
              "json",
              "csv"
            ],
            "type": "string"
          },
          "formula": {
            "description": "Optional (needs the Advanced insights feature): combine up to three series. Each series is a metric + aggregation over the same breakdown, window, interval, filters and range. `expression` (≤ 80 characters) uses A, B, C, numbers, + - * / and parentheses, e.g. \"A / B\". Rows are joined on their keys; a group missing from any series, or a division by zero, is null. n = the smallest n across series. `metric` defaults to (and must equal) series A's. Referral series can only be split by platform, weekday or hour.",
            "properties": {
              "expression": {
                "maxLength": 80,
                "type": "string"
              },
              "format": {
                "default": "number",
                "description": "percent = a ratio shown ×100 with a % sign.",
                "enum": [
                  "number",
                  "percent"
                ],
                "type": "string"
              },
              "series": {
                "properties": {
                  "A": {
                    "properties": {
                      "aggregation": {
                        "enum": [
                          "sum",
                          "mean",
                          "median",
                          "p90",
                          "count",
                          "pooled"
                        ],
                        "type": "string"
                      },
                      "metric": {
                        "enum": [
                          "impressions",
                          "views",
                          "engagement",
                          "engagementRate",
                          "clicks",
                          "ctr",
                          "reach",
                          "likes",
                          "comments",
                          "shares",
                          "saves",
                          "followersGained",
                          "watchTimeHours",
                          "avgWatchSeconds",
                          "completionRate",
                          "postsPublished",
                          "referralClicks"
                        ],
                        "type": "string"
                      }
                    },
                    "required": [
                      "metric"
                    ],
                    "type": "object"
                  },
                  "B": {
                    "properties": {
                      "aggregation": {
                        "enum": [
                          "sum",
                          "mean",
                          "median",
                          "p90",
                          "count",
                          "pooled"
                        ],
                        "type": "string"
                      },
                      "metric": {
                        "enum": [
                          "impressions",
                          "views",
                          "engagement",
                          "engagementRate",
                          "clicks",
                          "ctr",
                          "reach",
                          "likes",
                          "comments",
                          "shares",
                          "saves",
                          "followersGained",
                          "watchTimeHours",
                          "avgWatchSeconds",
                          "completionRate",
                          "postsPublished",
                          "referralClicks"
                        ],
                        "type": "string"
                      }
                    },
                    "required": [
                      "metric"
                    ],
                    "type": "object"
                  },
                  "C": {
                    "properties": {
                      "aggregation": {
                        "enum": [
                          "sum",
                          "mean",
                          "median",
                          "p90",
                          "count",
                          "pooled"
                        ],
                        "type": "string"
                      },
                      "metric": {
                        "enum": [
                          "impressions",
                          "views",
                          "engagement",
                          "engagementRate",
                          "clicks",
                          "ctr",
                          "reach",
                          "likes",
                          "comments",
                          "shares",
                          "saves",
                          "followersGained",
                          "watchTimeHours",
                          "avgWatchSeconds",
                          "completionRate",
                          "postsPublished",
                          "referralClicks"
                        ],
                        "type": "string"
                      }
                    },
                    "required": [
                      "metric"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "A"
                ],
                "type": "object"
              }
            },
            "required": [
              "series",
              "expression"
            ],
            "type": "object"
          },
          "interval": {
            "description": "Adds a time series bucketed by publish date.",
            "enum": [
              "day",
              "week",
              "month"
            ],
            "type": "string"
          },
          "metric": {
            "default": "impressions",
            "enum": [
              "impressions",
              "views",
              "engagement",
              "engagementRate",
              "clicks",
              "ctr",
              "reach",
              "likes",
              "comments",
              "shares",
              "saves",
              "followersGained",
              "watchTimeHours",
              "avgWatchSeconds",
              "completionRate",
              "postsPublished",
              "referralClicks"
            ],
            "type": "string"
          },
          "tzOffsetMinutes": {
            "description": "Offset from UTC for weekday / hour / day buckets (e.g. -300).",
            "type": "integer"
          },
          "window": {
            "default": "7d",
            "description": "Snapshot age: the metric as it stood 48h / 7d / 30d after publish, or the latest refresh.",
            "enum": [
              "48h",
              "7d",
              "30d",
              "lifetime"
            ],
            "type": "string"
          }
        },
        "type": "object"
      },
      "name": "query_insights"
    },
    {
      "description": "[PUBLISHES to social accounts · `publish` scope] `action`: \"skip\" passes over a queued item; \"retry\" puts a failed/skipped item back in line; \"unschedule\" deletes a booked platform post and puts the item back in line. Also `youtubeTitle`, per-item `platforms`, and `position` (0-based among waiting items). Needs the `publish` scope (or admin) on the Agency plan or higher.\n(Wraps PATCH /api/v2/queues/{id}/items/{itemId}.)",
      "inputSchema": {
        "additionalProperties": false,
        "properties": {
          "action": {
            "enum": [
              "skip",
              "retry",
              "unschedule"
            ],
            "type": "string"
          },
          "brandId": {
            "type": "string"
          },
          "id": {
            "type": "string"
          },
          "itemId": {
            "type": "string"
          },
          "platforms": {
            "description": "This item's own platforms, used when it is booked; null → back to the queue's. Only before the item is booked (queued / failed / skipped).",
            "items": {
              "type": "string"
            },
            "minItems": 1,
            "type": [
              "array",
              "null"
            ]
          },
          "position": {
            "description": "0-based index among the waiting (queued) items.",
            "minimum": 0,
            "type": "integer"
          },
          "youtubeTitle": {
            "description": "null or \"\" → back to the draft's stored title.",
            "maxLength": 100,
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "id",
          "itemId"
        ],
        "type": "object"
      },
      "name": "queue_item_action"
    },
    {
      "description": "[DESTRUCTIVE] DESTRUCTIVE: removes an item (the draft stays in the library). A booked (`scheduled`) item is refused (409) unless `unschedule: true`, which deletes its platform post first. Needs the `publish` scope (or admin) on the Agency plan or higher.\n(Wraps DELETE /api/v2/queues/{id}/items/{itemId}.)",
      "inputSchema": {
        "additionalProperties": false,
        "properties": {
          "brandId": {
            "description": "Account-wide keys: \"personal\" or an id from GET /api/v2/brands (omitted → the brand last active in the dashboard). A brand-bound key always uses its own brand.",
            "type": "string"
          },
          "id": {
            "type": "string"
          },
          "itemId": {
            "type": "string"
          },
          "unschedule": {
            "default": false,
            "description": "true: delete the booked post at the platform first (the dashboard's \"Unschedule\"), then delete. Without it a booked post is a 409 — nothing is ever left booked at the platform without its queue item.",
            "type": "boolean"
          }
        },
        "required": [
          "id",
          "itemId"
        ],
        "type": "object"
      },
      "name": "remove_queue_item"
    },
    {
      "description": "[PUBLISHES to social accounts · `publish` scope] Send every waiting (queued) item id exactly once, in the new order. Booked, posted, failed and skipped items keep their places. Needs the `publish` scope (or admin) on the Agency plan or higher.\n(Wraps POST /api/v2/queues/{id}/items/reorder.)",
      "inputSchema": {
        "additionalProperties": false,
        "properties": {
          "brandId": {
            "type": "string"
          },
          "id": {
            "type": "string"
          },
          "itemIds": {
            "items": {
              "type": "string"
            },
            "type": "array"
          }
        },
        "required": [
          "id",
          "itemIds"
        ],
        "type": "object"
      },
      "name": "reorder_queue_items"
    },
    {
      "description": "[writes flayr data · `write` scope] Edits a draft in place; only the fields you pass change. Any media field replaces the draft's media as a set (keeps the id, so queue items still point at it). Refused (409) for archived or already-published items, and a media swap is refused once a queue booked it. `labels`, `opus` and `videoWatermarked` may be changed on published items too. A video draft's custom thumbnail/cover: `thumbnailStorageId` (an image from upload_media) or `thumbnailUrl`, or `thumbnailFrameOffsetMs` for a frame of the video; `thumbnailUrl: null` clears it. It is sent to YouTube (long-form), Facebook, LinkedIn, Instagram Reels, TikTok and Pinterest at publish.\n(Wraps PATCH /api/v2/content/{id}.)",
      "inputSchema": {
        "additionalProperties": false,
        "properties": {
          "credits": {
            "description": "Creator credits for work this post uses. Added to the published caption above the flayr footer, one line each: model → \"Print your own: <url> (model by <author>)\"; other kinds → \"Credit: <title> by <author> <url>\". Never added twice (a URL already in the caption is skipped); on X a shorter line (or no URL) is used, or the credits are left out, before the caption text is ever cut. YouTube: description only, never the title.",
            "items": {
              "additionalProperties": false,
              "properties": {
                "author": {
                  "description": "Designer / artist / photographer.",
                  "maxLength": 200,
                  "type": "string"
                },
                "kind": {
                  "enum": [
                    "model",
                    "music",
                    "footage",
                    "image",
                    "other"
                  ],
                  "type": "string"
                },
                "site": {
                  "description": "Detected from the URL when omitted (e.g. MakerWorld, Printables, Thingiverse).",
                  "maxLength": 200,
                  "type": "string"
                },
                "title": {
                  "maxLength": 200,
                  "type": "string"
                },
                "url": {
                  "description": "Absolute http(s) link to the credited work.",
                  "format": "uri",
                  "maxLength": 2048,
                  "type": "string"
                }
              },
              "required": [
                "kind",
                "url"
              ],
              "type": "object"
            },
            "maxItems": 10,
            "type": [
              "array",
              "null"
            ]
          },
          "id": {
            "type": "string"
          },
          "imageStorageId": {
            "description": "Uploaded image storageId: the video's thumbnail, or on its own the post's single image. Not with imageUrl.",
            "type": "string"
          },
          "imageUrl": {
            "description": "http(s) image URL: the video's thumbnail, or on its own the post's single image. Not with imageStorageId.",
            "format": "uri",
            "type": "string"
          },
          "images": {
            "description": "Carousel image URLs. Cannot be combined with the other media fields.",
            "items": {
              "format": "uri",
              "type": "string"
            },
            "maxItems": 20,
            "minItems": 2,
            "type": "array"
          },
          "labels": {
            "description": "Replaces the labels ([] or null clears). Allowed on published items too. Normalized \"key:value\" labels (keys and values are lower-cased, spaces become \"-\"). Linked keys: campaign:<media campaign id>, experiment:<experiment id> (+ variant:<name> or the experiment's compareKey), type:<post type> (short, long-form, carousel, image, text, reel, story, poem, timelapse…). Free keys work too: cut:opus, lane:tech, parent:pantheon-part-1, ab:<pair id> (an A/B pair). Labels are copied onto every post made from the draft, and compared in GET /api/v2/analytics/experiments.",
            "items": {
              "pattern": "^[a-z0-9][a-z0-9_-]{0,31}:[a-z0-9][a-z0-9._\\-/+]{0,79}$",
              "type": "string"
            },
            "maxItems": 30,
            "type": [
              "array",
              "null"
            ]
          },
          "opus": {
            "additionalProperties": false,
            "description": "Link this row to an Opus Clip clip (owner only; allowed on published rows) so an Opus project import skips it instead of creating a duplicate. null unlinks.",
            "properties": {
              "clipId": {
                "description": "Opus clip id: the full \"{projectId}.{curationId}\" or the short curation id with projectId.",
                "type": "string"
              },
              "projectId": {
                "description": "Opus project id. Optional when clipId is the full \"{projectId}.{clipId}\" id.",
                "type": "string"
              }
            },
            "required": [
              "clipId"
            ],
            "type": [
              "object",
              "null"
            ]
          },
          "platformText": {
            "additionalProperties": {
              "maxLength": 10000,
              "type": [
                "string",
                "null"
              ]
            },
            "description": "Per-platform captions: platform id → the caption that platform gets instead of `text`, e.g. { \"twitter\": \"short version\" }. Each must fit its platform: X 280 (a link counts 23, an emoji 2), Bluesky 300, Threads 500, Instagram 2200, LinkedIn 3000. Platforms without one get `text` when it fits, else an automatic short version: the first line, the credits in short form, as many hashtags as fit and the short flayr link (hashtags go first, then the credit link becomes \"Model by <author>\"; words are never cut). PATCH merges: a null (or \"\") value clears that platform, platformText: null clears all. At publish every platform is validated on its own caption: one failing platform no longer fails the others — the post publishes to the rest and reports the failures in platformErrors.",
            "type": [
              "object",
              "null"
            ]
          },
          "platforms": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "text": {
            "maxLength": 10000,
            "minLength": 1,
            "type": "string"
          },
          "thumbnailFrameOffsetMs": {
            "description": "Use this frame of the video (ms from the start) as the cover. Instagram and TikTok get the offset; YouTube/Facebook/LinkedIn need an image, so pass the captured frame as thumbnailStorageId / thumbnailUrl too.",
            "maximum": 86400000,
            "minimum": 0,
            "type": "integer"
          },
          "thumbnailStorageId": {
            "description": "Custom video thumbnail: an image storageId from POST /api/v2/media/upload-url. Not with thumbnailUrl. null clears.",
            "type": [
              "string",
              "null"
            ]
          },
          "thumbnailUrl": {
            "description": "Custom video thumbnail: an http(s) image URL. Not with thumbnailStorageId. null clears.",
            "format": "uri",
            "type": [
              "string",
              "null"
            ]
          },
          "title": {
            "description": "Shorthand for youtube.title (the video title). No < or >.",
            "maxLength": 100,
            "minLength": 1,
            "type": "string"
          },
          "videoStorageId": {
            "description": "storageId from uploading to POST /api/v2/media/upload-url's uploadUrl. Not with videoUrl. Another account's upload is refused (422).",
            "type": "string"
          },
          "videoUrl": {
            "description": "http(s) URL of the new video. Not with videoStorageId.",
            "format": "uri",
            "type": "string"
          },
          "videoWatermarked": {
            "additionalProperties": false,
            "description": "The item's own video already has the \"Posted by flayr.social\" watermark: publish it as-is. null clears.",
            "properties": {
              "at": {
                "description": "When it was watermarked (epoch ms). Default: now.",
                "type": "integer"
              },
              "version": {
                "maximum": 1,
                "minimum": 1,
                "type": "integer"
              }
            },
            "required": [
              "version"
            ],
            "type": [
              "object",
              "null"
            ]
          },
          "youtube": {
            "description": "Merged into the stored YouTube options. YouTube upload options, applied to YouTube targets only. madeForKids is always sent to YouTube (default false). A missing title is derived from the first sentence of the caption, cut to 100 characters at a word boundary. SCHEDULED UPLOADS: the video is uploaded ahead of scheduledAt. \"public\" is uploaded as private and released by YouTube at the scheduled time; \"unlisted\" is watchable by anyone with the link from upload time; \"private\" stays private.",
            "properties": {
              "categoryId": {
                "description": "YouTube category id, e.g. \"28\" (Science & Technology). YouTube default: 22.",
                "pattern": "^\\d{1,3}$",
                "type": "string"
              },
              "containsSyntheticMedia": {
                "description": "AI-generated content disclosure.",
                "type": "boolean"
              },
              "firstComment": {
                "description": "For scheduled videos, posted at the scheduled time.",
                "maxLength": 10000,
                "type": "string"
              },
              "madeForKids": {
                "default": false,
                "type": "boolean"
              },
              "playlistId": {
                "description": "From GET /api/v2/youtube/playlists for the same brand.",
                "type": "string"
              },
              "tags": {
                "description": "At most 500 characters combined.",
                "items": {
                  "maxLength": 100,
                  "type": "string"
                },
                "type": "array"
              },
              "title": {
                "description": "No < or >.",
                "maxLength": 100,
                "minLength": 1,
                "type": "string"
              },
              "visibility": {
                "default": "public",
                "enum": [
                  "public",
                  "private",
                  "unlisted"
                ],
                "type": "string"
              }
            },
            "type": "object"
          }
        },
        "required": [
          "id"
        ],
        "type": "object"
      },
      "name": "update_content"
    },
    {
      "description": "[writes flayr data · `write` scope] Changes an experiment's fields (e.g. status \"running\" / \"done\", endAt, notes). Only the fields you pass change.\n(Wraps PATCH /api/v2/experiments/{id}.)",
      "inputSchema": {
        "additionalProperties": false,
        "properties": {
          "compareKey": {
            "type": "string"
          },
          "endAt": {
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "hypothesis": {
            "maxLength": 2000,
            "type": "string"
          },
          "id": {
            "type": "string"
          },
          "name": {
            "maxLength": 120,
            "type": "string"
          },
          "notes": {
            "maxLength": 2000,
            "type": "string"
          },
          "primaryMetric": {
            "enum": [
              "impressions",
              "views",
              "engagement",
              "engagementRate",
              "clicks"
            ],
            "type": "string"
          },
          "startAt": {
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "enum": [
              "draft",
              "running",
              "done"
            ],
            "type": "string"
          },
          "variants": {
            "items": {
              "type": "string"
            },
            "maxItems": 12,
            "type": "array"
          }
        },
        "required": [
          "id"
        ],
        "type": "object"
      },
      "name": "update_experiment"
    },
    {
      "description": "[writes flayr data · `write` scope] Frame every image posted to a platform automatically (frameRef), or stop (frameRef null). Applies to posts published or scheduled from now on. Confirm with the user first.\n(Wraps POST /api/v2/frames/settings.)",
      "inputSchema": {
        "additionalProperties": false,
        "properties": {
          "aspect": {
            "enum": [
              "1:1",
              "4:5",
              "9:16",
              "16:9"
            ],
            "type": "string"
          },
          "frameRef": {
            "type": [
              "string",
              "null"
            ]
          },
          "platform": {
            "type": "string"
          }
        },
        "required": [
          "platform",
          "frameRef"
        ],
        "type": "object"
      },
      "name": "update_frame_settings"
    },
    {
      "description": "[PUBLISHES to social accounts · `publish` scope] `status: \"paused\"` stops new bookings (already-booked posts stay booked); \"active\" resumes. Other fields (platforms, cadenceHours, timeOfDay, lookahead, startsAt, name) apply from the next booking. Needs the `publish` scope (or admin) on the Agency plan or higher.\n(Wraps PATCH /api/v2/queues/{id}.)",
      "inputSchema": {
        "additionalProperties": false,
        "properties": {
          "brandId": {
            "type": "string"
          },
          "cadenceHours": {
            "description": "1, 2, 3, 4, 6, 8, 12, or whole days (24, 48, …, 720).",
            "type": "integer"
          },
          "id": {
            "type": "string"
          },
          "lookahead": {
            "maximum": 10,
            "minimum": 1,
            "type": "integer"
          },
          "name": {
            "maxLength": 80,
            "type": "string"
          },
          "platforms": {
            "description": "Replaces the queue's platforms for items booked from now on. Already-booked items keep theirs; items with their own `platforms` keep using them.",
            "items": {
              "type": "string"
            },
            "minItems": 1,
            "type": "array"
          },
          "startsAt": {
            "description": "ISO 8601 with an offset (or Z). Nothing is booked before it; the first slot is the first timeOfDay (in the queue's timezone) at or after it, so a startsAt that is itself a slot is the first slot. A past date holds nothing back. null clears it.",
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "enum": [
              "active",
              "paused"
            ],
            "type": "string"
          },
          "timeOfDay": {
            "description": "24-hour \"HH:MM\" local time.",
            "type": "string"
          }
        },
        "required": [
          "id"
        ],
        "type": "object"
      },
      "name": "update_queue"
    },
    {
      "description": "[PUBLISHES to social accounts · `publish` scope] CHANGES WHAT A SCHEDULED POST WILL PUBLISH on real accounts, keeping its slot, accounts, options, labels and draft. `firstComment` sets per-platform first comments (linkedin, facebook, instagram, threads, youtube; null removes; X/Bluesky have none); `text` / `platformText` change the caption; `applyAutoFirstComment: true` adds the brand's auto first comment where none is set; `removePlatforms` drops platforms from this booking (e.g. a duplicate TikTok). Updated in place when the platform scheduler allows it, otherwise re-booked at the same slot (status \"rebooked\", new postId). Refused within 5 minutes of the slot. Repeating the same edit returns \"unchanged\". Needs the `publish` scope (or admin) and the Pro plan or higher; each publish counts against the plan's direct posts for the month. Confirm the new text with the user first.\n(Wraps PATCH /api/v2/posts/{id}.)",
      "inputSchema": {
        "additionalProperties": false,
        "properties": {
          "applyAutoFirstComment": {
            "description": "Add the brand's auto first comment where the post has none.",
            "type": "boolean"
          },
          "firstComment": {
            "additionalProperties": false,
            "description": "The first comment the post publishes, per platform: a string sets it, null or \"\" removes it; platforms not named keep theirs. X and Bluesky have no first comment at the platform scheduler and are refused (422). A Facebook / Instagram Story never gets one. flayr's own \"Posted with flayr.social\" line on a comment is kept. Also saved on the post's draft, so a later edit never puts the old one back.",
            "minProperties": 1,
            "properties": {
              "facebook": {
                "maxLength": 8000,
                "type": [
                  "string",
                  "null"
                ]
              },
              "instagram": {
                "maxLength": 2200,
                "type": [
                  "string",
                  "null"
                ]
              },
              "linkedin": {
                "maxLength": 1250,
                "type": [
                  "string",
                  "null"
                ]
              },
              "threads": {
                "maxLength": 500,
                "type": [
                  "string",
                  "null"
                ]
              },
              "youtube": {
                "maxLength": 10000,
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "type": "object"
          },
          "id": {
            "description": "The post: `convexPostId` from POST /api/v2/publish, or `posts[].id` from GET /api/v2/content/{id}.",
            "type": "string"
          },
          "platformText": {
            "additionalProperties": {
              "type": [
                "string",
                "null"
              ]
            },
            "description": "Per-platform captions, merged into the draft's: { \"linkedin\": \"…\" } sets one, null / \"\" clears it; null clears them all.",
            "type": [
              "object",
              "null"
            ]
          },
          "removePlatforms": {
            "description": "Platform ids to drop from this booking (e.g. a duplicate [\"tiktok\"]). Each must be on the post; dropping every platform is refused (use DELETE). The booking is rebuilt from the draft.",
            "items": {
              "type": "string"
            },
            "minItems": 1,
            "type": "array"
          },
          "text": {
            "description": "The caption (every platform without its own).",
            "maxLength": 10000,
            "minLength": 1,
            "type": "string"
          }
        },
        "required": [
          "id"
        ],
        "type": "object"
      },
      "name": "update_scheduled_post"
    },
    {
      "description": "[writes flayr data · `write` scope] Changes a format; only the fields you pass change. `archived: true` hides it.\n(Wraps PATCH /api/v2/text-templates/{id}.)",
      "inputSchema": {
        "additionalProperties": false,
        "properties": {
          "archived": {
            "type": "boolean"
          },
          "ctaSlots": {
            "items": {
              "properties": {
                "label": {
                  "type": "string"
                },
                "placement": {
                  "default": "body",
                  "enum": [
                    "body",
                    "first_comment",
                    "none"
                  ],
                  "type": "string"
                },
                "text": {
                  "type": "string"
                }
              },
              "required": [
                "label"
              ],
              "type": "object"
            },
            "maxItems": 10,
            "type": "array"
          },
          "defaultLabels": {
            "description": "\"key:value\" labels every draft started from this format gets (plus the format's own `label`).",
            "items": {
              "type": "string"
            },
            "maxItems": 30,
            "type": "array"
          },
          "example": {
            "maxLength": 20000,
            "type": [
              "string",
              "null"
            ]
          },
          "format": {
            "enum": [
              "post",
              "article",
              "carousel_script",
              "meme",
              "thread"
            ],
            "type": "string"
          },
          "guardrails": {
            "items": {
              "maxLength": 500,
              "type": "string"
            },
            "maxItems": 30,
            "type": "array"
          },
          "hookPatterns": {
            "description": "\"[slot]\" placeholders are kept as written.",
            "items": {
              "maxLength": 300,
              "type": "string"
            },
            "maxItems": 20,
            "type": "array"
          },
          "id": {
            "type": "string"
          },
          "length": {
            "properties": {
              "captionWords": {
                "description": "carousel_script: the caption that goes with the slides.",
                "properties": {
                  "max": {
                    "type": "integer"
                  },
                  "min": {
                    "type": "integer"
                  }
                },
                "type": "object"
              },
              "max": {
                "minimum": 1,
                "type": "integer"
              },
              "min": {
                "minimum": 0,
                "type": "integer"
              },
              "perSlideMaxWords": {
                "description": "carousel_script / thread: words allowed per slide or post.",
                "type": "integer"
              },
              "titleMaxWords": {
                "description": "carousel_script: words allowed in the title (first line of slide 1).",
                "type": "integer"
              },
              "unit": {
                "enum": [
                  "words",
                  "chars",
                  "slides"
                ],
                "type": "string"
              }
            },
            "required": [
              "unit",
              "min",
              "max"
            ],
            "type": "object"
          },
          "maxHashtags": {
            "maximum": 30,
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "name": {
            "maxLength": 120,
            "type": "string"
          },
          "platform": {
            "description": "e.g. \"linkedin\"; null = any platform.",
            "type": [
              "string",
              "null"
            ]
          },
          "sections": {
            "description": "In order. \"Start from a format\" writes one `[you fill: Label — guidance]` placeholder per section.",
            "items": {
              "properties": {
                "guidance": {
                  "maxLength": 1000,
                  "type": "string"
                },
                "key": {
                  "description": "Derived from the label when omitted.",
                  "type": "string"
                },
                "label": {
                  "maxLength": 80,
                  "type": "string"
                },
                "maxLines": {
                  "type": "integer"
                },
                "minLines": {
                  "type": "integer"
                },
                "required": {
                  "default": true,
                  "type": "boolean"
                }
              },
              "required": [
                "label"
              ],
              "type": "object"
            },
            "maxItems": 20,
            "type": "array"
          },
          "source": {
            "description": "Where the shape came from, e.g. an import path.",
            "maxLength": 200,
            "type": [
              "string",
              "null"
            ]
          },
          "useFor": {
            "description": "One line: when to pick this format.",
            "maxLength": 300,
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "id"
        ],
        "type": "object"
      },
      "name": "update_text_template"
    },
    {
      "description": "[writes flayr data · `write` scope] Step 1 of attaching a LOCAL file (video or image) to a draft. Returns a single-use, short-lived `uploadUrl`. Step 2: POST the raw file bytes to it with the file's Content-Type, e.g. `curl -X POST -H \"Content-Type: video/mp4\" --data-binary @clip.mp4 \"<uploadUrl>\"` — the JSON response has a `storageId`. Step 3: pass it as `videoStorageId` or `imageStorageId` on create_content or update_content. For media already on the web, skip this and pass `videoUrl` / `imageUrl` instead. Returns 413 when the account's storage is full.\n(Wraps POST /api/v2/media/upload-url.)",
      "inputSchema": {
        "additionalProperties": false,
        "properties": {},
        "type": "object"
      },
      "name": "upload_media"
    },
    {
      "description": "[read-only] Returns who this connection (an OAuth sign-in or an API key) belongs to: userId, the account's current plan tier, and brandId/brandName when the connection is bound to one brand (null = account-wide). Call this first to learn what the connection can reach.\n(Wraps GET /api/v2/ping.)",
      "inputSchema": {
        "additionalProperties": false,
        "properties": {},
        "type": "object"
      },
      "name": "whoami"
    }
  ]
}

What the leaf hashes

{"body":"b715ce9da6d51d9b61a6a9a8ac702912c6582cabec21462eafa36c0de4cfdc24","id":1226289,"kind":"mcp.tools","method":"probe.mcp/1","observed_us":1793332981459536,"prev":null,"subject":"b7b10d45-f6fb-49e6-abb9-091c4a879468","subject_kind":"endpoint"}

Proof

Its day has not closed yet. A day closes ten minutes after midnight UTC; the proof appears here then.

How to check it · This row as JSON