Skip to content

App SDK

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

Updated View as Markdown

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 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
Navigation

Type to search…

↑↓ navigate↵ selectEsc close