DEVELOPERS ยท avenia os v0.1 ยท api reference
BUILD ON AVENIA
Everything you need to submit runs, stream loss, list a GPU, and self-host the brain. The browser calls the same origin under /v1/*; the coordinator base URL is http://localhost:8788 in dev. The network settles in AVEN on Robinhood Chain.
Quickstart
Submit a run and read the streamed loss over SSE. The response is an event stream: routing โ progress* โ artifact โ usage โ done (or error).
curl -N -X POST http://localhost:8788/v1/train \
-H 'content-type: application/json' \
-H 'accept: text/event-stream' \
-H 'x-avenia-wallet: 0xYOURWALLET' \
-d '{
"model": "aven-qwen-0.5b",
"dataset": [{"prompt": "hi", "completion": "hello there"}],
"hyperparams": { "epochs": 3, "loraRank": 16 },
"stream": true
}'Architecture
You never talk to a GPU directly. The coordinator is the privacy chokepoint - it strips your identity before dispatch, so the worker only ever sees a dataset URL and hyperparameters.
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ YOU ยท client โ
โ dataset.jsonl + base model + hyperparams โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ POST /v1/train (SSE stream opens)
v
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ COORDINATOR ยท the brain โ
โ holder-gate ยท acquire idle worker ยท relay ยท settle โ
โ privacy chokepoint - strips identity before dispatch โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ assign DispatchedJob (no wallet / no ip)
v
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ GPU WORKER ยท someone else's card โ
โ pulls dataset ยท LoRA fine-tune ยท streams step + loss โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ job_done โ adapter.safetensors
v
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ BACK TO YOU โ
โ weights returned ยท dataset never persisted or logged โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโREST endpoints
All JSON. A missing or empty x-avenia-wallet fails open while the token CA is unset, so the API is usable before launch.
/healthLiveness probe. Returns instantly whether or not any worker is connected.
{ "status": "ok", "uptime": 128734 }/v1/modelsThe base-model registry plus whether at least one online worker can currently serve each one.
[
{
"id": "aven-qwen-0.5b",
"displayName": "Avenia Qwen 0.5B",
"family": "qwen2.5",
"params": 0.5,
"minVramGb": 6,
"pricePerGpuMinute": 4,
"available": true
}
]/v1/networkLive worker-mesh snapshot: every worker plus aggregate online / idle / busy stats. Drives the Network page.
{
"workers": [
{
"id": "wk_9d2", "label": "marketplace", "status": "busy",
"region": "us-west", "runtime": "cuda", "gpu": "A100 80GB", "vramGb": 80,
"models": ["aven-qwen-0.5b"], "throughput": { "aven-qwen-0.5b": 88.0 },
"jobsCompleted": 812, "lastSeen": 1720000000000
}
],
"stats": {
"online": 6, "idle": 3, "busy": 3, "totalJobs": 1801,
"modelsAvailable": ["aven-qwen-0.5b", "aven-llama-1b", "aven-llama-3b"],
"gpuSecondsServed": 4182540
}
}/v1/providersProvider leaderboard: jobs served, gpu-time, earnings, and current worker share (boosted when staked).
[
{
"id": "prov_north",
"jobsCompleted": 812, "gpuSecondsServed": 1940220,
"earnings": 24806.4, "hasActiveStake": true, "workerShare": 0.85,
"workers": [ /* NetworkWorker[] */ ]
}
]/v1/usageAccount credit balance plus the most recent settled jobs. Never includes dataset content.
| param | notes |
|---|---|
| account | the account/wallet to summarize (query string) |
{
"account": "0xYOURWALLET",
"credits": 1284.66, "jobsCompleted": 37, "gpuSeconds": 51234,
"recentJobs": [
{
"requestId": "req_a1f", "model": "aven-qwen-0.5b", "workerId": "wk_9d2",
"trainSteps": 120, "gpuSeconds": 138, "finalLoss": 0.61, "evalLoss": 0.63,
"credits": 9.2, "adapterUri": "avenia://adapter/โฆ", "status": "completed",
"createdAt": 1720000000000
}
]
}/v1/stakeRead the holder staking vault (GET), or stake / unstake (POST { account, amount }). Staking a worker's account unlocks the boosted share.
| param | notes |
|---|---|
| account | GET query / POST body - the holder account |
| amount | POST body - signed delta to stake (+) or unstake (โ) |
{
"account": "0xYOURWALLET",
"staked": 250000, "totalStaked": 41820000, "tier": "boosted",
"baseShare": 0.7, "boostedShare": 0.85,
"rewardsAccrued": 1284.66, "rewardPool": 96540.2
}/v1/trainSubmit a training job. With accept: text/event-stream the response is an SSE stream of TrainEvents (see below). Body is a TrainRequest.
| param | notes |
|---|---|
| model | base-model id from /v1/models (required) |
| dataset | inline [{prompt, completion}] for small runs |
| datasetUrl | or a URL the worker pulls the jsonl from |
| hyperparams | partial - omitted fields fall back to defaults |
| stream | true to receive SSE (otherwise a single JSON summary) |
data: {"type":"routing","workerId":"wk_9d2","workerLabel":"marketplace","gpu":"A100 80GB"}
data: {"type":"progress","step":40,"totalSteps":120,"loss":1.42,"etaMs":1800}
data: {"type":"artifact","adapterUri":"avenia://adapter/โฆ","adapterBytes":18400000,"evalLoss":0.63}
data: {"type":"usage","gpuSeconds":138,"trainSteps":120,"credits":9.2}
data: {"type":"done"}/v1/artifact/:idStream the trained LoRA adapter (adapter.safetensors) back to the submitter. Binary body; the id comes from the artifact event's adapterUri.
HTTP/1.1 200 OK content-type: application/octet-stream content-disposition: attachment; filename="adapter.safetensors" < binary safetensors bytes >
/v1/dataset/:idThe short-lived internal URL a worker pulls a job's dataset from. Hosted only for the run's duration; never persisted to the ledger.
{"prompt":"โฆ","completion":"โฆ"}
{"prompt":"โฆ","completion":"โฆ"} // jsonl, one example per line/v1/access/challengeGet a nonce to sign for the holder gate. The wallet signs the returned message to prove ownership before submitting.
| param | notes |
|---|---|
| wallet | the wallet address requesting access |
{ "nonce": "a1f9โฆ", "message": "Avenia access: sign nonce a1f9โฆ to submit a run" }/v1/access/statusWhether a wallet currently clears the hold-to-submit gate. Fails open (ok:true) while the token CA is unset.
| param | notes |
|---|---|
| wallet | the wallet address to check |
{ "wallet": "0xYOURWALLET", "holds": 248500, "required": 1000, "ok": true }SSE - the TrainEvent sequence
Each SSE data: line is one TrainEvent. A healthy run emits, in order:
routing- the worker + GPU your job landed on.progress* - repeated:step,totalSteps,loss,etaMs.artifact-adapterUri,adapterBytes,evalLoss(your quality receipt).usage-gpuSeconds,trainSteps,creditsbilled.done- terminal success (orerrorwith a message).
[1] SUBMIT POST /v1/train ยท dataset.jsonl + base model + hyperparams
โ
v
[2] ROUTE enforce holder-gate โ> acquire idle eligible worker
โ (selected by measured throughput)
v
[3] TRAIN the worker fine-tunes a LoRA ยท streams step + loss for minutes
โ
โ loss 2.41 โคโโโ
โ 1.78 โค โโโโ
โ 1.14 โค โโโโโ
โ 0.79 โค โโโโโโโ___
โ 0.63 โค โโโโ________
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ> step
v
[4] ADAPTER eval loss = your quality receipt ยท adapter.safetensors uploaded
โ
v
[5] SETTLE credits = gpu_minutes ร model_rate ยท one atomic tx, Robinhood Chain
worker keeps 70/85% ยท holders 80% of margin ยท treasury 20%WebSocket - the worker protocol
Workers connect to ws://localhost:8788/worker. One JSON object per frame, discriminated by type. Heartbeats every 5s; the coordinator waits up to 30s for an idle worker before failing a submit.
Worker โ Coordinator
register- capabilities (gpu, vram, runtime, models, throughput).heartbeat-{ status: idle | busy | offline }.benchmark_result- measured samples/sec per model.job_progress- step, totalSteps, loss, etaMs.job_done- adapterUri, gpuSeconds, finalLoss, evalLoss.job_error- jobId + message.
Coordinator โ Worker
registered- the assignedworkerId.job_assign- aDispatchedJob(no wallet / no IP).cancel- abort a running job by id.ping- keepalive.
{
"jobId": "job_7c3",
"model": "aven-qwen-0.5b",
"backend": "Qwen/Qwen2.5-0.5B-Instruct",
"datasetUrl": "http://localhost:8788/v1/dataset/ds_a1f",
"datasetRows": 3,
"hyperparams": {
"epochs": 3, "loraRank": 16,
"loraAlpha": 32, "learningRate": 0.0002,
"batchSize": 2
}
}Code samples
JavaScript (fetch + stream reader)
// The stream is a POST, so use fetch + a stream reader (not EventSource,
// which is GET-only). This is exactly what the live console does.
const res = await fetch("/v1/train", {
method: "POST",
headers: { "content-type": "application/json", accept: "text/event-stream" },
body: JSON.stringify({
model: "aven-qwen-0.5b",
dataset: [{ prompt: "hi", completion: "hello there" }],
hyperparams: { epochs: 3, loraRank: 16 },
stream: true,
}),
})
const reader = res.body.getReader()
const dec = new TextDecoder()
let buf = ""
for (;;) {
const { value, done } = await reader.read()
if (done) break
buf += dec.decode(value, { stream: true })
const blocks = buf.split("\n\n")
buf = blocks.pop() ?? ""
for (const block of blocks) {
const line = block.split("\n").find((l) => l.startsWith("data:"))
if (!line) continue
const ev = JSON.parse(line.slice(5).trim()) // TrainEvent
console.log(ev.type, ev)
}
}
// For a GET SSE endpoint you would instead use:
// const es = new EventSource(url); es.onmessage = (e) => JSON.parse(e.data)Python (requests, streamed)
import json, requests
with requests.post(
"http://localhost:8788/v1/train",
headers={"content-type": "application/json", "accept": "text/event-stream"},
json={
"model": "aven-qwen-0.5b",
"dataset": [{"prompt": "hi", "completion": "hello there"}],
"hyperparams": {"epochs": 3, "loraRank": 16},
"stream": True,
},
stream=True,
) as r:
for line in r.iter_lines():
if line and line.startswith(b"data:"):
ev = json.loads(line[5:].strip()) # TrainEvent
print(ev["type"], ev)Run a worker
A worker advertises which base models it can fine-tune (by VRAM) and earns 70โ85% of every job. The mock runtime needs no GPU; the Python runtime spawns a real trainer sidecar.
# mock runtime - no GPU, deterministic loss curve (dev / CI default) AVENIA_RUNTIME=mock npm run worker -- start --models aven-qwen-0.5b # real GPU - install the trainer sidecar first, then serve pip install -r packages/worker/trainer/requirements.txt npm run worker -- start \ --runtime python \ --models aven-qwen-0.5b,aven-llama-1b,aven-llama-3b \ --account acct_you \ --label marketplace
Self-host the coordinator
The coordinator is the whole backend: an HTTP + WebSocket server on port 8788. Point the web app at a hosted one with COORDINATOR_HTTP_URL.
# clone + install the workspace (coordinator, worker, cli, web) git clone <repo> avenia && cd avenia && npm install # boot the coordinator (HTTP + WS on :8788) with a mock worker npm run dev # point the web app at a hosted coordinator instead of localhost COORDINATOR_HTTP_URL=https://coord.example.com npm run web
Pricing & settlement
job_price = gpu_minutes ร model_rate. On completion the price is split in one atomic transaction:
JOB PRICE = gpu_minutes ร model_rate (100%)
โ
โโโ> WORKER REWARD ......... 70% (85% when the worker is staked)
โ
โโโ> PROTOCOL MARGIN ....... 30% (15% when the worker is staked)
โ
โโโ> HOLDER POOL .... 80% of margin โ> pro-rata to $AVEN holders
โ
โโโ> TREASURY ....... 20% of margin โ> ops ยท grants ยท runwayThe worker keeps 70% (85% if staked); of the remaining margin, 80% goes to the holder pool and the rest to the treasury. Steps are clamped at 5,000. Nothing is minted - supply is fixed at 1,000,000,000 AVEN.