# artcraft-services is the Rust and TypeScript backend behind the ArtCraft generation product

> artcraft-services holds ArtCraft's HTTP service, generation router, background workers and web frontends in one monorepo, with MySQL, Redis, Elasticsearch and S3-compatible storage behind a column of AI generation providers. It is a commercial product's source, and the last push was on 2026-10-15.

**storytold/artcraft-services** — backend, web frontend, etc.

- Repository: https://github.com/storytold/artcraft-services
- Stars: 1,396 · Forks: 1,398
- Language: Rust
- License: NOASSERTION
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/storytold-artcraft-services

## One monorepo, one product, several frontends

This repository contains ArtCraft's backend, HTTP API, background workers and web frontends, and it is a Rust and TypeScript monorepo with shared API definitions, provider clients, database queries and development tooling.

The split between this repository and the next one is stated at the top. The Tauri desktop application is maintained in storytold/artcraft, along with the product overview, feature demos and desktop downloads. So the desktop app is not here, and neither is the marketing surface for it, which makes this repository the service side of a product whose client lives elsewhere.

The layout inside is the interesting part. There is a crates/ tree holding the Rust side and a frontend/ tree holding the TypeScript side. The crates/ tree has service/web for HTTP services including storyteller_web, service/job for provider workers and media processing, service/plugins for shared billing and service components, api_clients for the ArtCraft API types and clients plus the router and the provider clients, schema for database access and public tokens, lib for shared Rust utilities, and cli for development and operations tools.

The CLI directory is described in the Cargo.toml workspace members and it is a good signal about the maturity of the deployment. There is dev-database-seed and dev-upload-media-file for development, and then in production there is db-backfill, db-backup, db-cleanup, elasticsearch-cli, kubectl-pod-cleanup, lru-delete-models, migration-tool and support-tool.

A database backup command and an Elasticsearch command-line tool are not things you write on day one of a product. They are what a product with data at stake and a support function needs, and the presence of both, as first class binaries, says the service has been running long enough to accumulate operational tooling.

## The request path crosses three processes and four stores

The README includes a mermaid diagram of the generation path, and the shape it draws is the architecture in one picture: clients go to storyteller-web, which routes through artcraft_router to the generation providers, providers call back with webhooks, background workers poll jobs, and both the API and the workers touch MySQL and object storage.

The four numbered steps of a typical generation request make it concrete. The API authenticates the caller, validates the request and input media, and checks the user's access and credits. The generation pipeline calculates the cost, bills the wallet, and uses artcraft_router to build and submit a provider-specific request, and the API records inference jobs in MySQL and returns job tokens to the client. Completion is handled through provider webhooks or polling workers depending on the integration, and results are downloaded into object storage, registered as media files and associated with the completed jobs. Clients then poll job-status endpoints and load the resulting media through CDN URLs.

So there are two completion mechanisms by design. Some providers can push a webhook, and for the ones that cannot, the workers poll. That is a realistic integration detail rather than a stylistic one, and it explains why the workers exist as a separate service tier rather than as a background job inside the API.

The billing step is also in the request path, not after it. The cost is calculated and the wallet billed before the provider call, which means a failed generation still costs the user something, and a successful one is recorded against a wallet rather than against a card. The reusable billing components live under crates/service/plugins, which is why that directory is described as shared billing and service components rather than as plugins in the browser extension sense.

## Four stores, each with a job it does not share

The README's storage table is short and each row is a deliberate choice.

MySQL with SQLx holds accounts, media metadata, inference jobs, wallets and bills. Redis handles caching, rate limiting and job progress. Elasticsearch holds search indexes and queries. And S3-compatible storage or R2 holds uploaded media, generated assets and derived files.

The split of job progress into Redis rather than MySQL is worth noting, because job status is the hottest read in the system. A client polling for the result of a video generation is polling continuously, and serving that from a cache rather than the transactional database keeps the polling load off the store that also has to record wallet transactions.

The SQLx mention matters for the codebase as well. The .sqlx/ directory at the top of the repository is where SQLx keeps its prepared query metadata, and the presence of diesel.toml alongside it suggests the project has used Diesel at some point and now compiles queries ahead of time with SQLx instead of letting the driver build them at runtime. That is a deliberate choice with a real cost, because the checked-in query metadata has to be regenerated whenever a query changes, and a mismatch between the metadata and the schema is a build failure rather than a runtime surprise.

The _database/ directory holds SQL migrations, materialized schemas and search schemas, and the README describes a shared mysql_queries crate under crates/schema/database so that handlers, workers and CLI tools use the same data access layer. That is the kind of sharing that keeps a monorepo from growing three slightly different versions of the same query.

## Two API surfaces with different authentication

The API exposes two generation interfaces, and the split is by caller rather than by feature.

