Publishing at volume
You have a pipeline producing videos and a set of accounts on 0bull phones. This page is the shortest path from "file on disk" to "posted", for a script that runs unattended.
Everything here needs a token with the submissions ability. See the
introduction for creating one, and the
API reference for every field.
The loop
Per video, a pipeline makes at most three calls:
- Hand over the video, by URL, signed upload, or multipart.
- Create the submission (one account) or the batch (many accounts), optionally
with
scheduled_atandwebhook_url. - Learn the outcome: wait for the webhook, or poll.
Code
That is one request for three posts on three platforms, held until 18:00 UTC tomorrow, each of which calls you back when it finishes.
Handing over the video
Both POST /v1/submissions and POST /v1/submissions/batches take the video in exactly
one of three ways. Sending two is a 422.
| Field | When to use it | Limits |
|---|---|---|
video_url | Files already on object storage or a CDN. Hand over a pre-signed link; 0bull downloads it. | https only. Redirects are not followed. Private and loopback addresses are refused. |
upload_id | Files that are not reachable from the internet. POST /v1/uploads returns a signed upload_url valid for 15 minutes; PUT or POST the bytes there, then submit with the upload_id. | 60 uploads per minute. |
video | Small clips from a script that has the file in hand. A multipart field. | Same size limit; the whole request is one upload. |
All three accept MP4 or MOV up to 200 MB.
One upload, many accounts
An upload_id is consumed by the submission that uses it, so it cannot be reused for a
second account. To post the same clip to several accounts from one upload, use the batch
endpoint below.
One clip, many accounts
POST /v1/submissions/batches creates one submission per entry in destinations, from a
single video, across TikTok, Instagram and YouTube in the same call. Each destination is a
{platform, account_id} pair; an account may appear once.
Code
Text is per platform. caption (up to 2200 characters) goes to TikTok and Instagram.
youtube_title (up to 100) becomes the Short's title on YouTube, and is required whenever
a destination is on YouTube.
The response is 201 with the batch and its submissions:
Code
Every submission in it is an ordinary submission: it shows up in GET /v1/submissions,
can be polled at GET /v1/submissions/{submission}, and cancelled on its own. The batch
adds two things:
GET /v1/submissions/batches/{batch}returns the whole drop, withrunningset while any post is still scheduled, queued or on a phone.POST /v1/submissions/batches/{batch}/cancelstops every post that has not finished and leaves the ones that already landed alone. It never fails on a partly finished drop.
Validation is all or nothing. If any destination is unknown, listed twice, or on an account
without a phone, the call is a 422 keyed on that entry (destinations.1.account_id) and
nothing is created.
Up to 50 destinations per call.
Scheduling
Both endpoints take scheduled_at, an ISO 8601 timestamp in the future. Omit it to publish
immediately.
A scheduled submission is created with status scheduled and does nothing until that time.
It can be cancelled until it starts; once it moves to pending it is treated like any other
queued post. On a batch, every post shares the same scheduled_at.
0bull does not spread posts out for you. A pipeline that wants "one post per account every two hours" computes the timestamps itself and sends one request per slot in the plan.
Learning the outcome
Publishing happens on a real phone after the request returns, so the status you get back is never final. There are two ways to learn how it went:
- Webhooks. Pass
webhook_urland 0bull POSTs to it once per submission when that submission reachespublished,drafted,failedorcancelled. See Webhooks for the payload and how to verify the signature. This is the right choice for a pipeline: no polling loop, no missed transitions. - Polling.
GET /v1/submissions/{submission}untilstatusis one of those four. A post typically takes one to three minutes on the phone; poll every 5 to 15 seconds, and give up after 15 minutes. The status table lists every value.
When status is failed, failure.step names the stage that failed and failure.message
says what to do. Retrying is your call: create a new submission with the same video.
Rate limits and pacing
The API allows 250 requests per minute per token, which is far more than a phone can post. A phone posts one video at a time and each post takes a few minutes, so the practical ceiling is set by how many phones you have, not by the API. Submissions to the same phone queue up and run in order.
Batches count as one request no matter how many destinations they carry.

