sprkly.app-mcp

sprkly.app enables users to publish their shortform content to tiktok, facebook, instagram, youtube, threads via natural language.

Documentation

Quick start

Two ways to authenticate. Pick by what your client supports.

Recommended

Connect with OAuth

For Claude Cowork, claude.ai, Claude Desktop, Claude Code and ChatGPT. Add the endpoint as a custom connector and sign in. Nothing to copy, and the connection is tied to your sprkly login rather than a long-lived secret.

  1. 1. Add https://sprkly.app/api/mcp as a custom connector.
  2. 2. Click Connect and approve the permissions.
  3. 3. Ask your agent what you have scheduled. Per-client setup, right below →

Scripts, CI, self-hosted agents

Send an API key

Create a key in Settings → API Keys and send it as a bearer token. There is no exchange step and no short-lived token to refresh.

curl -s https://sprkly.app/api/mcp \
  -H "Authorization: Bearer sk_live_…" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

MCP is included in the free trial. It is the same entitlement as the REST API, so a key that works for one works for both. Once a trial ends without a plan, calls return 403 with plan_required.

Client setup

Every client, click by click. Hosted agents sign in with OAuth; anything running locally can use an API key from Settings → API Keys instead.

Claude Cowork

Sign in with OAuth

Give Cowork your posting queue so it can plan and schedule alongside the rest of your work.

  1. 1.In Cowork, open Settings (your avatar, bottom-left) and click Connectors.
  2. 2.Click Add custom connector.
  3. 3.In the dialog, paste the server URL below into the URL field. The name can be anything. "sprkly" reads best.
  4. 4.Click Add. sprkly appears in the connector list. Click Connect next to it.
  5. 5.A sprkly sign-in tab opens. Sign in and click Approve on the consent screen.

The form, field by field

Name

sprkly

URL

https://sprkly.app/api/mcp

Done when: The connector card flips to Connected, and asking Cowork "what have I got queued this week?" answers from your real schedule.

Cowork connects from Anthropic's cloud, not your laptop. Nothing to install. "Couldn't reach the MCP server" almost always means a mistyped URL. Copy it, don't retype it.

The limits are real, not advisory

  • There is no publish now tool. Everything goes through the same queue, plan limits, and approval path as your own posts.
  • Deleting is a soft delete you can undo for 30 days; an agent cannot touch a post that has already published.
  • An agent only ever sees your own accounts, and an API key can be scoped to a subset of them.
  • Every tool call is logged. Disconnect any time from Settings.

Questions

Which AI agents can connect to sprkly?

Any client that speaks the Model Context Protocol. That includes Claude Cowork, claude.ai, Claude Desktop, Claude Code, ChatGPT in developer mode, Codex, Cursor, VS Code, and automation tools like n8n. Anything else can call the endpoint over plain HTTP with an API key.

Do I need to install anything?

No. sprkly runs a hosted MCP server, so you paste one URL into your agent and sign in. There is nothing to run on your machine and nothing to keep updated.

Can an AI agent publish without asking me?

It can only schedule to accounts you have already connected, and only if you ask it to. You can also require human approval, so anything an agent queues waits for you to sign off before it publishes. Posts that have already published cannot be deleted by an agent at all.

What does it cost?

Nothing extra. MCP access is included on every paid sprkly plan, and uses the same entitlement as the REST API.

Can I limit which accounts an agent sees?

Yes. Scope an API key to specific accounts in Settings and the agent can only read and post to those. Scopes also control whether it can write at all or only read.

Worked example

Initialize, list the tools, then schedule a post. Substitute your own credential.

TOKEN="sk_live_…"
MCP="https://sprkly.app/api/mcp"

# 1. Initialize (no credentials needed)
curl -s "$MCP" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize",
       "params":{"protocolVersion":"2025-11-25",
                 "capabilities":{},
                 "clientInfo":{"name":"curl","version":"1.0"}}}'

# 2. Which accounts can I post to?
curl -s "$MCP" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "MCP-Protocol-Version: 2025-11-25" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/call",
       "params":{"name":"sprkly_list_profiles","arguments":{}}}'

