# Sprites Documentation (Full Content)
> This file contains the complete documentation for Sprites, a product by Fly.io that provides persistent, hardware-isolated execution environments for arbitrary code.
Generated: 2026-06-05
Source: https://docs.sprites.dev/
Summary: https://docs.sprites.dev/llms.txt
---
# Getting Started
## Sprites Overview
URL: https://docs.sprites.dev/index.md
Persistent, hardware-isolated execution environments for arbitrary code

Running arbitrary code safely is hard. You need strong isolation, persistent state between runs, and fast startup times—without the complexity of managing infrastructure yourself.
Sprites are persistent, hardware-isolated Linux environments for running arbitrary code. It's like having a small, stateful computer you can spin up on demand. Unlike serverless functions, Sprites keep their filesystem and memory between runs. They're useful for things like AI agents, persistent development environments, or user-submitted code.
*Watch: A 3-minute introduction to Sprites—see how to create, execute commands, and manage persistent environments.*
## What makes Sprites different?
| Feature | Serverless Functions | Sprites |
|---------|----------------------|---------|
| **State** | Ephemeral | **Persistent** ✓ |
| **Filesystem** | Read-only or temporary | **Full ext4** ✓ — persists between runs |
| **Startup** | Cold starts (100ms–seconds) | **Instant** ✓ — wake from hibernation |
| **Billing** | Per-invocation | **Per-second** ✓ — compute free when idle |
| **Environment** | Fixed container image | **Full Linux** ✓ — install any tools |
| **Isolation** | Container-level | **Hardware-level** ✓ — dedicated microVM |
## Use Cases
Sprites are ideal for:
- **AI Code Execution** - Running code generated by language models (like Claude Code) in isolated, secure environments
- **Untrusted Code** - Isolating and executing user-submitted code safely without risking the rest of your system
- **Development Environments** - Building persistent development environments that maintain state between sessions
- **Long-lived Services** - Hosting services that go idle automatically and resume on request
- **CI/CD Tasks** - Testing code against live git repositories with full environment access
## Core Concepts
### Persistence
Every Sprite has a persistent, standard ext4 filesystem. During execution, data is written to fast NVMe storage. When the Sprite goes idle, that data is backed up to durable object storage and automatically restored when it wakes up.
### Automatic Idle Behavior
Sprites become `warm` immediately when idle, and may eventually go `cold`. While idle, there are no compute charges and your full filesystem is preserved. The Sprite wakes on the next request—`warm` Sprites resume quickly, `cold` Sprites take a bit longer.
### HTTP Access
Every Sprite gets a unique URL, making it easy to expose web services or APIs running inside, without a lot of extra setup.
## Next Steps
- [Quickstart](https://docs.sprites.dev/quickstart) - Install the CLI, create your first Sprite, and explore the features
- [CLI Reference](https://docs.sprites.dev/cli/commands) - Review the complete command documentation
---
## Quickstart
URL: https://docs.sprites.dev/quickstart.md
Get up and running with Sprites in 5 minutes

Sprites are cloud VMs that feel like persistent dev environments. You can run commands, install packages, create files—and everything stays exactly how you left it. Unlike containers that reset each time, Sprites remember your entire filesystem between sessions.
The magic: when you're not using your Sprite, it goes to sleep. Send a command or HTTP request, and it wakes up instantly. Everything is right where you left it.
This guide will walk you through creating your first Sprite. In just a few minutes, you'll have a persistent development environment that responds to HTTP traffic and remembers everything between runs.
## Install the CLI
Install with our install script (macOS/Linux):
```bash
curl -fsSL https://sprites.dev/install.sh | sh
```
This script auto-detects your platform, verifies checksums, and installs the latest Sprite CLI to `~/.local/bin`.
For Windows or manual installation, see [CLI Installation](https://docs.sprites.dev/cli/installation).
Verify installation:
```bash
sprite --help
```
## Authenticate
Sprites uses your Fly.io account for authentication:
```bash
sprite org auth
```
This opens a browser window to authenticate with Fly.io.
If authentication fails, try running `fly auth logout` followed by `fly auth login` first, then retry `sprite org auth`.
## Create Your First Sprite
```bash
sprite create my-first-sprite
```
This creates a new Sprite with default configuration, running and ready to accept commands.
Set it as your active Sprite to avoid adding `-s my-first-sprite` to every command:
```bash
sprite use my-first-sprite
```
Use `sprite list` to see all your Sprites, or `sprite destroy -s my-first-sprite` when you're done with one. You can have multiple Sprites running simultaneously—each with its own isolated environment.
## Run Commands
Execute commands in your Sprite:
```bash
# Run a simple command
sprite exec -- echo "Hello, Sprites!"
# Run multiple commands
sprite exec -- bash -c "cd /tmp && ls -la"
# Open an interactive shell
sprite console
```
**Auto-wakeup magic**: When you're not using your Sprite, it shuts down to save resources. When you run a command or hit its URL, it wakes up instantly—like magic. No manual starting or stopping required.
## See Persistence in Action
Your Sprite comes pre-configured with common development tools (Node.js, Python, Go, Git, and more). Here's the magic: **everything you install or create persists between commands**.
### Check available runtimes
See what's already installed and ready to use:
```bash
sprite exec -- bash -c "node --version && python3 --version && go version"
```
### Install a package
Install dependencies just like you would locally—they'll stick around:
```bash
sprite exec -- pip install requests
```
### Create and read files
Files you create persist across sessions. Write once, read anytime:
```bash
# Create a file
sprite exec -- bash -c "echo 'Hello from my persistent Sprite!' > /home/sprite/greeting.txt"
# Disconnect, get coffee, come back later...
# Everything is still there!
sprite exec -- cat /home/sprite/greeting.txt
sprite exec -- python -c "import requests; print(requests.__version__)"
```
Unlike containers that reset on each run, your Sprite keeps your installed packages, files, and entire filesystem intact.
## Start a Web Server
Every Sprite has a unique HTTP URL and can serve traffic. This makes it perfect for testing APIs, hosting prototypes, or running background services.
### Serve HTTP
First, get your Sprite's public URL:
```bash
sprite url
```
Then start a simple Python server:
```bash
sprite exec -- python -m http.server 8080
```
Visit the URL in your browser—you'll see Python's directory listing page. Press `Ctrl+C` to stop the server when you're done. Your Sprite automatically routes HTTP traffic to port 8080 and wakes up to handle requests.
By default, your Sprite's URL requires authentication. To make it publicly accessible, run:
```bash
sprite url update --auth public
```
The `default` auth mode requires a Sprite token with Bearer authentication.
### Test on-demand wake-up
Here's where it gets cool. Close your terminal, wait a minute, then visit the URL again. Your Sprite wakes up automatically to serve the request. That's the magic of on-demand wakeup—no manual starting or stopping required.
---
**You've got a working Sprite!** You've created a persistent environment, installed packages, created files, and served HTTP traffic—all of which will be there next time you connect. Try cloning a repo, running a build, or deploying a tiny HTTP service to see what else you can do.
## Using the SDKs
The CLI is perfect for day-to-day development, but if you're building tools, automation workflows, or integrating Sprites into your application, the SDKs give you programmatic control. Use them to dynamically create environments, orchestrate workloads, or embed Sprites into your product.
**JavaScript:**
```javascript
import { SpritesClient } from '@fly/sprites';
const client = new SpritesClient(process.env.SPRITE_TOKEN);
const sprite = await client.createSprite('my-sprite');
// Execute a command
const result = await sprite.execFile('python', ['-c', "print('hello')"]);
console.log(result.stdout);
// Stream output from long-running commands
const cmd = sprite.spawn('bash', ['-c', 'for i in {1..10}; do date +%T; sleep 0.5; done']);
for await (const line of cmd.stdout) {
process.stdout.write(line);
}
await sprite.delete();
```
**Go:**
```go
package main
import (
"context"
"fmt"
"io"
"os"
sprites "github.com/superfly/sprites-go"
)
func main() {
ctx := context.Background()
client := sprites.New(os.Getenv("SPRITE_TOKEN"))
sprite, _ := client.CreateSprite(ctx, "my-sprite", nil)
defer client.DeleteSprite(ctx, "my-sprite")
// Execute a command
cmd := sprite.Command("python", "-c", "print('hello')")
output, _ := cmd.Output()
fmt.Println(string(output))
// Stream output from long-running commands
cmd = sprite.Command("bash", "-c", "for i in {1..10}; do date +%T; sleep 0.5; done")
stdout, _ := cmd.StdoutPipe()
cmd.Start()
io.Copy(os.Stdout, stdout)
cmd.Wait()
}
```
**Elixir:**
```elixir
client = Sprites.new(System.get_env("SPRITE_TOKEN"))
{:ok, sprite} = Sprites.create(client, "my-sprite")
# Execute a command
{output, 0} = Sprites.cmd(sprite, "python", ["-c", "print('hello')"])
IO.puts(output)
# Stream output from long-running commands
sprite
|> Sprites.stream("bash", ["-c", "for i in {1..10}; do date +%T; sleep 0.5; done"])
|> Stream.each(&IO.write/1)
|> Stream.run()
Sprites.destroy(sprite)
```
---
## Next Steps
- [Working with Sprites](https://docs.sprites.dev/working-with-sprites) - Sessions, ports, persistence, and everything beyond the basics
- [CLI Reference](https://docs.sprites.dev/cli/commands) - Complete command-line documentation
---
## Working with Sprites
URL: https://docs.sprites.dev/working-with-sprites.md
Beyond the basics—sessions, ports, persistence, and everything you need to build real stuff

After you've made it through the [Quickstart](https://docs.sprites.dev/quickstart), you've got a working Sprite and a basic idea of how to use it. This guide picks up from there: how to run commands, manage sessions, keep processes alive, and make sure your environment stays consistent over time. The first half covers everything you need to build and deploy real stuff. The rest is there when you're ready to go deeper.
---
## Running Commands and Sessions
Sprites give you three main ways to interact:
### `sprite exec` – One-off commands and automation
Run a single command, wait for it to finish, get the output. Perfect for scripts, package installs, or quick checks.
```bash
sprite exec -- ls -la
sprite exec -- npm install express
sprite exec --tty -- vim
```
- Blocks until the command completes
- Returns stdout/stderr
- Use for automation or scripting
### `sprite console` – Interactive shell (like SSH)
Opens a full terminal session so you can explore, debug, or run multiple commands.
```bash
sprite console
# Inside:
# $ cd /home/sprite && ls -la && vim myfile.txt
```
- TTY enabled
- Stays open until you exit
- Use for manual work or debugging
### Sessions – Keep things running
All TTY sessions are automatically detachable. Start a command, disconnect with `Ctrl+\`, and reattach later. Great for dev servers, long builds, or background processes.
```bash
sprite exec --tty -- npm run dev # start a TTY session
# Press Ctrl+\ to detach
sprite sessions list # list running sessions
sprite s ls # short form list sessions
sprite sessions attach # reattach to session
sprite sessions kill # kill session
```
---
## Sprite Lifecycle: Idle Behavior and Persistence
When activity stops, Sprites immediately become `warm`. Over time they may transition to `cold`. `warm` Sprites resume quickly; `cold` Sprites take longer to wake. That means:
### What Persists (and What Doesn't)
**Filesystem persists**: All files, installed packages, git repos, databases—everything on disk stays intact
**RAM doesn't persist**: Running processes stop, in-memory data is lost
**Network config persists**: Open ports, URL settings, SSH access all remain configured
This means you can install dependencies once and they're there forever. But if you're running a web server, it'll need to restart when the Sprite wakes up.
### Wake-up Behavior
Wake-up is fast:
- ~100–500ms for normal wakes
- 1–2s on cold starts
When a request hits your Sprite's URL, it wakes automatically. To make sure your web server is ready to handle that request, use **Services** — processes that auto-restart whenever your Sprite wakes up:
```bash
sprite-env services create my-server --cmd node --args server.js
```
Services survive hibernation. TTY sessions don't — they're great for interactive work and debugging, but any process started with `sprite exec` or `sprite console` stops when the Sprite sleeps.
### Idle Detection
Your Sprite stays awake while there's activity, and sleeps when there isn't. Activity includes:
- Active exec/console commands
- Open TCP connections (like your app's URL)
- Running TTY sessions
- Active Services with open connections
---
## Networking: URLs and Port Forwarding
Every Sprite gets a URL: `https://.sprites.app`
### HTTP Access
```bash
sprite url # see URL
sprite url update --auth public # make public
sprite url update --auth default # make private again
```
- Routes to port 8080 by default (or first HTTP port opened)
- Wakes the Sprite on request — pair with a [Service](#wake-up-behavior) so your server is ready to handle it
- Private by default (auth token required)
**Security note**: Public URLs expose your Sprite to the internet. Only use public mode for demos, webhooks, or non-sensitive work.
### Port Forwarding
```bash
sprite proxy 5432 # access Sprite's port 5432 at localhost:5432
sprite proxy 3001:3000 # map local 3001 to remote 3000
sprite proxy 3000 8080 5432 # forward multiple ports
```
Use for database access, dev tools, or private ports. Press `Ctrl+C` to stop forwarding.
### Port Conflicts
If a local port is already in use, you'll get an error. Solutions:
1. **Choose a different local port**: `sprite proxy 3001:3000` forwards local 3001 to remote 3000
2. **Stop the conflicting process**: Find what's using the port with `lsof -i :3000` (macOS/Linux)
3. **Kill the old proxy session**: If you have an old proxy still running, stop it first
---
## Your Environment
Sprites run Ubuntu 25.10 with common tools preinstalled:
- **Languages**: Node.js, Python, Go, Ruby, Rust, Elixir, Java, Bun, Deno
- **AI/CLI Tools**: Claude CLI, Gemini CLI, OpenAI Codex, Cursor
- **Utilities**: Git, curl, wget, vim, and common dev tools
### Filesystem Basics
- **`/home/sprite/`** — your home directory, put your stuff here
- **`/home/sprite/.local/`** — for local binaries and user-installed tools
- **`/opt/`** — good for standalone applications
- **`/var/`** — for databases and application state
Install packages like you would locally:
```bash
sprite exec -- pip install pandas numpy
sprite exec -- npm install -g typescript
sprite exec -- cargo install ripgrep
```
They persist across hibernation. No rebuilds needed.
**Storage space**: Each Sprite has 100 GB of persistent storage. Check usage with:
```bash
sprite exec -- df -h
```
---
## Managing Sprites
### Set Active Sprite
```bash
sprite use my-sprite
# Now all commands target this sprite
sprite exec -- echo "hello world"
```
### List and Filter
```bash
sprite list
sprite list --prefix "dev-"
```
### Destroy
```bash
sprite destroy -s my-sprite
```
**Destruction is irreversible!** All data is permanently deleted: files, packages, checkpoints. No undo.
---
## Checkpoints
Snapshot your Sprite's filesystem so you can roll back later.
```bash
sprite checkpoint create
sprite checkpoint create --comment "before upgrade"
sprite checkpoint list
sprite restore
```
Use before risky changes, upgrades, or experiments.
**What gets saved:**
Entire filesystem (all files, installed packages, databases)
File permissions and ownership
Running processes (they stop during checkpoint creation)
In-memory state
**Good to know:**
- Checkpoints count against your storage quota
- Restoring replaces the entire filesystem—changes since the checkpoint are lost
- Creation takes 10–30 seconds depending on data size
---
## Optional: Going Deeper
These features are useful once you're comfortable.
### Mounting Filesystem Locally
Use SSHFS to mount your Sprite and edit files with your local tools.
Sprites don't expose SSH directly—you'll need to install an SSH server on your
Sprite and tunnel the connection through `sprite proxy`. This keeps your Sprite
secure while still allowing local filesystem access.
**Prepare an SSH server on your Sprite:**
```bash
# Install OpenSSH
sudo apt install -y openssh-server
# Create a service to automatically start it
sprite-env services create sshd --cmd /usr/sbin/sshd
```
**Install SSHFS on your local machine:**
```bash
# macOS
brew install macfuse sshfs
# Ubuntu/Debian
sudo apt-get install sshfs
# Fedora/RHEL
sudo dnf install fuse-sshfs
```
**Authorize your SSH public keys:**
```bash
sprite exec -- mkdir -p .ssh
cat ~/.ssh/id_*.pub | sprite exec -- tee -a .ssh/authorized_keys
```
**Add this helper to your shell config:**
```bash
# Add to ~/.zshrc or ~/.bashrc
spritemount() {
local sprite_name="$1"
local mount_point="/tmp/sprite-mount"
mkdir -p "$mount_point"
sshfs -o reconnect,ServerAliveInterval=15,ServerAliveCountMax=3 \
-o ProxyCommand="sprite proxy -s %h -W 22" "sprite@$sprite_name:" \
"$mount_point"
cd "$mount_point" || return 1
}
# Mount the sprite with "spritemount my-sprite"
```
**Unmount when done:**
```bash
umount /tmp/sprite-mount
# macOS may need: diskutil umount /tmp/sprite-mount
```
### Common Error Scenarios
**Connection errors:**
- Check auth: `sprite org auth`
- Verify Sprite exists: `sprite list`
- Wait a moment and retry
**Timeout errors:**
- Be patient on first wake-up (1–2 seconds)
- Check if command actually needs that long
**Sprite won't wake up:**
- Verify it exists with `sprite list`
- Wait 30 seconds and retry
- [Contact support](https://fly.io/dashboard/support) if persistent
**Storage full:**
- Clean up files: `sprite exec -- bash -c "du -sh /home/sprite/*"`
- Delete old checkpoints
- Create a new Sprite for additional workloads
**Quick debugging:**
```bash
sprite exec -- ps aux # running processes
sprite exec -- df -h # disk space
sprite exec -- free -h # memory usage
```
---
Sprites are meant to feel like your own Linux box in the sky—fast to wake, persistent when you need it, and flexible enough to run whatever weird stack you're building. As you get more comfortable, the advanced features are there when you need them.
---
## Keeping a Sprite Running
URL: https://docs.sprites.dev/keeping-sprites-running.md
Use the Tasks API to hold a Sprite open while an agent or other long-lived process is doing work
A Sprite pauses in two stages. It first goes `warm`: compute billing stops, the VM suspends, and the next inbound HTTP request wakes it in 100–500ms with process state preserved. If the Sprite stays idle long enough it transitions to `cold`. In-memory state is dropped and the next wake takes 1–2s.
Either way, work-in-progress stalls:
- **Open TCP connections drop on the pause, even on warm.** A websocket subscriber, a queue worker's broker connection, an agent's streaming API call: the remote end can't be held across the suspension.
- **Process state pauses on warm, dies on cold.** A warm-paused agent loop resumes its work on wake; a cold one starts over.
The Tasks API stops that pause. Register a task; the Sprite stays up. Delete it (or let it expire); the Sprite is free to pause again.
The model: a task is a hold on the current run. While at least one task is live, the Sprite runs.
## When to use it
- AI coding agents running in the background (Claude Code, Codex) that should still be there when you come back
- Queue workers waiting on jobs from an external broker
- Anything holding outbound connections: websockets, MQTT, replication streams
If you only need a process to come *back* after a pause, use a Service instead. A Service is a long-running process managed by the Sprite runtime: it auto-starts on boot, keeps running through a warm wake (the process is frozen, not terminated), and restarts automatically on a cold wake. Tasks keep the *current* run alive. They compose: a Service launches the agent, the agent registers a task while it's working.
## When not to use it
- A web server with no other long-lived state. A Service plus the URL's wake-on-request handles that without paying for compute while the Sprite is idle.
- A short script. Just run it and the Sprite stays up while it runs.
- Anything you might forget to clean up. A single forgotten task expires on its own, but a forgotten heartbeat loop keeps the Sprite billing until you notice.
## The basics
All Tasks API calls go to the management socket at `/.sprite/api.sock`. Plain HTTP, JSON body, virtual host `sprite`. Inside a Sprite, `sprite-env curl` is a shorthand that handles the socket and host. `sprite-env curl -X POST /v1/tasks -d '...'` is equivalent to the long form below; this guide uses the explicit `curl --unix-socket` form to keep the wire-level mechanics visible.
Create a task. The Sprite is now held in an active state for an hour (the maximum lifetime per task):
```bash
curl --unix-socket /.sprite/api.sock \
-H "Content-Type: application/json" \
-X POST http://sprite/v1/tasks \
-d '{ "name": "agent", "expire": "1h" }'
```
Refresh it before it expires:
```bash
curl --unix-socket /.sprite/api.sock \
-H "Content-Type: application/json" \
-X PUT http://sprite/v1/tasks/agent \
-d '{ "expire": "1h" }'
```
Release the hold when done:
```bash
curl --unix-socket /.sprite/api.sock \
-X DELETE http://sprite/v1/tasks/agent
```
## The heartbeat pattern
A single task expires after at most an hour, so anything that needs to run longer uses a heartbeat: a short expiry, refreshed on a shorter interval, deleted on exit. If the process crashes without cleaning up, the task expires on its own and the Sprite pauses.
```bash
#!/usr/bin/env bash
set -euo pipefail
api() {
curl -s --unix-socket /.sprite/api.sock \
-H "Content-Type: application/json" "$@"
}
trap 'api -X DELETE http://sprite/v1/tasks/agent' EXIT
while true; do
api -X PUT http://sprite/v1/tasks/agent -d '{ "expire": "5m" }' >/dev/null
sleep 60
done
```
A 5-minute expiry refreshed every minute is a reasonable default: four missed heartbeats of margin before the Sprite frees itself, and short enough that a forgotten task doesn't drag on.
## Verifying
```bash
curl --unix-socket /.sprite/api.sock http://sprite/v1/tasks
```
You should see your task with a future `expires_at`. If the list is empty, the Sprite isn't held and will pause on the next idle window.
## Tasks API reference
HTTP/JSON over `/.sprite/api.sock`, virtual host `sprite`.
### List
```text
GET /v1/tasks
```
```json
{ "tasks": [ { "name": "agent", "started_at": "...", "expires_at": "..." } ] }
```
### Get
```text
GET /v1/tasks/:name
```
200 with the task, 404 if missing.
### Create
```text
POST /v1/tasks
{ "name": "my-task", "expire": "1h" }
```
`expire` is seconds (integer) or a duration string (`"30m"`, `"1h"`). Maximum is 1 hour; longer holds require refreshing (see Upsert below). 201 on success, 409 if the name is taken.
### Upsert
Either form works. Creates the task or refreshes its expiry. Returns 200.
```text
PUT /v1/tasks/:name
{ "expire": "1h" }
```
```text
PUT /v1/tasks
{ "name": "my-task", "expire": "1h" }
```
### Delete
```text
DELETE /v1/tasks/:name
```
204 on success, 404 if missing.
---
## Sprite Maintenance
URL: https://docs.sprites.dev/sprite-maintenance.md
Keeping your Sprites happy and healthy
Sprites are persistent. Everything you leave is there when you come back, including the software. Long-living Sprites still need regular updates. This guide walks through some of the common tasks for maintaining the software environment in your Sprite.
---
## OS Upgrades
New Sprites run on a fresh install of Ubuntu 25.10. Older Sprites created with Ubuntu 25.04 must upgrade to continue receiving security and feature updates.
If you don't know what version of Ubuntu your Sprite currently runs, check with:
```bash
cat /etc/*release
```
Fortunately, Sprites are disposable. If your workload doesn't require the Sprite to live long-term, upgrading is easy: destroy the Sprite and create a new one! If you need to copy files over, try [mounting the filesystem locally](https://docs.sprites.dev/working-with-sprites/#mounting-filesystem-locally).
If creating a new Sprite is impractical, you can also upgrade Ubuntu in-place. Keep in mind that bundled software (such as agents and language toolchains) must be upgraded separately. Install the Ubuntu upgrader with:
```bash
sudo apt update
sudo apt install ubuntu-release-upgrader-core
```
With the upgrader installed, follow the [official instructions](https://ubuntu.com/server/docs/how-to/software/upgrade-your-release/) from Ubuntu. In short:
```bash
# upgrade to the latest packages for the current release
sudo apt upgrade
# checkpoint the filesystem
sprite-env checkpoints create --comment "Before Ubuntu upgrade"
# switch to the new release
sudo do-release-upgrade
# upgrade installed packages to the new release
sudo apt full-upgrade
```
---
# Concepts
## Connectors
URL: https://docs.sprites.dev/concepts/connectors.md
Give Sprites access to external APIs without putting provider credentials inside the Sprite
A Sprite that needs to talk to Slack, GitHub, OpenRouter, or another HTTP API runs into the same problem: where does the credential live? Pasting a token into the Sprite's environment works, but every Sprite that needs that token now holds a copy of a long-lived secret. Rotating it means touching every Sprite. Auditing access means reading process environments. Revoking it for one Sprite means revoking it for all of them.
Connectors solve this by storing the credential once, in your organization, and routing API calls through the Sprites gateway. Sprites never see the token. You decide which Sprites can use a connector and which provider endpoints they can reach.
## How it works
A connector has three pieces:
1. **A credential** — an OAuth token, your own API key, or a managed provider that's part of your Sprites plan. Stored encrypted in your organization's database. The token itself is never returned by the API.
2. **An access policy** — rules that decide which of your Sprites may use the connector, and which provider paths they may reach. **Deny-by-default**: a connector with no policy refuses every Sprite.
3. **A gateway endpoint** — `https://api.sprites.dev/v1/gateway///`. A Sprite calls this URL; Sprites authenticates the call using the requesting Sprite's identity, checks the policy, and forwards to the provider with the stored credential attached.
The Sprite never holds the provider token. It only knows the connector ID and the gateway URL.
## Providers
| Provider | Credential style | Notes |
| --- | --- | --- |
| Slack as User | OAuth (user token) | Acts as the user who authorized. |
| Slack as Bot | OAuth (bot token) | Acts as the bot user for that workspace. |
| GitHub | OAuth | Personal or org-scoped depending on requested scopes. |
| OpenRouter | API key (BYOK) **or** managed | Bring your own OpenRouter key, or use the managed OpenRouter connector — billed as part of your Sprites plan. |
| Custom API | API key + base URL | Wraps any token-authenticated HTTP API. |
## Setting up a connector
Most connector management happens in the dashboard. Open your organization, then **Connectors**. You'll see what's already configured, grouped into "Managed by Sprites" (provided by your plan) and "Your connections" (everything you've added yourself).

Click **Add connection** to pick a provider:

What happens next depends on the provider:
- **Slack, GitHub** — you're sent through the provider's OAuth consent screen in a new tab. After you approve, the connector appears in **Your connections** with the scopes you granted.
- **OpenRouter** — choose between bringing your own key (paste it in) or enabling the managed connector with one click.
- **Custom API** — fill in the base URL, the token, where the token goes (header / query string / URL path), and an optional test request to verify it works before saving.
Newly created connectors show up in the list immediately. Until you grant access to at least one Sprite, the connector is dormant — it exists, but no Sprite can use it.
## Configuring access
Click any connector to open its detail page. This is where you control which Sprites can use it and what they can do with it.

The **Access Configuration** card has three switches:
- **Name Prefix** — only Sprites whose name starts with the value you set (e.g., `prod-` matches `prod-1`, `prod-api`).
- **Sprite Labels** — only Sprites that carry **all** the labels you list. Labels are set on the Sprite, not here.
- **Allow all sprites** — every Sprite in the organization. Use sparingly; the dashboard flags it as a broad permission.
You can combine name prefix and labels — both must match. Allow-all overrides the others.
The **Authorized Sprites** list below the configuration will update live as you change the rules, so you can see exactly which Sprites will gain access before you save.
For OAuth connectors, the **Scopes** section shows what the provider granted, and lets you start an "add scopes" flow if you need more permissions later — Sprites builds the right authorize URL with `add_scopes` set.
The **Gateway Playground** at the bottom lets you fire a real request from any of your Sprites against the connector, without writing any code. Pick a Sprite, choose a method, fill in an endpoint, and hit **Send Request**. It's the fastest way to confirm the policy works end-to-end.
## Calling a connector from a Sprite
Once the policy grants access, a Sprite calls the connector by hitting the gateway URL:
```
https://api.sprites.dev/v1/gateway///
```
No `Authorization` header — the gateway identifies the calling Sprite from Fly.io's request signature. Whatever path you append after the connector ID gets forwarded to the provider with the stored credential attached.
For example, if you set up a Slack-as-Bot connector and granted access to Sprites with the `slack` label:
```bash
curl -X POST "https://api.sprites.dev/v1/gateway/slack_bot/conn_def456ghi789/chat.postMessage" \
-H "Content-Type: application/json" \
-d '{"channel": "#general", "text": "Hello from a Sprite"}'
```
The connector detail page shows the exact gateway URL for each connector — you don't need to construct it by hand.
## Endpoint allow- and block-lists
The access policy can also restrict which provider paths a Sprite can reach through the connector. This is useful when a connector has more permission than you want any single Sprite to use, for example, a Slack bot that can post messages should probably not be able to call admin endpoints.
Endpoint allow- and block-lists aren't yet editable from the dashboard. Set them via the [Connectors API](https://sprites.dev/api/connectors).
Patterns are exact paths or trailing-wildcard prefixes:
- `/chat.postMessage` — exactly that path.
- `/chat.*` — anything starting with `/chat.`.
- `/*` — everything (use sparingly).
Two lists work together:
- **Allowed endpoints** — if set, only matching paths are allowed.
- **Blocked endpoints** — matching paths are rejected. **Note: Block rules are checked before allow rules.**
If a path matches both blocked and allowed rules, the request is denied. Use blocks for an explicit deny-list inside a broad allow rule (for example, allow `/*` but block `/admin.*`).
## Managed providers
For some providers, Sprites offers a **managed connector** — you don't bring your own account or API key. Usage is included in your Sprites plan and billed alongside everything else, so there's no separate provider account to set up, no separate invoice, and no key to rotate.
OpenRouter is currently available as a managed connector, giving Sprites in your organization access to the OpenRouter model catalog without you signing up for OpenRouter at all. More managed providers are planned.
A managed connector behaves exactly like one you set up yourself — you still set an access policy, and the Gateway Playground still works against it.

The detail page adds a **Usage** strip at the top showing your spend over the last 24 hours, 7 days, and 30 days. That spend rolls into your Sprites bill.
## Using connectors with a coding agent
Every Sprite ships with the **`sprite-api-gateway`** skill pre-installed for Claude Code, Cursor, Codex, and Gemini. When a coding agent running inside a Sprite needs to talk to an external API, the skill kicks in automatically, so you don't have to teach it any of the URLs above.
Ask in plain language:
> "Post a message to #engineering on Slack saying the deploy finished."
>
> "Open a GitHub issue in my repo titled 'flaky test'."
>
> "List my last 10 OpenRouter completions."
The agent discovers what's available by calling the gateway list endpoint:
```bash
curl -s https://api.sprites.dev/v1/gateway/list
```
The response tells the agent which providers are connected (with their `gateway_base_url` and `usage_snippet`) and which are available but not yet set up (with a `setup_url` to share with you). The agent picks the right connector, builds the call against the gateway, and never sees a raw token.
If the connector exists but is missing scopes the agent needs, the skill points you at a `request_scopes_url` to grant the additional scopes. If no connector exists for the provider, it points you at a `setup_url` to create one.
The practical effect: once an organization admin has set up a Slack or GitHub connector and granted access to the right Sprites, anyone using a coding agent inside one of those Sprites can use the integration in natural language, with no API knowledge required.
The skill is loaded from `~/.claude/skills/sprite-api-gateway/`, `~/.cursor/skills/sprite-api-gateway/`, `~/.codex/skills/sprite-api-gateway/`, and `~/.gemini/skills/sprite-api-gateway/` inside every Sprite. It's part of the Sprite base image, so there's nothing to install.
## Common errors
**`403 forbidden — connection denies all sprites by default`**
The connector has no access policy, or the policy doesn't grant access to the calling Sprite. Open the connector and grant access via labels, name prefix, or allow-all.
**`403 forbidden — endpoint not allowed`**
Either the path matches a blocked endpoint, or an allowed-endpoints list is set and this path isn't on it. Check both lists on the connector's detail page.
**`401 unauthorized` from the gateway**
The request didn't arrive with valid Fly.io identity headers. Gateway calls must originate from inside a running Sprite, not from your laptop or CI.
**OAuth callback returns `failed to exchange authorization code`**
The authorization code was already used or has expired. Restart the connection from the dashboard.
## Automating with the API
Everything in the dashboard is also available as a REST API. Useful when you want to provision connectors as part of an org-setup script, or rotate them programmatically.
### Start an OAuth flow
```bash
curl -X GET "https://api.sprites.dev/v1/oauth/slack/authorize?scopes=chat:write,channels:read" \
-H "Authorization: Bearer $SPRITES_TOKEN"
```
Returns an `authorize_url` and a `state`. Send the user to the URL; on consent, Slack redirects back to the Sprites callback and the connector is created.
### Create an API-key (BYOK) connector
```bash
curl -X POST "https://api.sprites.dev/v1/oauth/connections/api_key" \
-H "Authorization: Bearer $SPRITES_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "provider": "openrouter", "api_key": "sk-or-v1-...", "access_policy": { "allow_all": true } }'
```
### Enable a managed connector
```bash
curl -X POST "https://api.sprites.dev/v1/oauth/connections/provision" \
-H "Authorization: Bearer $SPRITES_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "provider": "openrouter" }'
```
### Update the access policy
```bash
curl -X PUT "https://api.sprites.dev/v1/oauth/connections/" \
-H "Authorization: Bearer $SPRITES_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "access_policy": { "sprite_labels": ["slack"], "allowed_endpoints": ["/chat.*"] } }'
```
### List, inspect, delete
| Operation | Endpoint |
| --- | --- |
| List | `GET /v1/oauth/connections` |
| Get one | `GET /v1/oauth/connections/:id` |
| Delete | `DELETE /v1/oauth/connections/:id` |
Deleting a connector immediately revokes all Sprite access to it. The encrypted credential is destroyed and cannot be recovered — if the connector was OAuth, the user must re-authorize from the dashboard to recreate it.
For the full request and response schemas, see the [API reference](https://sprites.dev/api/connectors).
---
## Services
URL: https://docs.sprites.dev/concepts/services.md
Processes the Sprite runtime starts at boot, restarts when they crash, and can wire to your Sprite's URL
A Sprite pauses when nothing is using it, and processes don't reliably survive the trip. A warm wake resumes them where they left off; a cold boot drops them entirely. Anything you started by hand in a shell, a dev server, a database, an agent, is gone after a cold wake unless something brings it back.
That something is a service. A service is a process the Sprite runtime owns: it starts when the Sprite boots, restarts if it crashes, and can receive the HTTP traffic that hits your Sprite's URL. You define it once. The runtime keeps bringing it back.
## Services and the Sprite lifecycle
What happens to a service depends on how the Sprite went down and how it came back:
- **Warm wake.** The VM was suspended with your process inside it. The process resumes mid-thought; it is not restarted. Wakes take 100–500ms.
- **Cold boot.** Process state was dropped. The runtime starts every service fresh, in dependency order. Wakes take 1–2s.
- **Crash.** A service process that exits on its own gets restarted by the runtime.
- **Stop.** A service you stop explicitly stays stopped until you start it again.
Services don't keep a Sprite from pausing. A Sprite with ten services defined still pauses when it goes idle; the services come back on the next wake. A service that's actively handling HTTP requests counts as activity, the same as any other traffic, but a quiet one doesn't hold the Sprite in an active state. If you need a Sprite to stay up while work finishes, that's a job for the [Tasks API](https://docs.sprites.dev/keeping-sprites-running), not a service.
## Create a service
The `sprite-env` CLI ships in every Sprite and talks to the runtime's management socket. Create a service with a name, a command, and its arguments:
```bash
sprite-env services create web --cmd python3 --args "-m,http.server,3000" --http-port 3000
```
`--cmd` takes the binary only. Arguments go in `--args`, comma-separated.
The command starts the service and streams its first few seconds of output as NDJSON, so a service that dies on startup fails in front of you instead of silently in the background:
```json
{"type":"started","timestamp":1780420274555}
{"type":"complete","log_files":{"combined":"/.sprite/logs/services/web.log","stderr":"/.sprite/logs/services/web.log","stdout":"/.sprite/logs/services/web.log"},"timestamp":1780420276551}
```
The stream watches for 5 seconds by default. Pass `--duration 30s` to watch longer, or `--no-stream` to return immediately.
Confirm it's serving:
```bash
curl localhost:3000
```
### Create options
| Flag | What it does |
|------|--------------|
| `--cmd ` | The executable to run. Required. Binary only, no arguments here. |
| `--args ` | Comma-separated arguments: `--args "-m,http.server,3000"` |
| `--env ` | Comma-separated environment variables: `--env "PORT=3000,DEBUG=1"` |
| `--dir ` | Working directory for the process |
| `--needs ` | Services that must start before this one |
| `--http-port ` | Route the Sprite's URL to this port and auto-start the service on incoming requests |
| `--duration