For AI agents
Hand your agent an API key. It runs your social channels.
Markaestro is built to be operated by software. One workspace API key gives an agent everything it needs to 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, and Pinterest.
No SDK to install, no OAuth flow for the agent to survive, no platform-specific 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.
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, and Pinterest 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"]
},
"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, and Pinterest 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, job_runs.read, webhooks.manage. A research agent that only reads the calendar gets posts.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 | 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 servers
If your agent speaks MCP, wrap these six calls in a small server. There is nothing Markaestro-specific to install on either side.
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.