# 3. Check the caption before committing to it
curl -s "$MCP" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":3,"method":"tools/call",
       "params":{"name":"sprkly_validate_post_policy",
                 "arguments":{"caption":"Launch day.",
                              "platforms":["instagram"],
                              "mediaUrlsCount":1}}}'

# 4. Queue it
curl -s "$MCP" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":4,"method":"tools/call",
       "params":{"name":"sprkly_schedule_post",
                 "arguments":{"caption":"Launch day.",
                              "profile_ids":["profile_…"],
                              "media_urls":["https://example.com/launch.jpg"],
                              "scheduled_time":"2026-08-04T18:00:00.000Z"}}}'

What every post needs

Four things are always required: a caption, at least one platform, a time, and media for the platforms that demand it. Two platforms want more, and sprkly_schedule_post refuses the call rather than guessing.

FieldRequired forNotes
captionevery postChecked against each platform's length limit before anything is queued.
titleTikTok, YouTubeBoth platforms display the title, not the caption. YouTube caps it at 100 characters.
platform_meta.tiktok.privacyLevelTikTokTikTok rejects a post without one. Send the level the user asked for; if it is not one this creator allows, the error names the levels that are. Or read them first with sprkly_get_tiktok_posting_options.
media_urls / media_idsInstagram, TikTokNeither will take a text-only post. Pass a link straight through and sprkly pulls it into storage itself.

Array order is slide order. For a carousel or a photo set, the sequence you send in media_urls or media_ids is the sequence that publishes. Slide 1 carries the most weight: Instagram crops every other slide to match its shape, and Threads and Facebook publish only that one.

A file listing is not a sorted listing. Google Drive, Dropbox and most automation nodes return files in upload order, which rarely matches intent. Sort on the file name before you build the array, and name the files so that sorting works: 01.jpg, 02.jpg, 03.jpg. Pad the zero, because as text 10 sorts before 2. When the order was inferred rather than given, say so in the reply instead of presenting it as certain.

Tools

16 tools. Arguments below are exactly what tools/list returns.

sprkly_add_media_from_url WriteIdempotent

Download an image or video from a public link into sprkly and get a media_id back, for reuse across several posts. You usually do NOT need this: sprkly_schedule_post accepts a link directly in media_urls and pulls it into storage itself whenever the target platform requires that. Reach for this tool only when the user wants one media_id to attach to more than one post. Google Drive and Dropbox share links are converted automatically; the file must be shared publicly. Limit 50 MB.

ArgumentTypeDescription
urlrequiredstringDirect https link to the image or video file. Must be publicly reachable.

You say“Use this clip for all three of this week posts: https://cdn.example.com/clips/launch.mp4”

The agent calls

sprkly_add_media_from_url({
  "url": "https://cdn.example.com/clips/launch.mp4"
})

Returns a mediaId you can attach to several posts. For a SINGLE post do not call this at all: put the link straight into media_urls on sprkly_schedule_post and it is pulled into storage there.

sprkly_delete_scheduled_post WriteDestructiveIdempotent

Remove a post from the queue. This is a soft delete. The user can restore it from the Deleted tab for 30 days. Posts that have already published cannot be deleted this way. Always confirm with the user before calling.

ArgumentTypeDescription
post_idrequiredstringThe scheduled post id to delete.

You say“Scrap the Tuesday post, it's out of date now.”

The agent calls

sprkly_delete_scheduled_post({
  "post_id": "post_abc123"
})

Soft delete: restorable from the Deleted tab for 30 days. Agents confirm with the user before calling.

sprkly_draft_post Write

Compose a caption from a content hint and save it as a draft in sprkly, shaped to the tightest caption limit among the target platforms. Returns a draft id; the draft appears under /drafts for the user to review.

ArgumentTypeDescription
content_hintrequiredstringWhat the post should be about: a topic, phrase, or key message.
platformsstring[]Intended platforms, used to pick the caption length ceiling.
tonestringVoice for the draft. casual · professional · promotional
namestringOptional label for the draft.
profile_idsstring[]Optional accounts to pre-select on the draft. From sprkly_list_profiles.

