API reference

Slide Your App REST API

The same 15 operations your agent gets over MCP, as plain HTTP: workspaces, Blitz cards, publishing, analytics and warmed devices.

Introduction

The API drives the same platform the web app does: it reads your workspaces and their marketing analysis, swipes Blitz cards, schedules posts to your connected and warmed accounts, and reads the numbers back.

Every endpoint here has a matching tool on the MCP server — same operations, same rules, same plan gates. Use REST from your own code, MCP from an agent.

Base URL
https://app.slideyourapp.com/api/v1

All requests and responses are JSON. Timestamps are epoch milliseconds, UTC. Publishing endpoints keep the app's paywall: without an active subscription they answer 402.

Authentication

Every request carries a personal API key as a bearer token. Mint the key in the app under Settings → API & MCP — one key per account, shown once at creation. Regenerating replaces the old key the same second; revoking kills it outright.

Example request
curl https://app.slideyourapp.com/api/v1/workspaces \
  -H "Authorization: Bearer nl_your_api_key"
🔑

Keys start with nl_ and are stored hashed — we cannot show you an existing key again, only replace it.

Errors

Errors use standard HTTP status codes and carry a detail string.

StatusWhen
401Missing or invalid bearer key. The response carries WWW-Authenticate: Bearer.
402subscription_required — the action needs an active subscription.
404No such workspace, account, card or post on this account.
409The state does not allow it — cancelling a post that is already publishing, or a card with no publishable files.
413The fetched avatar is larger than the limit.
422The request is malformed — the detail says exactly what to fix.
Error response
{
  "detail": "Pass exactly one of card_id or media_ids"
}

Workspaces

A workspace is one app or site you market. Everything else — cards, accounts, posts — is scoped by its workspace_id.

GET/workspaces Every workspace on the account.
200 — Response
[
  {
    "id": "wk_3f9c…",
    "name": "Nomad Budget",
    "kind": "app",
    "app_url": "https://apps.apple.com/app/id123456789",
    "status": "done",
    "created_at": 1756500000000
  }
]
FieldDescription
id stringThe workspace id used everywhere else.
name stringWorkspace name, or "New workspace" until it is named.
kind string"app" for an App Store listing, "website" for a site.
app_url stringThe listing or site the analysis was built from.
status stringState of the newest run in this workspace.
created_at integerEpoch ms, UTC.
GET/workspaces/{workspace_id} One workspace with its marketing analysis and chosen audience.

Returns the same fields as the list, plus:

FieldDescription
analysis objectThe marketing analysis — ICP segments, pains, angles.
icp_choice objectThe audience picked for this workspace.
blitz_cards arrayThe workspace's current card state.

Answers 404 if the workspace is not on your account.

GET/credits AI-credit balance and plan. Generation spends credits; top-ups happen in the web app.
200 — Response
{
  "mode": "credits",
  "plan": "growth",
  "subscribed": true,
  "balance": 384
}

mode is "credits" or "byok". On "byok" the response carries spent_usd_month instead of balance.

Social accounts

Your connected accounts. source says how each one publishes: "zernio" for an account you connected over OAuth, "minionix" for a warmed account living on one of our phones — the only kind that can carry a music track.

GET/accounts Every connected account, with the workspace it is offered from.
200 — Response
[
  {
    "id": "acc_82f1",
    "workspace_id": "wk_3f9c",
    "provider": "tiktok",
    "username": "nomadbudget",
    "display_name": "Nomad Budget",
    "avatar_url": "https://…/avatar.jpg",
    "status": "active",
    "source": "minionix",
    "created_at": 1756500000000
  }
]
FieldDescription
workspace_id string|nullThe workspace this account is offered from. null means it is not bound anywhere yet.
provider string"tiktok", "instagram" or "youtube".
status string"active", "needs_reconnect" or "disconnected".
source string"zernio" (OAuth) or "minionix" (warmed phone).
PATCH/accounts/{account_id} Point an account at a workspace, or unbind it.
BodyDescription
workspace_id string|nullThe workspace to bind to. null unbinds — the account is offered nowhere until pointed at a workspace again.
Request
curl -X PATCH https://app.slideyourapp.com/api/v1/accounts/acc_82f1 \
  -H "Authorization: Bearer nl_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"workspace_id": "wk_3f9c"}'

