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.