You say“Draft something warm about the new studio space for Instagram.”

The agent calls

sprkly_draft_post({
  "content_hint": "first look at the new studio space",
  "platforms": [
    "instagram"
  ],
  "tone": "casual"
})

sprkly_get_account_summary Read-only

Plan tier, trial state, connected account count, scheduled post counts by status, and the next three upcoming posts. Never returns tokens or secrets.

No arguments.

You say“How is my sprkly account looking?”

The agent calls

sprkly_get_account_summary({})

sprkly_get_analytics Read-only

How the user's published posts actually performed: total views and engagement, week-on-week / month-on-month / year-on-year change, their best posting hour, weekday and content category, and the top posts behind those numbers. Every recommendation carries a `samples` count — say how thin the evidence is rather than presenting a one-post pattern as a finding. Every period-on-period percentage carries the post counts and raw totals it came from: quote those, because a big percentage off a tiny base is not a big change. `topPosts` is grouped by platform and ranked only inside each group; `relativeToPlatformBest` compares a post with others on its OWN platform and never across platforms, so use the absolute `value` and its `metric` label to weigh one platform against another. Instagram contributes likes and comments only, and Threads and Facebook produce no metrics at all, so read `coverage` before comparing platforms.

ArgumentTypeDescription
daysinteger (1 to 365)How many days back to analyse. Default 30.
profile_idsstring[]Limit to these accounts. Omit for every account this connection can see.

You say“How did my posts do last month, and when should I be posting?”

The agent calls

sprkly_get_analytics({
  "days": 30
})

Returns the numbers and the evidence behind them. The advice is yours to give: check `samples` and `coverage` before calling anything a pattern.

You say“Which of my TikTok posts worked best this week?”

The agent calls

sprkly_get_analytics({
  "days": 7,
  "profile_ids": [
    "prof_tiktok_main"
  ]
})

sprkly_get_billing_summary Read-only

Subscription status, current plan, period end, purchased handles and the last few billing events. No payment method details; the Stripe customer id is truncated.

No arguments.

You say“What plan am I on and when does it renew?”

The agent calls

sprkly_get_billing_summary({})

sprkly_get_post_approval_status Read-only

Whether a post is awaiting human review, approved or rejected, including reviewer notes and timestamps.

ArgumentTypeDescription
post_idrequiredstringThe scheduled post id.

You say“Has the launch post been approved yet?”

The agent calls

sprkly_get_post_approval_status({
  "post_id": "post_abc123"
})

sprkly_get_post_status Read-only

Full detail for one post: status, targets, scheduled and published times, permalink, and the failure reason if it did not publish. Media comes back as mediaIds in slide order, not as links. Ids and profile ids are plumbing: talk to the user about accounts by handle and about posts by their caption, and do not read ids out unless they ask for one.

ArgumentTypeDescription
post_idrequiredstringThe scheduled post id.

You say“Did last night's reel actually go out?”

The agent calls

sprkly_get_post_status({
  "post_id": "post_abc123"
})

sprkly_get_tiktok_posting_options Read-only

This creator's allowed TikTok privacy levels and interaction settings, fetched live from TikTok. You usually do NOT need this before scheduling: sprkly_schedule_post checks privacyLevel against this same list itself and, when it is wrong, returns the levels that would work. Call this only when the user asks what their options are, or you want to offer them a choice.

ArgumentTypeDescription
profile_idrequiredstringThe TikTok profile id to query, from sprkly_list_profiles.

You say“What privacy options do I have on TikTok?”

The agent calls

sprkly_get_tiktok_posting_options({
  "profile_id": "prof_tt_studio"
})

For showing the user their choices. Do NOT run it before sprkly_schedule_post as a matter of course: that call validates privacyLevel itself and names the allowed levels when one is wrong.

sprkly_list_connected_social_accounts Read-only

Every ACTIVE social account linked to this sprkly account: platform, handle, follower count, and whether it needs reconnecting. Disconnected/inactive accounts are never listed, so any profileId returned here is a valid posting target. Never returns access tokens.

