Zaarm TechZaarm Tech
Scheduler by ZaarmTech

Scheduler API

Schedule posts to a business's Facebook Pages, Instagram accounts and YouTube channel from your own website, shop or automation. One key per business, plain JSON over HTTPS.

https://zaarmtech.com/api/v1

Quick start

  1. In Scheduler, switch to the business you want to post for and open Accounts.
  2. Under API keys, create a key and copy it. It's shown once; we only keep a fingerprint of it.
  3. Schedule a text post to every account in that business:
Shell
curl -X POST https://zaarmtech.com/api/v1/posts \
  -H "Authorization: Bearer $ZAARM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"caption": "Fresh sourdough, out at 7 every morning", "scheduled_at": "2026-11-20T07:00:00Z"}'

That's it. The post shows up in the business's Queue in Scheduler with an API badge, and you can cancel it there or through the API. Posts with a photo or video take one extra call to upload the file first.

Authentication

Send your key in the Authorization header of every request. Keys start with zts_.

Authorization: Bearer zts_Xb3…

A key belongs to one business and can only see and post to that business's accounts. Create a separate key for each tool that uses the API, so you can revoke one without breaking the others. Revoking a key stops it working on the very next request; posts it already scheduled stay scheduled.

Requests and limits

  • Base URL: https://zaarmtech.com/api/v1. HTTPS only.
  • Requests and responses are JSON (Content-Type: application/json), except media uploads, which send the raw file.
  • Times are ISO 8601 in UTC, e.g. 2026-11-20T07:00:00Z. Ids are opaque strings.
  • Rate limits: each business can make 120 requests and 30 uploads an hour. The budget is shared by all its keys and by people using the Scheduler app for that business (an upload counts as a request too; in the app, each file uploaded and each Schedule press count). Your business's own limit is shown under Accounts → API keys. Over the limit you get 429 with a Retry-After header in seconds. Need more? Email admin@zaarmtech.com.
  • The API is meant for servers. Browsers on other sites can't call it, so your key never has to be in a web page.

List accounts

GET/api/v1/accounts

The accounts this key's business can post to right now. Use their ids to post to only some of them.

Response 200
{
  "accounts": [
    { "id": "cm3k1…", "platform": "facebook",  "name": "Corner Bakery",  "username": null },
    { "id": "cm3k2…", "platform": "instagram", "name": "cornerbakery",   "username": "cornerbakery" },
    { "id": "cm3k3…", "platform": "youtube",   "name": "Corner Bakery",  "username": null }
  ]
}

Accounts whose login needs reconnecting aren't listed until you reconnect them in Scheduler.

Upload media

POST/api/v1/media

Upload one photo or video as the raw request body (not a multipart form). The file type is read from the file itself, so the Content-Type header doesn't matter. Use the returned id in media_ids when you schedule a post.

TypeAcceptedMax size
PhotoJPG, PNG, WebP25 MB
VideoMP4, MOV (vertical 9:16 recommended for Reels and Shorts)300 MB
Shell
curl -X POST https://zaarmtech.com/api/v1/media \
  -H "Authorization: Bearer $ZAARM_API_KEY" \
  --data-binary @loaf.jpg
Response 201
{
  "id": "4f9c2a7e1b3d4c5e6f7a8b9c0d1e2f3a.jpg",
  "type": "image",
  "mime_type": "image/jpeg",
  "size_bytes": 482113,
  "expires_at": "2026-11-20T07:00:00.000Z"
}

An upload you don't use in a post is deleted after 7 days. Uploads belong to the business whose key uploaded them.

Schedule a post

POST/api/v1/posts

Schedules one post. It goes to every account you list, or, if you leave accounts out, to every account in the business that can take it. Each account gets its own post, so one failing never blocks the others.

