Developers
Public API
Upload media, compose posts and publish them from your own system. The surface is deliberately narrow: all of the "send a post" scenario and nothing beyond it. Every time is RFC 3339, every answer arrives in one envelope.
Base URL
https://api.akisla.com/v1Auth header
Authorization: Bearer smp_<prefix>_<secret>Authentication
Issue an API key from the dashboard and send it as a Bearer token. The secret is shown once and only its digest is stored, so a lost key cannot be recovered — it is replaced.
A key cannot reach past the person who issued it
A key belongs to the organization, carries an ordinary role and acts as its creator. You may restrict it to specific workspaces; with no restriction it reaches every workspace its creator does.
One door, one credential
/v1 accepts API keys only, never a dashboard session. A workspace outside the key's scope answers 404, not 403 — confirming that a record a credential cannot see exists is itself information.
Revoking takes effect immediately
The moment you revoke a key in the dashboard, its next request gets 401. To rotate, issue a new key, cut over, then revoke the old one.
Rate limits
The budget is per key, so one integration looping cannot starve the others. The bucket refills continuously — you may spend a whole minute's budget at once and then settle into the steady rate.
read
Read endpoints, per minute.
write
Write endpoints (media · posts · publishing), per minute. Publishing one post costs three requests.
Every response states the budget
HTTP/1.1 429 Too Many Requests
Retry-After: 12
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1785667890
{ "error": { "message": "rate limit exceeded", "code": "RATE_LIMITED" } }Errors
Every error arrives in the same envelope. The code is for machines and is stable: codes may be added, never renamed. The message is for people and may change — branch on the code, not the message.
Error envelope
{
"error": {
"message": "account_ids is required",
"code": "VALIDATION_ERROR"
}
}UNAUTHORIZED401The key is missing, malformed, expired or revoked.
FORBIDDEN403The key's role is not enough for this operation.
NOT_FOUND404No such record — or it is outside the key's scope.
INVALID_BODY400The body would not parse (malformed JSON, wrong type).
VALIDATION_ERROR400A field was rejected; the message names it.
BAD_REQUEST400The request is incomplete: no file on an upload, for instance.
EMPTY_POST400Neither text nor media; an empty post is not created.
MEDIA_NOT_FOUND400One of the media ids does not exist in this workspace.
ACCOUNT_NOT_FOUND400One of the account ids does not exist in this workspace or is not connected.
NO_TARGETS400No valid destination is left.
SCHEDULE_IN_PAST400scheduled_at is in the past.
UNSUPPORTED_TYPE415That file type is not supported.
NOT_PUBLISHABLE409The post cannot move to publishing from where it is (already published, for instance).
NO_QUEUE_SLOTS409The workspace has no queue slots defined.
RATE_LIMITED429The budget is spent; wait the Retry-After.
INTERNAL500Something failed on our side. Retry, and tell us if it persists.
Workspaces and accounts
Where everything starts: which customers the key reaches, and which connected accounts they hold.
/v1/workspacesreadThe workspaces the key can act in. A restricted key sees only its own list.
Request
curl -X GET "https://api.akisla.com/v1/workspaces" \
-H "Authorization: Bearer $SMP_API_KEY"Response
{
"data": [
{ "id": 24, "name": "Kahve Dünyası" }
],
"meta": { "has_more": false, "total": 1 }
}/v1/workspaces/{workspace_id}/accountsreadSocial accounts connected to the workspace. Tokens never appear here.
Request
curl -X GET "https://api.akisla.com/v1/workspaces/{workspace_id}/accounts" \
-H "Authorization: Bearer $SMP_API_KEY"Response
{
"data": [
{
"id": 41,
"platform": "instagram",
"handle": "kahvedunyasi",
"display_name": "Kahve Dünyası",
"status": "active"
}
],
"meta": { "has_more": false, "total": 1 }
}Media
Images and videos are uploaded and given an id before a post can carry them.
/v1/workspaces/{workspace_id}/mediawriteUploads a file into the workspace library. The returned url is a time-limited signature — do not store it.
Body fields
filerequiredThe file to upload (multipart field name file).
multipart/form-data
Request
curl -X POST "https://api.akisla.com/v1/workspaces/{workspace_id}/media" \
-H "Authorization: Bearer $SMP_API_KEY" \
-F "file=@post.jpg"Response
{
"data": {
"id": 318,
"file_name": "post.jpg",
"media_type": "image",
"mime_type": "image/jpeg",
"width": 1080,
"height": 1350,
"url": "https://…/blob/…?token=…"
}
}Uploads are capped at 25 MB per request. An image's dimensions are measured on the server; video dimensions and a cover frame are browser-side concerns, so a video uploaded through the API stays unmeasured — everything that reads those fields already handles that.
Posts
A post is composed as a draft first. Writing and sending are two decisions — they stay two for an integration too.
/v1/workspaces/{workspace_id}/postswriteCreates a draft. At least one of text or media is required.
Body fields
bodyThe post's text. Use overrides for per-platform wording.
string
media_idsMedia ids to attach, in the order they should appear.
int[]
Request
curl -X POST "https://api.akisla.com/v1/workspaces/{workspace_id}/posts" \
-H "Authorization: Bearer $SMP_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "body": "Yeni sezon başladı.", "media_ids": [318] }'Response
{
"data": {
"id": 62,
"workspace_id": 24,
"body": "Yeni sezon başladı.",
"status": "draft",
"created_at": "2026-08-02T14:05:00+03:00",
"targets": [],
"media_ids": [318]
}
}/v1/workspaces/{workspace_id}/postsreadLists posts under a filter. Platform and account match through the post's targets; a date range applies to the calendar instant, which therefore excludes drafts.
Query parameters
statusNarrows to one status. Empty means all of them.
string
platformMatches when one of the post's targets is on this platform.
string
account_idMatches when one of the post's targets is this account.
int
fromFrom this instant onwards (by calendar instant).
RFC 3339
toUp to this instant (by calendar instant).
RFC 3339
sortOrdering. An unknown value quietly falls back to the default.
string
limitHow many rows at most. Capped at 100; when it cuts, meta.has_more is true.
int
Request
curl -X GET "https://api.akisla.com/v1/workspaces/{workspace_id}/posts" \
-H "Authorization: Bearer $SMP_API_KEY"Response
{
"data": [ { "id": 62, "status": "scheduled", "…": "…" } ],
"meta": {
"has_more": false,
"total": 24,
"status_counts": { "draft": 9, "scheduled": 12, "published": 3 }
}
}/v1/posts/{post_id}readOne post with its targets. This is how you learn a publish's outcome: every target carries its own status and error message.
Request
curl -X GET "https://api.akisla.com/v1/posts/{post_id}" \
-H "Authorization: Bearer $SMP_API_KEY"Response
{
"data": {
"id": 62,
"status": "published",
"published_at": "2026-08-03T10:00:00+03:00",
"targets": [
{
"account_id": 41,
"platform": "instagram",
"handle": "kahvedunyasi",
"status": "published",
"format": "feed",
"provider_post_id": "17912…"
}
],
"media_ids": [318]
}
}values status accepts
- draft
- pending_approval
- approved
- scheduled
- publishing
- published
- partial_failed
- failed
values sort accepts
- created_desc
- created_asc
- date_desc
- date_asc
Publishing
The same payload on three endpoints: publish now, schedule for an instant, or drop it into the workspace's next free queue slot.
/v1/posts/{post_id}/publishwritePublishes the draft to the chosen accounts now. Publishing runs asynchronously: the answer is publishing, and the outcome is read back from the post.
Body fields
account_idsrequiredWhich accounts it goes to. At least one, at most twenty.
int[]
overridesText per platform. A platform not listed uses the shared text.
map<platform,string>
formatsFormat per platform (reels for instagram, for instance).
map<platform,string>
titlesTitle per platform — for the platforms that want one (YouTube).
map<platform,string>
Request
curl -X POST "https://api.akisla.com/v1/posts/{post_id}/publish" \
-H "Authorization: Bearer $SMP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"account_ids": [41],
"formats": { "instagram": "reels" },
"overrides": { "x": "Kısa hâli." }
}'Response
{
"data": { "id": 62, "status": "publishing", "…": "…" }
}/v1/posts/{post_id}/schedulewriteSchedules the draft for an instant. scheduled_at must be in the future.
Body fields
account_idsrequiredWhich accounts it goes to. At least one, at most twenty.
int[]
scheduled_atrequiredWhen it goes out, RFC 3339. Must be in the future.
RFC 3339
overridesText per platform. A platform not listed uses the shared text.
map<platform,string>
formatsFormat per platform (reels for instagram, for instance).
map<platform,string>
titlesTitle per platform — for the platforms that want one (YouTube).
map<platform,string>
Request
curl -X POST "https://api.akisla.com/v1/posts/{post_id}/schedule" \
-H "Authorization: Bearer $SMP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"account_ids": [41],
"scheduled_at": "2026-08-03T10:00:00+03:00"
}'Response
{
"data": {
"id": 62,
"status": "scheduled",
"scheduled_at": "2026-08-03T10:00:00+03:00"
}
}/v1/posts/{post_id}/queuewritePlaces the draft in the workspace's next free queue slot. Rejected if no slot is defined.
Body fields
account_idsrequiredWhich accounts it goes to. At least one, at most twenty.
int[]
overridesText per platform. A platform not listed uses the shared text.
map<platform,string>
formatsFormat per platform (reels for instagram, for instance).
map<platform,string>
titlesTitle per platform — for the platforms that want one (YouTube).
map<platform,string>
Request
curl -X POST "https://api.akisla.com/v1/posts/{post_id}/queue" \
-H "Authorization: Bearer $SMP_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "account_ids": [41] }'Response
{
"data": {
"id": 62,
"status": "scheduled",
"scheduled_at": "2026-08-10T10:00:00+03:00"
}
}One example, end to end
Upload an image, schedule it for tomorrow morning and read back the outcome — four requests.
bash
# 1. Upload the asset
MEDIA_ID=$(curl -s -X POST "https://api.akisla.com/v1/workspaces/24/media" \
-H "Authorization: Bearer $SMP_API_KEY" \
-F "file=@post.jpg" | jq .data.id)
# 2. Compose the draft
POST_ID=$(curl -s -X POST "https://api.akisla.com/v1/workspaces/24/posts" \
-H "Authorization: Bearer $SMP_API_KEY" \
-H "Content-Type: application/json" \
-d "{\"body\":\"Yeni sezon başladı.\",\"media_ids\":[$MEDIA_ID]}" | jq .data.id)
# 3. Schedule it — composing and sending stay two decisions
curl -X POST "https://api.akisla.com/v1/posts/$POST_ID/schedule" \
-H "Authorization: Bearer $SMP_API_KEY" \
-H "Content-Type: application/json" \
-d '{"account_ids":[41],"scheduled_at":"2026-08-03T10:00:00+03:00"}'
# 4. Ask how it went
curl "https://api.akisla.com/v1/posts/$POST_ID" \
-H "Authorization: Bearer $SMP_API_KEY"