Already-scheduled posts keep their destinations — this only changes where the account is offered from now on.

Blitz cards

The workspace's deck of ready-made TikTok carousel remixes. Accept a card to keep it — accepted cards are what POST /posts publishes.

GET/workspaces/{workspace_id}/blitz/cards The deck to review plus the already-accepted shelf.
FieldDescription
cards object[]The deck still to review.
accepted object[]The shelf — cards you kept, ready to publish.
free object|nullnull when the plan has no daily limit, else { shown_today, limit }.
generating booleanNew cards are being rendered right now.
generation_state string"ok", "slow" or "stalled".

A card carries id, variant ("collage", "textwall", "greenscreen"), files (URL paths on the platform host), texts, why, rank, decision (null / "accept" / "skip"), created_at and the trend it was built from. These cards publish only to accounts bound to this workspace.

POST/blitz/cards/{card_id}/decision Swipe one card.
BodyDescription
action stringrequired"accept" keeps the card, "skip" discards it.

Accept → skip takes a card off the shelf; other re-decisions are rejected. Answers { "ok": true, "free": … }, where free is the same daily-limit object the card list returns.

Posts

POST/posts Schedule (or immediately publish) one post to one or more accounts. Returns 201.
⚠️

This publishes to real social accounts.

BodyDescription
card_id stringAn accepted Blitz card. Exactly one source per post — this or media_ids.
media_ids string[]Media Bank uploads in slide order, up to 35.
caption stringThe post caption.
scheduled_at integerEpoch ms, UTC. Required unless publish_now.
timezone stringIANA name, e.g. Europe/Belgrade. Defaults to UTC.
publish_now booleanPublish immediately instead of scheduling.
targets object[]requiredWhere to publish: account_id plus a settings object.
music objectA track for the post. Only warmed accounts (source: "minionix") can carry one — OAuth-connected accounts ignore it, because TikTok's API offers no way in.
Request body
{
  "card_id": "cd_71ab",
  "caption": "three ways to cut your subscriptions",
  "scheduled_at": 1756742400000,
  "timezone": "Europe/Belgrade",
  "targets": [
    {
      "account_id": "acc_82f1",
      "settings": {
        "privacy_level": "PUBLIC_TO_EVERYONE",
        "content_preview_confirmed": true,
        "express_consent_given": true
      }
    }
  ]
}

TikTok targets require those settings — a privacy level plus the two consent booleans.

The post object

201 — Response
{
  "id": "pt_5d20",
  "collage_id": "cd_71ab",
  "media_ids": null,
  "caption": "three ways to cut your subscriptions",
  "slide_urls": ["https://…/slide-1.jpg", "https://…/slide-2.jpg"],
  "poster_url": "https://…/poster.jpg",
  "status": "scheduled",
  "scheduled_at": 1756742400000,
  "timezone": "Europe/Belgrade",
  "published_at": null,
  "error": null,
  "publishing_state": "ok",
  "targets": [
    {
      "id": "tg_9a11",
      "account_id": "acc_82f1",
      "provider": "tiktok",
      "username": "nomadbudget",
      "status": "pending",
      "remote_post_id": null,
      "remote_url": null,
      "error": null,
      "published_at": null,
      "account_status": "active"
    }
  ]
}
FieldDescription
status string"scheduled", "publishing", "published", "partial", "failed" or "canceled".
publishing_state string"ok", "slow" or "stalled". When it is not "ok", publishing_minutes says for how long.
targets[].status string"pending", "published", "failed" or "canceled" — each target has its own fate.
targets[].remote_url string|nullThe live post on the network, once it lands.

Errors specific to this endpoint

