Skip to content
MapaorPublic

About

An HTTP Rest API for Typst. Made with Rust using the official Typst crates instead of the CLI. Binaries and docker images available.

Topics

Resources

Stars

8 stars

Watchers

0 watching

Forks

Repository files navigation

Typst API

An HTTP REST API built in Rust to compile Typst documents into PDFs. It supports multiple files, source code, caching fonts, typst packages, authentication, configurable limits and more stuff.

There are two main ways to run the API:

  1. As a standalone binary (the recommended way is using the install script provided below).
  2. As a docker container (the recommended way it to use Docker Compose).

You can also of course compile and run the source code with Cargo.

The intended use of this repository is to allow people self-host a Typst server without much complications.

Requirements

For most documents you only need about 35MB of RAM, however for a very large textbook with images, custom fonts, etc. you might require about ~200MB.

The requirements for most cases are:

  • 0.5 GB of RAM
  • 1 GB of disk space
  • 1 core of CPU/vCPU

For cases where you are exposing your API to the public you should try to have:

  • 2 GB of RAM
  • 3 GB of disk space
  • 1 core of CPU/vCPU

Note: if you are exposing your API to the public, be sure to prevent API abuse by configuring maximum payload size and maximum concurrent compilations as explained below.

Starting the server

As a Docker container

Running with Docker Compose (Recommended)

  1. In a directory of your choice on your server create 2 files, a .env and a docker-compose.yml, and create a fonts directory.
  2. Add to the .env the contents of .env.example, configure the variables to your needs.
  3. Put the contents of the docker-compose.yml into your local docker compose file. It already has the proper configuration and pulls the Docker image from DockerHub.
  4. From this same local directory, do docker compose up -d.
  5. Check http://localhost:8080/health, it should return 200 (Ok). The root endpoint http://localhost:8080 should return a welcome json with the API version, the swagger-ui docs endpoint and other useful information.
  6. Visit http://localhost:8080/playground to interactively test the API in your browser.
  7. Visit http://localhost:8080/add-fonts to easily add new font to your font database.

You can also the logs of the API by running docker compose logs typst-api from that same directory.

Updating

To update simply do from the same directory:

docker compose pull

Or update the image used in the docker-compose.yml if you want a specific version that it's not the latest.

Now restart the container:

docker compose up -d --force-recreate

Running with Docker Compose (Local Development)

  1. Clone this repo git clone https://github.com/Mapaor/typst-api (and do cd typst-api).
  2. Copy the .env.example to .env and adjust the variables if needed.
  3. No need to modify the docker-compose.yml because we have a docker-compose.override.yml that already does the job.
  4. Run docker compose up --build -d. This will trigger the Dockerfile actions and start building the docker image and after that starting the container.
  5. The API will be available at http://localhost:8080
  6. Now you can modify the Rust source code as you please and every time you hit a docker compose up --build -d the container will restart with your new custom image. This way you can tweak the API even further to fit your exact needs.

Running with Docker

You can pull and run the image directly from Docker Hub by using:

docker run -p 8080:8080 mapaor4/typst-api:latest

The API will be available at http://localhost:8080. You can of course set any another port you like. For example, you can map the container's default port (8080) to the host's 3001 port:

docker run -p 3001:8080 mapaor4/typst-api:latest

The API will now be available at http://localhost:3001.

As a standalone binary

Running it with an install script (Recommended)

You can download the binary from GitHub releases and then execute it, but for ease of use an installer script is provided that already downloads the correct binary, configures a local directory, and optionally (only for Linux) sets up a systemd service.

Linux / macOS

Run the installation script:

# Default installation (gets installed at `~/.local/typst-api`)
curl -sSL https://raw.githubusercontent.com/Mapaor/typst-api/main/scripts/install.sh | bash

# Install a specific version
curl -sSL https://raw.githubusercontent.com/Mapaor/typst-api/main/scripts/install.sh | bash -s -- --version v0.2.4

# Install with a systemd service (Recommended. Linux only)
curl -sSL https://raw.githubusercontent.com/Mapaor/typst-api/main/scripts/install.sh | sudo bash -s -- --with-service

