API reference
Every endpoint, with the bodies your program will actually see.
Base address: https://api.afilipost.com · Machine-readable schema: openapi.json
GET /v1/accounts
The accounts connected to your workspace. Filter with ?platform=instagram.
Response
{
"accounts": [
{
"_id": "8b1e…7d",
"platform": "instagram",
"username": "afilipost",
"displayName": "Afilipost",
"isActive": true,
"status": "ok",
"profileId": { "_id": "0a4c…19" }
}
]
} POST /v1/media/presign
Ask for a signed upload address, then PUT the file straight to storage.
Request
{
"filename": "reel.mp4",
"contentType": "video/mp4",
"size": 18432000
} Response
{
"mediaId": "3d90…aa",
"uploadUrl": "https://…", // buraya PUT
"publicUrl": "https://cdn.afilipost.com/…mp4",
"expiresIn": 3600,
"contentType": "video/mp4"
} The bytes never pass through the API. The signed address is valid for an hour; an upload already in flight is not cut off when it expires.
POST /v1/posts
Create or schedule a post. One post can target several accounts at once.
Request
{
"content": "Today's repo: a terminal file manager.",
"mediaItems": [
{ "type": "video",
"url": "https://cdn.afilipost.com/…mp4",
"filename": "reel.mp4",
"mimeType": "video/mp4" }
],
"platforms": [
{ "platform": "instagram",
"accountId": "8b1e…7d",
"platformSpecificData": {
"shareToFeed": true,
"firstComment": "Comment REPO for the link"
} }
],
"scheduledFor": "2026-08-24T19:40:00.000Z",
"timezone": "UTC",
"idempotencyKey": "repo-4192-tr"
} Response
{
"post": {
"_id": "0f7a…42",
"status": "scheduled",
"scheduledFor": "2026-08-24T19:40:00.000Z",
"platforms": [
{ "platform": "instagram",
"accountId": "8b1e…7d",
"status": "scheduled",
"platformPostId": null }
]
}
} Send idempotencyKey and a retried request will not create a second post. Two extras neither competitor has: scheduledFor may be the literal string "auto" — the server picks the slot from the channel's rhythm (gap, daily cap, day window, collision buffer, isolated per API key); and a chain array attaches post-publish steps: [{kind:"crossPost", afterMinutes:5, accountIds:[…]}] publishes the same content elsewhere 5 minutes after this post goes live, [{kind:"followUp", …, content, mediaItems}] publishes new content.
GET /v1/posts
List posts. Filter with ?status=scheduled&accountId=…&limit=200.
Response
{ "posts": [ { "_id": "0f7a…42", "status": "scheduled", … } ] } Useful for spacing your own queue: read what is already scheduled on an account before you pick the next slot.
GET /v1/posts/{id}
One post with its per-destination status.
Response
{
"post": {
"_id": "0f7a…42",
"status": "published",
"platforms": [
{ "platform": "instagram",
"status": "published",
"platformPostId": "17912…04",
"permalink": "https://www.instagram.com/p/…",
"publishedAt": "2026-08-24T19:40:12.000Z" }
]
}
} platformPostId is null until the post actually goes out. A comment automation needs it, so poll here before creating one.
POST /v1/comment-automations
Attach a keyword → DM rule to a published post.
Request
{
"accountId": "8b1e…7d",
"platformPostId": "17912…04",
"name": "Repo link — TR",
"keywords": ["repo", "link", "edit"],
"matchMode": "contains",
"typoTolerance": true,
"commentReply": "Sent it to your DMs 📩",
"dmMessage": "Here's the link. Follow for a new one daily.",
"buttons": [
{ "type": "url", "title": "Open repo",
"url": "https://github.com/…" }
],
"followGate": {
"message": "Follow first, then tap below 👇",
"buttonLabel": "I'm following"
}
} Response
201 { "ok": true } Instagram only, and the post must already be live. Calling it twice for the same post is not an error — it answers 200 with existing: true. Send followGate and the link is held behind a button: when it is tapped we read Meta's is_user_follow_business flag and only hand the link over to a follower. If Meta does not return that flag, the link is sent anyway — a real follower is never left waiting at the door.