Docs · API
API quickstart
Send a few numbers to our server, and the answer is a font file. Nothing to install: any language that can make an HTTP request will do.
The hosted API runs the engine on Typomat's servers. It suits a server of yours that makes images, video or PDFs, or any product where installing an SDK isn't worth it. It makes TrueType, OpenType, WOFF and WOFF2 files, with all 271 characters.
1. Make a key
In your account, open the API part and make a key. It's shown once, when you make it, so copy it then. Anyone who has it can spend your plan, so it stays on your server, never in a web page or an app. Put it in your environment for the next step:
export TYPOMAT_KEY=tm_live_...2. Ask for a font
Send the key as a bearer token, and a JSON body with a name and a few parameters: numbers that set things like weight and contrast. Anything you leave out stays at its default. See them all.
curl https://engine.typomat.app/api/v1/fonts \
-H "Authorization: Bearer $TYPOMAT_KEY" \
-H "Content-Type: application/json" \
-o acme-display.ttf \
-d '{
"name": "Acme Display",
"parameters": {
"weight": 118,
"contrast": 0.92,
"serif": 0.5
}
}'import { writeFile } from "node:fs/promises";
const response = await fetch("https://engine.typomat.app/api/v1/fonts", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.TYPOMAT_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "Acme Display",
parameters: {
weight: 118,
contrast: 0.92,
serif: 0.5
}
}),
});
if (!response.ok) throw new Error((await response.json()).error.message);
await writeFile("acme-display.ttf", Buffer.from(await response.arrayBuffer()));import os
import requests
response = requests.post(
"https://engine.typomat.app/api/v1/fonts",
headers={"Authorization": f"Bearer {os.environ['TYPOMAT_KEY']}"},
json={
"name": "Acme Display",
"parameters": {
"weight": 118,
"contrast": 0.92,
"serif": 0.5
}
},
)
if not response.ok:
raise RuntimeError(response.json()["error"]["message"])
with open("acme-display.ttf", "wb") as file:
file.write(response.content)<?php
$context = stream_context_create(["http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("TYPOMAT_KEY") . "\r\nContent-Type: application/json\r\n",
"content" => json_encode(["name" => "Acme Display", "parameters" => ["weight" => 118, "contrast" => 0.92, "serif" => 0.5]]),
"ignore_errors" => true,
]]);
$body = file_get_contents("https://engine.typomat.app/api/v1/fonts", false, $context);
if (!str_contains($http_response_header[0], " 200 ")) {
throw new RuntimeException(json_decode($body, true)["error"]["message"]);
}
file_put_contents("acme-display.ttf", $body);require "json"
require "net/http"
uri = URI("https://engine.typomat.app/api/v1/fonts")
response = Net::HTTP.post(
uri,
{ name: "Acme Display", parameters: { weight: 118, contrast: 0.92, serif: 0.5 } }.to_json,
"Authorization" => "Bearer #{ENV.fetch("TYPOMAT_KEY")}",
"Content-Type" => "application/json",
)
raise JSON.parse(response.body)["error"]["message"] unless response.is_a?(Net::HTTPSuccess)
File.binwrite("acme-display.ttf", response.body)package main
import (
"io"
"net/http"
"os"
"strings"
)
func main() {
body := `{ "name": "Acme Display", "parameters": { "weight": 118, "contrast": 0.92, "serif": 0.5 } }`
request, err := http.NewRequest("POST", "https://engine.typomat.app/api/v1/fonts", strings.NewReader(body))
if err != nil {
panic(err)
}
request.Header.Set("Authorization", "Bearer "+os.Getenv("TYPOMAT_KEY"))
request.Header.Set("Content-Type", "application/json")
response, err := http.DefaultClient.Do(request)
if err != nil {
panic(err)
}
defer response.Body.Close()
data, err := io.ReadAll(response.Body)
if err != nil {
panic(err)
}
if response.StatusCode != http.StatusOK {
panic(string(data))
}
if err := os.WriteFile("acme-display.ttf", data, 0o644); err != nil {
panic(err)
}
}import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.nio.file.Path;
public class Main {
public static void main(String[] args) throws Exception {
String body = """
{ "name": "Acme Display", "parameters": { "weight": 118, "contrast": 0.92, "serif": 0.5 } }
""";
HttpRequest request = HttpRequest.newBuilder(URI.create("https://engine.typomat.app/api/v1/fonts"))
.header("Authorization", "Bearer " + System.getenv("TYPOMAT_KEY"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpClient client = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).build();
HttpResponse<byte[]> response = client.send(request, HttpResponse.BodyHandlers.ofByteArray());
if (response.statusCode() != 200) throw new RuntimeException(new String(response.body()));
Files.write(Path.of("acme-display.ttf"), response.body());
}
}using System.Net.Http.Headers;
using System.Net.Http.Json;
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("TYPOMAT_KEY"));
var response = await client.PostAsJsonAsync("https://engine.typomat.app/api/v1/fonts", new { name = "Acme Display", parameters = new { weight = 118, contrast = 0.92, serif = 0.5 } });
if (!response.IsSuccessStatusCode) throw new Exception(await response.Content.ReadAsStringAsync());
await File.WriteAllBytesAsync("acme-display.ttf", await response.Content.ReadAsByteArrayAsync());import Foundation
var request = URLRequest(url: URL(string: "https://engine.typomat.app/api/v1/fonts")!)
request.httpMethod = "POST"
request.setValue("Bearer \(ProcessInfo.processInfo.environment["TYPOMAT_KEY"]!)", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = Data(#"{ "name": "Acme Display", "parameters": { "weight": 118, "contrast": 0.92, "serif": 0.5 } }"#.utf8)
let (data, response) = try await URLSession.shared.data(for: request)
guard (response as? HTTPURLResponse)?.statusCode == 200 else {
fatalError(String(decoding: data, as: UTF8.self))
}
try data.write(to: URL(fileURLWithPath: "acme-display.ttf"))What you should see: a new file, acme-display.ttf, about 54 KB. Open it with your computer's font viewer to see the letters. The response's X-Typomat-Compile-Ms header says how long the engine took.
3. If it goes wrong
An error comes back as JSON instead of a font, with a code to check and a message to read:
{ "error": { "code": "invalid_key", "message": "..." } }missing_key or invalid_key (status 401) means the key didn't arrive, or isn't right: check TYPOMAT_KEY. invalid_request (400) usually means a parameter's name is misspelled. The API reference lists them all.
4. Make it smaller
A font holds all 271 characters unless you pick. For a few words on an image, ask for only the characters you need, and the file is a fraction of the size. Each request counts as one font against your plan, whatever it holds.
curl https://engine.typomat.app/api/v1/fonts \
-H "Authorization: Bearer $TYPOMAT_KEY" \
-H "Content-Type: application/json" \
-o acme.ttf \
-d '{
"name": "Acme Display",
"parameters": {
"weight": 118,
"contrast": 0.92
},
"characters": "Acme"
}'import { writeFile } from "node:fs/promises";
const response = await fetch("https://engine.typomat.app/api/v1/fonts", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.TYPOMAT_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "Acme Display",
parameters: {
weight: 118,
contrast: 0.92
},
characters: "Acme"
}),
});
if (!response.ok) throw new Error((await response.json()).error.message);
await writeFile("acme.ttf", Buffer.from(await response.arrayBuffer()));import os
import requests
response = requests.post(
"https://engine.typomat.app/api/v1/fonts",
headers={"Authorization": f"Bearer {os.environ['TYPOMAT_KEY']}"},
json={
"name": "Acme Display",
"parameters": {
"weight": 118,
"contrast": 0.92
},
"characters": "Acme"
},
)
if not response.ok:
raise RuntimeError(response.json()["error"]["message"])
with open("acme.ttf", "wb") as file:
file.write(response.content)<?php
$context = stream_context_create(["http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("TYPOMAT_KEY") . "\r\nContent-Type: application/json\r\n",
"content" => json_encode(["name" => "Acme Display", "parameters" => ["weight" => 118, "contrast" => 0.92], "characters" => "Acme"]),
"ignore_errors" => true,
]]);
$body = file_get_contents("https://engine.typomat.app/api/v1/fonts", false, $context);
if (!str_contains($http_response_header[0], " 200 ")) {
throw new RuntimeException(json_decode($body, true)["error"]["message"]);
}
file_put_contents("acme.ttf", $body);require "json"
require "net/http"
uri = URI("https://engine.typomat.app/api/v1/fonts")
response = Net::HTTP.post(
uri,
{ name: "Acme Display", parameters: { weight: 118, contrast: 0.92 }, characters: "Acme" }.to_json,
"Authorization" => "Bearer #{ENV.fetch("TYPOMAT_KEY")}",
"Content-Type" => "application/json",
)
raise JSON.parse(response.body)["error"]["message"] unless response.is_a?(Net::HTTPSuccess)
File.binwrite("acme.ttf", response.body)package main
import (
"io"
"net/http"
"os"
"strings"
)
func main() {
body := `{ "name": "Acme Display", "parameters": { "weight": 118, "contrast": 0.92 }, "characters": "Acme" }`
request, err := http.NewRequest("POST", "https://engine.typomat.app/api/v1/fonts", strings.NewReader(body))
if err != nil {
panic(err)
}
request.Header.Set("Authorization", "Bearer "+os.Getenv("TYPOMAT_KEY"))
request.Header.Set("Content-Type", "application/json")
response, err := http.DefaultClient.Do(request)
if err != nil {
panic(err)
}
defer response.Body.Close()
data, err := io.ReadAll(response.Body)
if err != nil {
panic(err)
}
if response.StatusCode != http.StatusOK {
panic(string(data))
}
if err := os.WriteFile("acme.ttf", data, 0o644); err != nil {
panic(err)
}
}import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.nio.file.Path;
public class Main {
public static void main(String[] args) throws Exception {
String body = """
{ "name": "Acme Display", "parameters": { "weight": 118, "contrast": 0.92 }, "characters": "Acme" }
""";
HttpRequest request = HttpRequest.newBuilder(URI.create("https://engine.typomat.app/api/v1/fonts"))
.header("Authorization", "Bearer " + System.getenv("TYPOMAT_KEY"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpClient client = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).build();
HttpResponse<byte[]> response = client.send(request, HttpResponse.BodyHandlers.ofByteArray());
if (response.statusCode() != 200) throw new RuntimeException(new String(response.body()));
Files.write(Path.of("acme.ttf"), response.body());
}
}using System.Net.Http.Headers;
using System.Net.Http.Json;
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("TYPOMAT_KEY"));
var response = await client.PostAsJsonAsync("https://engine.typomat.app/api/v1/fonts", new { name = "Acme Display", parameters = new { weight = 118, contrast = 0.92 }, characters = "Acme" });
if (!response.IsSuccessStatusCode) throw new Exception(await response.Content.ReadAsStringAsync());
await File.WriteAllBytesAsync("acme.ttf", await response.Content.ReadAsByteArrayAsync());import Foundation
var request = URLRequest(url: URL(string: "https://engine.typomat.app/api/v1/fonts")!)
request.httpMethod = "POST"
request.setValue("Bearer \(ProcessInfo.processInfo.environment["TYPOMAT_KEY"]!)", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = Data(#"{ "name": "Acme Display", "parameters": { "weight": 118, "contrast": 0.92 }, "characters": "Acme" }"#.utf8)
let (data, response) = try await URLSession.shared.data(for: request)
guard (response as? HTTPURLResponse)?.statusCode == 200 else {
fatalError(String(decoding: data, as: UTF8.self))
}
try data.write(to: URL(fileURLWithPath: "acme.ttf"))Nothing is stored on our side. The same numbers always make the same letters, so keep the numbers, and the file for as long as they don't change, and ask again when they do.