If you are on a Linux server I recommend the --with-service version because it sets up a service to automatically run the API on boot.

Then you can check the logs with sudo journalctl -u typst-api -n 100 --no-pager.

If you are on macOS (or Linux but chose not to use the systemd service flag), you'll need to run the server manually:

cd ~/.local/typst-api
./typst-api

To run it in the background (detached), you can use nohup:

cd ~/.local/typst-api
nohup ./typst-api > typst-api.log 2>&1 &

Windows

Run the PowerShell installation script:

# Default installation (gets installed at `$env:USERPROFILE\.local\typst-api`)
Invoke-Expression (Invoke-RestMethod -Uri "https://raw.githubusercontent.com/Mapaor/typst-api/main/scripts/install.ps1")

Run the server manually with:

cd $env:USERPROFILE\.local\typst-api
.\typst-api.exe

To run it in the background (detached), you can start it as a hidden process:

cd $env:USERPROFILE\.local\typst-api
Start-Process -FilePath ".\typst-api.exe" -WindowStyle Hidden

Configuration & Updating

The installer creates a .env file in the installation directory. You can edit this file to configure your server. If you installed the systemd service, the .env configuration is copied to /etc/typst-api/typst-api.env.

To update to a newer or re-read an updated .env file, simply re-run the installer script. Also don't forget to restart the service if you had set it up (sudo systemctl restart typst-api).

You can also re-run the installer script as many times as you want, it will simply override the previous installation.

Uninstalling

To uninstall, delete the installation directory (e.g., rm -rf ~/.local/typst-api) and, if using systemd, disable and remove the service (sudo systemctl disable typst-api && sudo rm /etc/systemd/system/typst-api.service).

Running it as a standalone binary

  1. Download the artifact for your OS and architecture from the GH releases.
  2. Put in the same directory where you want to save your binary a .env file (contents copied from .env.example) and a fonts/ empty folder.
  3. Execute the binary from that same directory.

Compiling from source (Local Development)

The API is a Rust binary crate, so if you have Rust installed you can clone the repo and run it with:

cargo run --release

You can run it with env variables as we've seen before.

On bash:

PORT=3001 cargo run --release

On PowerShell:

$env:PORT = "3001"
cargo run --release

Or you can simply create a .env file (or copy and rename the .env.example) in the root of the repository and set the variables there, it will be loaded automatically.

You can also use cargo build --release to build the binary without running it, and then run the binary located in target/release/typst-api like we explained before.

You could do cargo run or cargo build without the --release flag for local development but for production (the actual API usage) the flag is recommended (it does additional performance optimizations for the final binary).

Why there is no crates.io crate

typst-api is an application binary rather than a Rust library you can reuse. The Docker image lives on Docker Hub and the standalone binaries on GitHub. If you want you can still compile it from source using Cargo. The crate has not been published to crates.io because it wouldn't add much to the already existing standalone binaries and also to avoid any potential confusion with the official Typst crates such as typst-pdf, typst-cli, typst-kit, etc.

Using the API

Source code simple request

Send an application/json request with your typst source code like shown in examples/typst-source-code/README.md.

curl -X POST http://localhost:8080/compile/source -H "Content-Type: application/json" -d '{"source": "= Hello Typst!\nThis is a very simple document.", "filename": "main.typ"}' --output "examples/typst-source-code/example.pdf"

You can also visit http://localhost:8080/playground to test the API in the browser. If you check the static/ folder of this repo you'll see how the playground is internally doing the API call in vanilla javascript.

const response = await fetch('/compile/source', {
    method: 'POST',
    headers: headers,
    body: JSON.stringify({
        source: sourceCode,
        filename: "main.typ"
    })
});

Multipart example Request

Send a multipart/form-data request with your Typst files, images and other files. The main file should either be explicitly named in the form field as main or named main.typ in its filename.

With curl

curl -X POST http://localhost:8080/compile \
  -F "main=@my_document.typ" \
  --output result.pdf

Note: If compilation fails, curl saves the JSON error response to the output file. To see the error in your console instead, remove the --output flag.

With powershell