No arguments.

You say“Which social accounts do I have connected?”

The agent calls

sprkly_list_connected_social_accounts({})

sprkly_list_profiles Read-only

The profile ids needed to target a post, with each one's platform and handle. Call this before sprkly_schedule_post.

No arguments.

You say“Where can you post for me?”

The agent calls

sprkly_list_profiles({})

Agents call this first: the profile ids it returns are what sprkly_schedule_post targets.

sprkly_list_scheduled_posts Read-only

The post queue, newest first, with a caption preview, targets, status and failure reason. Supports a status filter and cursor pagination.

ArgumentTypeDescription
statusstringFilter by status. draft · pending_approval · scheduled · posted · failed
limitinteger (1 to 50)Maximum posts to return.
cursorstringPagination cursor. Pass the nextCursor value from a previous response.

You say“What have I got queued this week?”

The agent calls

sprkly_list_scheduled_posts({
  "status": "scheduled",
  "limit": 20
})

sprkly_request_post_approval Write

Submit a draft post for human review. Moves the post to pending_approval and returns an approval id to poll with sprkly_get_post_approval_status. Use this when the user wants a person to sign off before anything publishes.

ArgumentTypeDescription
post_idrequiredstringThe draft post id to submit.
notestringOptional context for the reviewer.

You say“Queue the week, but let me sign off before anything goes out.”

The agent calls

sprkly_request_post_approval({
  "post_id": "post_abc123",
  "note": "Captions drafted from the Tuesday shoot. Check the TikTok hook."
})

sprkly_schedule_post Write

Queue a post for publishing, in ONE call. Attach media by passing the user's link straight to media_urls: sprkly downloads it into storage itself for the platforms that need that, so no upload tool has to run first. Runs the same quota, duplicate-content and platform pre-flight checks as the sprkly app. Instagram and TikTok require media at submission time; YouTube and TikTok require a title, and TikTok also requires platform_meta.tiktok.privacyLevel — just send the level the user asked for and this tool names the allowed values if it is not one of them. It reads the real bytes of the media and the response says what will actually publish on each platform (a Reel, a 3-slide carousel, a photo set, a Page feed video) plus anything worth passing on: relay that to the user. Confirm the date, time and target accounts with the user first. If a target platform has more than one connected account and profile_ids is not given, the tool returns needsAccountChoice with the options instead of scheduling — put that choice to the user, then re-call.

ArgumentTypeDescription
captionstringPost caption, max 2200 characters.
platformsstring[]Platforms to publish to. A platform with exactly one connected account is targeted directly; one with several makes the tool answer needsAccountChoice so the user can pick.
profile_idsstring[]Specific accounts to publish to, from sprkly_list_profiles. When given, this list IS the target set — platforms are not fanned out.
all_accountsbooleanExplicitly post to EVERY connected account on every listed platform, skipping the needsAccountChoice question. Only pass true when the user has said they want all accounts.
scheduled_timestringISO 8601 timestamp to publish at. Must be in the future. If omitted the post goes out on the next publisher run, about a minute from now — there is no smart slot-picking, so pass an explicit time unless the user wants it published immediately. sprkly_get_analytics can suggest one.
media_urlsstring[]Publicly reachable image or video URLs to attach, in slide order. Pass links through for ANY platform. Instagram and Threads fetch them directly; for TikTok, YouTube and Facebook sprkly downloads the file into its own storage while scheduling, so a link works there too and any problem with it is reported now, in this call. Google Drive and Dropbox share links are converted automatically. JPEG, PNG, WebP, GIF, MP4, MOV and WebM only: AVIF and HEIC (the iPhone camera default) are refused with re-export instructions, because sprkly cannot convert them. Each file must be publicly reachable and under 50 MB.
media_idstringId of a single media file already uploaded to sprkly. Shorthand for a one-item media_ids.
media_idsstring[]Ids of media files already uploaded to sprkly, in slide order. Array order is the published order. Use these when the user already has media in sprkly, or when one file is going on several posts; for a link the user just gave you, media_urls is fewer steps. Every photo in a set must be the SAME shape or the call is refused: export them all at 1080x1920 (9:16), 1080x1440 (3:4), 1080x1350 (4:5) or 1080x1080 (1:1). Instagram takes at most 10 slides; TikTok photo sets take up to 35.
titlestringPost title. Required for YouTube (max 100 characters) and TikTok (max 150 characters).
categorystringOptional content category, e.g. "fitness".
platform_metaobjectPlatform-specific publishing options, keyed by platform.

