mojojojo mojojojo

Docs

One endpoint, one auth header, one unit of billing. If you can write a curl command you are done in a minute.

Authenticate

Either an API key from /keys, or — if you are on an app.nz page — the session cookie you already have. Same account, same credits, everywhere on the network. Server-side runs always need one of the two.

Authorization: Bearer mj_live_...

There is no anonymous execution endpoint, and that is deliberate: free compute behind an unauthenticated URL is free compute per request, which is a botnet with good documentation. The browser runtime needs no account because it runs on the caller's own machine and costs us nothing. Signing in also gets you 1000 credits ($1), once.

POST /v1/run

FieldTypeMeaning
codestringThe program. Required.
stdinstringFed to the program's stdin.
argsstring[]Becomes sys.argv[1:].
packagesstring[]PyPI specs, e.g. ["pandas==2.2.*"]. Usually unnecessary — see auto-install below. Installed by uv into a cached, content-addressed venv.
auto_installboolDefault true: your imports are read out of your code and installed. False gives you exactly packages.
timeout_msintDefault 10000, max 300000.
gpuboolRun where an RTX 5090 is visible. Billed at the GPU rate.
accelboolDefault true. Set false to run pure CPython with no transpilation.
languagestring"python" (default) or "mojo" — a whole Mojo program, compiled here and run with no interpreter. See below.
envobjectExtra environment variables.

Response

{
  "ok": true,
  "stdout": "3.141936\n",
  "stderr": "",
  "exit_code": 0,
  "timed_out": false,
  "wall_ms": 812.4,          // what your program took
  "cpu_ms": 795.1,
  "billable_ms": 812.4,      // wall, less any time our compiler stole
  "queue_ms": 0.4,           // not billed
  "accelerated": ["monte_carlo_pi"],
  "accel": {"compiled": 1, "native_calls": 0, "cache_hits": 0, "compile_ms": 4210},
  "env": {"key": "9dc899c5", "cached": true, "packages": ["numpy"]},
  "isolated": true,
  "charge": {"billed_ms": 812.4, "credits": 0, "owed_ms": 812.4,
             "balance": 4820, "ms_per_credit": 10000}
}

Errors

StatusMeans
400Bad request: no code, an invalid package spec, oversized program.
401Not signed in, and the request wants something the free tier does not cover.
402Out of credits (or out of free milliseconds). Top up at app.nz.
502The runner is unreachable. Nothing was billed.

A program that raises still costs — it used the machine. A request we refuse before it runs never does.

Billing, exactly

Credits

Credits are bought once on app.nz and spent everywhere on the network. /credits is the human view — balance, what it buys in machine time, the ledger line behind every charge, and the top-up button. GET /v1/credits is the same thing for a program, and it takes a session cookie or an API key:

{
  "signed_in": true,
  "credits": 3780,
  "balance": {"free": 1380, "plan": 0, "paid": 2400, "usd": 3.78},
  "buys": {"cpu_ms": 37800000, "gpu_ms": 7560000},
  "owed_ms": 119.7, "total_ms": 481203.4,
  "welcome_granted": true, "spent_credits": 16,
  "ledger": [{"at": "2026-07-26T19:45:00Z", "delta": -14,
              "reason": "exec:gpu:28000ms", "app": "mojojojo",
              "source": "spend", "balance_after": 5864}],
  "top_up": "https://app.nz/account"
}

buys.cpu_ms is how many milliseconds the account can still pay for. Checking it before a long job is cheaper than a 402 halfway through one — mojojojo credits in the CLI, or Client.credits() in the SDK.

Imports install themselves

import pandas is enough. The program's imports are parsed, the standard library and anything already present are skipped, module names that differ from their distribution are mapped (cv2 → opencv, PIL → pillow, sklearn → scikit-learn), and uv builds a content-addressed environment. The same set of packages is built once and reused by everyone afterwards.

{"env": {"key": "64b598df", "cached": false, "build_ms": 1440,
         "packages": ["httpx", "numpy", "pandas"],
         "auto": ["httpx", "pandas"]}}

A guess that will not install never fails a run that would otherwise work: the environment falls back to exactly what you asked for and says so in auto_failed. Builds are not billed.

Cold start

Runs land on a pre-warmed interpreter that already has the sandbox, the unprivileged uid and numpy in place, and each run is a fork() of it. Measured end to end on this box: ~60ms warm against ~220ms cold, and warm_start in the response says which you got. It changes latency, not price — the billing clock starts when your program does.

GPU

