New: AI voiceover + auto-captions - script to captioned video in one API call. Read the announcement

How-to

Automate TikTok Videos: Render the Recurring Post on a Schedule

Automate the TikTok post you repeat, not the idea. Pull this week's data from the system that already holds it, render one vertical video on a cron, then post it.

Automate TikTok Videos: Render the Recurring Post on a Schedule

The TikTok post worth automating is the one you repeat: same format, new data, every week. Point a scheduled job at the system that already holds that data, send one render request per account, and the week's posts arrive in about a minute. Each 31-second video costs one credit.

The finished video

This guide renders Stylist Availability & Booking - a 31-second 9:16 card for one practitioner rather than one business. Five scenes carry it: a full-bleed portrait, a 440px numeral for the slots still open, a three-row day table held for nearly ten seconds, a booking plate, and a sign-off with the handle and the link.

Nineteen fields drive 67 overlays. Watch the output on the template page before you write the job.

What you need

  • A Renderly API key. Create one in the dashboard under Settings → API keys.
  • Read access to wherever this week's data already lives: a booking tool, a rota sheet, a table in your own database.
  • A scheduler. A cron entry, a GitHub Action on a schedule trigger or Vercel Cron all work; the job runs once a week and exits.

Step 1 - Decide which TikTok post actually repeats

Automation cannot invent the idea. It can only run a post you already make, so the first job is to find the one whose format is fixed and whose data moves.

Three shapes of account qualify, and they want different machinery:

  • A written batch - facts, stories, tips. The content is authored in one sitting and drip-fed. See the faceless Shorts guide.
  • A log of finished work - the client, the result, the photograph. The queue is a record of what you did. See the Reels guide.
  • A recurring data post - this week's slots, this week's classes, today's price. The content is a query against a system you already run.

The third is the one to wire first, because its content expires. A barber's open slots are wrong by Tuesday, so the post has to be remade every week whether or not anyone automates it. That is a schedule, not a content calendar, and a schedule is a thing software is good at.

Step 2 - Point the job at the system that already holds the week

Do not build a spreadsheet for this. The week already exists in the tool that takes the bookings, and the template's fields are shaped like a row out of it:

Template fieldSource
open_countcount of unbooked slots this week
day_1_name / day_1_time / day_1_notethe first open slot, formatted
stylist_name, stylist_role, stylist_photothe staff record
week_labelthe date range, one value for everybody
booking_link, booking_note, handlefixed per practitioner

week_label is worth a note: it is one field, and the template spends it in six overlays - once under the portrait, once on each of the three middle scenes at y=80, and once in the closing footer band. Write it once per run.

Step 3 - Fill the three day rows and nothing else

The timetable is a strict shape. Three rows, three fields each, on a 440px pitch. day_1_name is 104px Fraunces in a 700px box, day_1_time is 58px mono under it, and day_1_note is a 26px chip in a 250px box to the right.

There is no fourth row, and adding one is a template change rather than a replacement. So the query has to choose: sort by whatever converts and take three.

Step 4 - Keep the frame the viewer reads out of TikTok's chrome

TikTok stacks the caption, the handle and the sound ticker over the bottom of the frame, and runs its action rail down the right side. The template's own coordinates tell you what survives that:

  • The day table runs from y=282 to y=1419 of a 1,920px frame, and it holds for nearly ten seconds. This is the frame people screenshot, and it is clear.
  • salon_line at y=1509 and the closing week_label at y=1835 sit inside the caption block. Treat them as a signature rather than as information.
  • The day_N_note chips sit at x=742 to x=992, which is the column TikTok fills with like, comment and share. The times in the left column carry the message; read the chips as decoration.

Then the wrapping, which is fixed-box and unforgiving:

{
  "personal_note": "Two morning cuts and one late\nSaturday. After that I'm full\nuntil September.",
  "booking_note": "A 10 pound deposit holds your slot -\nit comes off the bill."
}

personal_note is 52px in a 992x300 box, which is four lines. booking_note is 46px in a 992x190 box, which is three. Put a real newline where you want the break: a value that finds its own loses whatever falls out of the box, and the render still reports success.

Step 5 - Render on a schedule, one request per account

One practitioner becomes one POST:

curl -X POST https://renderly.video/api/v1/renders \
  -H "Authorization: Bearer $RENDERLY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "templateId": "stylist-availability-booking",
    "replacements": {
      "stylist_photo": "https://cdn.example.com/staff/danny.jpg",
      "stylist_name": "Danny Reyes",
      "stylist_role": "Barber - fades, beards, hot towel",
      "salon_line": "Chair 3 - Fold Barbers - Peckham",
      "week_label": "Mon 25 - Sat 30 Aug",
      "open_count": "4",
      "personal_note": "Two morning cuts and one late\nSaturday. After that I am full\nuntil September.",
      "day_1_name": "Thursday 28",
      "day_1_time": "10:00 - 12:30",
      "day_1_note": "2 slots",
      "day_2_name": "Friday 29",
      "day_2_time": "17:45 - 19:15",
      "day_2_note": "Late cut",
      "day_3_name": "Saturday 30",
      "day_3_time": "09:00 - 11:00",
      "day_3_note": "1 slot",
      "booking_link": "book.foldbarbers.co/danny",
      "booking_note": "A 10 pound deposit holds your slot -\nit comes off the bill.",
      "handle": "@dannycuts"
    },
    "webhookUrl": "https://your-app.com/hooks/renderly"
  }'

The job that sends it is small, because the schedule does the work:

// Sunday 20:00, one run a week.
const week = await bookingTool.openSlotsThisWeek();
const label = "Mon 25 - Sat 30 Aug";
 
for (const person of week.filter((p) => p.openCount > 0)) {
  await fetch("https://renderly.video/api/v1/renders", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.RENDERLY_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      templateId: "stylist-availability-booking",
      replacements: { ...toReplacements(person), week_label: label },
      webhookUrl: "https://your-app.com/hooks/renderly",
    }),
  });
}

The request returns a job ID immediately, because rendering is asynchronous. The filter is the load-bearing line: skipping a person with nothing open is cheaper than checking the output afterwards.

Step 6 - Take the finished video off the webhook and post it

Because webhookUrl was included, Renderly posts back per job:

{
  "event": "render.completed",
  "data": {
    "jobId": "cm9x2k4p10001qw8r7a3b2c1d",
    "status": "COMPLETED",
    "outputUrl": "https://cdn.renderly.video/renders/cm9x2k4p10001qw8r7a3b2c1d.mp4",
    "creditsUsed": 1,
    "durationInFrames": 930,
    "width": 1080,
    "height": 1920,
    "fps": 30
  },
  "timestamp": "2026-08-24T20:01:12.480Z"
}

Write outputUrl against the practitioner and let the publisher take it from there. Payloads are signed with HMAC-SHA256, so verify the signature before you trust the body; the webhooks guide covers the check.

What this costs at scale

Renderly bills 1 credit per minute of 1080p output, rounded up to the nearest half credit. This card runs 31 seconds, which rounds to a full credit - the first template in this cluster to cross that line.

Renders per monthCreditsPlan
4 - one person, one post a week4Creator, $29/mo
35 - eight chairs, weekly35Creator, $29/mo
260 - sixty chairs across five sites260Creator, $29/mo
1,0001,000Business, $99/mo

The third row is the one to sit with. Sixty practitioners posting their own availability every week is 87% of the cheapest paid plan, about ten cents a video - and a weekly design job for sixty people is not a budget line anyone approves.

Where to go next

The schedule needs a source. If the week lives in a spreadsheet rather than a booking tool, the Google Sheets guide has the loop as an Apps Script; if the account posts finished work instead of upcoming availability, the Reels guide runs the same machinery on a different unit.

And if the TikToks are paid rather than organic, the job is a variant matrix instead of a schedule: batch-rendering ad variants measures that one.

Frequently asked

Does Renderly post to TikTok for me?
No. A render returns a hosted MP4 URL, and publishing is a separate step through TikTok's Content Posting API or any scheduler that accepts a video URL and a caption. Keeping them apart is what makes the schedule work: the render job runs on Sunday night and the publisher releases the post on Monday morning, and neither one waits on the other.
What if only two days are open that week, or five?
The timetable is a fixed three-row shape, so send three rows. With two real openings, spend the third row on the first slot of the following week or on a waitlist line. With five, pick the three that sell and let the caption carry the rest. Any field you omit keeps the template's default, so a missing day_3_time ships someone else's date.
Why does a 31-second video cost a whole credit?
Renderly bills 1 credit per minute of 1080p output and rounds up to the nearest half credit. 31 seconds is 0.52 of a minute, which rounds to 1.0, where a 28-second render would round to 0.5. The half-credit line sits at 30 seconds, so it is worth knowing which side of it a template falls on before you multiply by 60 accounts.
What happens if the source is empty when the job runs?
The render still succeeds and still costs a credit, because an empty value is not an error. Guard the job instead of the render: read the week first, skip any row whose open count is zero, and only then send the request. The same guard catches a booking tool that returned nothing because its token expired.
Can one job cover several people on one account?
Yes, and that is the usual case. Eighteen of the nineteen fields are per-practitioner and only week_label is shared, so a salon with eight chairs sends eight rows and gets eight videos back, each one carrying the name and the portrait of the person in it. That is a different unit of work from one video per business, and it is the reason the format survives a weekly cadence.