You say“Schedule this to Instagram and TikTok on Thursday at 6pm.”

The agent calls

sprkly_schedule_post({
  "caption": "Behind the scenes of the studio setup",
  "platforms": [
    "instagram",
    "tiktok"
  ],
  "scheduled_time": "2026-08-13T18:00:00+08:00",
  "media_id": "media_abc123",
  "title": "Behind the scenes of the studio setup",
  "platform_meta": {
    "tiktok": {
      "privacyLevel": "PUBLIC_TO_EVERYONE"
    }
  }
})

With two Instagram accounts connected, this returns needsAccountChoice and the agent asks: "You have 2 Instagram accounts, which one?" Then it re-calls with profile_ids. TikTok posts need a title and a privacyLevel from sprkly_get_tiktok_posting_options.

You say“Yes, the studio account. Same for TikTok.”

The agent calls

sprkly_schedule_post({
  "caption": "Behind the scenes of the studio setup",
  "platforms": [
    "instagram",
    "tiktok"
  ],
  "profile_ids": [
    "prof_ig_studio",
    "prof_tt_studio"
  ],
  "scheduled_time": "2026-08-13T18:00:00+08:00",
  "media_id": "media_abc123",
  "title": "Behind the scenes of the studio setup",
  "platform_meta": {
    "tiktok": {
      "privacyLevel": "PUBLIC_TO_EVERYONE"
    }
  }
})

You say“Post these five cards as a carousel to Instagram and TikTok.”

The agent calls

sprkly_schedule_post({
  "caption": "Five things nobody tells you about scheduling",
  "platforms": [
    "instagram",
    "tiktok"
  ],
  "scheduled_time": "2026-08-14T09:00:00+08:00",
  "media_ids": [
    "media_c1",
    "media_c2",
    "media_c3",
    "media_c4",
    "media_c5"
  ],
  "title": "Five things nobody tells you about scheduling",
  "platform_meta": {
    "tiktok": {
      "privacyLevel": "PUBLIC_TO_EVERYONE"
    }
  }
})

media_ids order is slide order, and the ids come from media already in sprkly. Had the user pasted five links instead, media_urls takes them in the same order and sprkly pulls each one in while scheduling.

You say“Post this to TikTok tomorrow at 9am, just to me for now: https://drive.google.com/file/d/1AbCdEf/view?usp=sharing”

The agent calls

sprkly_schedule_post({
  "caption": "Testing the new scheduler",
  "platforms": [
    "tiktok"
  ],
  "scheduled_time": "2026-08-16T09:00:00+08:00",
  "media_urls": [
    "https://drive.google.com/file/d/1AbCdEf/view?usp=sharing"
  ],
  "title": "Testing the new scheduler",
  "platform_meta": {
    "tiktok": {
      "privacyLevel": "SELF_ONLY"
    }
  }
})

One call. The Drive share link is converted to its download form and pulled into sprkly storage during this call, so a broken or private link is reported here rather than failing quietly at publish time. Do not call an upload tool first, and do not look the privacy level up first.

sprkly_update_scheduled_post WriteIdempotent

Change the caption, publish time, target accounts or attached media on a post that has not published yet. Only posts with status "scheduled" can be edited.

ArgumentTypeDescription
post_idrequiredstringThe scheduled post id.
captionstringReplacement caption, max 2200 characters.
scheduled_timestringNew ISO 8601 publish time. Must be in the future.
profile_idsstring[]Replacement target accounts. Platforms are re-derived from them.
media_idstringReplacement sprkly media file id. Shorthand for a one-item media_ids.
media_idsstring[]Replacement media, in slide order. Replaces the whole set, it does not append — pass every slide you want the post to keep.