StatusDetail
422Both or neither of card_id and media_ids were passed.
422scheduled_at missing without publish_now.
422A target account is bound to a different workspace than the card. The message names both — rebind the account with PATCH /accounts/:id or pick a card from the account's own workspace.
409The card has no publishable files.
GET/posts The posting queue.
QueryDescription
filter string"upcoming" (default) — scheduled and publishing. "past" — published, partial, failed and canceled.
offset integerDefaults to 0.
limit integerDefaults to 50, clamped to 1–200.
GET/posts/{post_id} One post with per-target delivery statuses.

Each target account has its own fate — one failing delivery does not drag the rest down.

POST/posts/{post_id}/cancel Cancel a scheduled post.

May answer 409 when a delivery is already publishing — warmed-phone drops lock in earlier than OAuth ones.

POST/posts/{post_id}/retry Re-send the failed targets of a failed or partial post.

Analytics

GET/analytics Account-level stats for your connected accounts.
200 — Response
[
  {
    "account_id": "acc_82f1",
    "provider": "tiktok",
    "kind": "warmed",
    "followers": 12840,
    "following": 312,
    "likes_total": 98120,
    "views_total": null,
    "video_count": 47,
    "profile_synced_at": 1756740000000,
    "history": [
      { "t": 1756656000000, "followers": 12610, "likes_total": 96400,
        "views_total": null, "video_count": 45 }
    ],
    "videos": [
      { "id": "7658442622586899742", "provider": "tiktok",
        "desc": "three ways to cut your subscriptions",
        "cover_url": "https://…/cover.jpg",
        "plays": 898500, "likes": 34500, "comments": 280, "shares": 1210,
        "posted_at": 1756569600000 }
    ]
  }
]
FieldDescription
kind string"warmed" for an account on our phones, "integration" for one you connected.
views_total integer|nullnull on TikTok — the network does not publish a profile total.
history object[]Daily series, oldest first.
videos object[]Known videos with their counters.

Returns [] when the account has no eligible connections yet.

Warmed devices

Real phones running your warmed TikTok accounts — the ones that show up as source: "minionix" in /accounts. This surface is read-and-setup only: buying, cancelling and renewing devices stay in the web app by design.

GET/warmed/devices The roster: every device's warm-up stage and readiness.
200 — Response
{
  "connected": true,
  "can_buy": true,
  "own_key": false,
  "warmup_days": 3,
  "billed": {
    "paid": 4, "free": 0, "price_usd": 80,
    "status": "active", "cancel_at_period_end": false,
    "renews_at": 1759190400000
  },
  "accounts": [
    {
      "id": "mn_4c81",
      "stage": "warming",
      "step": "warming",
      "display_name": "Nomad Budget",
      "tiktok_handle": "nomadbudget",
      "avatar_url": "https://…/avatar.jpg",
      "warmup_day": 2,
      "warmup_days": 3,
      "subscription_days_left": 27,
      "needs_renewal": false,
      "has_device": true,
      "avg_views": 3120,
      "cancelled": false
    }
  ],
  "manage_url": "https://app.slideyourapp.com/accounts",
  "setup_url": "https://app.slideyourapp.com/accounts/setup"
}
FieldDescription
connected booleanfalse when the device farm is not reachable — the roster then holds empty slots only.
billed objectThe device subscription: paid/free counts, price, status, renewal date.
accounts[].stage string"ready", "warming", or "setup" for an empty, paid slot waiting to be deployed.
accounts[].warmup_day integer|nullDay of the warm-up, out of warmup_days.
manage_url stringWhere a human buys, cancels and renews devices.
setup_url stringThe manual alternative to the endpoint below.

Requires an active subscription.

POST/warmed/deploy Set up one paid, not-yet-deployed device — rents a phone and creates the TikTok profile on it.
BodyDescription
display_name stringrequiredThe profile's display name.
avatar_url stringrequiredA direct http(s) image link — the platform downloads it server-side. There is no file upload on this surface.
hashtags stringrequired1–20 warm-up tags, space or comma separated.
tiktok_handle stringPreferred handle.
bio stringProfile bio.
website stringProfile link.
deploy_id stringIdempotency key. Empty → derived from the device's own row.
⏱

Answers { "ok": true }. That means accepted, not ready — the phone sets itself up over the next few minutes and then shows as warming in the roster.

← Back to Slide Your App