REST API
Push videos and photo carousels from your own scripts. The API works on the same accounts and posts as the app.
Quick start
- In the app, open Settings → API keys and create a key. It's shown once, so copy it then.
- Upload your media with
POST /v1/media. - Create the post with
POST /v1/posts. - Open the post in the app, check its settings and confirm it. See Needs review.
# 1. upload a slide (repeat per file)
curl https://scheduler.rewirelabs.app/v1/media \
-H "Authorization: Bearer rs_…" \
-F file=@slide-01.jpg
# 2. create the post
curl https://scheduler.rewirelabs.app/v1/posts \
-H "Authorization: Bearer rs_…" \
-H "Content-Type: application/json" \
-d '{"account_id": "…", "kind": "photo", "media_ids": ["…", "…"],
"title": "weird food rules that stuck", "scheduled_at": "2026-11-22T12:50:00Z",
"ref": "carousel/02"}'
Base URL and authentication
All endpoints live under:
https://scheduler.rewirelabs.app/v1
Send your API key as a bearer token on every request:
Authorization: Bearer rs_…
- Create and revoke keys in the app under Settings → API keys. A new key is shown once; we store only a hash of it.
- You can have up to 10 active keys. Keys can only be created and revoked in the app, not through the API.
- A wrong or revoked key gets
401with{"error": "invalid_api_key"}. A request without a key gets401with{"error": "unauthorized"}.
Needs review
TikTok requires the creator to confirm each post's settings before it is published. So posts for TikTok accounts that you create with an API key land in "Needs review", and they are only published after you open them in the app, check the settings and confirm.
The API can't confirm a TikTok post for you. That's why action in POST /v1/posts has no effect on TikTok posts created with an API key.
Rate limits
- 120 requests a minute per user. All your API keys and your app session share this limit.
- Over the limit you get
429with{"error": "rate_limited", "message": "Slow down: 120 requests a minute."}. Wait for the minute to pass and try again.
Errors
Errors use HTTP status codes and a JSON body:
{ "error": "post_not_found", "message": "Post not found." }
error is a stable code you can branch on; message is a human-readable explanation (authentication errors return only error). Common codes:
| Status | error |
When |
|---|---|---|
| 400 | invalid_input |
A field is missing or invalid. message names the field. |
| 400 | no_file, bad_media |
The upload has no file field, or the file isn't supported. |
| 400 | media_not_found, duplicate_media |
A media id doesn't exist in your account, or appears twice. |
| 400 | caption_too_long, bad_title |
Video caption over 2,200 characters, or a title on a video post. |
| 401 | invalid_api_key, unauthorized |
The key is wrong or revoked, or no key was sent. |
| 404 | post_not_found, account_not_found |
No such post or account in your account. |
| 409 | not_editable, not_cancelable, cancel_first |
The post's status doesn't allow this change. |
| 429 | rate_limited |
Over 120 requests a minute. |
| 500 | server_error |
Something went wrong on our side. |
List accounts
GET /v1/accounts returns the TikTok accounts you've connected. Use an account's id as account_id when you create a post.
curl https://scheduler.rewirelabs.app/v1/accounts \
-H "Authorization: Bearer rs_…"
{
"accounts": [
{
"id": "…",
"adapter": "tiktok",
"external_id": "…",
"display_name": "…",
"avatar_url": "/v1/accounts/…/avatar",
"status": "…",
"created_at": 1794182400000
}
]
}
created_at is in milliseconds since the Unix epoch. avatar_url points to the copy of the avatar saved on our server; fetch it with the same Authorization header.
Upload media
POST /v1/media uploads one file as multipart/form-data, in a field named file. Upload each slide of a carousel separately and keep the ids in order.
curl https://scheduler.rewirelabs.app/v1/media \
-H "Authorization: Bearer rs_…" \
-F file=@clip.mp4
The file type is detected from the file's contents, not its name.
| Type | Accepted | Rules |
|---|---|---|
| Images | JPG, PNG, WebP, up to 60 MB | Converted to JPEG and scaled down (never up) to fit 1080×1920 portrait or 1920×1080 landscape. At least 360 px on each side after resizing. |
| Videos | MP4, MOV, WebM, up to 250 MB | H.264, H.265, VP8 or VP9. At least 3 seconds, 23 to 60 fps, at least 360 px on each side. |
Response (201):
{
"id": "…",
"kind": "image",
"mime": "image/jpeg",
"width": 1080,
"height": 1350,
"duration_ms": null,
"bytes": 245731,
"url": "https://scheduler.rewirelabs.app/m/…/1.jpg"
}
url is a long, unguessable link that TikTok uses to fetch the file when it publishes.
Create a post
POST /v1/posts with a JSON body:
| Field | Type | Notes |
|---|---|---|
account_id |
string, required | From GET /v1/accounts. |
kind |
"video" or "photo", required |
|
media_ids |
array of strings, required | One video for video; 1 to 35 images, in order, for photo. |
title |
string, up to 90 | Photo posts only. |
caption |
string, up to 4,000 | Video captions are limited to 2,200. Defaults to empty. |
scheduled_at |
ISO 8601 date-time with offset | For example 2026-11-22T12:50:00Z. |
tz |
IANA time zone | For example America/New_York. Defaults to your account's time zone. |
series |
string, up to 40 | Your own grouping label. |
ref |
string, up to 120 | Your own reference, for example carousel/02. Filter by it with ?ref=. |
options |
object | TikTok settings, below. |
action |
"draft" or "schedule" |
Defaults to "draft". Has no effect on TikTok posts created with an API key: they always start in Needs review. |
options (all optional; unknown fields are rejected). They are saved with the post, and you check them in the app before you confirm it:
| Field | Type | Notes |
|---|---|---|
privacy_level |
string | One of TikTok's privacy levels, for example "SELF_ONLY". |
allow_comment, allow_duet, allow_stitch |
boolean | Interactions, as far as the account allows them. |
auto_add_music |
boolean | Photo posts: TikTok adds a recommended sound. |
cover_index |
integer, 0 to 34 | Photo posts: which image is the cover. |
cover_ms |
integer | Video posts: the cover frame, in milliseconds from the start. |
brand_organic |
boolean | Commercial content disclosure: "Your brand". |
brand_content |
boolean | Commercial content disclosure: "Branded content" (a paid partnership). |
ai_generated |
boolean | The AI-generated content label. |
curl https://scheduler.rewirelabs.app/v1/posts \
-H "Authorization: Bearer rs_…" \
-H "Content-Type: application/json" \
-d '{
"account_id": "…",
"kind": "photo",
"media_ids": ["…", "…", "…"],
"title": "weird food rules that stuck",
"caption": "Five rules I actually kept.",
"scheduled_at": "2026-11-22T12:50:00Z",
"ref": "carousel/02",
"options": { "auto_add_music": true, "brand_organic": true }
}'
The response (201) is the post:
{
"id": "…",
"account_id": "…",
"kind": "photo",
"route": "tiktok_direct",
"status": "needs_review",
"source": "api",
"title": "weird food rules that stuck",
"caption": "Five rules I actually kept.",
"series": null,
"ref": "carousel/02",
"options": { "auto_add_music": true, "brand_organic": true },
"scheduled_at": "2026-11-22T12:50:00.000Z",
"tz": "America/New_York",
"published_at": null,
"confirmed_at": null,
"external_id": null,
"external_url": null,
"error": null,
"media": [{ "id": "…", "kind": "image", "url": "…", "thumbnail": "…", "width": 1080, "height": 1350, "duration_ms": null }],
"media_source": "local",
"created_at": "…",
"updated_at": "…"
}
status is one of draft, needs_review, scheduled, publishing, processing, published, failed or canceled. confirmed_at is set when you confirm the post in the app. If a post fails, error says why.
List posts
GET /v1/posts returns your posts, oldest scheduled time first, as {"posts": [ … ]}. Query parameters, all optional:
| Parameter | Notes |
|---|---|
from, to |
ISO dates or date-times. Posts scheduled from from and before to. Posts without a time are always included. |
status |
One or more statuses, comma-separated, for example needs_review,scheduled. |
account_id |
Only posts for this account. |
ref |
Only posts with exactly this reference. |
series |
Only posts in this series. |
limit |
1 to 1,000. Defaults to 500. |
curl "https://scheduler.rewirelabs.app/v1/posts?ref=carousel/02" \
-H "Authorization: Bearer rs_…"
Get a post
GET /v1/posts/:id returns one post.
curl https://scheduler.rewirelabs.app/v1/posts/… \
-H "Authorization: Bearer rs_…"
Update a post
PATCH /v1/posts/:id changes any of media_ids, title, caption, scheduled_at, tz, series, ref and options. Send only what changes; options is merged into the existing options. account_id and kind can't be changed.
Only posts that are draft, needs_review, scheduled, failed or canceled can be edited. Others return 409 not_editable.
Editing a scheduled TikTok post with an API key sends it back to Needs review, so you confirm the changed post in the app again.
curl -X PATCH https://scheduler.rewirelabs.app/v1/posts/… \
-H "Authorization: Bearer rs_…" \
-H "Content-Type: application/json" \
-d '{"scheduled_at": "2026-11-23T17:00:00Z", "options": {"auto_add_music": false}}'
Cancel a post
POST /v1/posts/:id/cancel unschedules a post and sets its status to canceled. Works for posts that are scheduled, needs_review, draft or failed; others return 409 not_cancelable. Returns the post.
curl -X POST https://scheduler.rewirelabs.app/v1/posts/…/cancel \
-H "Authorization: Bearer rs_…"
Delete a post
DELETE /v1/posts/:id deletes the post from Rewire Labs Scheduler and returns {"ok": true}. Cancel a scheduled post first (otherwise 409 cancel_first); a post that is being published can't be deleted until it finishes. Deleting here doesn't remove a post that is already on TikTok.
curl -X DELETE https://scheduler.rewirelabs.app/v1/posts/… \
-H "Authorization: Bearer rs_…"
More
Questions? Email connect@rewirelabs.app.