Documentation06 Sep 2026Joy Team4 min read

API quickstart

Create a token, price a server, deploy it, poll the job, read the credentials and destroy it — in curl, PHP and JavaScript.

1. Create a token

Console → Account → API tokens → New token. Name it, choose scopes (read, orders.write, instances.write for this guide), optionally set an expiry and an IP allow-list. Copy the secret: it is shown once. Export it in your shell:

export TOKEN=joy_xxxxxxxx.yyyyyyyy
export API=https://joy.services/api/v1

2. Who am I?

curl -s -H "Authorization: Bearer $TOKEN" $API/me | jq

You should see your account, wallet balance and the token's scopes. A 401 means the token is wrong; a 403 means API access is disabled for the account.

3. Check the catalogue

curl -s -H "Authorization: Bearer $TOKEN" $API/locations | jq '.locations[] | {code, available, ipv4_stock, os}'
curl -s -H "Authorization: Bearer $TOKEN" $API/plans | jq '.plans[] | {code, cores, ram_mb, price_usd_month, price_usd_hour}'

4. Quote before you buy

curl -s -X POST $API/quote -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"location":"mumbai","os":"ubuntu-24","plan":"micro","cycle":"hourly"}' | jq .quote

5. Deploy

curl -s -X POST $API/instances -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"location":"mumbai","os":"ubuntu-24","plan":"micro","cycle":"hourly","hostname":"t1","pay":"wallet"}' | tee order.json | jq
# → { "ok": true, "order": "ORD-…", "instance": "<ihash>", "job": 1842, "invoice": { "status": "paid", … } }

A 402 means the wallet could not cover it — pay the pay_url and the deployment starts automatically.

6. Poll the job

JOB=$(jq -r .job order.json); ID=$(jq -r .instance order.json)
until [ "$(curl -s -H "Authorization: Bearer $TOKEN" $API/jobs/$JOB | jq -r .job.status)" = done ]; do sleep 3; done

7. Read the credentials and log in

curl -s -H "Authorization: Bearer $TOKEN" $API/instances/$ID | jq '{ip: .instance.ip, user: .instance.credentials.user, pw: .instance.credentials.password}'
ssh root@$(curl -s -H "Authorization: Bearer $TOKEN" $API/instances/$ID | jq -r .instance.ip)

8. Do something to it

curl -s -X POST -H "Authorization: Bearer $TOKEN" $API/instances/$ID/snapshot -H "Content-Type: application/json" -d '{"op":"create","name":"first"}'
curl -s -X POST -H "Authorization: Bearer $TOKEN" $API/instances/$ID/reboot
curl -s -H "Authorization: Bearer $TOKEN" $API/instances/$ID/stats | jq '.series | length'

9. Destroy it

curl -s -X DELETE -H "Authorization: Bearer $TOKEN" $API/instances/$ID -H "Content-Type: application/json" -d '{"confirm":"t1"}'

The whole exercise costs less than one cent on hourly billing.

The same in PHP

$call = function (string $m, string $p, ?array $b = null) {
    $ch = curl_init(getenv('API') . $p);
    curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_CUSTOMREQUEST => $m,
        CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('TOKEN'), 'Content-Type: application/json'],
        CURLOPT_POSTFIELDS => $b ? json_encode($b) : null]);
    $d = json_decode((string)curl_exec($ch), true); curl_close($ch);
    if (!($d['ok'] ?? false)) throw new RuntimeException($d['error'] ?? 'request failed');
    return $d;
};
$o = $call('POST', '/instances', ['location' => 'mumbai', 'os' => 'ubuntu-24', 'plan' => 'micro', 'cycle' => 'hourly', 'hostname' => 't1', 'pay' => 'wallet']);
do { sleep(3); $j = $call('GET', '/jobs/' . $o['job'])['job']; } while ($j['status'] !== 'done' && $j['status'] !== 'failed');
print_r($call('GET', '/instances/' . $o['instance'])['instance']['credentials']);

The same in JavaScript (Node 18+)

const h = { Authorization: `Bearer ${process.env.TOKEN}`, 'Content-Type': 'application/json' };
const joy = async (m, p, b) => { const r = await fetch(process.env.API + p, { method: m, headers: h, body: b && JSON.stringify(b) }); const d = await r.json(); if (!d.ok) throw new Error(d.error); return d; };
const o = await joy('POST', '/instances', { location: 'mumbai', os: 'ubuntu-24', plan: 'micro', cycle: 'hourly', hostname: 't1', pay: 'wallet' });
let j; do { await new Promise(r => setTimeout(r, 3000)); j = (await joy('GET', `/jobs/${o.job}`)).job; } while (!['done', 'failed'].includes(j.status));
console.log((await joy('GET', `/instances/${o.instance}`)).instance.credentials);

Next

Read the full API reference for every field, the rate limits (600/min, 10 deployments/hour) and the legacy Instances API. Keep tokens server-side and use the smallest scope set that works.

apiautomation
Was this guide helpful?
Corrections and suggestions go straight to the team that wrote it.