FieldDescription
captionText of the post, up to 5,000 characters. For YouTube, the first line becomes the title (with #Shorts added).
media_idsOptional. Ids from POST /media: up to 10 photos, or exactly 1 video.
accountsOptional. Account ids from GET /accounts. Leave out to post to all suitable accounts.
scheduled_atOptional. When to publish, 10 minutes to 30 days from now (29 days for video). Defaults to 15 minutes from now.

A post needs a caption, media, or both.

Request body
{
  "caption": "Dumbbell curl: 3 mistakes to fix\nPin your elbows. Form first. #gym",
  "media_ids": ["9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d.mp4"],
  "accounts": ["cm3k1…", "cm3k3…"],
  "scheduled_at": "2026-11-20T09:00:00Z"
}
Response 201
{
  "posts": [
    {
      "id": "cm3p9…",
      "account": { "id": "cm3k1…", "platform": "facebook", "name": "Corner Bakery" },
      "caption": "Dumbbell curl: 3 mistakes to fix\nPin your elbows. Form first. #gym",
      "scheduled_at": "2026-11-20T09:00:00.000Z",
      "status": "scheduled",
      "error": null,
      "permalink": null,
      "published_at": null,
      "created_at": "2026-11-13T08:12:40.000Z"
    },
    {
      "id": "cm3pa…",
      "account": { "id": "cm3k3…", "platform": "youtube", "name": "Corner Bakery" },
      "status": "scheduled",
      "permalink": "https://youtube.com/shorts/abc123",
      "…": "…"
    }
  ]
}

Check each post's status. A 201 means the request was valid and every post was created, but a platform can still reject one, for example a photo it finds too small. That post comes back as failed with the platform's reason in error, and the rest are still scheduled. Videos are uploaded to Facebook and YouTube during the request, so allow up to a few minutes before timing out.

List posts

GET/api/v1/posts?limit=50

The business's posts, latest scheduled time first, whether they came from the API or the app. limit is 1–100 (default 50).

Response 200
{ "posts": [ { "id": "cm3p9…", "status": "published", "permalink": "https://www.facebook.com/…", "…": "…" } ] }

Get a post

GET/api/v1/posts/{id}

One post, in the same shape as above. Poll this to see when it's published and get its link.

Cancel a post

DELETE/api/v1/posts/{id}

Cancels a post that hasn't gone out yet, and removes it from Facebook's or YouTube's own queue too.

Response 200
{ "deleted": true }

You get 409 if it's already published or being published at that moment, and 502 if the platform wouldn't drop it (the post then stays in the Queue with the reason, so nothing goes out unnoticed).

Platform rules

PlatformTakesHow it's published
facebookText, up to 10 photos, or 1 video (a Reel)Handed to Facebook's own scheduler right away.
instagramUp to 10 photos (a carousel) or 1 video (a Reel). Not text alone.Published by Scheduler at the scheduled minute.
youtube1 video only (a Short)Uploaded as private right away; YouTube makes it public at the scheduled time.

TikTok accounts can be scheduled in the Scheduler app but not through the API: TikTok requires a person to choose each post's privacy and interaction settings on screen. They don't appear in GET /accounts.

When you leave out accounts, accounts that can't take the post are skipped: a photo post skips YouTube, and a text post skips Instagram and YouTube. When you name an account that can't take it, the request fails with unsupported_for_account and nothing is scheduled.

Post statuses

statusMeaning
scheduledHeld by Facebook or YouTube; it will publish at scheduled_at.
queuedInstagram: waiting for its time.
scheduling, claimed, publishingBeing handed over or published right now.
publishedLive. permalink links to it where the platform gives one.
failedThe platform rejected it; error says why.
missedIts time passed while publishing was unavailable, so it wasn't posted.
needs_reviewWe couldn't confirm whether it went out. Check the account before retrying.

The business owner also gets an email when a post ends up failed, missed or needs_review, or when a login needs reconnecting, so problems are noticed even if nobody is polling.

Errors

Every error has the same shape, with a stable code to check and a message for people.

{ "error": { "code": "invalid_time", "message": "scheduled_at must be between 10 minutes and 30 days from now." } }
HTTPcodeWhen
400invalid_json, empty_bodyThe body is missing or isn't a JSON object.
401unauthorizedNo key, a malformed key, or a revoked key.
404not_found, media_not_found, account_not_foundThe id doesn't exist in this business.
409already_published, publishingToo late to cancel.
413file_too_largePhoto over 25 MB or video over 300 MB.
415unsupported_typeNot a JPG, PNG, WebP, MP4 or MOV file.
422invalid_caption, invalid_media, empty_post, invalid_time, invalid_accounts, no_accounts, unsupported_for_accountThe post can't be scheduled as sent; nothing was created.
429rate_limitedThe business used its hourly requests or uploads; wait for Retry-After seconds.
502cancel_failedFacebook or YouTube refused to drop the post.

Full examples

Upload a video, then schedule it to every account in the business:

Node.js 18+
const BASE = "https://zaarmtech.com/api/v1";
const headers = { Authorization: `Bearer ${process.env.ZAARM_API_KEY}` };

const file = await fs.promises.readFile("curl.mp4");
const media = await (await fetch(`${BASE}/media`, { method: "POST", headers, body: file })).json();

const res = await fetch(`${BASE}/posts`, {
  method: "POST",
  headers: { ...headers, "Content-Type": "application/json" },
  body: JSON.stringify({
    caption: "Dumbbell curl: 3 mistakes to fix",
    media_ids: [media.id],
    scheduled_at: "2026-11-20T09:00:00Z",
  }),
});
const { posts, error } = await res.json();
if (error) throw new Error(`${error.code}: ${error.message}`);
for (const p of posts) console.log(p.account.platform, p.status, p.error ?? "");
Python (requests)
import os, requests

BASE = "https://zaarmtech.com/api/v1"
headers = {"Authorization": f"Bearer {os.environ['ZAARM_API_KEY']}"}

with open("loaf.jpg", "rb") as f:
    media = requests.post(f"{BASE}/media", headers=headers, data=f).json()

r = requests.post(f"{BASE}/posts", headers=headers, json={
    "caption": "Fresh sourdough, out at 7",
    "media_ids": [media["id"]],
    "scheduled_at": "2026-11-20T07:00:00Z",
})
r.raise_for_status()
for p in r.json()["posts"]:
    print(p["account"]["platform"], p["status"], p["error"])
Cancel
curl -X DELETE https://zaarmtech.com/api/v1/posts/cm3p9… \
  -H "Authorization: Bearer $ZAARM_API_KEY"

Security

  • Treat a key like a password. Keep it in an environment variable or secret store on your server, never in a web page, app bundle or public repository.
  • We store only a SHA-256 fingerprint of each key, so we can't show it to you again, and neither can anyone who got hold of our database.
  • A key can only reach its own business: its accounts, its uploads and its posts. It can't connect accounts, read anything else, or touch other businesses.
  • If a key might have leaked, revoke it under Accounts → API keys and create a new one. Each key shows when it was last used.
  • Uploaded files are checked by their content, stored under random names outside any public folder, and deleted 7 days after their post finishes.

See also the privacy policy and terms.