atelier-api — the backend

atelier-api is the server behind the app: login, team cloud, storage and building together. One instance is enough for the whole team. This page shows you how to self-host it.

Overview

atelier-api is a lean Bun service with MongoDB as its database. Assets live in a content-addressed store on disk (addressed by SHA-256, deduplicated). By default the server listens on port 3095.

  • Discord login with an approval workflow (pending → approved).
  • Versioned pack revisions, resumable uploads, team locks over WebSocket.
  • A registry lane with a service token so websites can query published packs.
  • Web admin dashboard at /admin — logs, storage, build downloads and fxmanifest overrides.

Source & issues: atelier-api on GitHub.

Requirements

  • Bun (1.x) — to run the server.
  • MongoDB — your own instance or MongoDB Atlas.
  • A Discord application for login (free in the Discord Developer Portal).
  • Persistent storage for the assets — see Storage & volume.

Run it locally

Terminal
cd atelier-api
bun install
cp .env.example .env.local   # fill in the values
bun run dev                  # with auto-reload

# Running? Health check:
curl http://127.0.0.1:3095/health

Environment variables

Configuration goes through .env.local (locally) or your host's environment. The most important variables:

VariableDescription
MONGODB_URIRequired — connection string to MongoDB (e.g. mongodb+srv://…).
MONGODB_DB_NAMEDatabase name (your choice), defaults to atelier.
ATELIER_JWT_SECRETRequired — secret for signing tokens (long & random).
ATELIER_SERVICE_TOKENRequired — token for the service lane (registry for websites).
ATELIER_PUBLIC_ORIGINPublic base URL of the server (https://… in production).
ATELIER_DISCORD_CLIENT_IDClient ID of your Discord application.
ATELIER_DISCORD_CLIENT_SECRETClient secret of your Discord application.
ATELIER_ADMIN_DISCORD_IDSComma-separated list of Discord IDs that are admins automatically.
ATELIER_STORAGE_ROOTPath for the assets — must be persistent (default ./data).
PORTHTTP port, defaults to 3095.
HOSTBind address — 0.0.0.0 in production.
ATELIER_BUILD_CONCURRENCYParallel server builds, defaults to 2.

Never commit secrets

ATELIER_JWT_SECRET and ATELIER_SERVICE_TOKEN should be long and random (e.g. openssl rand -hex 32) and belong only in the environment — never in the repo. The full list including defaults lives in .env.example.

Discord OAuth & access

Create a Discord application

  1. 1
    In the Discord Developer Portal create a new application (name it e.g. "atelier").
  2. 2
    Under OAuth2 copy the client ID and client secret into your env (ATELIER_DISCORD_CLIENT_ID / ATELIER_DISCORD_CLIENT_SECRET).
  3. 3
    Add both redirects exactly — one for the desktop app, one for the web admin dashboard:
    {ATELIER_PUBLIC_ORIGIN}/api/v1/auth/discord/callback
    {ATELIER_PUBLIC_ORIGIN}/admin/callback
    Locally that's http://127.0.0.1:3095/api/v1/auth/discord/callback and http://127.0.0.1:3095/admin/callback. The identify scope is requested automatically.

Approval workflow

New members start out as pending and have to be approved once. Discord IDs in ATELIER_ADMIN_DISCORD_IDS are admins and approved automatically — those admins then approve everyone else directly in the app.

  • pending — signed in, but blocked from cloud actions.
  • approved — full access to packs, sync and builds.
  • locked — blocked, all device sessions are revoked.

Storage & volume

Every uploaded file lives under ATELIER_STORAGE_ROOT in a content-addressed store. MongoDB only holds the metadata and references (by SHA-256).

ATELIER_STORAGE_ROOT/
cas/      finalized assets (immutable, by hash)
tmp/      uploads in progress
builds/   cached build artifacts

The volume MUST be persistent

If this directory is wiped on a redeploy (e.g. a container without a volume), the MongoDB references remain but the files are gone — downloads then fail with asset_not_found. Always put ATELIER_STORAGE_ROOT on a persistent volume and back it up regularly. After a wipe, owners have to re-upload their packs.

Deployment (Dokploy)

atelier-api ships with a Dockerfile (oven/bun) so it runs as a container anywhere. With Dokploy, in short:

  1. 1
    In Dokploy, create a new application from the atelier-api repository (or the Docker image).
  2. 2
    Set the environment variables — in particular MONGODB_URI, ATELIER_JWT_SECRET, ATELIER_SERVICE_TOKEN, the two Discord values and HOST=0.0.0.0.
  3. 3
    Mount a persistent volume at ATELIER_STORAGE_ROOT (e.g. /data). This is the most important step — without it you lose all assets on the next deploy.
  4. 4
    Assign a domain and expose port 3095. Set the same public HTTPS URL as ATELIER_PUBLIC_ORIGIN and register it as a Discord redirect.
  5. 5
    Deploy and verify with GET /health.
Alternatively: plain Docker
docker run -d --name atelier-api \
  -p 3095:3095 \
  -v atelier-data:/data \
  --env-file .env \
  atelier-api

Admin dashboard (web)

The server ships its own web dashboard at /admin — sign-in is limited to the Discord IDs in ATELIER_ADMIN_DISCORD_IDS. Sign-in runs through a dedicated Discord web flow (separate from the desktop app), the session lives in an HttpOnly cookie, and admin status is re-checked on every request.

  • Overview — storage size (CAS/builds/tmp) and key figures (assets, packs, revisions, builds, users).
  • Logs — server logs live (SSE) plus an activity log.
  • Packs & builds — generate or rebuild server builds per revision and download the finished packages as ZIP.
  • fxmanifest — override the resource name and an fxmanifest.lua template per pack. It takes effect on the next server build; without an override everything stays byte-identical to the desktop build.
  • Users — approve and lock.

Prerequisite

Real Discord credentials and the second redirect URI {ATELIER_PUBLIC_ORIGIN}/admin/callback (see Discord OAuth). After that it's reachable at {ATELIER_PUBLIC_ORIGIN}/admin.

Endpoint overview

All endpoints live under /api/v1. User endpoints need a bearer token (from login), the registry lane needs a service token. The main groups:

GroupPurpose
auth / deviceDiscord login, token exchange and renewal, logout.
me / devicesManage your own profile and signed-in devices.
adminList members, approve, lock, set roles.
/admin (web)Browser dashboard: overview, logs, build downloads, fxmanifest — cookie login, admins only.
uploads / assetsResumable chunk uploads and asset download (with Range/ETag).
packs / revisionsPacks, members and versioned revisions.
locksAdvisory locks per drawable (TTL, heartbeat).
buildsQueue server builds and fetch artifacts.
registryService lane: published packs for websites (service token).
wsWebSocket for presence, locks and live status.

Health & service token

A quick liveness check works without a token via GET /health. Websites talk to the registry via the x-fg-service-token header. Server builds contain no binary YMTs (those are produced in the desktop build) — they serve preview and distribution.