---
name: slai
description: Build and run multi-step AI pipelines (text, image, audio, video, document) on slai through its REST API or MCP server. Use when the user wants to generate or transform media, chain AI models, run one of their slai pipelines or a Discover app, or check their slai balance.
---

# slai

slai chains AI models into pipelines that run entirely server-side and are paid from the owner's balance. You act as the owner of the key: pipelines you create are theirs, runs spend their balance, and everything you do shows up in their slai app. Money: every price, cost and balance is in US dollars — `price_usd`, `delta_usd`, `total_cost_usd`, `balance_usd`, `amount_usd` are decimal dollar amounts. There is no other unit.

## Setup

- Base URL: `https://slai-api.flybywire.se`
- Credential: the environment variable `SLAI_API_KEY` (an `slai_…` key made under Settings → API & agents in the slai studio, or Settings → API keys in the app). Send it on every request as `Authorization: Bearer $SLAI_API_KEY`. If it is unset, ask the owner for a key instead of guessing.
- Prefer MCP when the client supports it — the same operations as tools, with sign-in in the browser and no key to paste:
  `claude mcp add --transport http slai https://slai-api.flybywire.se/mcp`
  (or with a key: `claude mcp add --transport http slai https://slai-api.flybywire.se/mcp --header "Authorization: Bearer $SLAI_API_KEY"`)
- Full reference: https://slai-api.flybywire.se/docs

## Workflow

1. Check the balance: `GET /v1/balance` → `balance_usd`, `topup_url`.
2. Find models: `GET /v1/models?view=summary&output=image` (filters: `provider`, `input`, `output`, `available=true`); `GET /v1/models/{id}` for one model's options and prices.
3. Create a pipeline: `POST /v1/pipelines` with the shape below. A 422 says exactly what to fix.
4. Read its run contract: `GET /v1/pipelines/{id}/spec` — inputs a run must send, options open to runners, outputs, the price (total_cost_usd), an example request.
5. Run it: `POST /v1/pipelines/{id}/executions` with `{"inputs": {...}, "step_options": {...}}` → 202 with the execution.
6. Wait for the result: `GET /v1/executions/{id}?wait=60` holds until the run has completed or failed (up to 120 s) and returns the latest snapshot; results sit under `results[step_id].outputs`.

Or let slai's builder compose the pipeline from a brief: `POST /v1/pipelines/draft` with `{"brief": "...", "test_budget": 0}` → 202 with status `drafting`; `GET /v1/pipelines/{id}?wait=60` until status is `ready` (or `draft_failed` with `draft_error`), then review and adjust with `PUT`.

```bash
curl "https://slai-api.flybywire.se/v1/models?view=summary&output=image" -H "Authorization: Bearer $SLAI_API_KEY"
curl -X POST https://slai-api.flybywire.se/v1/pipelines -H "Authorization: Bearer $SLAI_API_KEY" -H "Content-Type: application/json" -d @pipeline.json
curl -X POST https://slai-api.flybywire.se/v1/pipelines/PIPELINE_ID/executions -H "Authorization: Bearer $SLAI_API_KEY" -H "Content-Type: application/json" \
  -d '{"inputs": {"brief": {"type": "text", "text": "a lighthouse at dawn"}}}'
curl "https://slai-api.flybywire.se/v1/executions/EXECUTION_ID?wait=60" -H "Authorization: Bearer $SLAI_API_KEY"
```

## Pipeline shape

```json
{
  "name": "Poster maker",
  "description": "A one-line brief becomes a poster",
  "steps": [
    {
      "step_id": "write",
      "name": "Write the image prompt",
      "model_id": "google-gemini-pro",
      "input_sources": {
        "text": {
          "pre_input": "Write one vivid, specific image prompt for this brief:\n",
          "sources": [{"step_id": "", "output_key": "brief"}]
        }
      }
    },
    {
      "step_id": "paint",
      "name": "Paint it",
      "model_id": "openai-image",
      "input_sources": {
        "text": {"sources": [{"step_id": "write", "output_key": "text"}]}
      },
      "options": {
        "size": {"ask": true, "default": "1024x1536", "note": "Pick the canvas"}
      }
    }
  ],
  "input_meta": {
    "brief": {"label": "Brief", "help": "What should the poster show?"}
  }
}
```

