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 |