try {
    Invoke-WebRequest -Uri http://localhost:8080/compile -Method Post -Form @{
        main = Get-Item "my_document.typ"
    } -OutFile "result.pdf" | Out-Null
    Write-Host "Success! Saved to result.pdf"
} catch {
    $_.ErrorDetails.Message
}

With Postman

If you have Postman desktop installed (and optionally the VSCode Extension as well), you can easily test the API by doing the following:

  1. Create a new POST request to http://localhost:8080/compile.
  2. Go to the Body tab and select form-data (instead of raw).
  3. Add a key named main, change its type from Text to File.
  4. Select (upload) your .typ file in the value column.
  5. Click Send. Postman is great because it will show the visual PDF if the request is successful or it will show the JSON error if it fails.

SVG Output

Both /compile and compile/source default to PDF but they also support SVG output. Simply add a query parameter ?format=svg to the request to choose SVG output. If the output has multiple pages it will be returned as a ZIP (containing page-1.svg, page-2.svg, etc.).

Compile source example

curl -X POST "http://localhost:8080/compile/source?format=svg" -H "Content-Type: application/json" -d '{"source":"#set page(fill: none, width: auto, height: auto, margin: 0cm)\n#text(fill: red)[$ e^(i pi)+1=0 $]"}' --output result.svg

Note: SVG backgrounds are white by default, use #set page(fill: none) to get a transparent background.

Compile multipart example

curl -X POST "http://localhost:8080/compile?format=svg" -F "main=@my_document.typ" --output result.zip

You can optionally add a pages query parameter to select which pages you want to export, for example: ?format=svg&pages=2,3,7-9,11-, like in the Typst CLI. You can also use pages for PDFs.

More examples

You can check a full set of examples in the examples/ directory, which contain both the Typst and related files, the PowerShell and Curl commands to run the examples and the expected PDF output.

Admin Endpoints

The API includes admin endpoints which are prefixed with /admin. To set authentication for them add an ADMIN_TOKEN to the .env file. If the variable is not set, these endpoints fall back to AUTH_TOKEN authentication. If neither is set, all endpoints are publicly accessible.

  • GET /admin/info returns a json with useful info like status, config options and stats.
  • POST /admin/fonts accepts a multipart/form-data payload containing font files and saves them to the server's fonts directory and updates the font cache.
  • POST /admin/fonts/refresh reloads local fonts (from the TYPST_FONT_PATHS directory) without having to restart the container.
  • GET /admin/packages returns a list of all cached Typst packages and their total size.
  • POST /admin/packages/preload receives a JSON array of packages to be downloaded and added to the cache.
  • POST /admin/packages/sync-all triggers a background sync of all packages from the Typst registry. As of september 2026 all the versions of all the packages are about ~1.9 GB.
  • DELETE /admin/packages/cache clears the local package cache.
  • POST /admin/fonts/sync triggers a background sync of the curated 133 fonts mirror repository (used in the Typst Web App).

By default, when using Docker Compose the package cache persists across container restarts because it uses a docker named volume, additionally you can configure in the .env file the following variables:

  • PRELOAD_PACKAGES: A list of comma-separated essential packages you want to have available on start up.
  • CACHE_ALL_PACKAGES: If set to true downloads the entire Typst registry (~1.9GB) in the background when the server starts.
  • CACHE_ALL_133_TYPST_FONTS: If set to true downloads and caches all 133 curated fonts from the typst web app mirror in the background when the server starts.

Exposing the API

You can then use any reverse proxy, tunnel or VPN you might typically use to expose your containers or running services to your other devices or to the whole internet.

My recommendations:

  • If you are the only one who is gonna use the API from a specific set of owned devices, use Tailscale and make the requests to the tailnet IP of your server or home server.

  • If you have a home server (without a public IP) and you want the API to be accessible through the internet use Tailscale Funnel to expose your api.

    ⟶ Check out my guide to expose your API in less than 5 minutes: FUNNEL_GUIDE.md.

  • If you have a home server without a public IP and you want the API to be accessible through the internet and also at a custom domain, use Caddy as a reverse proxy on your server and then Cloudflare Tunnel (Cloudflared) for creating a tunnel between your host and the world.

    ⟶ Checkout my guide to set up typst-api with Caddy and Cloudflare Tunnel on a Debian-based OS on a custom domain: CUSTOM_DOMAIN_GUIDE.md

  • Use Pangolin (instead of Cloudflare Tunnel) running on a VPS if you plan to work with very big documents or concurrent requests and want to avoid the Cloudflare 100MB limitation, or if you want to serve your api at a subsubdomain (api.typst.mydomain.com) and don't want to pay for Cloudflare Advanced Certificate Manager.