- Steps run in array order. Each step has step_id (^[a-z0-9_-]{1,50}$), model_id (a catalog id), input_sources, and optional name and options.
- input_sources has one entry per input TYPE the model declares — the key is the type name ("text", "image", "audio", "video", "document"), the value is {"sources": [...]} with an optional "pre_input" (text inputs only: hidden text placed before the joined sources).
- A source is exactly one of: an earlier step's output {"step_id": "<earlier step>", "output_key": "<that step's output type>"}; a run input the caller supplies at execution time {"step_id": "", "output_key": "<input name>"}; a fixed text value {"value": {"type": "text", "text": "..."}} (bake prompts and instructions in here); or a baked file {"value": {"type": "image|audio|video|document", "file_id": "..."}} — a file the account uploaded that every run reuses (a driving clip for motion transfer, a style frame, a logo), so runners never supply it. Only reference file_ids that exist on the account.
- Types line up exactly: an image input needs an image source. Text inputs may fan in several sources (joined in order); binary inputs take one source unless the model's multi_inputs allows more (then order matters, e.g. clips join in wiring order).
- Run input names are shared identity: two steps wiring the same name receive the same caller value; different names mean separate values. Name inputs after what they are ("photo", "clip", "brief"), never a different type than the input they feed.
- Options: {"value": "<canonical string>"} fixes an option; {"ask": true, "default": "...", "note": "..."} opens it to runners, who pass it as step_options[step_id][key] at run time. Only option keys the model lists exist; get_model shows them with choices, ranges and what each does to the price.
- input_meta gives run inputs a label, help text and (text inputs only) a fixed list of choices, keyed by input name.
- deliverables names what a run gives: a list of {"name", "step_id", "output"} promoting step outputs to results by name (e.g. hero → step "page" output "web"); runs return them under deliverables, the spec lists them, and screens show them first. Set them on every pipeline whose result is more than its last step's output.
- Two output kinds exist only as step outputs, never as uploads: web (one self-contained HTML page: its url renders it in a sandbox, its download_url is the file) and archive (a zip, download only). tool-web-page makes both from a template baked into its template option — placeholders {{text}}, {{image}}, {{video}}, {{audio}}, {{document}} and {{video.mime}} and the like, nothing executable — so a pipeline can hand over a piece of a website, not just a clip.
- Keep pipelines lean — no steps the goal does not need — and price with get_pipeline_spec (total_cost_usd) before running.

## Values and files

Every input and output is `{"type": "text", "text": "..."}` or `{"type": "image|audio|video|document", "file_id": "..."}`. Upload binary inputs first (images, audio, video, PDF, DOCX):

```bash
curl -X POST https://slai-api.flybywire.se/v1/files -H "Authorization: Bearer $SLAI_API_KEY" -F "file=@photo.jpg"
```

and pass the returned `id` as `file_id` — as a run input, or baked into a step as a fixed value (`{"value": {"type": "video", "file_id": "..."}}`) so every run reuses it, the way a driving clip feeds a motion-transfer step. Over MCP, files on disk go through `begin_upload` (returns a file id and a presigned PUT url), a plain `curl -sS -T file '<upload_url>'` from the shell, then `finish_upload`; the bytes never pass through the agent. `upload_file` is for small files as base64 or a public https url. Over REST the same two steps are `POST /v1/files/uploads` and `POST /v1/files/{id}/finish`. Binary outputs come back with a presigned `url` that expires after about an hour; download promptly or re-read the execution for fresh links.

## Building a pipeline with a person

When the person wants a new pipeline made, work under the build contract and keep the record as a build (`POST /v1/builds`, entries at `POST /v1/builds/{id}/entries`, `POST /v1/builds/{id}/finalize`; over MCP: start_build, log_build, get_build, finalize_build, and the `build_pipeline` prompt carries the contract). The short form: frame the brief and wait for a yes; propose numbered stages, riskiest first, and wait for a yes; build and run one stage at a time as a scratch pipeline on the person's real sample inputs, log the ids and cost, show the outputs, ask one question (approve, change or stop) and wait; assemble the approved stages into the real pipeline and run it once end to end; finalize with the pipeline id and delete the scratch pipelines. The person judges outputs; you measure and report. Say the cost before every run.

## Balance and funds

`GET /v1/balance` returns `balance_usd` and `topup_url` (add `?amount_usd=25` to preselect an amount). A run or draft the balance cannot cover is refused with 402 before anything starts; the error body carries `topup_url`. You cannot add funds — give the owner the link, then retry.

## Reference

