Custom fonts in a video API
Find out what happens when a video API cannot find your font. 30 of 46 production jobs asked for a typeface that was not installed and were billed anyway.
Short version
We went looking through production job records for how often callers asked for a font we did not have. The answer was 30 of 46 text-bearing compose jobs, and every one of them succeeded. The render completed, the invoice went out, and the video came back set in a typeface nobody had chosen.
The requests were not exotic. Every one of those 30 asked for the same thing, the string "Arial, sans-serif", which is a perfectly ordinary CSS font stack. Our resolver read the first entry, found no Arial on the render image, and handed that name to fontconfig, which substituted something. The sans-serif fallback the author had actually written was never consulted.
That is the whole problem with fonts in a rendering API. Nothing errors. You get a video, and it looks slightly wrong, and you may not notice for weeks.

Silent substitution is the expensive failure
A failed render is annoying and cheap. You see the error, you fix the input, you resubmit. A render that silently substitutes a font is expensive because the feedback loop is broken. The job status says complete. The output plays. The only signal is visual, and it only fires if somebody looks closely at the right frame.
Font resolution on Linux goes through fontconfig, whose job is to always return something. Ask for a family it does not have and it will find the closest match it can rather than refuse. That behaviour is correct for a desktop and wrong for a billed API call, because the caller has no way to tell the two outcomes apart.
The fix is not to make fontconfig stricter. It is to decide the substitution before it happens, and to say so when it does.
A stack is a failure policy
CSS font stacks already encode exactly the right idea. "Display, Playfair Display, Inter, sans-serif" means try these in order and use the first one you can supply. The author has already told you what they want to happen when their first choice is unavailable.
Honouring that turns three separate failure modes into one predictable behaviour. A font file that returns a 404, a family the catalogue does not publish, a font service that is unreachable: all of them fall through to the next entry, and the render ships. The cost of any of those failures is the font, never the video.
It also means the resolution order is worth stating plainly. A file you supplied wins, then a family bundled into the render image, then a generic CSS keyword, then any family in the Google Fonts catalogue, fetched at render time. Declaring a font under a name that already exists shadows the built-in, which is the same thing @font-face does in a browser.
Naming a font you uploaded
There is a smaller trap underneath all of this. A font file has a family name recorded inside it, and that name is frequently not the filename. Upload BrandSans-Bold.otf and the renderer may only answer to Brand Sans.
Shotstack documents this directly and warns about it, because you have to reference the embedded name and callers get it wrong. Their custom fonts guide also requires you to host the TTF yourself: there is no lookup by family name, and the format support is TTF only. Creatomate takes uploads in OTF, TTF and WOFF through its editor or a fonts.add modification. JSON2Video accepts a font URL inline in the font-family value.
We took a different position. You name the file yourself when you declare it, and we keep the mapping from your name to the one inside the file. You never open your font in an inspector.
One format note worth publishing, because it cost us a debugging session. fontconfig will happily index a WOFF2 and report its family name, and the renderer then cannot rasterise from it and silently substitutes. We verified this by matching a WOFF2 face by name and watching a completely different typeface come out. WOFF2 is what the web serves, so a developer copying a font URL out of their own site’s CSS will hit this. We decompress it on ingest rather than handing it to the renderer.
What actually renders
Font coverage claims are cheap, so we rendered 16 scripts through the engine and looked at the output.
Thirteen worked with no configuration at all: Latin, Cyrillic, Greek, Vietnamese with stacked diacritics, Turkish with its dotted and dotless i, Arabic with correct right-to-left joining, Hebrew, Thai with stacked vowels and tone marks, Devanagari with conjuncts, Bengali, Tamil, Amharic and Khmer. That is harfbuzz doing real shaping work, not glyph substitution.

Chinese, Japanese and Korean rendered as empty boxes. That was one missing font package, now installed. It is worth saying that CJK failed loudly, as visible tofu boxes, which is the better failure mode. You see it immediately.
Colour emoji did not render at all. We tested five ways: default fallback, a fontconfig rule that made emoji codepoints resolve correctly, the font forced explicitly, the native bitmap strike size, and two alternative text producers. The glyphs are dropped by the text path in every case. That is a limit of the rendering engine rather than something a font feature fixes, and we would rather write it down than let you discover it in a render.
Where this stops
This is one engine, MLT 7.41, and one text path. The multilingual results come from rendering those scripts and looking at the frames, not from a coverage table, so they hold for this image and this build and nothing else.
We read the published documentation and OpenAPI definitions for Shotstack, Creatomate and JSON2Video to describe what they offer. We did not test their renderers. We do not know whether their engines handle CJK, and we do not know whether they drop colour emoji the way ours does. Any claim in that direction would be a guess, so we are not making one.
The competitor capabilities described here were checked on the date this post carries. Font support is exactly the kind of thing that changes quietly, so treat the specifics as a snapshot rather than a standing fact, and check the linked sources before relying on them.
None of this covers the raw FFmpeg path, where the same missing font produces a failed render rather than a silent substitution. That case is measured separately in how to burn subtitles into a video, where font access is one of three preconditions that broke all 8 raw attempts in our archive.
Frequently asked questions
Why did my video render in the wrong font?
Almost certainly because the family you named is not installed on the render machine. fontconfig substitutes a face rather than failing, so the job succeeds and you are billed for a video in a typeface you did not pick. Name a family the renderer has, supply your own file, or write a fallback stack so the substitution is your choice rather than the system's.
Can I use any Google Font in a video API?
In Rendobar's compose and captions jobs, yes, by name. The family is resolved and fetched at render time. Shotstack does not offer families by name at all: their custom-fonts guide requires you to download the TTF yourself and host it at a public URL.
How do I use my own brand font?
Put the file URL in the job's font input and reference it by a name you choose. The alternative, used by Shotstack, is to reference the family name embedded inside the file, which you have to discover with a font inspector. Their documentation warns about this because callers get it wrong.
Does a video API support Chinese, Japanese or Korean text?
It depends entirely on which font packages are installed on the render image, and that is rarely documented. We tested 16 scripts through our own engine. Latin, Cyrillic, Greek, Vietnamese, Turkish, Arabic, Hebrew, Thai, Devanagari, Bengali, Tamil, Amharic and Khmer all rendered with no configuration. CJK rendered as empty boxes until the Noto CJK package was added.
Do video APIs support colour emoji in text?
Ours does not, and we tested it five ways before saying so. The glyphs are dropped by the text renderer even when the emoji font is installed and fontconfig matches it correctly. We have not tested other providers' renderers, so we make no claim about them.