You say“Move Friday's post to Saturday morning instead.”

The agent calls

sprkly_update_scheduled_post({
  "post_id": "post_abc123",
  "scheduled_time": "2026-08-15T09:00:00+08:00"
})

sprkly_validate_post_policy Read-only

Check a caption against each target platform's posting rules before scheduling: caption length, media requirements, hashtag ceilings, whether links are clickable, required YouTube titles, and PII or prohibited-content warnings. Pure analysis. Writes nothing.

ArgumentTypeDescription
captionrequiredstringThe caption to check.
platformsrequiredstring[]Target platforms to check against.
mediaUrlsCountinteger (0 to -)How many images or videos will be attached. Instagram and TikTok require at least one.
hashtagsstring[]Hashtags posted alongside the caption, if they are not already in it.
titlestringPost title. Required for YouTube, max 100 characters.
platformMetaobjectPlatform-specific publishing options, keyed by platform.

You say“Check this caption against TikTok's rules first.”

The agent calls

sprkly_validate_post_policy({
  "caption": "Three edits that doubled watch time. Full breakdown in the comments.",
  "platforms": [
    "tiktok"
  ],
  "mediaUrlsCount": 1
})

Run this before scheduling: it catches a too-long caption, missing media or a missing YouTube title while they are still cheap to fix.

Transport

Streamable HTTP, stateless. JSON-RPC 2.0 messages are POSTed to a single endpoint and answered with application/json. No session id is issued, so there is nothing to track between calls.

MethodBehaviour
POSTCarries every MCP message. Include Accept: application/json, text/event-stream.
GET405. This server offers no server-initiated SSE stream.
DELETE405. Stateless, so there is no session to terminate.

Protocol versions

Negotiated on initialize: the server echoes your revision when it supports it, otherwise it answers with its newest. Supported: 2025-11-25 2025-06-18 2025-03-26. Send the negotiated value as MCP-Protocol-Version on subsequent requests. An unsupported value is a 400.

Methods

initialize, ping, tools/list, tools/call, resources/list and prompts/list (both empty). Notifications are acknowledged with 202 and no body.

Authentication

initialize, ping and tools/list work without credentials, so a client can show what sprkly offers before anyone signs in. The first tools/call is challenged.

The 401 challenge

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer error="invalid_token",
  error_description="Missing bearer credentials",
  resource_metadata="https://sprkly.app/.well-known/oauth-protected-resource/api/mcp",
  scope="profile mcp:read mcp:write"

Follow resource_metadata to learn which authorization server to use. That document names this origin, whose metadata lives at https://sprkly.app/.well-known/oauth-authorization-server.

OAuth 2.1

  • Dynamic Client Registration (RFC 7591) and Client ID Metadata Documents are both supported. PKCE with S256 is required.
  • Scopes: profile mcp:read mcp:write. Add offline_access for a refresh token.
  • mcp:read covers every read-only tool; mcp:write adds drafting, scheduling and deleting.

API key scopes

A key can hold *, sprkly:*, or any of post:read, post:write, post:draft, account:read, approval:request. A key scoped to specific accounts only ever sees and touches those.

Never put an API key or token in the connector URL as a query parameter. URLs are recorded in logs, proxies and browser history, and the MCP specification prohibits it. Use the Authorization header or OAuth.

Deprecated

These still work. Nothing new needs them.

  • https://mcp.sprkly.app/mcp. It proxies to the endpoint above. Point new integrations at https://sprkly.app/api/mcp instead.
  • POST /auth and POST /api/auth/mcp-token. the API-key-to-JWT exchange. The API key is now accepted directly as a bearer token, so the exchange and its 24-hour refresh loop are unnecessary.

Need a hand?

MCP access follows the same entitlement as the REST API. If a connector will not connect, the fastest signal is whether an unauthenticated tools/call returns a 401 with a WWW-Authenticate header.