| Route | Purpose |
|---|---|
| `GET /v1/users/me` | The account behind the key. |
| `GET /v1/balance` | Balance in dollars, top-up link and offer. |
| `GET /v1/ledger` | Balance and the last 100 movements. |
| `GET /v1/models` | The catalog; filters and `view=summary`. |
| `GET /v1/models/{id}` | One model with its full option schema. |
| `POST /v1/pipelines` | Create. |
| `GET /v1/pipelines` | List, newest first; `view=summary` omits steps. |
| `GET /v1/pipelines/{id}` | Read; `?wait=` while drafting. |
| `PUT /v1/pipelines/{id}` | Full replace, same validation as create. |
| `DELETE /v1/pipelines/{id}` | Delete; past runs stay readable. |
| `POST /v1/pipelines/draft` | Builder draft from a brief (202, then poll). |
| `GET /v1/pipelines/{id}/spec` | The run contract. |
| `POST /v1/pipelines/{id}/executions` | Start a run (202). |
| `GET /v1/executions` | Runs, newest first; `?pipeline_id=`. |
| `GET /v1/executions/{id}` | Full state with results; `?wait=` until settled. |
| `GET /v1/executions/{id}/events` | SSE stream of full snapshots. |
| `POST /v1/files` | Upload (multipart `file`). |
| `POST /v1/files/uploads` | Begin a direct upload: `{filename|mime, size_bytes}` → file id and a presigned PUT url. |
| `POST /v1/files/{id}/finish` | Finish a direct upload after the PUT: the file is inspected and becomes ready. |
| `GET /v1/files/{id}` | Metadata with a fresh download url. |
| `POST /v1/builds` | Open a build: `{name, brief}`. |
| `GET /v1/builds` | Builds, open ones first. |
| `GET /v1/builds/{id}` | A build with its journal; file refs carry fresh urls. |
| `POST /v1/builds/{id}/entries` | Append a journal entry: `{kind, author, title, body, stage, verdict, refs}`. |
| `POST /v1/builds/{id}/finalize` | Close the build with `{pipeline_id}`. |
| `POST /v1/builds/{id}/abandon` | Close the build without a pipeline. |
| `GET /v1/publications` | Discover apps; `?query=&sort=stars|runs&filter=starred`. |
| `GET /v1/publications/{id}` | One app; `/spec` for its run contract. |
| `POST /v1/publications/{id}/execute` | Run an app on your balance (202). |
| `POST /mcp` | The MCP server (JSON-RPC over Streamable HTTP). |

Pagination is `?limit=&offset=`; ids are UUIDs. Key-authed requests are budgeted at 120 per minute (burst 60). Errors are `{"error": "...", "details": "..."}`: 401 bad key, 402 balance, 404 unknown or not yours, 409 still drafting, 422 does not validate, 429 slow down (`Retry-After`).

## MCP tools

Connected over MCP, the same operations are tools:

- `whoami` — The account this connection belongs to: email, username and balanceUsd. get_balance adds the top-up link.
- `list_models` — The model catalog: id, provider, input and output types, price (price_usd + price_unit), typical response time, availability and option keys. Filter by input/output type to find a capability (e.g. output=video). Use get_model for a model's option schema and long description.
- `get_model` — One model in full: what it is good for, its inputs and outputs, and every option with type, default, choices or range, and how each choice changes the price (delta_usd).
- `list_pipelines` — The account's pipelines, newest first: id, name, description, status (ready, drafting, draft_failed), step count, cost per run. Set detailed for the full steps.
- `get_pipeline` — One pipeline with its steps, wiring, options, input_meta, status and cost. While a draft is in flight (status drafting), wait_seconds holds until it is ready or has failed; draft_events narrates the builder's progress.
- `create_pipeline` — Save a new pipeline. The body is validated against the catalog: every declared input wired, types matching, sources referencing earlier steps only, options known to the model. A 422 says exactly what to fix. The pipeline is ready to run on success.
- `update_pipeline` — Replace a pipeline's name, description, steps and input_meta in full (there is no partial update — send the whole definition). Same validation as create_pipeline. Refused while a draft is still in flight.
- `delete_pipeline` — Delete a pipeline. Its past runs stay readable under their execution ids.
- `draft_pipeline` — Have slai's builder compose a pipeline from a plain-language brief — useful when the goal is clear but the model choice and prompt craft are not. Asynchronous: returns the pipeline in status drafting; poll get_pipeline with wait_seconds until it is ready, then review the steps and adjust with update_pipeline. With a test_budget_usd the builder runs real test generations while drafting (the budget is reserved up front, the unused part refunded); 0 drafts in one shot without spending.
- `get_pipeline_spec` — The run contract of a pipeline: the inputs a run must send (name, type, choices), the options open to runners, the outputs, the price (total_cost_usd), and an example request body. Read this before run_pipeline.
- `run_pipeline` — Start a run. The whole run's price is reserved from the owner's balance up front (402 if short) and the run continues server-side. By default the call waits up to 30 seconds for the run to finish and returns the execution: status, per-step results (text inline, binary outputs as file_id plus a download url), error. If it is still running, keep polling with get_execution.
- `get_execution` — One run's state: status (pending, running, completed, failed), current step, per-step results and errors, inputs. wait_seconds holds the call until the run settles. Download urls on binary outputs expire after about an hour; read again for fresh ones.
- `list_executions` — Recent runs, newest first, optionally for one pipeline: status, timing, step progress (no inputs or results — use get_execution).
- `start_build` — Open a build: the journal of a pipeline made stage by stage under the build contract (the build_pipeline prompt has the contract; follow it). Returns the build with its id. The name becomes the pipeline's name.
- `log_build` — Append one entry to a build's journal. kind: frame (the restated brief), plan (the numbered stages), stage (one scratch pipeline built and run: give stage, and refs with pipeline_id, execution_id, inputs, outputs, cost_usd), review (the person's verdict: author person, stage, verdict approve|change|stop, their words in body), assemble (the final pipeline run end to end), note (anything learned). Log before you ask the next question; every id you mention goes in refs.
- `get_build` — A build with its whole journal, file references carrying fresh urls. Read it to resume a build: continue from the last entry.
- `list_builds` — The account's builds, open ones first.
- `finalize_build` — Close a build with the pipeline it produced. Do this after the person approved the assembled pipeline's end-to-end run, then delete the scratch pipelines.
- `abandon_build` — Close a build without a pipeline, when the person stops. The journal stays readable.
- `begin_upload` — Start a direct upload of a file on disk: returns a file id and a presigned PUT url, valid 30 minutes. Send the bytes there yourself from the shell (curl -sS -T photo.jpg '<upload_url>') — nothing passes through the tool call — then call finish_upload with the file id. This is the way to upload photos and videos; images, audio and documents up to 25 MB, video up to 200 MB. Say what the file is with a filename that has a known extension (jpg, png, webp, gif, mp3, m4a, wav, webm, ogg, mp4, mov, pdf, docx) or a mime type.
- `finish_upload` — After begin_upload and your PUT: the bytes are inspected (type sniffed, size checked) and the file becomes ready, usable as a run input or a baked step value. Returns the file like get_file. Harmless to call again on a finished file; a 409 means nothing has been PUT to the url yet.
- `upload_file` — Upload a small file or a public link. Send the bytes as base64 (up to 25 MB decoded, but every byte travels inside this call, so keep it to small files) or a public https url the server fetches (images, audio and documents up to 25 MB, video up to 200 MB); exactly one of the two. For a photo or video on disk use begin_upload instead. The type is sniffed from the bytes — set mime when the bytes alone are ambiguous (m4a audio). Returns the file: id (use it as file_id), kind, mime_type, size_bytes and a download url.
- `get_file` — Metadata and a fresh download url (valid about an hour) for a file the account uploaded or a run produced. Upload with begin_upload + finish_upload (files on disk) or upload_file (small files, links), or through REST: POST /v1/files as multipart with the same key. A 409 names a pending upload that was never finished.
- `list_apps` — Published apps on slai's Discover shelf — pipelines other users made public, runnable on the caller's balance. Search by words in the title, tagline or author; sort by newest (default), stars or runs.
- `get_app` — One published app with its steps and inputs.
- `get_app_spec` — The run contract of a published app: required inputs, open options, outputs, the price (total_cost_usd) and an example request. Read this before run_app.
- `run_app` — Run a published app on the caller's balance, exactly like run_pipeline: reserved up front, continues server-side, waits up to 30 seconds by default, then get_execution.
- `get_balance` — The owner's balance in US dollars (balance_usd) and topup_url, the page where they add funds. Call it when a run or draft is refused with 402 and hand the owner the link — you cannot add funds yourself. amount_usd preselects a suggested amount on that page.
- `ledger` — The account's last 100 balance movements (amount_usd each): run reservations and refunds (with execution ids), purchases, grants. For the balance alone with the top-up link, use get_balance.
