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.

One key, one brand
Every API key is bound to a single brand when you create it. An agent holding that key can only ever see and post to that brand — cross-brand requests are rejected at authentication, not by convention.
Discovery, not hardcoded ids
The agent asks which accounts it can post to and gets back opaque ids to pass straight back. No Page ids, no Business Manager spelunking, no config file that rots when a connection is re-linked.
Idempotent writes
Send an Idempotency-Key on any create or publish. A retried call inside 24 hours replays the original response instead of creating a second post — the failure mode agents hit most.
A human stays in the loop
Facebook, Instagram, and TikTok posts are manual-first: your agent prepares them, a person posts them natively. Nothing goes out unattended unless you explicitly opt that post in.

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.

01Discover
GET /api/connect/v1/social-accounts

Returns 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.

02Upload media
POST /api/connect/v1/media/create-upload-url → PUT

Mint 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.

03Draft or schedule
POST /api/connect/v1/posts

Pass 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.

04Publish
POST /api/public/v1/posts/:id/publish

Queues 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.

05Report back
GET /api/public/v1/job-runs/:id · webhooks

Poll 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.

1. Discover the accounts
The first call in every run. Connections change; ids should never be baked into a prompt.
bash
# 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"
response
{
  "data": [
    {
      "id": "prod_123#instagram:instagram:ig_123",
      "product_id": "prod_123",
      "product": "Northwind Coffee",
      "platform": "instagram",
      "username": "northwindcoffee"
    }
  ]
}
2. Upload the media
Two steps: mint a signed, single-use URL, then PUT the bytes. The URL expires after 15 minutes and needs no auth header of its own.
bash
# 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.png
3. Schedule it, then watch it
Creating is draft-first by default. Send is_draft: false with a scheduled_at timestamp to put the post on the calendar instead.
bash
# 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.

Tool schemas
Six tools: list accounts, upload media, create, publish, list, delete. Wire each one to the matching endpoint above.
tools.json
[
  {
    "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"]
    }
  }
]
Agent brief
Paste into your system prompt. It encodes the behaviors that separate a reliable publishing agent from one that double-posts and declares victory early.
system prompt
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

Publish and confirm
Create a draft, publish it explicitly, then poll the run. The only honest way to tell the operator a post went out.
bash
# 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"
Audit and cancel the queue
List what is scheduled, show it to a human, delete what they reject. Both calls use scopes an existing key already carries.
bash
# 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" }
Fill a week in one call
Batch create takes up to 25 posts and returns per-item results, so a single malformed item does not sink the run.
bash
# 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 }
Get called instead of polling
Long-running agents should register a webhook and sleep. Deliveries are HMAC-signed with a secret shown once at creation.
bash
# 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.

StatusCodeWhat the agent should do
401UNAUTHENTICATEDKey is missing, revoked, or expired. Stop and ask a human for a new one — retrying will not help.
403FORBIDDENThe key lacks the scope for this call. Report which call failed; scopes are changed in Settings → API.
403API_KEY_NOT_BOUND_TO_PRODUCTA key issued before brand binding. Ask for a replacement key.
400VALIDATION_*The payload broke a channel rule (missing media, bad delivery mode, wrong scheduled_at). Fix the request; do not retry unchanged.
400VALIDATION_IDEMPOTENCY_KEY_REUSEDThe same Idempotency-Key was sent with a different body. Mint a new key per distinct request.
400VALIDATION_POST_IS_PUBLISHINGTried to delete a post while a publish run is in flight. Wait for the run to settle, then delete.
409VALIDATION_POST_ALREADY_PUBLISHINGA publish run for this post is already queued. Do not publish again — poll the existing run instead.
402QUOTA_EXCEEDED_MEDIA_UPLOADSThe workspace hit its monthly upload quota. Stop uploading and surface it — existing media still publishes.
404NOT_FOUNDThe id is outside this key's brand. Answered as 404 rather than 403 so keys cannot probe for ids they do not own.
429RATE_LIMITEDSleep 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.

Give your agent something real to do

Connect your channels, mint a brand-scoped key, and hand it over. The quickstart above is the entire integration.