{"id":1226289,"recorded_at":"2026-10-30T04:03:01.459621Z","observed_at":"2026-10-30T04:03:01.459536Z","subject_kind":"endpoint","subject":"b7b10d45-f6fb-49e6-abb9-091c4a879468","kind":"mcp.tools","method":"probe.mcp/1","body":{"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"}]},"body_sha256":"b715ce9da6d51d9b61a6a9a8ac702912c6582cabec21462eafa36c0de4cfdc24","prev_id":null,"leaf":"0xa3df3afc50f77b0ae35f09dde3a06829c2fff638f6682d710bb58cb3149ee45e","leaf_text":"{\"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\"}","day":{"day":"2026-10-30","leaves":243099,"root":"0x0826c7ad6039e06a2360ca0a4a3a4fe7f2626c6dc19f71a37599386f78b39875","chained":"0x36b4c8a1063758f0b8765fa1bdf75fa92fb543cc183add3e261880ee2bd1f822","anchor_chain":"eip155:5042002","anchor_tx":"0x8704744146c6967c8abc9e9a99de4db7283b29e840922ea83a69b6fe776e6861","anchor_block":69923247},"proof":["0xa3df7655eb0351c151cf6ad21ff22b5952db00f456cde86bd07a012fad2fb64a","0xceeabcf4b33ff98c075134b857eb145085616764c1b02a8edeccb6b1fe1f43f1","0x666b07ae3d3169f821c3d59a775a9282549d745dd067f93ef8b28bb6e96ff1fb","0x9221a85804776b428d6adb9e6e3d7c6bfdab02159370657970d9fb2be669c198","0xf35fdca9d3fe2a3e8cbebac7b41dd4b8ca70dda48ea7ce784366dc925ac77379","0x795d09b439f88ad9147206ff86041e0985825729097bf9938117b6cb803d7ab4","0xaa695ac0332b2b831785650d4508a0674f12c2d83728193f54d88de8f7cdaf83","0xf50aa23a12477b5d6be976f59c5811ad1974395c6656b8ab83c42d3faf97332b","0x45e3d9c831ae3aab531ce7e9e9d23628ef15e104061a72f017a86d615fa0c0cf","0xc0ee657e9db62eb0fb4c59b1a2ae4732d1375b6652a2bb20c9a9421546185c90","0x441dd4eaca133f54ba2a00d17a1b99b96964ee3e9c9ce935c11fc4fe428edb9f","0x87cd233b723532856630fb5df2ad521ed7322e91a7197b20eaffb92da234f5a0","0xa2b1de1ad77c2c402e9780077d54e1e375f0df615775330ca3b7f3a0ca384ce1","0xe11a711cdcc033b4d3ff4f10f0568bda761703429a7ddfbf68a7fbd1d22911ff","0x11b43ed362a2fd4976e11eceb94ee4b7edbaf6823474ee1c36489f8f0a0496c4","0x835211170e79bdce1a823ec914e3532e128f77751e9074286da35091b0200284","0x61ef83255d34604cb052882568ca1d1386ac3cfb8284509715bc1734cff77831","0x0c43c73b62a5d49ee1444b5dc4195fc5b16b141d3f4458e2e9fbd5fd6ecb367d"]}