For developers
The Welder MCP server
Welder is a remote MCP server at https://weldergtm.com/mcp. Call get_context first, publish with create_post, and hand files over with create_upload (a shell) or create_upload_link (a chat app). Technical words live on this page only; the rest of the site speaks plainly.
Endpoint#
POST https://weldergtm.com/mcp- Streamable HTTP, stateless: no session id is needed. Protocol versions 2025-11-25, 2025-06-18, 2025-03-26 and 2024-11-05.
- Answers are JSON; with Accept: text/event-stream you may get a stream carrying exactly one response.
- Requests up to 1 MiB, batches of up to 20. GET and DELETE on /mcp answer 405.
- Every tool returns structured content plus a short text version the model can read aloud, and advertises a title and the four MCP annotations (read-only, destructive, open world, idempotent).
Authentication and scopes#
OAuth 2.1 (preferred). Clients discover everything themselves: an unauthenticated call gets 401 with a pointer to the protected resource metadata, clients register dynamically, and sign-in happens in the browser with PKCE. Access tokens last an hour and refresh for 90 days.
https://weldergtm.com/.well-known/oauth-protected-resource
https://weldergtm.com/.well-known/oauth-authorization-serverScopes. Every tool requires sign-in and declares the scope it needs in securitySchemes. read covers lookups inside the workspace, validation and stored numbers (9 tools); get_post may also ask TikTok for a pending link and store it, so it is not marked read-only. publish covers uploads, account connection links, publishing, cancelling, deleting and dismissing warnings, and every call that can change state or spend credits (12 tools): waiting on an upload link, refreshing numbers and reading replies are among them. A credential without the scope gets forbidden.
API keys. For clients without OAuth, scripts and CI. Create one under Advanced → Keys and send it as a bearer token. Keys start with wk_live_, are shown once and carry both scopes.
curl -X POST https://weldergtm.com/mcp \
-H "Authorization: Bearer {key}" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'A credential belongs to exactly one workspace, so there is no workspace header to send.
Install in a coding app#
Each app has a friendly guide with the exact command or config. The short version:
Claude CodeGuide
claude mcp add --transport http welder https://weldergtm.com/mcpCodex CLIGuide
codex mcp add welder --url https://weldergtm.com/mcpCursorGuide
{
"mcpServers": {
"welder": { "url": "https://weldergtm.com/mcp" }
}
}WindsurfGuide
{
"mcpServers": {
"welder": { "serverUrl": "https://weldergtm.com/mcp" }
}
}Gemini CLIGuide
gemini mcp add --transport http welder https://weldergtm.com/mcpVS Code (Copilot)Guide
code --add-mcp '{"name":"welder","type":"http","url":"https://weldergtm.com/mcp"}'ClineGuide
Open Cline → MCP Servers → Remote Servers and add a server named welder with the URL https://weldergtm.com/mcp.
Any client or scriptGuide
https://weldergtm.com/mcpOr let the agent install itself:
Install the Welder MCP server for this client the native way (HTTP transport, URL https://weldergtm.com/mcp). Then call the `get_context` tool and tell me which accounts are connected. Never print credentials back to me.The Welder plugin#
Welder: TikTok, Instagram & YouTube Autoposting packages five ready-made workflows with a connection to this server: get started and connect, publish content, schedule content, review performance and engage your audience. The package holds readable instructions, connection settings and brand images. It installs no program, background process or hook, and it carries no credentials: you sign in with Welder in your browser, through your app’s own sign-in.
Claude Code. Add the marketplace and install the plugin, one line at a time, inside Claude Code. Then run /mcp, pick the Welder server and choose Authenticate; your browser opens to sign in.
/plugin marketplace add smvls/welder-plugins
/plugin install welder-social-manager@welder-pluginsCodex. Codex CLI installs the same package from the public repository. Sign in with Welder through Codex’s own sign-in flow; your browser opens to approve access.
codex plugin marketplace add smvls/welder-plugins
codex plugin add welder-social-manager@welder-pluginsPrefer the server on its own, without the workflows? Add it and sign in once:
codex mcp add welder --url https://weldergtm.com/mcp
codex mcp login welderClaude and ChatGPT apps. Add https://weldergtm.com/mcp as a custom connector in their settings and sign in; the Claude guide and the ChatGPT guide show each screen.
Directory listings. The Claude and ChatGPT plugin directories each review and approve a plugin before listing it. Meanwhile, install from the public repository or connect Welder directly.
Tools#
Workspace and accounts
Files
Posts
Results and reference
Needs the publish scope. The rest need read.
Workspace and accounts
Who is connected, what they can post, and how to connect another network.
get_connected_profile
readGet connected Welder profile. Annotations: Read-only, Idempotent
Identifies the Welder workspace this credential belongs to, so a host that supports several accounts can tell them apart.
No input.
Returns
{ id }
- id is a stable, opaque workspace id resolved only from the validated credential. It never contains an email, the workspace name or a credential.
- Marked as the authenticated profile tool (_meta["openai/profile"]: true). The text content carries the same JSON as the structured result.
get_connected_profile({}){ "id": "7c1e2a9d-…" }get_context
readGet Welder publishing context. Annotations: Read-only, Idempotent
Start here. The workspace, whether publishing is open right now, this month’s allowance and usage, connected accounts, what each network accepts, and how to hand over files.
No input.
Returns
{ workspace: { id, name, plan_status, limits: { posts, x_credits }, usage: { posts, x_credits }, can_publish }, accounts: Account[], capabilities: { tiktok, instagram, youtube, threads, bluesky, x }, media_hint, links: { app, connect_accounts }, server: { version }, next_step? }
- When can_publish is false, next_step says so: “Publishing is unavailable under this account's current entitlement. Account setup, uploads and validation remain available.” Subscriptions are managed on the Welder website, never through an assistant.
- capabilities is keyed by network, with the same limits as get_capabilities.
- In the simulated review workspace the result also carries review_demo: true and a data_source line, and media_hint explains the sample media. See Review workspace below.
get_context({}){
"workspace": {
"id": "7c1e2a9d-…",
"name": "Nightshift Studio",
"plan_status": "active",
"limits": { "posts": 1500, "x_credits": 100 },
"usage": { "posts": 3, "x_credits": 8 },
"can_publish": true
},
"accounts": [{ "id": "b2d4…", "provider": "x", "username": "nightshiftHQ", "status": "active", … }],
"capabilities": { "tiktok": { … }, "instagram": { … }, … },
"media_hint": "In chat apps without a shell, use create_upload_link and show its URL to the person; then get_upload_link for media ids. With a shell, use create_upload and finalize_upload.",
"links": {
"app": "https://weldergtm.com/app",
"connect_accounts": "https://weldergtm.com/app/accounts"
},
"server": { "version": "0.1.0" }
}list_accounts
readList connected social accounts. Annotations: Read-only, Idempotent
Connected accounts with their status and what each can publish. Use the ids as create_post targets.
No input.
Returns
{ accounts: [{ id, provider, username, display_name, avatar_url, profile_url, status: 'active' | 'reauth_required' | 'disconnected', capabilities, connected_at, expires_at, defaults, last_error }] }
- defaults holds provider facts the agent may need, such as the TikTok privacy levels an account offers or the Instagram account type.
connect_account
publishConnect a social account. Annotations: Changes data
A short-lived link (10 minutes) that opens the connect flow for a network in the person’s browser. Show it, then call list_accounts once they finish.
| Input | Type | Description |
|---|---|---|
providerrequired'tiktok' | 'instagram' | 'youtube' | 'threads' | 'bluesky' | 'x' | 'tiktok' | 'instagram' | 'youtube' | 'threads' | 'bluesky' | 'x' | The network to connect. |
Returns
{ provider, url, expires_in }
- The person signs in on the network’s own screen; the assistant never sees a password. For Bluesky the link opens the app-password form.
- Refused with forbidden in the simulated review workspace, whose sample accounts are provisioned by Welder.
Files
Getting a photo or video into Welder: an upload link for chat apps, a signed upload for shells, or a public URL.
create_upload_link
publishCreate a browser upload link. Annotations: Changes data
For chat apps without a shell: a link where the person drops their files in the browser. Show it, then call get_upload_link.
| Input | Type | Description |
|---|---|---|
notestring (≤140) | string (≤140) | Shown on the upload page, e.g. “Drop your latte video here”. |
max_files1–10 | 1–10 | How many files the link takes. Default 1. |
kind'video' | 'image' | 'any' | 'video' | 'image' | 'any' | What the link accepts. Default any. |
Returns
{ upload_link_id, url, expires_in, max_files, kind, next }
- The link works for 2 hours. Its URL is a secret: show it to the person, never log it.
get_upload_link
publishGet browser upload results. Annotations: Changes data
The files the person dropped on an upload link, as media ids for create_post. Wait for them with wait_seconds.
| Input | Type | Description |
|---|---|---|
upload_link_idrequiredstring (uuid) | string (uuid) | From create_upload_link. |
wait_seconds0–50 | 0–50 | Wait until the person finishes, then answer. Call again while the status is still open. Default 0. |
Returns
{ status: 'open' | 'completed' | 'expired', max_files, media: Media[] }
- Only ready media are returned, in upload order.
- Needs the publish scope: a call can mark a link that ran out of time as expired.
list_media
readList uploaded media. Annotations: Read-only, Idempotent
Recent uploads, newest first, for requests such as “post my latest upload”. Expired files are left out.
| Input | Type | Description |
|---|---|---|
limit1–50 | 1–50 | Default 10. |
status'ready' | 'pending' | 'ready' | 'pending' | Filter by status. |
Returns
{ media: [{ id, kind, mime, bytes, width, height, duration_ms, filename, status, error, preview_url: null, expires_at, created_at }] }
create_upload
publishCreate a media upload. Annotations: Changes data
For agents with a shell: reserves a media slot and returns a signed upload URL plus the exact curl command to PUT a local file.
| Input | Type | Description |
|---|---|---|
filenamerequiredstring | string | Original file name, e.g. launch.mp4. |
mimerequired'image/jpeg' | 'image/png' | 'image/webp' | 'video/mp4' | 'video/quicktime' | 'image/jpeg' | 'image/png' | 'image/webp' | 'video/mp4' | 'video/quicktime' | File type. |
bytesrequiredinteger | integer | File size in bytes. Images up to 30 MB, videos up to 500 MB. |
Returns
{ media_id, upload: { method: 'PUT', url, headers }, expires_in, curl }
- The upload URL is valid for 2 hours and is a secret. Original media is kept for 7 days; each media post keeps one small image in its history.
curl -sS -X PUT -H "Content-Type: video/mp4" --upload-file "launch.mp4" "<upload url>"finalize_upload
publishFinalize a media upload. Annotations: Changes data, Idempotent
Checks the uploaded object (size, type, and dimensions and duration when possible) and marks it ready. Calling it again on a ready file returns the same media.
| Input | Type | Description |
|---|---|---|
media_idrequiredstring (uuid) | string (uuid) | From create_upload. |
Returns
{ media: Media }
- If the file is not there yet the result is media_not_ready, which is safe to retry shortly.
import_media
publishImport media from a public URL. Annotations: Changes data, Open world
Fetches a public https file into Welder’s storage instead of uploading from disk.
| Input | Type | Description |
|---|---|---|
urlrequiredstring (https) | string (https) | A public file URL. Private networks and more than three redirects are refused. |
filenamestring | string | Optional file name. |
Returns
{ media: Media }
- The simulated review workspace imports only Welder’s public sample files; any file can still be uploaded the normal way.
Posts
Check, publish, schedule, follow, cancel, take down and dismiss warnings.
validate_post
readValidate a social post. Annotations: Read-only, Idempotent
A dry run of create_post: publishing access and allowance, accounts, media per network, caption lengths, image counts and known limits. Nothing is posted and nothing is used up.
| Input | Type | Description |
|---|---|---|
…same as create_post | same as create_post | Pass exactly what you would pass to create_post. |
Returns
{ ok, issues, targets: [{ account_id, provider, username, ok, issues, resolved }], cost: { posts, x_credits } }
- Issues with code warning are advice, for example that Instagram shows no caption on Stories. They never block publishing.
create_post
publishPublish or schedule a social post. Annotations: Changes data, Destructive, Open world, Idempotent
Publishes one caption and media set to one or more accounts, now or at schedule_at. Returns at once with the post, or waits up to wait_seconds for the result.
| Input | Type | Description |
|---|---|---|
captionrequiredstring | string | Caption, description or post text, depending on the network. |
targetsrequired[{ account_id } | { provider }] | [{ account_id } | { provider }] | A provider shorthand targets every active account of that network. |
mediastring[] | string[] | Media ids. Leave empty for a text post (Threads, Bluesky, X). |
titlestring | string | YouTube title or TikTok photo title; derived from the caption when absent. |
idempotency_keystring | string | Recommended: one stable key per post, reused on every retry. Without one, identical inputs are still deduplicated automatically. |
schedule_atISO 8601 | ISO 8601 | Publish later, up to 30 days ahead. |
optionsobject | object | Per-network options: tiktok, youtube, instagram, threads, bluesky, x (Stories, drafts, replies, quotes, polls, alt text and more). See Options per network below. |
wait_seconds0–50 | 0–50 | Block until every target settles or the time runs out, then return the current state. |
Returns
{ post: { id, status, source, client, caption, title, media, media_count, targets: [{ id, account_id, provider, username, status, attempt, url, platform_post_id, error_code, error_message, ambiguous, published_at, updated_at, kind }], scheduled_at, created_at, updated_at, completed_at } }
- A request that names the exact caption, accounts and time already approves the post; no second confirmation is needed. Ask only about a missing or conflicting detail, such as no caption, an account name that matches two profiles or two different times: a published post can’t always be taken back.
- TikTok privacy and YouTube visibility default to the workspace settings (public unless the person changes them). The AI-generated label is on by default for TikTok and YouTube.
- options.instagram.kind = "story" posts an Instagram Story; a reply option (threads.reply_to_id, x.reply_to_post_id, bluesky.reply_to) answers a comment from list_comments.
- If a target comes back ambiguous, the network may have published it: check the account instead of posting again with a new key.
create_post({
idempotency_key: "cold-brew-launch",
caption: "Cold brew season is open. Twelve hours, zero heat.",
media: ["3ed1…"],
targets: [{ provider: "tiktok" }, { provider: "youtube" }],
wait_seconds: 30
})get_post
readGet social post status. Annotations: Changes data, Open world, Idempotent
The current state of a post and each of its targets, with the live link once a network confirms it.
| Input | Type | Description |
|---|---|---|
post_idrequiredstring (uuid) | string (uuid) | From create_post. |
wait_seconds0–50 | 0–50 | Wait for targets to settle, and for a pending TikTok link, before answering. Default 0. |
Returns
{ post: Post }
- needs_attention is true while a failed or partly published post has a warning nobody dismissed; attention_dismissed_at says when one was dismissed.
- A TikTok target with url_pending has been posted, but TikTok hasn’t supplied its public link yet (it can still be in moderation). Call again later; a private post may never get a public link, and none is ever made up.
- For a pending TikTok link, get_post may ask TikTok for that post’s status and store the link once it exists. That is the only change it can make: it never publishes, retries or changes usage, so it needs only the read scope. The provider read makes it open-world, and storing the link means it is not read-only. The simulated review workspace never asks TikTok.
list_posts
readList social posts. Annotations: Read-only, Idempotent
Recent and scheduled posts, newest first.
| Input | Type | Description |
|---|---|---|
limit1–50 | 1–50 | Default 20. |
cursorstring | string | From next_cursor of the previous page. |
status'queued' | 'publishing' | 'published' | 'partial' | 'failed' | 'canceled' | 'needs_attention' | 'queued' | 'publishing' | 'published' | 'partial' | 'failed' | 'canceled' | 'needs_attention' | Only posts in this state. needs_attention lists failed and partly published posts whose warning hasn’t been dismissed. |
Returns
{ posts: Post[], next_cursor }
dismiss_post_attention
publishDismiss a post warning. Annotations: Changes data, Idempotent
Dismisses the warning on a failed or partly published post. The post keeps its results in history and leaves the needs_attention list.
| Input | Type | Description |
|---|---|---|
post_idrequiredstring (uuid) | string (uuid) | The post whose warning to dismiss. |
Returns
{ post: Post }
- Does not retry, publish, delete or cancel anything, and contacts no network. The original errors, media and usage stay as they were.
- Safe to call again. A post without an unresolved warning answers conflict. A later explicit retry clears the dismissal, so new failures show up again.
dismiss_post_attention({ post_id: "8c1f…" })cancel_post
publishCancel a scheduled social post. Annotations: Changes data, Destructive, Idempotent
Stops queued and scheduled targets before they go out. Targets already publishing finish.
| Input | Type | Description |
|---|---|---|
post_idrequiredstring (uuid) | string (uuid) | The post to cancel. |
Returns
{ post: Post }
- Cancelling can’t be undone; schedule the post again to bring it back.
delete_post
publishDelete a published social post. Annotations: Changes data, Destructive, Open world, Idempotent
Takes a published post down. Removes it from X and Bluesky; for the other networks the result says to delete it in their app. It can’t be undone: a clear request to delete that post is the go-ahead, and an unclear post or account is worth one question first.
| Input | Type | Description |
|---|---|---|
post_idrequiredstring (uuid) | string (uuid) | The post to take down. |
account_idsstring[] | string[] | Only these accounts’ copies. Default every published copy. |
Returns
{ post: Post, results: [{ target_id, account_id, provider, result: 'deleted' | 'already_deleted' | 'unsupported' | 'not_published' | 'failed', message }] }
- A removed copy keeps the status deleted on the post, and its numbers stop updating. Deleting never gives back used posts or X credits.
- Refused with forbidden in the simulated review workspace: its posts never reached a network.
delete_post({ post_id: "8c1f…", account_ids: ["e7a9…"] })Results and reference
Numbers, replies and what each network accepts.
get_analytics
readGet social analytics. Annotations: Read-only, Idempotent
How posts are doing from stored numbers: views, likes, comments, shares and followers per network and account, plus the top posts. A missing number means the network doesn’t share it; the note says why.
| Input | Type | Description |
|---|---|---|
days1–365 | 1–365 | Posts published in the last N days. Ignored when since is set. |
since, untilISO 8601 | ISO 8601 | A custom period, by publish time. |
provider'tiktok' | 'instagram' | 'youtube' | 'threads' | 'bluesky' | 'x' | 'tiktok' | 'instagram' | 'youtube' | 'threads' | 'bluesky' | 'x' | Only one network. |
account_idstring (uuid) | string (uuid) | Only one account. |
sort'published_at' | 'views' | 'likes' | 'comments' | 'shares' | 'interactions' | 'engagement_rate' | 'published_at' | 'views' | 'likes' | 'comments' | 'shares' | 'interactions' | 'engagement_rate' | Order of top_posts. Default published_at. |
limit1–50 | 1–50 | How many top posts. Default 20. |
Returns
{ period: { since, until }, as_of, posts, measured, totals, interactions, engagement_rate, by_provider: [{ provider, posts, measured, totals, note }], accounts: [{ account_id, provider, username, status, note, metrics, change, captured_at, history }], top_posts: TargetAnalytics[] }
- The period picks posts by publish time; every number is that post’s latest total, not growth inside the period.
- Counters: views, reach, likes, comments, shares, reposts, quotes, saves, link_clicks, profile_visits, follows, engagements, video_views, watch_time_s, avg_watch_time_s. A counter the network does not report is absent, never 0.
- status, per post and account: available, pending, permission_required, unsupported, unavailable or error.
get_analytics({ days: 28, sort: "views", limit: 5 })get_post_analytics
publishGet or refresh post analytics. Annotations: Changes data, Open world
One post’s numbers on each network, with the reason when some are missing. refresh asks the networks now; history shows how the numbers grew.
| Input | Type | Description |
|---|---|---|
post_idrequiredstring (uuid) | string (uuid) | The post. |
refreshboolean | boolean | Collect fresh numbers now instead of waiting for the schedule (at most once every 10 minutes per post). Default false. |
historyboolean | boolean | Add every collected point, oldest first. Default false. |
Returns
{ post_id, caption, created_at, targets: [{ target_id, account_id, provider, username, url, status, note, metrics, extra, interactions, engagement_rate, captured_at, next_refresh_at, history? }], totals, as_of }
- Needs the publish scope because refresh: true reads the networks and stores the new numbers; those provider reads make it open-world.
- Numbers are collected about 1 hour, 6 hours, 1 day, 3 days, 7 days, 30 days and 90 days after publishing (Instagram Stories: 1, 6 and 20 hours; X: 1 hour, 1, 7, 29 and 90 days).
- Refreshing too often returns rate_limited with details.retry_after_seconds. In the simulated review workspace refresh is ignored and the fixed sample numbers are returned.
get_post_analytics({ post_id: "8c1f…", refresh: true, history: true })list_comments
publishRead comments on a post. Annotations: Changes data, Open world
Replies under a published post, network by network. To answer one, call create_post with that account as the target and the comment id in the option named by reply_option.
| Input | Type | Description |
|---|---|---|
post_idrequiredstring (uuid) | string (uuid) | The post. |
account_idstring (uuid) | string (uuid) | Only this account’s copy. Default every published copy. |
limit1–50 | 1–50 | Default 20. |
Returns
{ post_id, targets: [{ target_id, account_id, provider, username, status, note, reply_option, comments: [{ id, author: { username, display_name, avatar_url }, text, created_at, likes, replies, url, parent_id, is_own, hidden }] }] }
- Needs the publish scope: each read of X replies uses 1 X credit, and a network read can refresh the account’s sign-in. Reading comments from the networks makes it open-world.
- Readable today: Bluesky, YouTube (read only), X with the account’s X entitlement (the last 7 days), and Threads where the connection holds the replies permission. Instagram answers permission_required and TikTok unsupported.
- reply_option is threads.reply_to_id, x.reply_to_post_id or bluesky.reply_to; null where Welder cannot reply (YouTube). Send an answer when the person asks for it with its words; when they ask for a draft, write it in the chat and send nothing.
list_comments({ post_id: "8c1f…", account_id: "b2d4…" })
create_post({
caption: "Not yet, but it’s on the list!",
targets: [{ account_id: "b2d4…" }],
options: { threads: { reply_to_id: "17890…" } }
})get_capabilities
readGet social network capabilities. Annotations: Read-only, Idempotent
What every network accepts: media types, sizes, durations, image counts and caption limits. Static, the same for every workspace.
No input.
Returns
{ providers: { tiktok: { text_only, video: { max_bytes, min_duration_s, max_duration_s, mimes }, images: { max_bytes, min_count, max_count, mimes }, mixed_carousel, caption_max, title_max, story?, notes }, … } }
Options per network#
Pass these under options.<network> in create_post and validate_post. Combinations a network refuses (a poll with media, a caption on a Story) come back from validate_post as issues; advice comes back as warning and never blocks publishing.
TikTokoptions.tiktok
| Option | What it does |
|---|---|
privacypublic · friends · followers · private | Who can see the post. Defaults to your workspace setting. |
ai_generatedtrue · false | Adds TikTok’s AI-generated content label. Default true. |
disable_comment, disable_duet, disable_stitchtrue · false | Turn interactions off for this post. |
brand_content, brand_organictrue · false | Disclose paid partnerships or your own brand promotion. |
modedirect · draft | draft sends the video or photos to the creator’s TikTok inbox to finish in the app. Default direct. |
cover_index0–34 | Photo posts only: which image is the cover. |
cover_timestamp_msmilliseconds | Videos only: the frame TikTok shows as the cover. |
auto_add_musictrue · false | Photo posts only: let TikTok add recommended music. Default true. |
Instagramoptions.instagram
| Option | What it does |
|---|---|
kindreel · image · carousel · story | Override the inferred post type. A Story is never inferred; Meta allows Stories from Business accounts. |
share_to_feedtrue · false | Reels only: also show the Reel in the feed. Default true. |
cover_media_idmedia id | Reels only: an uploaded image to use as the cover (not part of media). |
thumb_offset_msmilliseconds | Reels only: the frame used as the cover when no cover image is given. |
collaboratorsup to 3 usernames | Invite collaborators on a feed post or Reel (not Stories). |
user_tags[{ username, x?, y?, media_index? }], up to 20 | Tag people. x and y (0 to 1) place a tag on an image; Reels and Stories take usernames only. |
location_idlocation id | Add a location to a feed post or Reel. |
alt_textsup to 10 strings | Alt text per image, in media order (feed images). |
audio_nameup to 100 characters | Reels only: the name shown for the original audio. |
trial{ graduation: manual · performance } | Reels only: a trial Reel is shown to non-followers first; graduation decides when followers see it. |
YouTubeoptions.youtube
| Option | What it does |
|---|---|
visibilitypublic · unlisted · private | Who can watch. Defaults to your workspace setting. |
titleup to 100 characters | Taken from the caption when you leave it out. |
tagsstring list | Search tags for the video. |
made_for_kidstrue · false | Required disclosure for content made for children. |
ai_generatedtrue · false | Synthetic media disclosure. Default true. |
thumbnail_media_idmedia id | An uploaded JPEG or PNG up to 2 MB, used as the custom thumbnail (not part of media). |
category_idYouTube category id | The video’s category, e.g. 22 for People & Blogs. |
publish_atISO 8601 time | YouTube makes the video public at this time; until then it stays private. |
notify_subscriberstrue · false | Tell subscribers about the upload. Default true. |
licenseyoutube · creative_commons | The video’s license. |
embeddabletrue · false | Allow other sites to embed the video. |
public_stats_viewabletrue · false | Show the view count on the watch page. |
default_languagelanguage code, e.g. en | Language of the title and description. |
default_audio_languagelanguage code | Language spoken in the video. |
recording_dateISO 8601 time | When the video was recorded. |
Threadsoptions.threads
| Option | What it does |
|---|---|
reply_linkURL | Posted as the first reply under your post. |
reply_controleveryone · accounts_you_follow · mentioned_only · parent_post_author_only · followers_only | Who can reply. Default everyone. |
topic_tagone tag, without # | A topic tag for the post (no periods or ampersands). |
reply_to_idThreads post id | Publish as a reply to this post: build a thread, or answer someone. |
quote_post_idThreads post id | Quote this post. |
link_attachmentURL | Text posts only: a link preview card. |
poll{ options: 2 to 4, up to 25 characters each } | Text posts only: a poll. |
spoilertrue · false | Blur the photos and videos until tapped. |
alt_textsup to 20 strings | Alt text per image or video, in media order. |
reply_approvalstrue · false | Hold replies for your approval before they show. |
Blueskyoptions.bluesky
| Option | What it does |
|---|---|
alt_textsup to 4 strings | Alt text for each image, in order. |
langsup to 3 language codes, e.g. en | Languages of the text. Default en. |
labelssexual · nudity · porn · graphic-media | Content warnings shown before the media. |
reply_controlnobody, or any of mentioned · following · followers | Who can reply. Default everyone. |
disable_quotestrue · false | Stop other people from quoting the post. |
reply_toat:// URI or bsky.app link | Reply to this post: build a thread, or answer someone. |
quoteat:// URI or bsky.app link | Quote this post (with or without your own images or video). |
Xoptions.x
| Option | What it does |
|---|---|
alt_textsup to 4 strings | Alt text for each image, in order. |
reply_settingsfollowing · mentionedUsers · subscribers · verified | Who can reply. Default everyone. |
reply_to_post_idX post id | Reply to this post: build a thread, or answer someone. |
quote_post_idX post id | Quote this post. |
poll{ options: 2 to 4, duration_minutes: 5 to 10080 } | Text posts only: a poll, open 5 minutes to 7 days. |
made_with_aitrue · false | Disclose AI-generated media. |
paid_partnershiptrue · false | Disclose a paid partnership. |
community_idX community id | Post into an X Community the account belongs to. |
Numbers and replies#
get_analytics and get_post_analytics read the numbers Welder collects on a schedule after each post goes live; list_comments reads replies. What a network shares depends on the permissions it granted Welder:
- TikTok: None yet. TikTok hasn’t approved stats for Welder, so views, likes and followers stay in the TikTok app for now.
- Instagram: Likes and comments on each post, and your follower count. Views, reach, saves and Story views need a permission Instagram hasn’t given Welder yet.
- YouTube: Public views, likes and comments on each video, plus subscribers. Nothing to set up.
- Threads: Views, likes, replies, reposts, quotes and shares on each post, plus followers. Threads is still reviewing Welder, so until it approves, it may ask for a permission instead.
- Bluesky: Likes, reposts, replies, quotes and bookmarks on each post, plus followers. Bluesky doesn’t count views.
- X: Views, likes, replies, reposts, quotes and bookmarks on each post, plus followers; link clicks and profile visits too, for a post’s first 30 days.
A counter a network does not report is absent, never 0, and each post or account carries a status (available, pending, permission_required, unsupported, unavailable, error) with a plain note. Welder deletes posts only on X and Bluesky (delete_post); elsewhere the result tells the person to delete it in the app.
Files from chat apps#
An assistant without a shell calls create_upload_link, shows the link, waits for the person to drop their files on the Welder upload page, then calls get_upload_link with wait_seconds and passes the media ids to create_post. A shell-capable agent uses create_upload, one PUT and finalize_upload. list_media finds what a person uploaded in the app.
Errors#
Domain failures come back as tool results with isError: true, never as protocol errors, so the model can read them and act. Messages never contain URLs, credentials or email addresses.
{ "error": { "code": "quota_exceeded", "message": "Monthly post quota exceeded.", "retryable": false } }| Code | Meaning | Retry? |
|---|---|---|
unauthorized | Missing, expired or revoked credentials.Retry: No: sign in again or use a valid key. | No: sign in again or use a valid key. |
forbidden | The credential lacks the scope this tool needs, or the action isn’t allowed for this workspace (account changes in the simulated review workspace, for example).Retry: No | No |
validation | The input does not match the tool schema.Retry: No: fix the input. | No: fix the input. |
not_found | The post, media, upload link or account does not exist in this workspace.Retry: No | No |
quota_exceeded | A limit was reached: this month’s posts or X credits, connected profiles, unfinished uploads or media storage. The message says which.Retry: No | No |
plan_required | Publishing, or X publishing, is unavailable under the account’s current entitlement. Setup, uploads and validation still work.Retry: No | No |
account_not_connected | No active account for the requested network.Retry: No: call connect_account. | No: call connect_account. |
account_reauth_required | The network asked the person to sign in again.Retry: No: call connect_account for a fresh link. | No: call connect_account for a fresh link. |
media_not_ready | The upload has not arrived or is still being checked.Retry: Yes, shortly. | Yes, shortly. |
media_too_large | The file is over the size limit.Retry: No | No |
unsupported | The network does not accept this media or combination.Retry: No | No |
provider_error | The network rejected the request; details.provider_code has its reason.Retry: Depends on the reason. | Depends on the reason. |
rate_limited | Too many calls. details.retry_after_seconds says when to try again.Retry: Yes, after the wait. | Yes, after the wait. |
conflict | The request conflicts with the current state (for example, cancelling a finished post or dismissing a warning that isn’t there).Retry: No | No |
closed | The upload link expired or was already completed.Retry: No: make a new link. | No: make a new link. |
internal | Something failed on our side.Retry: Yes | Yes |
Rate limits#
- Per workspace: 60 tool calls a minute and 600 an hour.
- create_post, create_upload and create_upload_link: 30 a minute each. Refreshing numbers: 10 a minute.
- Unauthenticated OAuth endpoints and the public upload-link routes: 30 to 60 a minute per IP.
- Over the limit you get
rate_limitedwithdetails.retry_after_seconds, or HTTP 429 with Retry-After outside tool calls.
Prompts and resources#
- Prompt
post_thiswalks an agent through upload, validation and publishing for a file and a list of networks. - Resource
welder://capabilitiesis the same data as get_capabilities;welder://docs/quickstartis a short guide in markdown, so agents can read the docs without a browser.
Review workspace#
Plugin directory reviewers get one dedicated Welder workspace, labelled Simulated review workspace on every page of the app. It runs the same tools and checks as every workspace, but its social side is simulated: no social network is ever contacted. If you followed a post link from that workspace, this is where it lands.
Synthetic
- Six sample accounts, one per network, named demo_coffee_… and shown as “Demo Coffee · Simulated”. They hold no real sign-in.
- A sample coffee photo and three sample posts on the Instagram and Bluesky sample accounts, dated September 29 to October 1, 2026. Their numbers are fixed example values, not network results.
- One fictional reply on the Bluesky copy of the October 1 post. Replies are drafted in the chat and never sent.
Works as usual
- Uploads through upload links and the app, validation, allowances, idempotency, scheduling, cancelling and post history.
- Publishing records the post and returns a simulated receipt for each account: it shows as published, and its link opens this page instead of a network. New posts get no invented numbers or replies.
- Connecting your AI app: signing in, keys and removing access work as usual.
Turned off
- Connecting, renewing or disconnecting social accounts.
- Checkout, plan changes and payment settings.
- Changing settings or deleting the workspace.
- Taking posts down (delete_post) and refreshing numbers from the networks.
- Importing files from anywhere other than Welder’s public sample files.
get_context returns review_demo: true and a data_source line for this workspace, and the app receives the same flag. Comments come back with a note that they are fictional samples. Every other workspace publishes to real networks and never sees simulated results.