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 the mojojojo session cookie used on this site. 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). Manage them on the mojojojo credits page.
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.

Scale-to-zero or persistent

Choose the lifecycle that matches the program. They use the same project, deployment, storage, auth, and credit ledger, but they do not have the same billing contract.

Scale-to-zero functionPersistent service
Best forHTTP APIs, webhooks, jobs, conversionsWebSockets, warm models, custom servers, live Mojo loops
LifetimeOne requestReserved until stopped
StateManaged data, storage, vector, room brokerMemory may survive requests; durable state still uses managed storage
BillingMeasured execution milliseconds; no idle chargeResource profile hourly in advance, including idle time
Statusavailablelimited preview

Most applications should start scale-to-zero. Multiplayer rooms do not alone require a persistent server: the managed room broker and data API keep durable coordination outside an individual function process. Reserve a process only when the process itself must live.

{
  "runtime": "app",
  "entrypoint": "server.mojo",
  "compute": {
    "mode": "persistent",
    "profile": "cpu-1",
    "health_path": "/health",
    "min_instances": 1
  }
}

Persistent manifests currently return a clear limited-preview error; they never silently deploy as functions. The production contract includes health checks, bounded restarts, connection draining, immutable updates, and paused billing whenever reserved capacity is unavailable.

Billing, exactly

Credits

Credits are managed on mojojojo.cc. /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://mojojojo.cc/credits?buy=credits"
}

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.

Open-source Python SDK and CLI

pip install mojojojo
export MOJOJOJO_API_KEY=mj_live_...

mojojojo run job.py
mojojojo run kernel.mojo --language mojo
mojojojo credits
from mojojojo import Client

client = Client()  # reads MOJOJOJO_API_KEY
result = client.run("print(sum(range(100)))")
result.check()
print(result.stdout, result.charge.usd)

The SDK has no runtime dependencies: one small Python package over urllib. It is MIT licensed and developed in the mojojojo repository; the HTTP API remains the contract, so using curl or another language is equally supported.

Precompile as a deployment stage

Put preparation between tests and traffic when first-request latency matters. Python kernels need representative argument and return types, so this stage executes the sample workload once. Its execution is billed normally; package builds, transpilation, and Mojo compilation are not.

from mojojojo import Client

client = Client()
prepared = client.precompile(open("backtest.py").read())
print(prepared.ready, prepared.kernels, prepared.message)

# The same source now starts with its content-addressed kernels available.
result = client.run(open("backtest.py").read())
# A CI or release pipeline can make the stage explicit.
python -m pytest
mojojojo precompile backtest.py --json
mojojojo precompile main.mojo --language mojo --json
mojojojo deploy "$PROJECT_ID" app.zip

For accelerated Python, the representative script must actually call the hot numeric functions; declaring an unused function does not reveal its types. For a full Mojo program, preparation fills the content-addressed binary cache. The result keeps stdout, compiler diagnostics, and the execution charge visible—precompile is not a hidden or privileged execution path.

Deploy an app end to end

Every hosted app is an immutable ZIP. Static files are served directly; declared functions run at /p/PROJECT_ID/api/FUNCTION. A new deploy uploads new bytes and moves the active deployment pointer atomically.

Python function + frontend

hello-app/
├── index.html
├── mojojojo.json
└── functions/
    └── hello.py
{
  "name": "hello-python",
  "runtime": "app",
  "entrypoint": "index.html",
  "functions": {
    "hello": {
      "language": "python",
      "entrypoint": "functions/hello.py",
      "packages": [],
      "timeout_ms": 3000
    }
  }
}
# functions/hello.py
def handler(request):
    name = request.get("body", {}).get("name", "world")
    return {"status": 200, "body": {"message": f"Hello {name}"}}
<button id="hello">Call Python</button>
<script>
hello.onclick = async () => {
  const response = await fetch("./api/hello", {
    method: "POST", headers: {"Content-Type": "application/json"},
    body: JSON.stringify({name: "Ada"})
  });
  hello.textContent = (await response.json()).message;
};
</script>

Mojo function + the same frontend

Change only the function declaration and entrypoint. A Mojo function reads the request envelope as JSON on stdin and writes either a response envelope or plain body on stdout:

"functions": {
  "hello": {"language":"mojo","entrypoint":"functions/hello.mojo","timeout_ms":5000}
}
# functions/hello.mojo
def main():
    print("{\"body\":{\"message\":\"Hello from compiled Mojo\"}}")

Publish and verify

