I run a visa agency in Bali. The other half of my week goes into Apify Actors, and one of them takes a public Instagram Reel apart into a reusable script: the hook and why it works, the beats with what is on screen in each one, the call to action, and a fill-in template. It is not a transcript tool with a summary bolted on. The model gets frames sampled from the video β one every 2 seconds, 360 px wide, at most 12 β plus our own speech-to-text and the caption, and it answers in JSON.
Which means half the output is an enum and half is prose written fresh each time. I have been quietly collecting evidence on what that costs, because like every Actor I publish, this one has a scheduled check: once a day, send one fixed Reel, make sure rows come out.
Sixteen days of that check, 24 October to 8 November 2026, gave me 26 breakdowns of the same 10.4-second Reel β same model (deepseek-flash), same prompt, JSON mode, no temperature set, so the provider default applies. The Reel is a brow artist walking past a building: three lines of text burned into the picture, a trending song on the audio, π Link in bio to book in the caption. Each row cost $0.0506.
If you are about to put LLM output into a database column, this is the field-by-field answer to "how much of it is stable".
What never moved
| Field | Across 26 runs |
|---|---|
ai.hook.source |
on-screen β 26/26 |
ai.onScreenText |
the same three lines, byte for byte β 26/26 |
ai.speechIsSong |
true β 26/26 |
ai.hook.text |
Some people call me Kara β 25/26 |
ai.hook.type |
statement β 25/26 |
| first beat label |
hook β 25/26 |
| last beat end |
10.4 β 26/26 |
That is a better result than I expected, and it is the useful half of the finding. The model's judgement β where the hook lives, what it says, whether the audio is a song β is near-deterministic. The one run that disagreed on hook.text returned Some people call me Kara / Some people call me Kara Ann, which is the same hook with the second line glued on, not a different reading.
What moved, and it is the fields you were going to filter on
The beat count flipped. Four beats in 14 runs, three in 12. The labels behind those counts came back as ten distinct sequences:
hook, setup, cta 5 runs
hook, setup, value, outro 4
hook, setup, twist, outro 3
hook, setup, value, cta 3
hook, setup, twist, cta 3
hook, setup, twist 3
hook, value, cta 2
hook, value, outro 1
hook, setup, cta, outro 1
setup, setup, cta 1
Same ten seconds of video. Is the middle of it a setup, a value or a twist? The model has no strong opinion, and neither would I. Fifteen of the 26 runs contained a beat labelled cta; eleven did not. The boundaries moved too β 17 distinct sets of end-seconds across 26 runs, though the first beat ended at 3.0 s in 17 of them and the hook window never stretched past 3.5 s.
cta.type gave three answers to one caption. link_in_bio 19 times, book 6 times, other once. The caption says π Link in bio to book β it is genuinely both, and the enum forces a choice. Worse for anyone grouping by it, five runs picked the spoken line But you can call me when you want the best brows as the CTA text instead of the caption line.
That is the practical warning. cta.type is exactly the field an agency dashboard groups by, and on a Reel where two readings are defensible it will move between runs for free.
The prose never repeats, and that is not a bug
| Free-text field | Distinct values in 26 runs |
|---|---|
ai.scriptTemplate |
26 |
ai.hook.whyItWorks |
26 |
ai.format |
26 |
ai.topic |
25 |
ai.audience |
21 |
ai.emotion |
10 (Playful and confident 15 times) |
Two runs of the same Reel, sixteen days apart:
24 Oct topic: Brow artist personal branding / booking intro
8 Nov topic: Eyebrow artist personal-brand introduction in Greenville, SC,
with a name-pun hook that lands on her brow services
24 Oct template: Some people call me [formal name]. Some people call me
[nickname or full name]. But you can call me when you want
the [your signature result / service]. π Link in bio to
[booking action]. π[city]
8 Nov template: Some people call me [First name]. Some people call me
[Full name]. But you can call me when you want the
[best service / result you offer].
Both are correct. Neither is the same string. whyItWorks ranged from 141 to 366 characters and the placeholder count in the template ran from 3 to 8.
So the rule I now follow, and would hand to anyone storing model output:
-
Diff on enums, never on prose. A scheduled watch that re-reads a creator weekly and alerts on "the breakdown changed" will fire every single week if it compares
topicorscriptTemplate. Comparehook.source,hook.type,cta.type,speechIsSong, and the on-screen lines. -
Do not
GROUP BY ai.topic. Twenty-five labels for one video is not a taxonomy. Map to your own closed list, or group byhook.typeandcta.type, closed lists of 8 and 10 values respectively. -
Treat prose as the thing a human reads once.
whyItWorksandscriptTemplateare worth the money precisely because they are written for this Reel. Store them, show them, do not join on them. - If you need one canonical answer, run it once and keep it. The Actor has an "only new videos" switch with named watch lists for this: re-running a Reel you already have buys a new opinion, not a correction.
Why the AI layer exists at all: the transcript is often not the hook
The same Actor also fills rule-based fields for free β hookText, ctaText, beats, wordsPerMinute β from the transcript and caption, no model involved. Across 11 distinct Reels I have real breakdowns for (10 Instagram, 1 TikTok; 10.4 s to 136 s; natgeo, zachking, Duolingo, two small businesses), the free hook and the AI hook pointed at different sources in 5 of 11:
| Reel | free hookSource
|
ai.hook.source |
Why |
|---|---|---|---|
| brow artist, 10 s | speech |
on-screen |
the audio is a song; the transcript is lyrics |
| natgeo pangolin, 19 s | caption |
visual |
no speech at all β hook is [close-up of a scaly pangolin foraging in a rotting log]
|
| coffee shop, 12 s | caption |
on-screen |
no speech; the hook is "So what brought you here?" typed on the frame |
| natgeo, 15 s | caption |
on-screen |
no speech; City, desert, mountains, or coastβwhich would you explore first?
|
| Duolingo TikTok, 10 s | speech |
caption |
the speech is Japanese conversational filler |
Three of the eleven Reels transcribed to an empty string. A transcript-only tool returns nothing for those, and a hook heuristic reading the caption gets Presented by @Rolex. Nine of the eleven came back with on-screen lines in ai.onScreenText even though exact OCR was switched off β the model reads its frames for free. The free CTA detector found 4 CTAs in 11 Reels, one of them a mid-video sentence that is not a CTA; the AI found 7, including Which animal do you want to see next? (type comment) β the last sentence of a 50-word natgeo caption that my keyword rules walked straight past.
Where the two agree β a talking-head Reel with real speech β the free fields are fine and you can switch the model off and pay $0.003. It is the silent, text-on-screen Reel that needs the frames, and that is a large slice of Instagram.
Calling it
curl -X POST "https://api.apify.com/v2/acts/lergassy~instagram-reel-script-extractor/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"videoUrls":["https://www.instagram.com/reel/DckEW9NNVsJ/"],
"aiAnalysis":true,"includeOnScreenText":false}'
Profiles work too ("profiles":["natgeo"], up to the 12 newest Reels Instagram shows logged out). Pricing is per event: $0.05 for the breakdown, $0.003 per Reel, $0.0015 per started 10 seconds of speech on the Free plan β the 10.4-second Reel above is $0.056 there and $0.0506 on ours. A breakdown that fails is not charged; the row arrives with the reason in aiError. The Actor is Instagram Reel Script Extractor; if you only want words and timestamps, the transcript-only version is the same engine with the model off by default.
When this is the wrong tool
- You need reproducible output. Everything above. Run once, store the row, treat a re-run as a second opinion.
- You need every burned-in line with timestamps. The frames give you the text, not when it appeared. That is the separate OCR switch at $0.06 per Reel.
- You think the model watched the video. It saw up to 12 stills and read our transcript. A fast cut, a gesture between frames, a sound effect β all invisible to it.
- You need verbatim evidence. Quoted speech comes from speech recognition, which mishears. The breakdown is a strong first draft, not a transcript of record.
The whole measurement in this post is a GET over the run history of one Actor and forty lines of Python. If you ship anything that puts an LLM's JSON into a table, point it at one fixed input for two weeks and count the distinct values per column before you decide which ones get an index.
Top comments (0)