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.
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.
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.
| Status | When |
|---|---|
| 401 | Missing or invalid bearer key. The response carries WWW-Authenticate: Bearer. |
| 402 | subscription_required — the action needs an active subscription. |
| 404 | No such workspace, account, card or post on this account. |
| 409 | The state does not allow it — cancelling a post that is already publishing, or a card with no publishable files. |
| 413 | The fetched avatar is larger than the limit. |
| 422 | The request is malformed — the detail says exactly what to fix. |
{
"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.
[
{
"id": "wk_3f9c…",
"name": "Nomad Budget",
"kind": "app",
"app_url": "https://apps.apple.com/app/id123456789",
"status": "done",
"created_at": 1756500000000
}
]
| Field | Description |
|---|---|
| id string | The workspace id used everywhere else. |
| name string | Workspace name, or "New workspace" until it is named. |
| kind string | "app" for an App Store listing, "website" for a site. |
| app_url string | The listing or site the analysis was built from. |
| status string | State of the newest run in this workspace. |
| created_at integer | Epoch ms, UTC. |
Returns the same fields as the list, plus:
| Field | Description |
|---|---|
| analysis object | The marketing analysis — ICP segments, pains, angles. |
| icp_choice object | The audience picked for this workspace. |
| blitz_cards array | The workspace's current card state. |
Answers 404 if the workspace is not on your account.
{
"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.
[
{
"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
}
]
| Field | Description |
|---|---|
| workspace_id string|null | The 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). |
| Body | Description |
|---|---|
| workspace_id string|null | The workspace to bind to. null unbinds — the account is offered nowhere until pointed at a workspace again. |
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.
| Field | Description |
|---|---|
| cards object[] | The deck still to review. |
| accepted object[] | The shelf — cards you kept, ready to publish. |
| free object|null | null when the plan has no daily limit, else { shown_today, limit }. |
| generating boolean | New 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.
| Body | Description |
|---|---|
| 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
This publishes to real social accounts.
| Body | Description |
|---|---|
| card_id string | An 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 string | The post caption. |
| scheduled_at integer | Epoch ms, UTC. Required unless publish_now. |
| timezone string | IANA name, e.g. Europe/Belgrade. Defaults to UTC. |
| publish_now boolean | Publish immediately instead of scheduling. |
| targets object[]required | Where to publish: account_id plus a settings object. |
| music object | A 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. |
{
"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
{
"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"
}
]
}
| Field | Description |
|---|---|
| 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|null | The live post on the network, once it lands. |
Errors specific to this endpoint
| Status | Detail |
|---|---|
| 422 | Both or neither of card_id and media_ids were passed. |
| 422 | scheduled_at missing without publish_now. |
| 422 | A 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. |
| 409 | The card has no publishable files. |
| Query | Description |
|---|---|
| filter string | "upcoming" (default) — scheduled and publishing. "past" — published, partial, failed and canceled. |
| offset integer | Defaults to 0. |
| limit integer | Defaults to 50, clamped to 1–200. |
Each target account has its own fate — one failing delivery does not drag the rest down.
May answer 409 when a delivery is already publishing — warmed-phone drops lock in earlier than OAuth ones.
Analytics
[
{
"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 }
]
}
]
| Field | Description |
|---|---|
| kind string | "warmed" for an account on our phones, "integration" for one you connected. |
| views_total integer|null | null 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.
{
"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"
}
| Field | Description |
|---|---|
| connected boolean | false when the device farm is not reachable — the roster then holds empty slots only. |
| billed object | The 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|null | Day of the warm-up, out of warmup_days. |
| manage_url string | Where a human buys, cancels and renews devices. |
| setup_url string | The manual alternative to the endpoint below. |
Requires an active subscription.
| Body | Description |
|---|---|
| display_name stringrequired | The profile's display name. |
| avatar_url stringrequired | A direct http(s) image link — the platform downloads it server-side. There is no file upload on this surface. |
| hashtags stringrequired | 1–20 warm-up tags, space or comma separated. |
| tiktok_handle string | Preferred handle. |
| bio string | Profile bio. |
| website string | Profile link. |
| deploy_id string | Idempotency 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.