TypeScript SDK
@gpufarm/sdk is a typed client for the REST API with zero runtime dependencies (it uses fetch and WebCrypto: Node 18.17+, Bun, Deno, edge runtimes). It mirrors the API's models field for field, keeps money in integer micro-dollars and token base units, and funds escrow from your own wallet through viem.Install#
sdk/ (ESM + CommonJS builds with types). Build it with npm run sdk:build and install it from that path; viem is an optional peer dependency needed only for jobs.fund().npm run sdk:build # in the GPUFarm repository
npm install ./gpufarm/sdk viem # in your project (viem only if you fund from code)import { GPUFarm } from "@gpufarm/sdk";
export const gpufarm = new GPUFarm({
apiKey: process.env.GPUFARM_API_KEY, // gf_live_… on mainnet, gf_test_… elsewhere — keep it server-side
baseUrl: "https://robingpu.farm", // or set GPUFARM_API_URL
});Create a job#
Only image and gpu are required. create runs the workload policy, prices the job on live capacity and locks its escrow terms. Nothing is charged yet. An Idempotency-Key is sent automatically, so retries never create duplicates.
const job = await gpufarm.jobs.create({
image: "pytorch/pytorch:2.5.1-cuda12.4-cudnn9-runtime",
gpu: { minimumVramGb: 24, count: 1 },
command: ["python", "-c", "import torch; print(torch.cuda.get_device_name())"],
maxRuntimeSeconds: 900,
maxPricePerGpuHour: 0.65, // USD cap; omit to cap at the priciest current offer that fits (see job.defaults)
paymentToken: "stable", // "eth" | "stable" (USDG)
});
job.status; // "CREATED"
job.estimate; // low / high / max cost in micro-USD, with the assumptions
job.funding?.calls; // the exact escrow calls your wallet will sendServer defaults when omitted: workloadType: "custom", maxRuntimeSeconds: 3600, no outbound network, outputs collected from /output. To try the network end to end, run the deterministic canary — its result is re-computed by the server (RESULT VERIFIED):
await gpufarm.jobs.create({ image: "gpufarm/selftest:1", command: ["sha256-chain", "1000"], workloadType: "batch", gpu: { count: 1 } });Fund the escrow with a viem wallet#
Funding is a transaction from your wallet to ComputeEscrow on Robinhood Chain; GPUFarm never holds your keys or funds. The wallet must be the account that owns the API key (the escrow job key is derived from it) — the SDK checks this before sending anything and switches the wallet's chain when the client supports it.
import { createPublicClient, createWalletClient, http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
const chain = { id: 4663, name: "Robinhood Chain", nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 }, rpcUrls: { default: { http: [process.env.RPC_URL!] } } } as const;
const walletClient = createWalletClient({ account: privateKeyToAccount(process.env.CUSTOMER_KEY as `0x${string}`), chain, transport: http() });
const publicClient = createPublicClient({ chain, transport: http() });
const result = await gpufarm.jobs.fund(job, walletClient, { publicClient });
// approve (stablecoin only; skipped when the allowance already covers it) → createJob → wait for receipts
// → POST /v1/jobs/:id/fund-confirm, where GPUFarm reads the JobFunded event from the chain itself
result.transactions; // [{ title, kind, hash }]
result.confirmation?.job.status; // "QUEUED"Prefer another signer (ethers, a hardware wallet, a multisig)? Send the raw calls yourself:
const funding = await gpufarm.jobs.funding(job.id);
for (const call of funding.calls) {
// send { to: call.to, data: call.data, value: BigInt(call.value) } from funding.signer, in order
}
await gpufarm.jobs.confirmFunding(job.id, fundTxHash);Wait, logs and results#
const done = await gpufarm.jobs.wait(job.id, {
onStatus: (j) => console.log(j.status, j.assignment?.farm?.name ?? ""),
});
if (done.status === "COMPLETED") {
done.verificationLevel; // RESULT_VERIFIED | EXECUTION_VERIFIED | HOST_REPORTED — never assumed
done.result.hashesMatch; // host-reported hash === server re-hash
done.cost.totalMicroUsd; // final charge
const { data, sha256 } = await gpufarm.artifacts.download(done.result.outputArtifactId!);
// the download is checked against the hash GPUFarm recorded; a mismatch throws
}
for await (const line of gpufarm.jobs.followLogs(job.id)) {
console.log(`[${line.stream}] ${line.line}`);
}wait()polls with backoff (2 s growing to 15 s) and throwsGPUFarmTimeoutErrorafter 24 h by default — the job keeps running. Pass{ signal }to stop waiting, oruntilto resolve on other statuses.- The job stays in escrow's challenge window after completion; approve early or dispute from the job page. See the trust model.
Capacity, pricing, billing#
const offers = await gpufarm.capacity.list({ model: "rtx-4090", minVramGb: 24, onlyAvailable: true, sort: "price_asc" });
const pricing = await gpufarm.pricing.get(); // live per-class min / median / max, fee, billing units
const billing = await gpufarm.billing.list({ limit: 50 });capacity.list() and pricing.get() work without an API key; they read the same live data as the marketplace.
Method reference#
| Method | API | Scope |
|---|---|---|
jobs.create(spec, { idempotencyKey? }) | POST /v1/jobs | jobs:create |
jobs.get(id) | GET /v1/jobs/:id | jobs:read |
jobs.list({ status, limit, cursor }) · jobs.listAll() | GET /v1/jobs | jobs:read |
jobs.wait(id, opts) | polls GET /v1/jobs/:id | jobs:read |
jobs.logs(id, opts) · jobs.followLogs(id) | GET /v1/jobs/:id/logs | jobs:read |
jobs.funding(id) | GET /v1/jobs/:id/funding | jobs:create |
jobs.fund(job, walletClient, opts) | funding + your wallet + fund-confirm | jobs:create |
jobs.confirmFunding(id, txHash) | POST /v1/jobs/:id/fund-confirm | jobs:create |
jobs.cancel(id, { reason }) | POST /v1/jobs/:id/cancel | jobs:create |
artifacts.get(id) · artifacts.download(id) | GET /v1/artifacts/:id | artifacts:read |
capacity.list(filters) | GET /v1/capacity | — |
pricing.get() | GET /v1/pricing | — |
billing.list({ limit, cursor }) | GET /v1/billing | billing:read |
Errors and retries#
Every API error is a GPUFarmError with status, a stable code, message, details and the server's requestId. Client-side codes cover the wallet path: network_error, timeout, wallet_mismatch, wrong_chain, integrity_error.
import { GPUFarmError } from "@gpufarm/sdk";
try {
await gpufarm.jobs.create({ image: "someone/thing:latest", gpu: { count: 1 } });
} catch (e) {
if (e instanceof GPUFarmError && e.code === "policy_rejected") console.log(e.details); // the policy findings
}GETs and job creation are retried automatically on network errors, 429 (honoring Retry-After) and 5xx — maxRetries defaults to 2. The full error list is in the API reference.