Overview
The Asteria Engine API lets your own tools work in an Asteria Engine workspace: a client reporting dashboard, an automation in Zapier or Make, your CRM, or a script your team writes. It can read posts, analytics and brand settings, create drafts, add media, have Asteria write and design posts, plan content, and send posts for approval.
The API and webhooks are included on the Agency and Enterprise plans.
Nothing the API does publishes on its own. Drafts and generated posts wait in the workspace, and the workspace's approval rules decide what goes out, exactly as they do for your team.
Create an API key
- Open Settings, then API & Webhooks (turn on Show all tools at the bottom of the menu if you can't see it).
- Select Create key.
- Give it a name that says what it's for, such as "Client reporting dashboard".
- Choose what it may do:
- See posts, brand settings, the content strategy, connected accounts, analytics and credits
- Create drafts, add media, and change the content strategy and brand profile once you agree
- Create content and content plans with Asteria Engine (designing spends credits)
- Send posts for approval, approve them and schedule them (your approval rules still apply)
- See and run paid ads within your workspace's spending limits
- Choose when it expires, then select Create key.
- Copy the key straight away. It is shown only once. If you lose it, revoke it and create another.
Workspace owners and managers can create and revoke keys. A key:
- works in the workspace it was created in, and no other;
- acts as the person who created it, with their permissions;
- stops working when it is revoked, when it expires, if its creator leaves the workspace, or if the workspace's plan no longer includes the API.
Create one key per tool or client integration, so you can revoke any of them without affecting the rest.
Make a request
Send the key in the Authorization header of every request. The address of the API is shown in Settings › API & Webhooks.
curl https://mcp.asteriaengine.com/api/public/v1/posts/ \
-H "Authorization: Bearer ae_live_..."
Requests and responses are JSON, and field names are camelCase (for example pageSize, scheduledFor).
What you can do
| Endpoint | What it does | Permission |
|---|---|---|
GET /workspace/ | The key's workspace | See |
GET /workspace/brand/ | Brand voice, audience, offerings and rules | See |
PATCH /workspace/brand/ | Change the brand profile (changes, confirmed); needs a person who can manage settings | Create drafts |
GET /accounts/ | Connected social accounts | See |
GET /credits/ | Credits left this period | See |
GET /posts/ | Posts, filtered by status and search, paginated | See |
GET /posts/{id}/ | One post | See |
POST /posts/ | Create a draft (caption, optional platformAccountId, mediaIds, hashtags, proposedTime) | Create drafts |
GET /media/ | Media library, filtered by type and search, paginated | See |
GET /products/ | Product Library (id, name, variant, price, link, photo), filtered by search, paginated | See |
POST /media/ | Add a picture or video exactly as it is: a public https:// address (url), the file as base64 (data with mimeType), or a multipart file | Create drafts |
POST /media/music/ | Compose a song, instrumental or sound effect (brief, kind, durationSeconds, style, lyrics) | Create content |
POST /media/voiceovers/ | Speak text exactly as written in the workspace's narration voice (direction, speed) | Create content |
POST /media/motion-graphics/ | Plan and render a motion graphics video (brief, production) | Create content |
GET /media/jobs/{id}/ | Whether music, a voiceover or a motion graphics video is ready, and the finished media | Create content |
POST /posts/{id}/music/ | Put a music library track on a post (audioId, confirmed) | Create drafts |
POST /posts/{id}/media/ | Put library media on an existing post (mediaIds, mode replace or add, confirmed); an approved post goes back to review | Create drafts |
GET /analytics/summary/ | Performance for a dateRange, or startDate and endDate | See |
GET /story-series/ | Your story series (episodic dramas with a fixed cast), paginated | See |
POST /story-series/ | Create a series (title, premise, style cinematic or animated, episodeSeconds, episodeCount, postWeekdays, postTime, platformAccountIds…) | Create drafts |
GET /story-series/{id}/ | One series with its cast, artwork and episodes (episodesPage) | See |
PATCH /story-series/{id}/ | Change a series | Create drafts |
POST /story-series/{id}/characters/ | Add a character (name, description, mediaIds, voice, draw) | Create drafts |
DELETE /story-series/{id}/characters/{characterId}/ | Remove a character (pause a running season first) | Create drafts |
POST /story-series/{id}/outline/ | Write the season outline in the background | Create content |
POST /story-series/{id}/start/ | Approve the outline and start; without "confirmed": true it returns the posting times and credits per episode | Create content |
POST /story-series/{id}/pause/, /resume/ | Pause the season (nothing more is made or posts) or resume it (episodes move to the next slots) | Create drafts |
POST /story-series/{id}/episodes/{number}/remake/ | Make a failed or not-yet-posted episode again (cost first, then "confirmed": true) | Create content |
POST /story-series/{id}/episodes/{number}/post/ | Post as it is an episode held because its video left out a scripted line (its note says which, held is true) | Send and schedule |
POST /story-series/{id}/artwork/ | Draw the key art each episode opens and closes on again (cost first) | Create content |
POST /generations/ | Have Asteria write and make posts for platforms: pictures, carousels, videos, motion graphics or songs; productIds (up to 3, from GET /products/) makes the media with those products | Create content |
GET /generations/{id}/ | Whether a generation has finished, and its posts | Create content |
POST /content-plans/previews/ | Propose a content plan (nothing is saved) | Create content |
POST /content-plans/ | Create a plan from a previewId | Create content |
POST /content-plans/drafts/ | Check a plan you wrote item by item (items, focus): warnings and a draftId, nothing saved | Create content |
POST /content-plans/drafts/{draftId}/ | Create the plan exactly as written | Create content |
GET /strategy/ | The content strategy: pillars, sell share, cadence, posting windows, format mix, topics to avoid, experiments | See |
PATCH /strategy/ | Change the strategy (changes, version, confirmed) | Create drafts |
GET /platforms/ | What each platform can publish: formats, media rules, caption and hashtag limits | See |
PATCH /posts/{id}/ | Change a caption or hashtags (confirmed to apply). An approved or scheduled post goes back to review | Create drafts |
POST /posts/{id}/visual/ | Redesign a post's image, carousel or document | Create content |
POST /posts/{id}/archive/ | Archive a post | Create drafts |
POST /posts/{id}/tags/ | Add or remove a post's tags | Create drafts |
GET /posts/{id}/approvals/ | Approval history: who, role, channel, time and version | See |
POST /posts/{id}/approval-request/ | Ask the workspace's approvers (notified in Asteria Engine) | Send and schedule |
GET /content-plans/ and GET /content-plans/{id}/ | Plans and their items | See |
PATCH /content-plans/items/{id}/ | Change a planned item | Create content |
POST /content-plans/items/{id}/rewrite/ | Rewrite an item's idea (instruction, then draftId to keep it) | Create content |
POST /content-plans/items/{id}/lock/ | Lock or unlock an item | Create content |
POST /content-plans/{id}/generate/ | Make posts from chosen itemIds, with one credit estimate | Create content |
GET /content-plans/production-options/ | What production can set for videos, motion graphics, songs and pictures | See |
GET /videos/options/, POST /videos/previews/, POST /videos/, GET /videos/{id}/, POST /videos/{id}/attach/ | Authored videos: the script format, check a script and its cost, generate it, check it, and make a draft post from it | Create content |
GET /analytics/posts/ | Each post's results measured at the same age (normaliseDays, default 7), filtered by tag | See |
GET /analytics/compare/ | Two periods side by side, with a breakdown by platform, format or tag | See |
GET /analytics/metrics/, GET /analytics/conversions/ | What each metric means; conversion events | See |
GET /inbox/, GET /comments/ | Conversations, and comments still waiting for an answer | See |
POST /comments/{id}/reply-drafts/, POST /comments/replies/ | Draft a reply, then send that exact draft | Create drafts / Send and schedule |
GET /leads/ | Leads without personal details | See |
GET /meetings/slots/, POST /meetings/ | Free calendar times; book a meeting with a lead | See / Send and schedule |
GET /ads/guardrails/, GET /ads/accounts/, GET /ads/campaigns/, GET /ads/campaigns/{id}/, GET /ads/analytics/, GET /ads/recommendations/ | Ad limits, accounts, campaigns, results and suggestions | Run ads |
POST /ads/campaigns/{id}/pause/, POST /ads/campaigns/{id}/resume/, POST /ads/campaigns/{id}/budget/ | Pause (straight away), resume, or change a daily budget (raising needs confirmed) | Run ads |
POST /posts/{id}/submit/ | Send a post for approval | Send and schedule |
POST /posts/{id}/schedule/ | Schedule an approved post for scheduledFor | Send and schedule |
The full reference, with every field, is published at /api/public/v1/docs/ on the same address, and the machine-readable OpenAPI description at /api/public/v1/schema/.
Plans and the strategy
A plan preview (POST /content-plans/previews/) is built from the workspace's saved strategy. Planning a month can take a few minutes: when the answer has "status": "working", send just its previewId to the same endpoint until it says done. Its interpretation says how the brief was read, and warnings names anything to check, such as a platform with no free posting slots or a format a platform can't publish. To decide every post yourself, send the items to POST /content-plans/drafts/ instead. Dates, platforms and formats are kept exactly as written. The response lists warnings, for example more posts in a week than the strategy's cadence, or a time that clashes with a post already in the calendar.
Each item can also say how it should be made, in production. For a video, the recommended way is your own authored script in production.script (the format from GET /videos/options/): the planned post is made from it exactly as written, scene by scene. Without a script, give durationSeconds and optionally model, aspectRatio and direction, and Asteria Engine's designer plans the scenes at that exact length. Motion graphics items can set the length, look, art style, narration, music, sound effects, captions and presenter. Song items can set the length, style and lyrics. What you set wins over the workspace's defaults. Anything that can't be made, such as a length the platform doesn't accept, is refused with the reason before anything is saved. Anything Asteria Engine decides for you comes back as a warning, for example "Made with omni: the workspace's veo_fast can't make exactly 35 seconds". GET /content-plans/production-options/ lists every setting.
PATCH /strategy/ without confirmed: true only returns the before and after, so you can show it first. Send the version from GET /strategy/ so a change is refused if someone edited the strategy in the meantime. Parts you save this way are kept when Asteria refreshes the strategy automatically.
Launching new ad campaigns isn't available with an API key: starting to spend needs a person, in Asteria Engine or through their AI assistant. Ad spending limits are set only in Asteria Engine.
Generating content spends credits
POST /generations/ first returns the number of credits it would use and, in madeAs, how each post will be made. Send the same request again with "confirmed": true to start. The response has a generation id; check it with GET /generations/{id}/ until it says done. With your own caption, the posts come back straight away instead and their media follows. The finished posts wait for review in the workspace.
contentType can also be video, motion_graphics or song (values are sent as written). Add production to set how the media is made: it takes the same settings as a plan item. For a video, your own authored script in production.script is the recommended way: it is made exactly as written. For a picture (contentType image), production.prompt is sent to the picture model exactly as written, and the picture comes back as it was made, with no text or logo laid over it. It is only fitted to the account's picture shape.
Pagination
Lists take page (from 1) and pageSize (up to 50). The response says which page comes next.
Webhooks
Webhooks tell your systems the moment something happens, so you don't have to keep asking. In Settings › API & Webhooks, select Add endpoint, enter an https:// address and choose the events:
| Event | Sent when |
|---|---|
post.waiting_for_review | A post is waiting for someone to approve it |
post.scheduled | A post is scheduled |
post.published | A post has been published |
post.failed | A post could not be published and Asteria Engine has stopped retrying |
You get a signing secret (whsec_...) once, when you add the endpoint. Keep it on your server. You can rotate it at any time.
Each event is a POST with a JSON body:
{
"id": "evt_...",
"type": "post.published",
"createdAt": "2026-11-12T09:30:00+00:00",
"workspace": {"id": 109, "name": "Kirkgate Café"},
"data": {"post": {"id": 1135, "status": "published", "platform": "instagram", "caption": "...",
"scheduledFor": "...", "publishedAt": "...", "platformUrl": "...", "error": null,
"openInAsteria": "https://..."}}
}
Check the signature
Every request has an Asteria-Signature header like t=1762939800,v1=5f2c.... To check it, compute an HMAC-SHA256 of the timestamp, a full stop and the raw request body, using your signing secret, and compare it with v1. Reject requests whose timestamp is more than five minutes old.
import hashlib, hmac, time
def is_from_asteria(raw_body: bytes, header: str, secret: str) -> bool:
parts = dict(item.split("=", 1) for item in header.split(","))
expected = hmac.new(secret.encode(), f"{parts['t']}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts["v1"]) and abs(time.time() - int(parts["t"])) < 300
Asteria-Event names the event and Asteria-Delivery identifies the delivery. A delivery keeps the same Asteria-Delivery when it is retried, so you can ignore any you have already handled.
Delivery and retries
Answer with any 2xx status within 10 seconds. Otherwise Asteria Engine tries again after 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours. If five events in a row can't be delivered, the endpoint is turned off and the reason is shown in Settings; turn it back on once it is fixed. Send test sends a webhook.test event straight away, and Recent deliveries shows the last 30 days.
Webhooks are only sent to public addresses, and redirects are not followed.
Errors
Errors have the same shape everywhere:
{"error": {"message": "What went wrong, in plain words", "code": "missing_scope"}}
| Status | Meaning |
|---|---|
| 400 | Something in the request needs fixing; the message says what |
| 401 | No key, or the key is wrong, revoked or expired |
| 403 | The key isn't allowed to do this (missingScopes lists what it lacks), or the workspace's plan no longer includes the API |
| 429 | More than 60 requests a minute with this key; wait and try again (see Retry-After) |
Activity and security
Every request made with a key is recorded in the workspace's activity, with the key that made it. Keys are stored only as a one-way hash, so nobody, including Asteria Engine staff, can read a key after it is created.
Keep keys out of browser code and public repositories. If a key may have leaked, revoke it in Settings › API & Webhooks; it stops working immediately.