For AI Agents
Connect your agent in one step. It runs your social channels.
Markaestro is built to be operated by software. An MCP client such as Claude Code, Cursor, ChatGPT, or Grok signs in through the browser and is handed a key scoped to one brand or to every brand in the workspace; any other agent gets the same kind of key from Settings. Either way the agent can discover which accounts it can post to, upload media, draft and schedule posts, publish them, and report back on what actually shipped, across Facebook, Instagram, TikTok, LinkedIn, Threads, Pinterest, and X.
No SDK to install and no platform credentials to babysit. Your team connects the accounts once in the dashboard; the agent talks to one bearer-token API from then on.
Designed for Autonomy, Bounded on Purpose
Why an API Key Is the Whole Integration
The hard part of letting an agent touch social media is not the HTTP. It is making sure a confused model cannot post to the wrong brand, double-post on a retry, or ship something nobody read. Those guarantees are in the API surface itself, not in your prompt.
MCP Clients
Sign in from the client. Nothing to paste.
Claude Code, Claude, Cursor, ChatGPT, Grok, Grok Bot, OpenClaw, Hermes, and every other client that speaks the Model Context Protocol can connect to Markaestro's hosted MCP server with no credentials configured. The first tool call opens your browser: sign in, choose the workspace and the brand the agent may act on (or all brands), review the permissions, and click Allow. The client receives a key with that brand scope and refreshes it on its own.
This is standard OAuth 2.1 with PKCE and dynamic client registration, the same mechanism behind other hosted MCP servers, so it works without a Markaestro-specific plugin. The server lives at https://markaestro.com/api/public/v1/mcp and exposes thirty-four tools for social media management: brand profiles and destinations, media upload, drafting, scheduling, and editing posts, publishing with job-run polling, analytics with on-demand refresh and best times to post, Intelligent Evergreen, and the per-channel rules. Account settings, billing, team, API keys, and channel connections stay in the app, and agents cannot delete published posts.
Connect Your Agent
Pick your agent. Three steps, then it can post.
Every client below reaches the same hosted MCP server. Most sign in through the browser: the first tool call opens a consent page where you pick the workspace and the brand the agent may act on (or all brands), and the client receives a key with that scope. Clients that cannot open a browser take a workspace API key instead. Same server, same permissions, same list in Settings.
Claude Code
The plugin installs the skill and the hosted server together. Nothing to configure, nothing to paste.
Before You Start
- You are a workspace owner or admin with a verified email, on a workspace with an active plan and at least one brand.
- Claude Code is installed and signed in to your Anthropic account.
Sign In
- Ask Claude anything about Markaestro, or run /mcp and choose markaestro.
- Your browser opens the consent page. Pick the workspace and brand, review the permissions, click Allow.
- To switch brands later, run /mcp again, sign out, and sign in with the other brand.
Verify
- Ask the agent to call list_products. It should answer with the brands you granted and their connected channels.
- The connection is listed in Settings, API with a Connected Agent badge, its last-used time, and its request volume. Revoke it there at any time.
Add the Server
- Run the two plugin commands in any terminal, or add just the server with the third command.
# The plugin bundles the skill and the hosted server.
claude plugin marketplace add markaestro/markaestro-agents
claude plugin install markaestro@markaestro
# Or add just the server. No key, no header: the first call opens the browser.
claude mcp add --transport http markaestro https://markaestro.com/api/public/v1/mcpClaude
claude.ai and Claude Desktop take the server URL as a custom connector and sign in through the same consent page.
Before You Start
- You are a workspace owner or admin with a verified email, on a workspace with an active plan and at least one brand.
- Every Claude plan can add a custom connector. On Team and Enterprise an owner may need to enable them.
Sign In
- Click Connect next to Markaestro. The consent page opens in a new tab.
- Pick the workspace and brand, review the permissions, click Allow. The tab closes and the connector shows as connected.
- In a chat, enable Markaestro from the tools menu when you want the agent to use it.
Verify
- Ask the agent to call list_products. It should answer with the brands you granted and their connected channels.
- The connection is listed in Settings, API with a Connected Agent badge, its last-used time, and its request volume. Revoke it there at any time.
Add the Server
- Open Customize, Connectors, then Add custom connector.
- Paste the server URL below, leave the OAuth client fields empty, and click Add.
https://markaestro.com/api/public/v1/mcpCursor
One click adds the server to Cursor. The first tool call opens the browser sign-in; Cursor keeps the token in your OS keychain.
Before You Start
- You are a workspace owner or admin with a verified email, on a workspace with an active plan and at least one brand.
- Cursor with MCP enabled. Remote MCP servers work on every Cursor plan.
Sign In
- Open Cursor Settings, Tools & MCP. Markaestro shows Needs login; click it.
- Your browser opens the consent page. Pick the workspace and brand, click Allow. Cursor picks up the token and lists the tools.
- Grok Bot inside Cursor uses this same server entry.
Verify
- Ask the agent to call list_products. It should answer with the brands you granted and their connected channels.
- The connection is listed in Settings, API with a Connected Agent badge, its last-used time, and its request volume. Revoke it there at any time.
Add the Server
- Click Add to Cursor and confirm the install prompt in Cursor.
- Or paste the JSON into .cursor/mcp.json in a project (shared with your team through git) or ~/.cursor/mcp.json (just you).
// .cursor/mcp.json (project) or ~/.cursor/mcp.json (global)
{
"mcpServers": {
"markaestro": {
"url": "https://markaestro.com/api/public/v1/mcp"
}
}
}For a shared build machine or CI, a workspace API key in the headers replaces the sign-in.
// Without a browser: pass a workspace API key instead.
{
"mcpServers": {
"markaestro": {
"url": "https://markaestro.com/api/public/v1/mcp",
"headers": { "Authorization": "Bearer mk_live_..." }
}
}
}ChatGPT
ChatGPT connects to Markaestro as a custom app in Developer mode and signs in through the consent page.
Before You Start
- You are a workspace owner or admin with a verified email, on a workspace with an active plan and at least one brand.
- Developer mode needs ChatGPT Pro, Business, Enterprise, or Edu. Pro exposes read-only tools; Business, Enterprise, and Edu expose all of them.
- On Business, Enterprise, and Edu an admin may need to allow custom apps for the workspace.
Sign In
- The consent page opens while ChatGPT scans the tools. Pick the workspace and brand, click Allow, then click Create.
- In a chat, click the plus button, More, then Markaestro to make the tools available.
- ChatGPT registers itself with Markaestro once per connection. Reconnecting creates a new connection you can revoke separately.
Verify
- Ask the agent to call list_products. It should answer with the brands you granted and their connected channels.
- The connection is listed in Settings, API with a Connected Agent badge, its last-used time, and its request volume. Revoke it there at any time.
Add the Server
- Open Settings, Apps & Connectors, Advanced settings, and turn on Developer mode.
- Back in Apps & Connectors, click Create. Name it Markaestro, paste the server URL, choose OAuth for authentication, then click Scan Tools.
https://markaestro.com/api/public/v1/mcpGrok
Grok reaches Markaestro three ways: as a custom connector on grok.com, from the Grok Build terminal, and as a remote MCP tool in the xAI API.
Before You Start
- You are a workspace owner or admin with a verified email, on a workspace with an active plan and at least one brand.
- grok.com connectors work on personal plans. Grok Business and Enterprise need a team admin to provision the connector.
- The xAI API path runs server-side, so it always uses a workspace API key.
Sign In
Verify
- Ask the agent to call list_products. It should answer with the brands you granted and their connected channels.
- The connection is listed in Settings, API with a Connected Agent badge, its last-used time, and its request volume. Revoke it there at any time.
Add the Server
- grok.com: open grok.com/connectors, click New Connector, choose Custom, and paste the server URL. If the dialog asks for a client id, use the values below.
- Grok Build: run the two commands in a terminal. Grok Build also picks up a Markaestro entry from Claude Code's .mcp.json or Cursor's mcp.json.
- xAI API: add the tool block to the tools array of a Responses API request.
grok.com custom connector
Server URL: https://markaestro.com/api/public/v1/mcp
grok.com's Custom Connector asks only for a name and this URL. It registers
itself and opens the browser sign-in, no client id or secret to enter.
(If a future dialog does ask, use client id markaestro-grok-web with a blank secret.)Grok Build terminal
# Grok Build (terminal). The first tool call opens the browser.
grok mcp add --transport http markaestro https://markaestro.com/api/public/v1/mcp
grok mcp doctor markaestro
# Headless: pass a workspace API key instead.
grok mcp add --transport http markaestro https://markaestro.com/api/public/v1/mcp \
--header "Authorization: Bearer ${MARKAESTRO_API_KEY}"xAI Responses API tool block
// xAI Responses API: one entry in the request's "tools" array.
// Server-side, so it always uses a workspace API key.
{
"type": "mcp",
"server_url": "https://markaestro.com/api/public/v1/mcp",
"server_label": "markaestro",
"authorization": "Bearer mk_live_...",
"allowed_tools": ["list_products", "list_destinations", "upload_media",
"create_post", "publish_post", "get_job_run"]
}Grok Bot
Grok Bot adds a custom MCP server when you ask for it in chat, then signs in through the browser. A workspace API key also works as a header.
Before You Start
- You are a workspace owner or admin with a verified email, on a workspace with an active plan and at least one brand.
- Grok Bot is in early beta on SuperGrok plans and inside Cursor Pro. Enterprise access is by waitlist.
Sign In
- Click Authorize on the connect card. Pick the workspace and brand, then click Allow.
- To use a key instead, tell the Bot the header name and the key. It stores them on the server entry, never in the URL. Create a key scoped to one brand with an expiry.
Verify
- Ask the agent to call list_products. It should answer with the brands you granted and their connected channels.
- The connection is listed in Settings, API with a Connected Agent badge, its last-used time, and its request volume. Revoke it there at any time.
Add the Server
- In a chat with your Bot, send the message below.
- The Bot confirms before it adds the server, then shows a connect card.
Message to send your Bot
Add a custom MCP server called Markaestro at https://markaestro.com/api/public/v1/mcpHeader values, if you use a key instead
Server URL: https://markaestro.com/api/public/v1/mcp
Header name: Authorization (or x-api-key if that is the only field)
Header value: Bearer mk_live_... (with x-api-key: just mk_live_...)OpenClaw
OpenClaw adds remote MCP servers from its CLI and completes the sign-in on a loopback port, so it works on the machine that runs your gateway.
Before You Start
- You are a workspace owner or admin with a verified email, on a workspace with an active plan and at least one brand.
- OpenClaw is installed and the gateway is running.
- A headless server can finish the sign-in with the --code fallback.
Sign In
- openclaw mcp login markaestro prints the sign-in URL and waits on a loopback port.
- Open the URL, pick the workspace and brand, click Allow. OpenClaw stores the credentials outside the config file.
- Run openclaw mcp reload so running agents pick up the tools.
Verify
- Ask the agent to call list_products. It should answer with the brands you granted and their connected channels.
- The connection is listed in Settings, API with a Connected Agent badge, its last-used time, and its request volume. Revoke it there at any time.
Add the Server
- Run the three commands, or add the server block to ~/.openclaw/openclaw.json.
- Once the Markaestro skill is on ClawHub, openclaw skills install markaestro adds the agent instructions too.
openclaw mcp add markaestro \
--url https://markaestro.com/api/public/v1/mcp \
--transport streamable-http \
--auth oauth
openclaw mcp login markaestro # prints the sign-in URL; add --code <code> when headless
openclaw mcp reloadEquivalent config entry
// ~/.openclaw/openclaw.json
{
"mcp": {
"servers": {
"markaestro": {
"url": "https://markaestro.com/api/public/v1/mcp",
"transport": "streamable-http",
"auth": "oauth"
}
}
}
}Without a browser
# Without a browser: a workspace API key from the environment.
openclaw mcp add markaestro \
--url https://markaestro.com/api/public/v1/mcp \
--transport streamable-http \
--header "Authorization: Bearer ${MARKAESTRO_API_KEY}"Hermes
Hermes Agent registers HTTP MCP servers from config.yaml and runs the sign-in itself, storing the token under ~/.hermes/mcp-tokens.
Before You Start
- You are a workspace owner or admin with a verified email, on a workspace with an active plan and at least one brand.
- Hermes Agent is installed. Secrets belong in ~/.hermes/.env and are referenced as ${VAR} in config.
Sign In
Verify
- Ask the agent to call list_products. It should answer with the brands you granted and their connected channels.
- The connection is listed in Settings, API with a Connected Agent badge, its last-used time, and its request volume. Revoke it there at any time.
Add the Server
- Add the mcp_servers block to ~/.hermes/config.yaml.
- In a running session, send /reload-mcp. The tools appear as mcp_markaestro_<tool>.
# ~/.hermes/config.yaml
mcp_servers:
markaestro:
url: "https://markaestro.com/api/public/v1/mcp"
auth: oauthWithout a browser
# Without a browser: a workspace API key, kept in ~/.hermes/.env
mcp_servers:
markaestro:
url: "https://markaestro.com/api/public/v1/mcp"
headers:
Authorization: "Bearer ${MARKAESTRO_API_KEY}"Other MCP Client
Any client that speaks Streamable HTTP and OAuth 2.1 with dynamic client registration connects with the server URL alone.
Before You Start
- You are a workspace owner or admin with a verified email, on a workspace with an active plan and at least one brand.
- The client supports remote MCP servers over Streamable HTTP and can open a browser for OAuth. If it cannot, use the API Key tab.
Sign In
Verify
- Ask the agent to call list_products. It should answer with the brands you granted and their connected channels.
- The connection is listed in Settings, API with a Connected Agent badge, its last-used time, and its request volume. Revoke it there at any time.
Add the Server
- Add the server URL in the client's MCP configuration. The JSON below is the common mcpServers shape.
- Do not configure a client id or secret. The client registers itself on first use.
{
"mcpServers": {
"markaestro": {
"type": "http",
"url": "https://markaestro.com/api/public/v1/mcp"
}
}
}API Key
For CI jobs, cron workers, and clients that cannot open a browser: a workspace API key in the Authorization header reaches the same server with the same permissions.
Before You Start
- You are a workspace owner or admin with a verified email, on a workspace with an active plan and at least one brand.
- You can create keys: workspace owner or admin with a verified email.
Use an API Key
Verify
- Ask the agent to call list_products. It should answer with the brands you granted and their connected channels.
- The connection is listed in Settings, API with a Connected Agent badge, its last-used time, and its request volume. Revoke it there at any time.
Add the Server
- Create a key bound to one brand with only the scopes the agent needs and an expiry.
- Pass it as a bearer header on the hosted server, or as MARKAESTRO_API_KEY to the local stdio server, which can also upload files from disk.
# CI, cron, or any client without a browser: pass a key instead.
claude mcp add --transport http markaestro https://markaestro.com/api/public/v1/mcp \
--header "Authorization: Bearer mk_live_..."
# Local stdio server (can also upload files from disk)
claude mcp add markaestro -e MARKAESTRO_API_KEY=mk_live_... -- npx -y @markaestro/mcp{
"mcpServers": {
"markaestro": {
"type": "http",
"url": "https://markaestro.com/api/public/v1/mcp",
"headers": { "Authorization": "Bearer mk_live_..." }
}
}
}What Happens When the Client Connects
Five steps, all of them handled by the client and the browser. You see only the consent page.
POST /api/public/v1/mcp → 401 + WWW-AuthenticateThe client calls the MCP endpoint without a credential. Markaestro answers 401 with a WWW-Authenticate header that names the protected-resource metadata document. That header is what tells the client a sign-in is available.
GET /.well-known/oauth-protected-resource · /.well-known/oauth-authorization-serverThe client reads two public documents: which authorization server protects the endpoint, and where that server's registration, authorization, and token endpoints are. Both are served on markaestro.com and cacheable.
POST /api/public/v1/oauth/registerThe client registers itself with a name and its callback address. Loopback addresses, https callbacks, and native app schemes are accepted; plain http to a real host is refused. No pre-shared client id is needed.
GET /oauth/authorize (browser)Your browser opens the consent page. A workspace owner or admin with a verified email picks the workspace and brand, adjusts the permissions, and clicks Allow. Markaestro sends the browser back to the client with a single-use code.
POST /api/public/v1/oauth/tokenThe client exchanges the code plus its PKCE verifier for an access token and a refresh token. The access token is an ordinary workspace API key scoped to the brand you chose, or to all brands. It expires after 30 days; a refresh rotates its secret and extends it for another 30.
The Token Is a Real API Key
Scopes, brand binding, rate limits, subscription checks, idempotency, and revocation are the same code path as a key created by hand. There is no second permission model to reason about.
Listed and Revocable in Settings
A connected agent shows in Settings, API with a Connected agent badge, its last-used time, and its request volume. Revoke it there and the client's next call fails; the client can also revoke its own token when you disconnect it.
You Choose the Brand Scope
At consent you pick one brand or all brands in the workspace. A single-brand connection can never reach another brand; to add one, connect again and pick it, or choose all brands. No connection can ever reach a workspace it was not granted.
Codes and Refresh Tokens Are Single Use
Authorization codes live ten minutes and are consumed atomically, so a replayed code fails. Refresh tokens are rotated on every use and stored hashed. Idle client registrations expire after 180 days.
Endpoints
For anyone building an MCP client or auditing the flow. All of it is discoverable from the two well-known documents; nothing here needs to be configured by hand.
# Discovery (public, cacheable)
GET /.well-known/oauth-protected-resource RFC 9728
GET /.well-known/oauth-authorization-server RFC 8414
# Authorization server
POST /api/public/v1/oauth/register RFC 7591, public clients (PKCE) or client_secret
GET /oauth/authorize?response_type=code&client_id=…&redirect_uri=…
&code_challenge=…&code_challenge_method=S256&state=…
POST /api/public/v1/oauth/token grant_type=authorization_code | refresh_token
POST /api/public/v1/oauth/revoke RFC 7009
# Token response
{ "access_token": "mk_live_<ws>.<client>.<secret>", "token_type": "Bearer",
"expires_in": 2592000, "refresh_token": "…", "scope": "products.read posts.write …" }The Agent Loop
Five Calls, Start to Finish
Every Markaestro automation is a variation on this loop. Steps one through three are the Connect API, the flat surface most agents should target. Steps four and five reach into the full /api/public/v1 API for explicit publishing and run tracking.
GET /api/connect/v1/social-accountsReturns every connected, publishable account for the key's brand, each with a platform, username, and an opaque id. Call it at the start of a run, connections change.
POST /api/connect/v1/media/create-upload-url → PUTMint a short-lived, single-use signed URL, then PUT the raw bytes to it. You get back a media id. Images up to 10 MB; the full API also takes video up to 250 MB.
POST /api/connect/v1/postsPass the caption, the media ids, and the account ids verbatim. Leave it a draft for review, or send is_draft false with scheduled_at to put it on the calendar.
POST /api/public/v1/posts/:id/publishQueues an async run. LinkedIn, Threads, Pinterest, and X go out over the official API. Facebook, Instagram, and TikTok land in the workspace's To post queue for a human to post natively.
GET /api/public/v1/job-runs/:id · webhooksPoll the run id, or register a webhook endpoint and let Markaestro push post.published, post.action_required, and post.failed to you. Never assume a publish finished synchronously.
Quickstart
A Working Integration in Four Commands
First, mint the key: open Settings → API, pick the brand it is allowed to touch, tick the scopes it needs, and optionally give it an expiry. The key is shown once, put it straight into your agent's secret store. Creating keys requires an admin or owner with a verified email.
# The API is served from the marketing apex and the app subdomain alike.
export MARKAESTRO_URL="https://markaestro.com"
export MARKAESTRO_API_KEY="mk_live_<workspaceId>.<clientId>.<secret>"
# 1. What can this key post to?
curl -s "$MARKAESTRO_URL/api/connect/v1/social-accounts" \
-H "Authorization: Bearer $MARKAESTRO_API_KEY"{
"data": [
{
"id": "prod_123#instagram:instagram:ig_123",
"product_id": "prod_123",
"product": "Northwind Coffee",
"platform": "instagram",
"username": "northwindcoffee"
}
]
}# 2. Mint a signed upload url, then PUT the bytes.
RESP=$(curl -s -X POST "$MARKAESTRO_URL/api/connect/v1/media/create-upload-url" \
-H "Authorization: Bearer $MARKAESTRO_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "mime_type": "image/png", "size_bytes": 184320, "name": "cold-brew.png" }')
# → { "media_id": "ast_777", "upload_url": "https://.../media/upload?token=..." }
curl -X PUT "<upload_url>" \
-H "Content-Type: image/png" \
--data-binary @cold-brew.pngis_draft: false with a scheduled_at timestamp to put the post on the calendar instead.# 3. Put it on the calendar. Pass the account id back verbatim.
curl -X POST "$MARKAESTRO_URL/api/connect/v1/posts" \
-H "Authorization: Bearer $MARKAESTRO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"caption": "Cold brew season starts Friday.",
"media": ["ast_777"],
"social_accounts": ["prod_123#instagram:instagram:ig_123"],
"is_draft": false,
"scheduled_at": "2026-08-14T15:00:00.000Z"
}'
# 4. Check where everything stands.
curl -s "$MARKAESTRO_URL/api/connect/v1/posts?limit=20" \
-H "Authorization: Bearer $MARKAESTRO_API_KEY"Drop-In
Tool Definitions and an Agent Brief
Two things to copy. The first is a set of tool schemas covering the whole publishing loop, written in JSON Schema, so they work as Claude tool definitions, OpenAI functions, or the input shape for an MCP server you host. The second is the operating brief that keeps a model from doing something surprising with them.
[
{
"name": "markaestro_list_accounts",
"description": "List the social accounts this Markaestro key can publish to. Call this first in every run. Never hardcode account ids. Returns id, platform, and username.",
"input_schema": { "type": "object", "properties": {}, "required": [] }
},
{
"name": "markaestro_upload_media",
"description": "Upload one image or video to Markaestro and return a media asset id. Images: png, jpeg, webp, gif up to 10 MB. Video: mp4, mov, webm up to 250 MB.",
"input_schema": {
"type": "object",
"properties": {
"file_path": { "type": "string", "description": "Local path to the file to upload." },
"mime_type": { "type": "string", "description": "MIME type of the file." }
},
"required": ["file_path", "mime_type"]
}
},
{
"name": "markaestro_create_post",
"description": "Create a post for one channel. Facebook, Instagram, and TikTok are manual-first: a human posts them natively from the To Post queue. Omit delivery_mode unless the user explicitly asked for unattended publishing.",
"input_schema": {
"type": "object",
"properties": {
"channel": {
"type": "string",
"enum": ["facebook", "instagram", "tiktok", "linkedin", "threads", "pinterest", "x"]
},
"caption": { "type": "string", "description": "Caption text, max 4000 characters." },
"media_asset_ids": {
"type": "array",
"items": { "type": "string" },
"description": "Ids from markaestro_upload_media. Instagram and TikTok require at least one."
},
"destination_id": {
"type": "string",
"description": "From markaestro_list_accounts. Required only when the brand has more than one destination on that channel."
},
"delivery_mode": {
"type": "string",
"enum": ["manual_reminder", "direct_publish", "platform_inbox"],
"description": "Omit for the channel default."
}
},
"required": ["channel", "caption"]
}
},
{
"name": "markaestro_publish_post",
"description": "Queue an async publish run for an existing post. Returns a run id. Poll it, do not assume the post is live.",
"input_schema": {
"type": "object",
"properties": { "post_id": { "type": "string" } },
"required": ["post_id"]
}
},
{
"name": "markaestro_list_posts",
"description": "List posts for this brand, newest first. Filter by status: draft, scheduled, publishing, published, platform_action_required, failed, partial_failed.",
"input_schema": {
"type": "object",
"properties": {
"status": { "type": "string" },
"limit": { "type": "integer", "minimum": 1, "maximum": 100 }
},
"required": []
}
},
{
"name": "markaestro_delete_post",
"description": "Remove a post from Markaestro. Use it to cancel something scheduled. Deleting an already-published post does NOT retract the live copy on the platform.",
"input_schema": {
"type": "object",
"properties": { "post_id": { "type": "string" } },
"required": ["post_id"]
}
}
]You have a Markaestro API key for exactly one brand. Markaestro is the
publishing layer: you supply the caption and the media, it handles the
platform rules, the calendar, and delivery.
Base URL: https://markaestro.com
Auth: Authorization: Bearer $MARKAESTRO_API_KEY
Rules:
- Call GET /api/connect/v1/social-accounts before posting. Pass the returned
account ids back verbatim. Never invent or cache an id across runs.
- Upload media before creating a post; posts reference media ids, not files.
- Facebook, Instagram, and TikTok are manual-first. Creating and publishing
them queues a reminder for a human. That is the intended behavior. Only
send deliveryMode "direct_publish" if the operator explicitly asked for it.
- Send a unique Idempotency-Key on every POST. Reuse the SAME key when
retrying the SAME request; never reuse it for a different one.
- On 429, wait the number of seconds in Retry-After, then retry. On 4xx other
than 429, do not retry. Report the error code and requestId and stop.
- Publishing is async. POST /publish returns a run id; poll
GET /api/public/v1/job-runs/<id> until succeeded or failed.
- To cancel, list with ?status=scheduled and DELETE the post id. Deleting a
published post does not remove it from the platform.
- Never claim a post is live until a run reports succeeded or a post reports
published.Your agent can also fetch this itself: curl https://markaestro.com/llms.txt returns a plain-text brief of the whole API, endpoints, rules, and error handling, small enough to sit in context.
Recipes
The Four Workflows Agents Actually Run
# Full control: draft → publish → poll. No productId needed:
# the key is already bound to one brand.
POST_ID=$(curl -s -X POST "$MARKAESTRO_URL/api/public/v1/posts" \
-H "Authorization: Bearer $MARKAESTRO_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: post-2026-08-14-linkedin" \
-d '{
"channel": "linkedin",
"caption": "We shipped agent-driven publishing.",
"mediaAssetIds": ["ast_777"]
}' | jq -r .post.id)
RUN_ID=$(curl -s -X POST "$MARKAESTRO_URL/api/public/v1/posts/$POST_ID/publish" \
-H "Authorization: Bearer $MARKAESTRO_API_KEY" \
-H "Idempotency-Key: publish-$POST_ID" | jq -r .run.id)
# queued → running → succeeded | failed
curl -s "$MARKAESTRO_URL/api/public/v1/job-runs/$RUN_ID" \
-H "Authorization: Bearer $MARKAESTRO_API_KEY"# Review the queue, then cancel what the operator rejected.
curl -s "$MARKAESTRO_URL/api/public/v1/posts?status=scheduled&limit=100" \
-H "Authorization: Bearer $MARKAESTRO_API_KEY"
curl -X DELETE "$MARKAESTRO_URL/api/public/v1/posts/pst_123" \
-H "Authorization: Bearer $MARKAESTRO_API_KEY"
# → { "deleted": true, "id": "pst_123" }# One call, up to 25 posts. Per-item results: one bad item
# does not fail the batch.
curl -X POST "$MARKAESTRO_URL/api/public/v1/posts" \
-H "Authorization: Bearer $MARKAESTRO_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: week-33-drop" \
-d '{
"posts": [
{ "channel": "instagram", "caption": "Monday", "mediaAssetIds": ["ast_1"] },
{ "channel": "facebook", "caption": "Tuesday", "mediaAssetIds": ["ast_2"] },
{ "channel": "linkedin", "caption": "Thursday" }
]
}'
# → { "results": [...], "created": 3, "total": 3 }# Let Markaestro call you instead of polling.
curl -X POST "$MARKAESTRO_URL/api/public/v1/webhook-endpoints" \
-H "Authorization: Bearer $MARKAESTRO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-agent.example.com/hooks/markaestro",
"events": ["post.published", "post.action_required", "post.failed"]
}'
# Each delivery carries:
# X-Markaestro-Event post.action_required
# X-Markaestro-Timestamp 2026-08-14T15:00:04.000Z
# X-Markaestro-Signature HMAC of the body with your webhook secret
# The secret is shown once at creation and stored hashed. Verify before acting.Guardrails
What the Agent Can and Cannot Do
Autonomy is only useful if the blast radius is small. Markaestro's defaults assume the caller is software that might be wrong.
Facebook, Instagram, and TikTok Are Manual-First
Posts your agent creates for those channels default to manual_reminder: Markaestro never calls the platform's API for them. Publishing moves the post into the workspace's To post queue, where a person downloads the media, posts natively, and confirms, so the post looks exactly like it was made by hand, and a human sees every one before it exists publicly. An agent can opt a single post into official-API publishing with deliveryMode: "direct_publish", and on TikTok that means the creator-inbox handoff, never an unattended public post. LinkedIn, Threads, Pinterest, and X publish programmatically once your agent explicitly asks.
Scope the Key Down
Pick only the scopes the agent needs: products.read, media.write, posts.read, posts.write, posts.publish, evergreen.read, evergreen.write, analytics.read, job_runs.read, webhooks.manage. A research agent that only reads the calendar and the numbers gets posts.read and analytics.read and nothing else.
Give It an Expiry
Keys can be created with an expiry. An expired key behaves exactly like a revoked one, so a key that leaks out of an agent's environment stops working on its own.
Rotate and Revoke
Rotate a key in place or revoke it outright from Settings → API. Every key shows its last-used time and request volume, so an agent that goes quiet, or goes rogue, is visible.
Rate Limits Are Enforced
60 requests per minute per endpoint and 240 per minute per key. Every response carries X-RateLimit-Limit, -Remaining, and -Reset; a 429 carries Retry-After. Honor it rather than hammering.
Markaestro Never Writes for You
There is no generation step. The caption comes from your agent, the media comes from your library or your agent's pipeline. Markaestro is the hands, not the voice.
Deletes Are Markaestro-Side
Deleting a scheduled post cancels it before it ships. Deleting a published post only stops Markaestro tracking it, the live post stays up until someone removes it on the platform.
Failure handling
Teach it which errors are worth retrying
Every error response is JSON with a stable error code and a requestId. Have your agent quote the requestId when it reports a failure, it is what support needs to trace the call.
| Status | Code | What the agent should do |
|---|---|---|
| 401 | UNAUTHENTICATED | Key is missing, revoked, or expired. Stop and ask a human for a new one. Retrying will not help. |
| 403 | FORBIDDEN | The key lacks the scope for this call. Report which call failed; scopes are changed in Settings → API. |
| 403 | API_KEY_NOT_BOUND_TO_PRODUCT | A key issued before brand binding. Ask for a replacement key. |
| 400 | VALIDATION_* | The payload broke a channel rule (missing media, bad delivery mode, wrong scheduled_at). Fix the request; do not retry unchanged. |
| 400 | VALIDATION_IDEMPOTENCY_KEY_REUSED | The same Idempotency-Key was sent with a different body. Mint a new key per distinct request. |
| 400 | VALIDATION_POST_IS_PUBLISHING | Tried to delete a post while a publish run is in flight. Wait for the run to settle, then delete. |
| 409 | VALIDATION_POST_ALREADY_PUBLISHING | A publish run for this post is already queued. Do not publish again. Poll the existing run instead. |
| 402 | SUBSCRIPTION_REQUIRED | No active plan is attached to this workspace. Ask a workspace owner to review billing in Settings. |
| 402 | QUOTA_EXCEEDED_MEDIA_UPLOADS | The workspace hit its monthly upload quota. Stop uploading and surface it. Existing media still publishes. |
| 404 | NOT_FOUND | The id is outside this key's brand. Answered as 404 rather than 403 so keys cannot probe for ids they do not own. |
| 429 | RATE_LIMITED | Sleep for Retry-After seconds, then retry the same request with the same Idempotency-Key. |
Bring Your Own Stack
If It Can Make an HTTPS Request, It Can Publish
There is no Markaestro client library to adopt and no framework to standardize on. Bearer token, JSON in, JSON out.
Claude & the Claude Agent SDK
Drop the tool definitions above into your tool list. The JSON Schema shapes are already in Claude tool-use format.
OpenAI Function Calling
The same schemas map one-to-one onto function definitions. Rename input_schema to parameters.
MCP Clients
Claude Code, Claude, Cursor, ChatGPT, Grok, Grok Bot, OpenClaw, Hermes: add the hosted server URL and sign in through the browser. For stdio-only clients, npx -y @markaestro/mcp runs the same thirty-four tools locally.
n8n, Make, Zapier
Every endpoint is a plain HTTP request with a bearer token. No SDK, no signing ceremony, no OAuth dance for the agent.
LangChain & LlamaIndex
Standard REST tools. The two-step media upload is the only multi-call flow, and it is two lines.
A cron job and curl
Not every agent needs a framework. The quickstart above is a complete, working integration in four commands.