The application endpoints are under /v1/omni_gen/generate/* and use user sessions for image, video, audio, mesh and splat generation. Alongside them, /v1/omni_gen/models/* and /v1/omni_gen/cost/* expose model discovery and cost estimation without requiring a user session, and application job status is under /v1/jobs.

The programmatic endpoints are under /v1/omni_api and use an API key in the Authorization header, providing image and video generation, image, video and audio uploads, and job-status polling. The header is Authorization: Bearer <api-key>, and API access must be enabled for the account.

The example is a video request to POST /v1/omni_api/generate/video, whose response contains an inference_job_token, which the caller polls with GET /v1/omni_api/job_status/job/{token}. The guide in _docs/omni_api covers authentication, request and response bodies, URL inputs and runnable examples.

The model and cost endpoints being available without a session is the part with an engineering purpose. A client rendering a model picker needs the catalogue, and a client showing a price needs the estimate, and neither of those should require the user to be logged in.

The README then points at where to start when adding or tracing an endpoint, and the list is a map of the codebase. Route registration maps URLs and HTTP methods to handlers for generation, media, users, billing, API keys and other service areas. The API definitions crate contains shared Rust request, response and error types. The Rust API client and the TypeScript API library provide client implementations, and the provider clients implement upstream HTTP integrations, with the generation router adapting generation requests and cost estimates across providers.

## The frontend is an Nx workspace with shared generation logic

The frontend directory contains the Nx workspace for React and TypeScript apps and shared libraries. The main web frontends use Vite, with Zustand and signals for state, Three.js for 3D scenes, and shared UI and generation tools.

The table of paths explains the split. frontend/apps/artcraft-webapp is the browser application at app.getartcraft.com. frontend/apps/artcraft-website-next is the product website at getartcraft.com, and it is Next.js, which is the one part of the frontend that is not Vite. frontend/libs/api holds the HTTP clients, API host selection and models. frontend/libs/omni-gen holds shared generation logic. frontend/libs/components holds reusable UI, editors and generation controls. And frontend/libs/tauri-api holds frontend bindings for native desktop commands.

The libraries and apps separation is what lets the same generation logic run in the browser and in the desktop app, which is why omni-gen exists as a library rather than living inside the webapp.

The web apps call the backend through the shared API library, which handles JSON and multipart requests and session credentials. The interesting note is the tauri-api and tauri-utils libraries, which remain because shared web components still import their types, helpers and browser-compatible behaviour, while the native desktop app and libraries used only by that app live in the separate desktop repository. So the split between this repository and the desktop one is not clean, and the leftover bindings are the seams of that split.

The backend side of the frontend contract is the shared API definitions, and there are two implementations of the client, one in Rust and one in TypeScript, generated from or kept in step with the same definitions.

## Where this repository is the wrong thing to depend on

The first limit is the licence. The repository is classified as a custom licence that GitHub cannot categorise, and there is a LICENSE.md at the top. That means you cannot assume MIT, Apache or any other standard terms, and you have to read the file before you build anything on it. It is a commercial product's source, published with a licence that permits reading rather than a general-purpose open source grant.

The second is that this is one company's system, not a framework. The provider clients in crates/api_clients are named for specific vendors, with fal_client, runwayml_client, openart_client, worldlabs_api_client, grok_api_client, muapi_client and others, and the router exists to adapt between them. None of that generalises. There is no library here you can drop into an unrelated product, and the workspace members list shows it, with 20-odd API client crates each tied to a service.

The third is the operational surface. To run this you need MySQL, Redis, Elasticsearch and S3-compatible storage, a set of provider API keys, and enough configuration in the .env file at the top of the tree to wire them together. The CLI binaries for db-backup, db-cleanup and elasticsearch-cli are evidence of that surface. A project that just needs an API for one image provider is using a sledgehammer.

The fourth is that the desktop application is elsewhere. A developer who wants to work on the Tauri app has to work in the other repository, and the leftover tauri-api bindings here are a reminder that the two codebases are entangled in a way the directory split does not fully express.

The fifth is the docs. There is a README, an AGENTS.md, a CLAUDE.md, a ROADMAP.md, docs/ and _docs/, and the _docs/omni_api guide is the one document the README points at for the public API. For a codebase of this size, that is a thin documentation surface, and the mermaid diagram in the README and the route registration file are likely the fastest way to understand the system for anyone who is not already on the team.

## Conclusion

artcraft-services suits a developer joining the ArtCraft team or integrating against its API, and it is not a general purpose open source framework, since the licence is custom and the repository is one company's product rather than a reusable library. Before trying to run it, read the README's storage table and the repository layout carefully, because the deployment needs MySQL, Redis, Elasticsearch and S3-compatible storage at once. Verify first that the provider clients in crates/api_clients are the ones you need, and note that a separate desktop repository holds the Tauri application rather than this one.

## FAQ

### What is artcraft-services?

It is a Rust and TypeScript monorepo holding ArtCraft's backend, HTTP API, background workers and web frontends, with shared API definitions, provider clients, database queries and development tooling. The Tauri desktop application lives in a separate repository, storytold/artcraft.

### What does the ArtCraft HTTP service handle?

The storyteller-web service, built with Rust, Actix Web and Tokio, handles authentication, accounts, media uploads and libraries, generation requests, job status, credits and Stripe billing, and its hosted API uses https://api.storyteller.ai.

### How does a generation request flow through artcraft-services?

The API authenticates the caller and checks access and credits, the pipeline calculates the cost and bills the wallet before the router submits a provider-specific request, jobs are recorded in MySQL and tokens returned to the client, and completion arrives through provider webhooks or polling workers, with results downloaded into object storage and registered as media files.

### What data stores does the ArtCraft backend use?

MySQL with SQLx for accounts, media metadata, inference jobs, wallets and bills, Redis for caching, rate limiting and job progress, Elasticsearch for search indexes and queries, and S3-compatible storage or R2 for uploaded media, generated assets and derived files.

### How do the application and programmatic APIs differ?

The application endpoints under /v1/omni_gen use user sessions for generation, with model and cost endpoints available without a session. The programmatic endpoints under /v1/omni_api use an API key in the Authorization header as a bearer token, and a programmatic request returns an inference_job_token that is polled with GET /v1/omni_api/job_status/job/{token}.

## Sources

- [Issues](https://github.com/storytold/artcraft-services/issues)
- [README](https://github.com/storytold/artcraft-services/blob/main/README.md)
- [storytold/artcraft-services on GitHub](https://github.com/storytold/artcraft-services)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/storytold-artcraft-services
