API documentation

Schedule Instagram and TikTok videos and carousels from your own code. The API is included in the Pro and Business plans; every request uses your account's API key and the same monthly quota as the dashboard.

Authentication

API access is available on the Pro and Business plans. Create your key in the dashboard under API. Each account has one key: generating a new one revokes the previous key immediately. Send it as a Bearer token over HTTPS:

Authorization: Bearer pl_live_…

Base URL: https://planudo.com/api/v1. Requests are limited to 120 per minute per key.

Quota

Each post you create (with the API or the dashboard) uses one scheduled post, however many accounts it targets. Quotas reset on the 1st of every month (UTC). Canceling a post before it is published gives it back.

PlanPosts / monthConnected accountsAPI
Free51No
Pro305Yes
Business6010Yes

GET/me

{ "email": "you@example.com",
  "quota": { "plan": "pro", "limit": 30, "used": 4, "remaining": 26, "accountsLimit": 5, "resetsAt": "2026-12-01T00:00:00.000Z" } }

Connected accounts

Accounts are connected in the dashboard with the official Instagram and TikTok logins. Use their id when you create a post.

GET/accounts

{ "accounts": [
  { "id": "ig_7f3a91bc0d2e4a55", "network": "instagram", "username": "fruitninja.daily", "avatar": "https://…", "lastError": null },
  { "id": "tt_91bc44e0a7d2c310", "network": "tiktok", "username": "Fruit Ninja Daily", "directPost": false, "lastError": null } ] }

Create a post

POST/posts

Send a multipart/form-data body:

FieldDescription
accountsRequired. JSON array of account ids.
videoA video file (MP4, MOV or WebM, 3 s to 10 min, 500 MB max). Published as a Reel on Instagram. Send either video or images.
imagesFor a carousel: one file per slide, in order (JPEG, PNG or WebP, 30 MB max each). Instagram: 10 max, TikTok: 35 max.
captionFull caption, used as-is on Instagram and as the TikTok description. Either caption or title is required.
title, description, hashtagsAlternative to caption: Planudo builds the caption from these. hashtags is a JSON array or a space-separated string (30 max). The TikTok title is limited to 90 characters.
scheduledAtISO 8601 date. Empty or missing: publish now. Up to one year ahead.
instagramCarousels only, optional: a different set of slides for Instagram (for example 4:5 versions).
tiktokOptional JSON object, see TikTok options.
refOptional. Your own identifier (200 characters max), to find the post back with GET /posts?ref=….
linkOptional. A URL stored with the post (not published).
curl https://planudo.com/api/v1/posts \
  -H "Authorization: Bearer $PLANUDO_KEY" \
  -F caption="Fruit Ninja – Season 1, episode 1 🍉 #fruitninja" \
  -F accounts='["ig_7f3a91bc0d2e4a55","tt_91bc44e0a7d2c310"]' \
  -F scheduledAt=2026-12-01T18:00:00Z \
  -F tiktok='{"mode":"inbox"}' \
  -F video=@episode-1.mp4

Response 201 with the post. Videos that are not already MP4 H.264/AAC (or larger than 1080 × 1920) are converted. Carousel images are resized for each network (Instagram: between 4:5 and 1.91:1, 1440 px wide max; TikTok: 1080 × 1920 max, no cropping).

Read posts

GET/posts?status=scheduled&ref=…&limit=50

GET/posts/:id

{ "id": "6745…", "status": "partial", "scheduledAt": "2026-12-01T18:00:00.000Z", "source": "api", "ref": "season1-ep1",
  "targets": [
    { "network": "instagram", "username": "fruitninja.daily", "status": "published", "url": "https://www.instagram.com/p/…", "error": null },
    { "network": "tiktok", "username": "Fruit Ninja Daily", "status": "failed", "error": "…" } ] }

Post status: scheduled, publishing, done, partial (some accounts failed), failed, canceled. Target status: pending, publishing, published, inbox (sent to the TikTok inbox as a draft), failed, canceled. Filter status also accepts upcoming (scheduled or publishing). Results are sorted by date, newest first; paginate with before (an ISO date).

Cancel and retry

POST/posts/:id/cancel

Only while the post is still scheduled. The scheduled post goes back to your quota.

POST/posts/:id/retry

Publishes again on the accounts that failed only: an account that already received the post never gets it twice. Retrying does not use your quota.

TikTok options

By default ({"mode":"inbox"}) the video or carousel is sent to the creator's TikTok inbox as a draft, to finish in the TikTok app (TikTok does not let apps pre-fill the caption of a video draft). When direct posting is available for an account (directPost: true), you can publish directly. TikTok then requires the creator to choose these settings themselves:

tiktok={"mode":"direct","privacy":"PUBLIC_TO_EVERYONE","allowComments":true,"brandContent":false,"brandOrganic":false,
        "allowDuet":true,"allowStitch":true,"aiGenerated":true}

privacy must be one of the options returned for this creator by GET /accounts/:id/tiktok-creator, which also gives the longest video they can post (maxVideoSec). Branded content cannot be private. allowDuet, allowStitch and aiGenerated (label the video as AI-generated) apply to videos; autoAddMusic applies to carousels.

Errors

Errors return JSON: { "error": "message" }.

400Invalid request (missing field, bad date, too many images…)
401Missing or invalid API key
403Your plan does not include API access (Free plan)
402Monthly quota reached: upgrade your plan or wait for the 1st of next month
404Post or account not found
429Too many requests

Node.js example

import fs from 'node:fs/promises';

const form = new FormData();
form.append('caption', 'AI video · Episode 4 ✨');
form.append('accounts', JSON.stringify(['ig_7f3a91bc0d2e4a55']));
form.append('scheduledAt', new Date(Date.now() + 3600e3).toISOString());
form.append('video', new Blob([await fs.readFile('episode-4.mp4')], { type: 'video/mp4' }), 'episode-4.mp4');
const res = await fetch('https://planudo.com/api/v1/posts', {
  method: 'POST', headers: { Authorization: `Bearer ${process.env.PLANUDO_KEY}` }, body: form,
});
console.log(res.status, await res.json());