---
title: "App SDK"
description: "env.MURL, the complete capability surface of a murl app: AI calls, durable chains, storage, uploads, and jobs, all scoped and metered."
---

> Documentation Index
> Fetch the complete documentation index at: https://docs.murl.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# App SDK

Every murl app receives one binding: **`env.MURL`**. Because the sandbox has
no network egress and no other bindings, this SDK is, by construction, the
complete capability surface of any app.

Three invariants hold everywhere:

- **Scoped**: every call is bound to `(publication, runner)`, meaning the app
  being run and the signed-in user whose wallet pays. Injected by the host;
  never visible or forgeable from app code.
- **Async and structured**: all methods are async RPC; errors are structured
  with user-presentable messages and stable codes.
- **Money in micros**: $1 = 1,000,000 micros; 1 credit = $0.01. The SDK
  returns both where relevant.

## AI

### `estimateCost(modelId, params?)`

The posted price *before* running, quoted from the same pricing source the
run settles from, so the number on the button is the number on the ledger.
Free, works in preview, never touches the wallet.

### `callModel(modelId, params?)`

The only route from app code to AI, and the money chokepoint: quote, budget
guard, wallet hold, gateway call with provider failover, attributed usage
log, settle. Returns output URL/text/image plus the exact charged amount.
Fails cleanly: a provider failure releases the hold; an unaffordable quote is
a 402; a budget cap is a structured 429.

## Chains: durable multi-step runs

When one user action needs several model calls in sequence, apps start a
**chain** instead of awaiting calls in a request handler. Chains run
server-side on a durable workflow: they survive the user closing the page,
each step spends through the same chokepoint, and a failed chain keeps its
completed steps' outputs.

### `startChain(spec)`

Up to 8 steps; a step's params can reference earlier outputs
(`{image_url: '$0.outputUrl'}`). The spec is validated against the model
registry upfront, the whole chain is quoted upfront, and the runner's wallet
must cover the full quote before anything runs; no starting chains that
can't finish. Money still moves per-step, hold-then-settle.

### `getChain(chainId)` / `approveChainStep(chainId, stepIndex, approve?)`

Poll for status; terminal states keep partial outputs. Steps marked
`approval: true` pause the chain *before* running and **re-quote from
resolved inputs**, giving exact duration and size pricing for the step that
will actually run. Nothing is charged or held while paused, so a decline or
timeout is money-clean.

## Storage

### `storage(collection)`

Per-`(app, user)` document collections (history, favorites, settings) backed
by SQLite. Full CRUD (`insert` / `get` / `find` / `update` / `delete` /
`count`) with filtering and pagination. An app can only ever see its own
runner's scope.

## Assets

### `uploadAsset(data, {contentType})` / `listAssets()` / `deleteAsset(id)`

Runner-owned media (reference images, uploads) in object storage under the
app's namespace, scope-jailed by key prefix; an app can never reach outside
its own `(publication, runner)` namespace.

## Jobs

### `startJob(inputParams?)` / `getJob(jobId)`

Runs the app's own published block chain through the standard pipeline, with
live progress over a WebSocket. The right tool for apps built on a published
page; ad-hoc sequences use `startChain`.

## Billing semantics

- **Hold-then-settle everywhere.** Quote, hold, run, settle actual. A failed
  provider call releases the hold; a failed settle keeps it. Never a free
  run.
- **Budget breakers** inside the chokepoint: per-call and per-app daily caps.
- **Attribution for free.** Every spend row carries the app's publication id;
  [creator payouts](/economics) aggregate from these rows, so royalties need
  nothing from app code.
- **Preview is honest.** Quotes are real; every spending call stops at the
  hold step.

## Error codes

| Code | Meaning |
|---|---|
| `model_not_found` | Unknown or inactive model id |
| `insufficient_credits` | 402: wallet below quote |
| `app_budget_exceeded` | 429: per-call or per-day cap |
| `chain_error` | 422: chain spec failed validation |
| `chain_not_found` / `chain_not_awaiting` | Wrong chain scope or approval state |
| `app_storage_error` | Storage/asset caps and validation |
| `job_not_found` | Job outside this app's scope |

> **Tip**
>
> The canonical reference lives in the platform repo and is build-gated:
> adding an SDK method without documenting it does not ship.

Source: https://docs.murl.ai/sdk/index.mdx
