API Reference
The endpoints, exactly as they exist today.
Everything here is live. If a thing is not on this page, it is not built yet - this page is written from the routes, not from a plan.
Base URL: https://agentiq.social/api/public/v1
Authentication
One key per workspace, made in Settings → API. It is shown once.
curl https://agentiq.social/api/public/v1/platforms \
-H "Authorization: Bearer aiq_..."The header also works without Bearer, because several tools send it that
way. A key is the whole workspace: give each tool its own, and revoke the
one you stop using.
Endpoints
| Method | Path | What it does |
|---|---|---|
GET | /platforms | The accounts this workspace can post to |
GET | /posts | Drafts, scheduled, published and failed posts |
POST | /posts | Write a post - a draft, or scheduled |
GET | /posts/{id} | One post, with its thread and link |
POST | /posts/{id}/schedule | Send a draft that already exists |
DELETE | /posts/{id} | Take a post off the queue, or delete a draft |
POST | /media | Upload a picture or video, get a key back |
POST | /carousels | Turn a thread into an Instagram carousel |
POST | /posts/{id}/attach | Put a picture that already exists onto a post |
DELETE | /posts/{id}/attach | Take the attached pictures off again |
POST | /posts/{id}/picture | Draw one branded picture for a LinkedIn or Facebook post |
POST | /conversations | Talk to the agent that writes the posts |
GET | /conversations | The conversations in this workspace |
GET | /conversations/{id} | One conversation, as words |
GET | /brain | What it knows: the brand, the facts, the proof, the shelf |
POST | /brain/facts | Save one durable fact about the business |
POST | /brain/proof | Add one result this brand can back up |
DELETE | /brain/{id} | Forget a fact or a proof entry |
POST | /brain/material | Keep a page or a document to write from |
GET | /brain/material/{id} | Read one document from the shelf |
GET | /results | What normal looks like, and how recent posts did |
Platforms
GET /platforms
{
"platforms": [
{
"platform": "x",
"handle": "alexgodlewsky",
"name": "Alex Godlewski",
"followers": 1240,
"needsReconnect": false
}
]
}Call this first. Every other call takes a platform, and only the ones
listed here exist for this workspace.
Writing a post
POST /posts
Without scheduledAt it is a draft and nothing goes anywhere. That is
deliberate: a machine should not publish because it decided the moment had
come.
{
"platform": "x",
"caption": "65% of inbound messages arrive after five.",
"thread": ["So the reply cannot only happen at eleven in the morning."],
"scheduledAt": "2026-08-24T18:00:00Z",
"crosspostTo": ["threads"]
}| Field | Type | Notes |
|---|---|---|
platform | string | x, threads, instagram, linkedin, linkedin-page, facebook, tiktok |
caption | string | The post itself |
thread | string[] | The rest of a thread, in order. The first entry is the SECOND post |
mediaKeys | string[] | Keys from /media. Instagram and TikTok need at least one |
scheduledAt | string | ISO time, or "now". Left out: a draft |
crosspostTo | string[] | Other networks to send the same words to, where they fit |
tags | string[] | Your own labels |
tiktok | object | Required for TikTok - see below |
{
"id": "c1e2f6ca-…",
"status": "scheduled",
"scheduledAt": "2026-08-24T18:00:00.000Z"
}Scheduling goes through the same door the app's own composer uses, so the same refusals apply and they arrive as plain sentences: "Instagram is not connected. Connect it under Platforms first.", "Add the video first. TikTok has no text-only post."
TikTok
TikTok will not take a post until the person has chosen who may see it and what people may do with it - their rule, and the app enforces it here too:
{
"platform": "tiktok",
"caption": "How we answer DMs after five.",
"mediaKeys": ["organizations/…/create/….mp4"],
"scheduledAt": "now",
"tiktok": {
"privacyLevel": "PUBLIC_TO_EVERYONE",
"allowComment": true,
"allowDuet": false,
"allowStitch": false
}
}privacyLevel must be one of the options that account actually has; read
them from TikTok itself in the app, under Create.
Sending a draft that exists
POST /posts/{id}/schedule
{ "at": "2026-08-27T08:00:00Z" }"now" sends it as soon as a worker picks it up. POST /posts writes and
sends in one call; this is for everything written earlier - including every
post whose picture was drawn, because drawing one puts the post back to a
draft.
The same gate the app's own composer uses, so the same refusals arrive as sentences: a network that is not connected, a post with no picture where the network takes no text, a TikTok post whose privacy level nobody chose.
Carousels
POST /carousels
A thread on X or Threads, typeset as an Instagram carousel - one slide per post, in the person's own words set in X's own typeface. No image model, so it costs nothing and takes seconds.
{ "postId": "600f0be1-…", "theme": "dark" }{ "id": "65b306c8-…", "slides": 2, "merged": false, "status": "draft" }merged is true when the thread was longer than ten posts and neighbours
had to share slides - said out loud because it changes what was written.
A thread too long to pair up is refused with a sentence rather than
silently cut.
A picture you already have
POST /posts/{id}/attach
{ "url": "https://pbs.twimg.com/media/Gk2xY1.jpg?name=large" }The picture's own address - the one ending in .jpg or .png - not the
page it sits on. Paste a tweet's address and it answers with the one thing
worth saying: "That is a web page, not a picture. Open it, right-click the
image and copy the image address."
Free and immediate. Reach for this before drawing whenever the picture already exists; drawing costs about four cents and makes a different picture.
An attached picture is what goes out. A drawn one stays on the post
underneath, unused, in case you change your mind. replace: true clears
whatever was attached before instead of adding to it, and
DELETE /posts/{id}/attach takes them all off - leaving any drawn picture
to become what the post carries again.
POST /posts takes the same thing as mediaUrls, so one call can write a
post and give it a picture that exists:
{
"platform": "x",
"caption": "the ladder, drawn out",
"mediaUrls": ["https://pbs.twimg.com/media/Gk2xY1.jpg?name=large"]
}Addresses are fetched by this server, so only public ones work - anything
that resolves inside a private network is refused. If a picture needs a
login, download it and send it to /media instead.
A picture for a post
POST /posts/{id}/picture
One branded graphic for a LinkedIn or Facebook draft: the brand's colours and typefaces, one line carried big, like a carousel's cover.
This one costs about four cents and takes a couple of minutes. It
answers 202 immediately; poll the post until rendering is false and the
picture is in pictures.
{
"headline": "65% of inbound messages arrive after five",
"subline": "The busiest hour is 21:00"
}The headline must be the post's OWN claim, shortened - not a new one.
Reading a post's pictures
GET /posts/{id} returns what the post carries as links that open:
{
"id": "…",
"rendering": false,
"pictures": [{ "index": 0, "url": "https://…signed…" }]
}The bucket is private, so those are signed URLs with an hour on them. Uploaded pictures come first; drawn slides when there are none - the same order the publishers use, so what you see is what would go out.
Talking to the agent
POST /conversations
The machine that writes this brand's posts, with its voice, its proof and its memory behind it - the same one the app's Ideate screen talks to. Use it for anything that needs writing or thinking rather than fetching.
{
"message": "Write one short X post about answering DMs after five.",
"surface": "ideate"
}{
"conversationId": "f23796e7-…",
"reply": "Saved one short X post as a draft…",
"made": [
{
"id": "a290f2e2-…",
"platform": "x",
"status": "draft",
"preview": "5pm is not a deadline…"
}
]
}made is the point: "I have written you a thread" is worth nothing to a
caller who cannot then find the thread. Every id in it works with
GET /posts/{id}.
Pass conversationId back to carry on. The conversation is the same one the
app shows under Chat history, so a person can open Ideate and continue where
their agent left off.
It blocks, twenty seconds to two minutes: a turn can search the web,
read the brand's own calls and write several drafts. surface is ideate
(ideas and posts) or create (the carousel flow with its gates).
What it knows
GET /brain
Everything the writers draw on, in one read:
{
"brand": {
"name": "Agentiq",
"voice": "Plain, warm, no jargon.",
"strategy": "…",
"audience": "Founders who post for their own business",
"language": "Polish",
"colors": {
"background": "#0B0B0C",
"accent": "#E5484D",
"text": "#FFFFFF"
}
},
"perNetwork": [{ "platform": "linkedin", "instructions": "…" }],
"facts": [
{ "id": "…", "fact": "Lead magnet: CODING IN CLAUDE, by DM.", "at": "…" }
],
"proof": [
{
"id": "…",
"claim": "65% of inbound messages arrive after five.",
"source": "our dashboard, August 2026",
"hasReceipt": true,
"movesAt": "2026-08-01T00:00:00.000Z"
}
],
"material": [
{
"id": "…",
"kind": "fathom",
"title": "Call with Kallway",
"hasMaterial": true
}
]
}talk_to_agent already writes from all of it. Read it before you teach it
something, so you do not save what it already knows.
Teaching it
curl -X POST https://agentiq.social/api/public/v1/brain/facts \
-H "Authorization: Bearer aiq_..." -H "Content-Type: application/json" \
-d '{"fact":"We answer every DM within an hour, by hand."}'Three shelves, and they are not interchangeable:
| Shelf | What belongs on it |
|---|---|
/brain/facts | One plain sentence that stays true - an offer, a campaign, a standing preference. Fifty of them, then old ones have to go |
/brain/proof | A result with something behind it. A post can claim it only if it is here |
/brain/material | A page or a document to write FROM: a transcript, a case study, notes |
Proof takes timeSensitive - true when the number MOVES. Revenue, followers,
team size and a conversion rate are all true the day they are written and
quietly wrong a quarter later, so those get dated and the writers are told
how old they are.
{
"claim": "We cut their reply time from 9 hours to 12 minutes.",
"source": "Kallway, August 2026",
"timeSensitive": false
}Material comes in two ways, one per call - a page to read, or text you already have:
{ "url": "https://example.com/case-study" }{ "title": "Call with Kallway, 14 Aug", "text": "…the transcript…" }A link is fetched, stripped and summarised, which takes a few seconds. Text is kept as written - no summary, on purpose, because the writers prefer a summary when there is one and a machine's account of somebody's own words is the opposite of why they sent them.
GET /brain/material/{id} reads one back: the summary when there is one,
otherwise the text, cut at 24,000 characters with truncated: true saying
so.
DELETE /brain/{id} forgets a fact or a proof entry - one path for both,
because from out here they are both a thing the brain knows with an id
beside it.
The brand itself is read-only through this door. Colours, typefaces, voice and strategy are the one thing the person sat down and decided; a machine quietly rewriting them is how a brand drifts without anybody choosing to change it. Those are edited in the app.
Media
POST /media, multipart, one file per request.
curl -X POST https://agentiq.social/api/public/v1/media \
-H "Authorization: Bearer aiq_..." \
-F "file=@cover.jpg"{
"key": "organizations/…/create/9f2c….jpg",
"bytes": 184320,
"type": "image/jpeg"
}JPEG, PNG, WebP, MP4 or MOV, up to 50MB per request. Put the key into
mediaKeys on a post.
Results
GET /results?limit=20
{
"normal": [
{
"platform": "x",
"medianViews": 328,
"from": "your own posts",
"sample": 14
}
],
"posts": [
{
"id": "…",
"platform": "x",
"text": "65% of inbound messages…",
"views": 1180,
"likes": 9,
"settled": true,
"standing": "outlier"
}
]
}normal is the median of finished posts, not the average - one big post
would drag an average up and hide every good post behind it. settled says
whether the numbers have stopped moving; standing is quiet, normal,
outlier or breakout against that median.
Errors
| Status | error | When |
|---|---|---|
400 | bad_request | The body is wrong, or a network refused it |
401 | unauthorized | No key, an unknown key, or a revoked one |
404 | not_found | No post with that id in this workspace |
413 | too_large | The upload is over 50MB |
500 | server_error | Our side. It is written down; tell us |
Every error carries a message written for a person to read.