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

MethodPathWhat it does
GET/platformsThe accounts this workspace can post to
GET/postsDrafts, scheduled, published and failed posts
POST/postsWrite a post - a draft, or scheduled
GET/posts/{id}One post, with its thread and link
POST/posts/{id}/scheduleSend a draft that already exists
DELETE/posts/{id}Take a post off the queue, or delete a draft
POST/mediaUpload a picture or video, get a key back
POST/carouselsTurn a thread into an Instagram carousel
POST/posts/{id}/attachPut a picture that already exists onto a post
DELETE/posts/{id}/attachTake the attached pictures off again
POST/posts/{id}/pictureDraw one branded picture for a LinkedIn or Facebook post
POST/conversationsTalk to the agent that writes the posts
GET/conversationsThe conversations in this workspace
GET/conversations/{id}One conversation, as words
GET/brainWhat it knows: the brand, the facts, the proof, the shelf
POST/brain/factsSave one durable fact about the business
POST/brain/proofAdd one result this brand can back up
DELETE/brain/{id}Forget a fact or a proof entry
POST/brain/materialKeep a page or a document to write from
GET/brain/material/{id}Read one document from the shelf
GET/resultsWhat 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"]
}
FieldTypeNotes
platformstringx, threads, instagram, linkedin, linkedin-page, facebook, tiktok
captionstringThe post itself
threadstring[]The rest of a thread, in order. The first entry is the SECOND post
mediaKeysstring[]Keys from /media. Instagram and TikTok need at least one
scheduledAtstringISO time, or "now". Left out: a draft
crosspostTostring[]Other networks to send the same words to, where they fit
tagsstring[]Your own labels
tiktokobjectRequired 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:

ShelfWhat belongs on it
/brain/factsOne plain sentence that stays true - an offer, a campaign, a standing preference. Fifty of them, then old ones have to go
/brain/proofA result with something behind it. A post can claim it only if it is here
/brain/materialA 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

StatuserrorWhen
400bad_requestThe body is wrong, or a network refused it
401unauthorizedNo key, an unknown key, or a revoked one
404not_foundNo post with that id in this workspace
413too_largeThe upload is over 50MB
500server_errorOur side. It is written down; tell us

Every error carries a message written for a person to read.

On this page