"gpu": true runs where an RTX 5090 is visible, at 1 credit per 2000ms. The device is time-sliced: a small number of GPU runs execute at once and the rest queue, because four concurrent CUDA programs thrash VRAM and make everyone's wall clock — which is what you pay for — worse. Queue time is not billed.

When we run out of machines

Sustained demand that this box cannot serve spills over to rented capacity: RunPod for GPUs, Hetzner for CPU. The controller rents a machine only when all of the following hold, and it logs its reason either way:

Idle rented machines are returned as soon as the queue empties. Prices do not change when this happens; a queue does, and ran_on in the response names the worker if your run went to one.

What gets compiled

A top-level function is handed to mojosub when it takes arguments, contains a loop, and stays inside the numeric subset — ints, floats, arithmetic, comparisons, math, and calls to other eligible functions. Anything else runs on CPython, which is most code, and that is fine.

def sma_crossover(prices, fast: int, slow: int) -> float:   # compiled
    ...

def load(path):                                             # CPython
    with open(path) as fh:
        return json.load(fh)

The first native result is checked against CPython's before the compiled variant is trusted; a mismatch retires it silently and you keep the Python answer. Pass "accel": false to skip all of it.

Or write the Mojo yourself

Send "language": "mojo" and the whole program is Mojo: we build it on our machines — there is no compiler inside the sandbox — and run the binary. Nothing is interpreted, so there is no first call to warm up and no verification pass; it is native from the first instruction.

curl -s https://mojojojo.app.nz/v1/run \
  -H "Authorization: Bearer $MOJOJOJO_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"language":"mojo","code":"def main():\n    print(\"hello\")\n"}'

The build is never billed — you wait for it the first time anybody runs that exact source, and never again, because binaries are content-addressed the same way compiled kernels are. build_ms says how long it took and build_cached whether it was already built. A program that does not compile comes back with the compiler's diagnostics, "ok": false, and a bill of zero.

This Mojo is 1.0: def rather than fn, and the standard library is under std.from std.sys import argv. Mojo runs always go to our machines; the browser runtime has no wasm build of the toolchain to fall back to.

numpy arrays cross for free

Anything exposing __array_interface__ is passed by address. A list[float] has to be copied on every call, and for a million elements that copy costs more than the arithmetic saves — so pass arrays, not lists, to hot functions.

The browser runtime

<script type="module">
import { run, plan } from "https://mojojojo.app.nz/mojojojo.js"

plan(code)                     // {where: "browser"|"server", why: "..."}
await run(code)                // free locally, billed only on fallback
await run(code, {gpu: true})   // always server
await run(code, {where: "server", apiKey: KEY})
</script>

It boots Pyodide (CPython on WebAssembly) on the caller's machine and goes to our servers only for a GPU, a package with no wasm wheel, an import we know wasm cannot serve, or a browser runtime that failed to start. Every result carries where and why.

Sandbox

Your program runs as an unprivileged user in its own network namespace — no network, no other tenant's files — with limits on memory, CPU seconds, file size, and process count, in a scratch directory deleted when it exits. isolated: true in the response confirms it; if you ever see false, that is a development machine, not production.

Package installs happen outside the sandbox, before your code starts, which is why imports work despite there being no network inside.

There is no compiler inside the sandbox and no writable shared state. A run sees the compile cache as symlinks it can read and replace only within its own directory; when it wants a kernel built, the request is queued and we compile it, afterwards, from the source its content hash names. What a run teaches the cache is one thing — the return type it learned — and never a verdict about whether a compiled variant is correct. Every run re-checks its first native result against CPython for itself.

SDKs

Python

pip install mojojojo

from mojojojo import run, remote

r = run("print(6*7)")
r.stdout          # '42\n'
r.charge.usd      # 4.1e-06

@remote(packages=["numpy"])
def sharpe(returns, window):
    import numpy as np
    ...

sharpe(prices, 64)     # runs there
sharpe.local(prices, 64)  # runs here

JavaScript

import { run, plan } from
  "https://mojojojo.app.nz/mojojojo.js"

plan(code)     // where this would run, and why
await run(code)             // browser if it can
await run(code, {gpu: true}) // always server

CLI

mojojojo run script.py --gpu
app exec script.py --packages numpy

Self-host

The execution layer is mojosub, which is open source and runs anywhere Mojo does. mojojojo is the metered, hosted version of it.

How it works, in detail

The research notes are the long version of this page: why there is no compiler inside the sandbox and what that buys, how early we can tell a function will not compile, and where the time actually goes when it does. Every number in them is measured on the machine serving this page.