Note: If you do expose the API running on your server to the general public (the internet) make sure to set the ADMIN_TOKEN to protect sensible routes. And also, either set authentication (so that only you and people who you trust can use the API) or enforce limits like maximum payload size and maximum concurrent compilation to prevent API abuse.

Examples

Here are some client-side example of websites that do requests to a server selfhosting this Typst:

  • md.katex.cc — A markdown online live editor that supports rendering equations. It uses KaTeX for the HTML rendering and Typst for the download PDF option.

If you create a selfhosted instance of typst-api which is public, please share it! Simply fill an issue stating your example website or do a PR editing this REAMDE.

ROADMAP

  • Dynamic custom fonts, allow users to upload custom .ttf or .otf files as part of the multipart/form-data payload (allowing ephemeral custom fonts per compile request).
  • Allow to refresh the font cache without needing a container restart.
  • Add better diagnostics (return line and column) in the format_errors response.
  • Allow to make a HTTP request with typst code (instead of a typst file). Using an application/json new endpoint /compile/source.
  • Handle CORS with tower-http and add configuration options in the env file.
  • Add concurrency limits (not only timeout of individual requests but also a maximum of active compilations)
  • Test the API authentication all the possible cases manually.
  • Publish the first image of the library to DockerHub
  • Handle package cache properly, create config options in the env file as well as endpoints for handling them.

After we have a first stable/complete version of the API

  • Create an initial test suite
  • Create an OpenAPI documentation
  • Generate also a linux/arm64 docker image (besides the current linux/amd64).
  • Handle the creation and publishing of binaries for new releases with cargo-dist

Polishing it

  • Improve logs (with http-tower trace feature) and add the env variable of logging level to the env example
  • Create guides to expose the API using Tailscale Funnel and Cloudflare Tunnel
  • Create a root endpoint GET / whose json response contains useful information
  • Create a GET /admin/info with useful admin information like config options and stats
  • Create a very simple static client-side example /playground to test the API in the browser .
  • Create a bash script to run the download the standalone binaries and set up a running service
  • Create an /add-fonts static page to easily upload fonts to the backend.

Next steps

  • Implement SVG output using typst-svg
  • Add PNG support using default Typst PNG export or converted from SVG using resvg
  • Investigate how the Typst web app handles the (still experimental) HTML export, probably with typst-html.
  • Allow additional output configuration (eg. PDF or PDF-A, DPI, PDF metadata, etc.) Check the current output options of the typst compiler and the typst web app.

In the far future

  • Implement something similar like a 'watch' option (like the CLI) for compiling a file that is constantly changing without having to compile it all again. Maybe a websockets layer of the API. The good thing is axum already supports websockets. We could also use socketioxide (like socket.io for rust). Or maybe a 2-layer is not needed, a single WebSocket endpoint (ws://localhost:8080/watch) where the client connects, sends an initial "setup" payload with files, and then sends diffs or update messages. Or maybe none of this is needed and debounce compilation on the client-side is already enough.
  • Create a way to generate versioned docker images corresponding to a few typst compiler versions. Find a way to name them properly, for example: typst-api:0.2.2-v0.15.1 or typst-api:latest-v0.14.2.
  • Also create an endpoint to check for a particular package version which typst requirements it has. Maybe typst-kit already has some sort of package resolution/compatibility internal information(?).
  • Add other limits (maximum file count or maximum source size or maximum package fetching?) although maybe our current global payload limit already handles their combination correctly so that the API cannot be abused. Not very important for now as if someone imports lots of packages they'll hit the timeout limit.

License

MIT

About

An HTTP Rest API for Typst. Made with Rust using the official Typst crates instead of the CLI. Binaries and docker images available.

Topics

Resources

Stars

8 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages