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/v1

Auth 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.

My API keys

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.

    300

    read

    Read endpoints, per minute.

    60

    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"
  }
}
  • UNAUTHORIZED401

    The key is missing, malformed, expired or revoked.

  • FORBIDDEN403

    The key's role is not enough for this operation.

  • NOT_FOUND404

    No such record — or it is outside the key's scope.

  • INVALID_BODY400

    The body would not parse (malformed JSON, wrong type).

  • VALIDATION_ERROR400

    A field was rejected; the message names it.

  • BAD_REQUEST400

    The request is incomplete: no file on an upload, for instance.

  • EMPTY_POST400

    Neither text nor media; an empty post is not created.

  • MEDIA_NOT_FOUND400

    One of the media ids does not exist in this workspace.

  • ACCOUNT_NOT_FOUND400

    One of the account ids does not exist in this workspace or is not connected.

  • NO_TARGETS400

    No valid destination is left.

  • SCHEDULE_IN_PAST400

    scheduled_at is in the past.

  • UNSUPPORTED_TYPE415

    That file type is not supported.

  • NOT_PUBLISHABLE409

    The post cannot move to publishing from where it is (already published, for instance).

  • NO_QUEUE_SLOTS409

    The workspace has no queue slots defined.

  • RATE_LIMITED429

    The budget is spent; wait the Retry-After.

  • INTERNAL500

    Something 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.

GET/v1/workspacesread

The 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 }
}
GET/v1/workspaces/{workspace_id}/accountsread

Social 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.

POST/v1/workspaces/{workspace_id}/mediawrite

Uploads a file into the workspace library. The returned url is a time-limited signature — do not store it.

Body fields

  • filerequired

    The 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.

POST/v1/workspaces/{workspace_id}/postswrite

Creates a draft. At least one of text or media is required.

Body fields

  • body

    The post's text. Use overrides for per-platform wording.

    string

  • media_ids

    Media 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]
  }
}
GET/v1/workspaces/{workspace_id}/postsread

Lists 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

  • status

    Narrows to one status. Empty means all of them.

    string

  • platform

    Matches when one of the post's targets is on this platform.

    string

  • account_id

    Matches when one of the post's targets is this account.

    int

  • from

    From this instant onwards (by calendar instant).

    RFC 3339

  • to

    Up to this instant (by calendar instant).

    RFC 3339

  • sort

    Ordering. An unknown value quietly falls back to the default.

    string

  • limit

    How 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 }
  }
}
GET/v1/posts/{post_id}read

One 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.

POST/v1/posts/{post_id}/publishwrite

Publishes 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_idsrequired

    Which accounts it goes to. At least one, at most twenty.

    int[]

  • overrides

    Text per platform. A platform not listed uses the shared text.

    map<platform,string>

  • formats

    Format per platform (reels for instagram, for instance).

    map<platform,string>

  • titles

    Title 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", "…": "…" }
}
POST/v1/posts/{post_id}/schedulewrite

Schedules the draft for an instant. scheduled_at must be in the future.

Body fields

  • account_idsrequired

    Which accounts it goes to. At least one, at most twenty.

    int[]

  • scheduled_atrequired

    When it goes out, RFC 3339. Must be in the future.

    RFC 3339

  • overrides

    Text per platform. A platform not listed uses the shared text.

    map<platform,string>

  • formats

    Format per platform (reels for instagram, for instance).

    map<platform,string>

  • titles

    Title 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"
  }
}
POST/v1/posts/{post_id}/queuewrite

Places the draft in the workspace's next free queue slot. Rejected if no slot is defined.

Body fields

  • account_idsrequired

    Which accounts it goes to. At least one, at most twenty.

    int[]

  • overrides

    Text per platform. A platform not listed uses the shared text.

    map<platform,string>

  • formats

    Format per platform (reels for instagram, for instance).

    map<platform,string>

  • titles

    Title 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"
Social Media Management Platform