cd hello-app
python -m zipfile -c ../hello-app.zip index.html mojojojo.json functions/hello.py
cd ..

mojojojo project "Hello app" --runtime app
# copy the returned project id
mojojojo deploy "$PROJECT_ID" hello-app.zip

curl -H 'Content-Type: application/json' -d '{"name":"Ada"}' \
  "https://mojojojo.cc/p/$PROJECT_ID/api/hello"

The function receives method, path, query, selected request headers, and a parsed JSON or text body. The app owner pays function execution from the same credit balance; static delivery has no execution charge.

Managed data API

A database is a tenant-scoped JSONB namespace in mojojojo Postgres. Linking a vector project makes search_text a durable vector document while the JSON record stays authoritative.

database = client.create_database("Customers", vector_project_id="vec_PROJECT")
client.data_upsert(database["id"], "customer:123", {
    "name": "Ada", "plan": "starter"
}, search_text="Ada builds services with Mojo")

records = client.data_records(database["id"])
client.data_delete(database["id"], "customer:123")

Use the Data UI for the same create, edit, list, and delete flow. The first shared tier is intentionally bounded—10 databases, 64 KiB JSON values, 10,000 records and 100 MiB per database—so it adds nearly zero idle cost. Dedicated Postgres remains a future isolation/performance tier, not a process we keep running for every empty account.

Managed vector search

A vector project owns durable source documents and a rebuildable 512-dimensional int8 index. The bundled folded model means callers send text, not model-specific vectors. Projects are tenant-scoped at the control plane; workers see only an opaque project ID.

# create
curl -H "Authorization: Bearer $MOJOJOJO_KEY" -H 'Content-Type: application/json' \
  -d '{"name":"Product docs","tier":"exact"}' \
  https://mojojojo.cc/v1/vector/projects

# index up to 256 documents per request
curl -H "Authorization: Bearer $MOJOJOJO_KEY" -H 'Content-Type: application/json' \
  -d '{"documents":[{"id":"mojo","text":"Mojo compiles fast kernels.",
       "metadata":{"section":"runtime"}}]}' \
  https://mojojojo.cc/v1/vector/projects/vec_PROJECT/upsert

# retrieve
curl -H "Authorization: Bearer $MOJOJOJO_KEY" -H 'Content-Type: application/json' \
  -d '{"query":"fast compiled code","limit":10}' \
  https://mojojojo.cc/v1/vector/projects/vec_PROJECT/search
{
  "ok": true,
  "matches": [{"id":"mojo","score":0.8731,"metadata":{"section":"runtime"}}],
  "vector_count": 24012,
  "backend": "mojo-int8-exact",
  "latency_ms": 0.74,
  "charge": {"accrued_credits":0.5,"charged_credits":0,"owed_credits":0.5}
}

exact provides exhaustive retrieval. performance automatically accelerates larger collections.

Your documents stay durable while indexes are managed automatically.

Projects, assets, and ZIP deployments

Create a project once, then deploy immutable versions. A bundle contains a mojojojo.json manifest and its entrypoint:

curl -H "X-Api-Key: $MOJOJOJO_API_KEY" -H 'Content-Type: application/json' \
  -d '{"name":"fractal","runtime":"static"}' \
  https://mojojojo.cc/v1/projects

curl -H "X-Api-Key: $MOJOJOJO_API_KEY" \
  -F "project_id=$PROJECT_ID" -F "[email protected]" \
  https://mojojojo.cc/v1/deployments

For direct assets, request a signed URL and PUT the exact number of bytes to it:

POST /v1/uploads/sign
{"filename":"model.onnx","content_type":"application/octet-stream","size_bytes":330}

{"method":"PUT","upload_url":"https://mojojojo.cc/v1/uploads/…",
 "asset_url":"https://assets.example/uploads/…"}

Storage

Deployments and uploaded assets are durable, versioned, and available to your projects. Compute workers use managed copies, so a worker is never the only place your data exists.

Multi-file Mojo

{
  "language": "mojo",
  "entrypoint": "main.mojo",
  "files": {
    "main.mojo": "from palette import shade\\n...",
    "palette.mojo": "def shade(iterations: Int) -> Int: ..."
  }
}

Up to 32 relative .mojo files (512 KiB total) are built as one content-addressed project. Helper changes create a new build key; the build remains unbilled and only the isolated binary is executed.

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.

Automatic capacity

Capacity expands with sustained demand and scales back when it is idle. Prices stay the same, and time spent waiting in a queue is never billed.

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.cc/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.cc/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.cc/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.