Generation, Brand DNA, and product screens. One bearer token. The same pipeline as the dashboard, callable from your code. Available on every plan, Spark included. An active subscription is required to mint live keys; video is the one gate that needs it.
Agents can skip keys entirely: add https://mcp.mysiren.ai/mcp in Claude Code, Claude, Cursor, ChatGPT, or Grok, then Authorise. Recipes: Connect an agent over MCP.
Authentication
Every request authenticates with a bearer API key. Mint keys at app.mysiren.ai → Settings → Developer → API keys. Two prefixes:
sk_live_…. Production keys. They hit the production pipeline and count against your plan caps.sk_test_…. Test keys. Mock-pipelined. They never post. Useful for CI.
Authorization: Bearer sk_live_YOUR_API_KEY_HERE
Per-key scopes. When you mint a key you set:
asset_types. Which types this key can generate (default: all)channels. Which channels this key can target (default: all)connected_accounts. Restrict posting to specific connected accountsauto_post+auto_post_min_score. Auto-dispatch when the quality score clears your thresholddaily_limit. Hard cap on requests per UTC day for this keyAny request that violates a scope returns
403with a structured error.
Quickstart
Mint a sk_test_… key with asset_type: card and channel: twitter. Then:
# 1. Verify the key is alive
curl https://api.mysiren.ai/api/v1/programmatic/me \
-H "Authorization: Bearer sk_test_…"
# 2. Submit a brief
RUN_ID=$(curl -s -X POST https://api.mysiren.ai/api/v1/programmatic/generate \
-H "Authorization: Bearer sk_test_…" \
-H "Content-Type: application/json" \
-d '{"brief":"Welcome message","channel":"twitter","asset_type":"card"}' \
| jq -r .id)
# 3. Poll until done
while true; do
STATUS=$(curl -s https://api.mysiren.ai/api/v1/programmatic/runs/$RUN_ID \
-H "Authorization: Bearer sk_test_…" | jq -r .status)
echo "status: $STATUS"
[ "$STATUS" = "succeeded" ] && break
[ "$STATUS" = "failed" ] && exit 1
sleep 2
done
# 4. Fetch the result
curl https://api.mysiren.ai/api/v1/programmatic/runs/$RUN_ID \
-H "Authorization: Bearer sk_test_…" | jq
On a healthy day, end-to-end is about 10 seconds for cards, 60-90 seconds for videos.
Endpoints
The response shapes below are what we commit to publicly. We may add fields without warning. We will not remove or rename them without a deprecation period and prior notice.
POST /api/v1/programmatic/generate
Create a generation run from a prompt. Returns immediately with status=queued. Poll /runs/{id}, or use the auto-post path. Enforces the key's daily limit and the workspace's per-asset-type plan cap (for example, the video library is Pro+, 15 / 60 / 300 videos per month by tier).
curl -X POST https://api.mysiren.ai/api/v1/programmatic/generate \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{
"brief": "Announce our Q2 update. Contrarian angle on the slop economy",
"channel": "twitter",
"asset_type": "card"
}'
Response. 202 Accepted
{
"id": "0a8d…",
"status": "queued",
"brief": "Announce our Q2 update. Contrarian angle on the slop economy",
"channel": "twitter",
"asset_type": "card",
"output_text": null,
"asset_url": null,
"asset_urls": null,
"impact_score": null,
"verdict": null,
"audience_score": null,
"audience_verdict": null,
"audience_themes": null,
"audience_risks": null,
"audience_opportunities": null,
"error_message": null,
"created_at": "2026-05-05T14:32:11Z",
"started_at": null,
"finished_at": null,
"auto_post_eligible": false
}
Notes:
- Asset types:
card·video·slideshow·carousel·infographic·og_image 400ifasset_typeis unsupported ·403if key scopes do not permitasset_type/channel·429if the daily limit is hitauto_post_eligiblereflects the key'sauto_postflag. When true, posting fires automatically once the quality score clears the key'sauto_post_min_score
GET /api/v1/programmatic/runs/{run_id}
Fetch a single run by ID. Returns the same shape as POST /generate. Status transitions: queued → running → succeeded | failed. Quality fields populate after the run reaches succeeded.
curl https://api.mysiren.ai/api/v1/programmatic/runs/0a8d… \
-H "Authorization: Bearer sk_live_…"
Response. 200 OK
{
"id": "0a8d…",
"status": "succeeded",
"brief": "Announce our Q2 update. Contrarian angle on the slop economy",
"channel": "twitter",
"asset_type": "card",
"output_text": "The slop economy thinks volume is the moat…",
"asset_url": "https://r2.mysiren.ai/.../card.png",
"asset_urls": null,
"impact_score": 82,
"verdict": "approved",
"audience_score": 78,
"audience_verdict": "approved",
"audience_themes": [
{"label": "contrarian-frame", "weight": 0.41},
{"label": "engineering-honesty", "weight": 0.27}
],
"audience_risks": ["may read elitist to mass audience"],
"audience_opportunities": ["strong with technical founders"],
"error_message": null,
"created_at": "2026-05-05T14:32:11Z",
"started_at": "2026-05-05T14:32:12Z",
"finished_at": "2026-05-05T14:32:21Z",
"auto_post_eligible": false
}
Notes:
404if the run is not in this key's workspaceaudience_*fields are legacy (optional historical quality path). Preferimpact_scoreandverdictfor product logic
GET /api/v1/programmatic/runs
List runs in this key's workspace, newest first. Paginates with limit (1 to 100, default 20) and offset. Returns all workspace runs by default, including dashboard-created ones, so an automation can audit teammate activity. Filter to your own programmatic runs with ?created_via=api.
curl "https://api.mysiren.ai/api/v1/programmatic/runs?limit=10" \
-H "Authorization: Bearer sk_live_…"
Response. 200 OK
[
{ "id": "0a8d…", "status": "succeeded", "asset_type": "card", … },
{ "id": "1f2c…", "status": "running", "asset_type": "video", … },
…
]
POST /api/v1/programmatic/runs/{run_id}/post
Dispatch an approved run to a connected social account. Required when your key has auto_post=false (the default). Review the quality score, then trigger the post. Only works on runs whose status=succeeded AND verdict=approved.
curl -X POST https://api.mysiren.ai/api/v1/programmatic/runs/0a8d…/post \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{ "platform": "x" }'
Response. 200 OK
{
"id": "post-uuid",
"run_id": "0a8d…",
"platform": "x",
"status": "posted",
"external_post_id": "1789…",
"url": "https://x.com/.../status/1789…",
"posted_at": "2026-05-05T14:34:02Z"
}
Notes:
400if the run is not approved- Platform options:
x·linkedin·instagram·youtube·tiktok(the channel must be connected in the dashboard) - An audit log row is written for every dispatch
GET /api/v1/programmatic/me
Introspect the bearer key. Returns the key's scopes and workspace ID. Useful for SDK clients to verify they are authorized before making real calls.
curl https://api.mysiren.ai/api/v1/programmatic/me \
-H "Authorization: Bearer sk_live_…"
Response. 200 OK
{
"key_id": "key-uuid",
"workspace_id": "ws-uuid",
"asset_types": ["card", "video"],
"channels": ["twitter", "linkedin"],
"connected_accounts": null,
"auto_post": false,
"auto_post_min_score": 70,
"daily_limit": 100
}
GET /api/v1/programmatic/brand-dna
Read this key's Brand DNA. Overview, tagline, offer, features, products, audience, palette, logos. Brand Memory is not on this API.
curl https://api.mysiren.ai/api/v1/programmatic/brand-dna \
-H "Authorization: Bearer sk_live_…"
PATCH /api/v1/programmatic/brand-dna
Merge fields. Send only what changed. Nested bags (offer, product_map, icp) merge so you can patch features without wiping product_name.
curl -X PATCH https://api.mysiren.ai/api/v1/programmatic/brand-dna \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{
"tagline": "One brief. On-brand.",
"overview": "Deterministic visual marketing engine.",
"offer": {
"product_name": "Siren",
"features": ["Brand DNA", "deterministic render"]
}
}'
Product screens
Screenshots of the product UI. Stored on R2. Used on device-framed masters.
# List
curl https://api.mysiren.ai/api/v1/programmatic/brand-assets/screens \
-H "Authorization: Bearer sk_live_…"
# Upload from a public https URL
curl -X POST https://api.mysiren.ai/api/v1/programmatic/brand-assets/screens \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{
"name": "Home",
"screen_type": "home",
"device": "phone",
"theme": "dark",
"image_url": "https://example.com/home.png"
}'
Pass image_base64 instead of image_url if the agent has the bytes. screen_type: home, feature, feed, chat, results, verdict, dashboard, desktop, onboarding, detail, settings, other. device: phone, desktop, tablet. theme: light, dark, unknown.
PATCH /api/v1/programmatic/brand-assets/screens/{id} retags. DELETE removes the row.
MCP wrappers: get_brand_dna, update_brand_dna, list_product_screens, upload_product_screen, update_product_screen, delete_product_screen. Brand Memory is not on MCP.
The same key also reaches the logo (POST /api/v1/programmatic/brand-assets/logo), the people library (GET/POST /brand-assets/people, DELETE /brand-assets/people/{id}), the mascot (GET/POST /mascot, POST /mascot/step with keep, retry or pose), uploaded media (GET /media, POST /media/delete), brand videos (GET/POST /brand-assets/videos, DELETE /brand-assets/videos/{id}; Gemini reads frames and files each clip), results (GET /performance?window=30d, GET /posts), and credit top-ups (POST /credits/checkout returns a Stripe link the human pays). MCP wrappers: upload_logo, list_people, add_person, delete_person, get_mascot, create_mascot, mascot_step, list_media, delete_media, list_brand_videos, upload_brand_video, delete_brand_video, get_performance, list_posts, buy_credits.
POST /api/v1/studio/generate
Fire an event-driven card from your own systems (Siren Studio). Bind a Studio profile to this key in the dashboard first. The profile sets the allowed templates, the theme, and where finished cards deliver (Telegram bot, connected socials, or download-only). Your bot or webhook then calls this endpoint on its own events (a trade closes, a sale lands). Siren renders and delivers.
Same guard order as /programmatic/generate: scopes → daily limit → workspace → rate limit → pause gate.
curl -X POST https://api.mysiren.ai/api/v1/studio/generate \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{
"output_type": "sale-notification",
"data": {
"amount": "$49.00",
"productName": "Starter · Monthly",
"customerCount": "76"
},
"caption": "New subscriber just landed. #76 and counting.",
"delivery": "auto"
}'
Response. 202 Accepted
{
"run_id": "44921844-…",
"poll_url": "/api/v1/programmatic/runs/44921844-…",
"delivery": ["telegram", "social"]
}
Notes:
captionis optional. When set, it is used verbatim as the post text on every delivery target (Telegram caption, and the caption on X, LinkedIn, and other connected socials). Omit it to fall back to the profile'sdefault_caption.deliveryreflects what actually fires:["telegram"],["social"], both, or[]if the profile has neither enabled. Download-only integrations poll forasset_url.402if the workspace plan does not include Studio, or a bound Telegram bot is over its plan's account cap- See the Studio page in the dashboard for the live template catalog and each template's exact
datafields
Errors
All errors return JSON with a detail field. Common codes:
400. Bad request (unsupportedasset_type, malformed body)401. Missing or invalid bearer token403. Key scope violation (asset_type,channel,connected_account)404. Run not in this workspace422. Validation error from Pydantic429.daily_limitexceeded for this key, or per-asset-type plan cap reached5xx. Pipeline issue; retry with exponential backoff after about 30s
{ "detail": "unsupported asset_type: 'foo'" }
Plans
The programmatic API and Siren Studio are on every plan, Spark included. An active subscription is required to mint live keys; video render is the one thing that needs Pro or above.
