VRAMfarm logo VRAMfarm
EN
Sign up
Documentation

VRAMfarm API

Launch GPU servers from scripts and CI: find an available machine, start it with a ready-made app or your own Docker image, wait for SSH, stop it. Plain HTTPS and JSON, no SDK needed.

API base URLhttps://vramfarm.com/api
  • Responses are JSON, UTF-8. POST requests need the Content-Type: application/json header.
  • Money is in euros. price is per hour for the whole server, all cards included, as a decimal with cents (for example 0.39).
  • Times in responses are ISO 8601 in UTC; the updated field in the catalogue is Unix milliseconds.
  • Error messages come in your account language, or in the language of the Accept-Language header, falling back to English.

Quick start

The script launches an RTX 4090 server, waits until it is ready, prints the SSH command and stops the server, even if something fails halfway.

Launch, wait for SSH, stop
import os, time, requests

API = "https://vramfarm.com/api"
s = requests.Session()
s.headers["Authorization"] = f"Bearer {os.environ['GF_TOKEN']}"

r = s.post(f"{API}/instances", json={"gpu": "rtx-4090", "count": 1, "template": "pytorch"})
r.raise_for_status()
sid = r.json()["id"]
try:
    while True:
        srv = s.get(f"{API}/instances/{sid}").json()
        if srv["status"] == "running":
            break
        if srv["status"] == "stopped":
            raise RuntimeError(srv["error"])
        print(srv["stage_note"] or srv["stage"])
        time.sleep(10)
    print(srv["access"]["ssh"]["command"])
    # your work goes here: ssh, scp, requests to the app on the server
finally:
    s.post(f"{API}/instances/stop", json={"id": sid})

Authentication

Create a token in the dashboard → Settings → API tokens. A token looks like gf_live_… and is shown once: store it in your CI secrets or in the GF_TOKEN environment variable. You can revoke tokens in the same place. Up to 10 tokens can be active.

Send the token in the Authorization: Bearer gf_live_… header. Nothing else is needed: requests are not signed, there is no timestamp or recvWindow, and cookies and Origin are not checked for token requests.

import os, requests

H = {"Authorization": f"Bearer {os.environ['GF_TOKEN']}"}
print(requests.get("https://vramfarm.com/api/account", headers=H).json())

A token only opens the methods on this page. Your password, SSH key and issuing new tokens are available only in the dashboard; with a token those endpoints return 403. A leaked token lets someone launch and stop servers, but not manage your account.

The catalogue and /health work without a token. But if you send an Authorization header with a wrong or revoked token, every endpoint returns 401.

Call the API from your own server, script or CI. Requests from a browser on another website will not go through, and any visitor could see the token there.

Before your first launch, add a public SSH key in the dashboard settings: it is installed on every new server. You can check that everything is ready with GET /api/account.

Errors and limits

On an error you get an HTTP status and the body {"error": "message", "code": "snake_case_code"}. The error text is safe to show to a person as is; branch your code on code, never on the text. Some responses add a hint for programs: unverified on launch, saving on stop.

StatusWhen
400Invalid parameters: image, disk, not enough VRAM, server limit
401Wrong or revoked token, or the account is blocked
402Not enough balance; error says how much is needed
403Email not confirmed, or the method is not available with a token
404Server not found or it belongs to another account; unknown endpoint
409No machines of this type are available; the server folder is still being saved
415POST without Content-Type: application/json
429Rate limit exceeded; wait as many seconds as the Retry-After header says
500An error on our side
502The catalogue is temporarily unavailable
503The machine provider is overloaded; retry the launch in a minute

Limits

  • 60 requests per minute per token, across all methods. One token’s limit does not affect others.
  • 10 launches per minute per token. Failed launches count too.
  • The catalogue is additionally limited to 60 requests per minute per IP address.
  • Up to 4 servers at a time; the exact number is in max_active_instances. Need more? Write to us.
  • Request body up to 64 KB.

Server status changes at most every few seconds, so poll it every 5–10 seconds.

Retry on 429, 502 and 503
import time, requests

def call(method, url, **kw):
    for attempt in range(5):
        r = requests.request(method, url, **kw)
        if r.status_code not in (429, 502, 503):
            return r
        time.sleep(int(r.headers.get("Retry-After", 30)))
    return r

Catalogue

Available machines

GET/api/offers

A list of available machines with their price in euros per hour, cheapest first. The catalogue refreshes every 10 minutes.

Query parameters

ParameterTypeDescription
gpuoptionalstringGPU model, for example h200. Without it, or with an unknown model, all models are returned. The model list is below the table.
countoptionalintegerNumber of cards in the machine: 1, 2, 4 or 8. Other values are ignored.
alloptionalstring1 also shows unverified machines. By default only verified ones (verified: true).

Models: b200 h200 h100 a100 a100-40 l40s l40 l4 rtx-pro-6000 rtx-a6000 rtx-a5000 rtx-a4000 rtx-5090 rtx-5080 rtx-5070-ti rtx-4090 rtx-4080 rtx-4060-ti rtx-3090 rtx-3080 rtx-3060. a100 is 80 GB, a100-40 is 40 GB.

Machine fields
idintegerPass it as offer_id when launching
gpu, gpu_namestringModel: code for requests and display name
num_gpusintegerNumber of cards
vramintegerVRAM of one card, GB
cpu, cpu_cores, cpu_ghzstring, integer, numberCPU, available cores, clock speed
ramintegerRAM, GB
disk, disk_bwintegerHow many GB of disk you can take; disk speed, MB/s
net_down, net_upintegerNetwork, Mbit/s
countrystringCountry, ISO code
reliabilitynumberMachine reliability from 0 to 1
cuda, drivernumber, stringHighest supported CUDA and driver version
dlperf, tflops, mem_bw, pcienumberPerformance: DLPerf score, TFLOPS, memory and PCIe bandwidth
verifiedbooleanThe machine is verified
pricenumberEUR per hour for the whole server, with the template’s minimum disk included
disk_eurnumberEUR per hour for 1 GB of disk above the included amount
traffic_eur, traffic_up_eurnumberEUR per 1 GB of downloaded and uploaded traffic
durationintegerHow many more days the machine can be rented
static_ip, portsboolean, integerStatic IP, number of open ports

If live is false, the catalogue is switched off and offers is empty. updated is the response time in Unix milliseconds.

Errors

  • 401Wrong or revoked token
  • 429More than 60 requests per minute
  • 502The catalogue is temporarily unavailable; retry in a minute
Request
import os, requests

API = "https://vramfarm.com/api"
H = {"Authorization": f"Bearer {os.environ['GF_TOKEN']}"}

r = requests.get(f"{API}/offers", headers=H, params={"gpu": "h200", "count": 4})
r.raise_for_status()
for o in r.json()["offers"][:5]:
    print(o["id"], o["gpu_name"], o["price"], "EUR/h", o["country"])
Response · 200
{
  "live": true,
  "updated": 1761040000000,
  "offers": [
    {
      "id": 18273645,
      "gpu": "h200",
      "gpu_name": "H200",
      "num_gpus": 4,
      "vram": 141,
      "cpu": "AMD EPYC 9354",
      "cpu_cores": 64,
      "ram": 512,
      "disk": 2000,
      "net_down": 8000,
      "net_up": 8000,
      "country": "DE",
      "reliability": 0.998,
      "cuda": 12.8,
      "verified": true,
      "price": 33.16,
      "disk_eur": 0.00005,
      "traffic_eur": 0.007,
      "traffic_up_eur": 0.007
    }
  ]
}

Machine types

GET/api/offer-types

One row per “model × number of cards” pair: the cheapest machine with reliability of 0.97 or more and how many such machines are available. Handy for picking gpu and count for a launch.

No parameters.

Fields are the same as for a catalogue machine, plus available, how many machines of this type are free, and max_dlperf, the best performance score among them. The response is cached for a minute.

Errors

  • 401Wrong or revoked token
  • 429More than 60 requests per minute
  • 502The catalogue is temporarily unavailable
Request
import os, requests

API = "https://vramfarm.com/api"
H = {"Authorization": f"Bearer {os.environ['GF_TOKEN']}"}

types = requests.get(f"{API}/offer-types", headers=H).json()["types"]
for t in types:
    print(f'{t["num_gpus"]}x {t["gpu_name"]}: from {t["price"]} EUR/h, {t["available"]} available')
Response · 200
{
  "live": true,
  "updated": 1761040000000,
  "types": [
    {
      "id": 20311822,
      "gpu": "rtx-3090",
      "gpu_name": "RTX 3090",
      "num_gpus": 1,
      "vram": 24,
      "price": 0.3,
      "reliability": 0.991,
      "available": 37,
      "max_dlperf": 41
    }
  ]
}

Servers

Launch a server

POST/api/instances

Rents a machine and starts the chosen app or your Docker image on it. The response comes back immediately; the server takes 2–5 minutes to get ready, or 8–15 with your own image.

Request body, JSON

ParameterTypeDescription
offer_idoptionalintegerMachine id from the catalogue. If it has been taken, we pick a similar one of the same type. Either offer_id or gpu is required.
gpuoptionalstringModel, if the exact machine doesn’t matter: we take the cheapest reliable one that fits.
countoptionalintegerNumber of cards when choosing by gpu, 1 to 8. Default 1.
templateoptionalstringWhat to run, pytorch by default. See the table below. An unknown value is treated as pytorch.
imageoptionalstringRequired for custom: a public image, written as in docker pull.
cmdoptionalstringFor custom: a command to run at start, up to 4000 characters.
diskoptionalintegerDisk, GB. Less than the template minimum is raised to the minimum. No more than 2000 and no more than the machine has.
ws_restoreoptionalbooleanDefault true: your saved working folder comes back on the server. false gives a clean server.
Templates (template)
CodeWhat’s insideDisk fromCard memory from
pytorchPyTorch, CUDA, Jupyter60 GB
jupyterJupyter40 GB
customYour image from image60 GB
comfyuiComfyUI and Stable Diffusion 1.5100 GB
comfy-fluxComfyUI and Flux schnell100 GB16 GB
comfy-sdxlComfyUI and SDXL100 GB12 GB
comfy-wanComfyUI and Wan 2.2 5B, video100 GB24 GB
comfy-wan14ComfyUI and Wan 2.2 14B, video160 GB48 GB
comfy-qwenComfyUI and Qwen-Image100 GB24 GB
comfy-qeditComfyUI and Qwen-Image-Edit100 GB24 GB
comfy-hidreamComfyUI and HiDream-O160 GB24 GB
chatOpen WebUI and Ollama, model qwen3:8b80 GB12 GB
chat-proOpen WebUI and Ollama, model qwen3:32b100 GB24 GB
chat-mimoOpen WebUI and Ollama, model MiMo 9B60 GB12 GB
vllmvLLM, model Qwen/Qwen2.5-7B-Instruct100 GB
tts-qwen3Qwen3-TTS text to speech, Gradio40 GB12 GB
tts-voxcpmVoxCPM2 text to speech, Gradio40 GB12 GB
tts-chatterboxChatterbox text to speech, Gradio40 GB12 GB

Billing runs by the minute from the moment the server becomes running, with a minimum of 15 minutes per launch. Your balance must cover at least one hour of all your servers including the new one. When the balance runs out, servers stop and their disks are wiped.

An identical request within 15 seconds returns the server already created, with "duplicate": true; we won’t rent a second machine. If a launch fails because of the balance or an unconfirmed email, it is not queued for token requests: retry it yourself.

Errors

  • 400No GPU given, invalid image, disk too large, not enough VRAM for the template, or already 4 servers
  • 402Not enough balance for an hour; the amount is in error
  • 403Email not confirmed; the response has "unverified": true
  • 409No machines of this type are available
  • 429More than 10 launches per minute per token
  • 503The machine provider is overloaded; retry in a minute
Request
import os, requests

API = "https://vramfarm.com/api"
H = {"Authorization": f"Bearer {os.environ['GF_TOKEN']}"}

r = requests.post(f"{API}/instances", headers=H, json={
    "gpu": "h200", "count": 4, "template": "custom",
    "image": "nvidia/cuda:12.4.1-devel-ubuntu22.04", "disk": 200,
    "cmd": "pip install -q torch && python /workspace/train.py",
})
if not r.ok:
    raise SystemExit(f"{r.status_code} {r.json()['code']}: {r.json()['error']}")
print("server", r.json()["id"])
Response · 200
{
  "ok": true,
  "id": 1234,
  "status": "provisioning"
}

List servers

GET/api/instances

Your servers, newest first, up to 100. By default: running, starting, and stopped in the last 7 days.

Query parameters

ParameterTypeDescription
statusoptionalstringprovisioning, running or stopped: only servers in that state.

Each item of instances has the same shape as the GET /api/instances/{id} response.

Errors

  • 401Wrong or revoked token
Request
import os, requests

API = "https://vramfarm.com/api"
H = {"Authorization": f"Bearer {os.environ['GF_TOKEN']}"}

r = requests.get(f"{API}/instances", headers=H, params={"status": "running"})
for s in r.json()["instances"]:
    print(s["id"], s["gpu_name"], s["cost_eur"], "EUR")
Response · 200
{
  "instances": [
    { "id": 1234, "status": "running", "gpu": "h200", "gpu_count": 4, "cost_eur": 0.55, "...": "..." }
  ]
}

Server and access

GET/api/instances/{id}

The state of one server. Once status is running, access holds SSH, Jupyter and the app address.

Path parameters

ParameterTypeDescription
idrequiredintegerid from the launch response
Response fields
statusstringprovisioning: getting ready; running: running and billed; stopped: stopped
stage, stage_notestring | nullOnly while provisioning: the stage (for example pick, loading) and what is happening, in your account language
gpu, gpu_name, gpu_countstring, integerModel and number of cards
template, imagestringTemplate and image (for custom)
disk_gbintegerDisk, GB
price_eur_hournumberPrice per hour for the whole server, EUR
cost_eurnumberCharged for this launch, EUR; for a running server, up to the current minute
traffic_eurnumberCharged for traffic, EUR
specsobject | nullMachine specs
access.sshobject | nullhost, port, user and a ready command
access.jupyterstring | nullJupyter link with a token
access.appstring | nullApp address for ComfyUI and vLLM; for custom, if the image opens its own port. More in Apps on the server
access.workdirstringWorking folder, /workspace
errorstring | nullWhy the server stopped on its own: the image failed to download, the balance ran out
created_at, started_at, stopped_atstring | nullTime in ISO 8601, UTC

access is filled only for a running server. The SSH key is the one added in your dashboard settings. If a machine fails to start, we take another one for free; the server id stays the same.

Working with the server
ssh -p 31001 root@vramfarm.com nvidia-smi
scp -P 31001 root@vramfarm.com:/workspace/out.ckpt ./

Errors

  • 404No server with this id, or it isn’t yours
Request
import os, requests

API = "https://vramfarm.com/api"
H = {"Authorization": f"Bearer {os.environ['GF_TOKEN']}"}

s = requests.get(f"{API}/instances/1234", headers=H).json()
if s["status"] == "running":
    print(s["access"]["ssh"]["command"])
else:
    print(s["status"], s["stage_note"] or s["error"])
Response · 200
{
  "id": 1234,
  "status": "running",
  "stage": null,
  "stage_note": null,
  "gpu": "h200",
  "gpu_name": "H200",
  "gpu_count": 4,
  "template": "custom",
  "image": "nvidia/cuda:12.4.1-devel-ubuntu22.04",
  "disk_gb": 200,
  "price_eur_hour": 33.16,
  "cost_eur": 0.55,
  "traffic_eur": 0,
  "specs": {
    "vram_gb": 141, "cpu": "AMD EPYC 9354", "cpu_cores": 64, "ram_gb": 512,
    "country": "DE", "cuda": 12.8, "net_down_mbps": 8000, "net_up_mbps": 8000
  },
  "access": {
    "ssh": {
      "host": "vramfarm.com",
      "port": 31001,
      "user": "root",
      "command": "ssh -p 31001 root@vramfarm.com"
    },
    "jupyter": "https://u.vramfarm.com/api/j/…/lab?token=…",
    "app": null,
    "workdir": "/workspace"
  },
  "error": null,
  "created_at": "2026-10-21T10:00:00.000Z",
  "started_at": "2026-10-21T10:04:12.000Z",
  "stopped_at": null
}

Stop a server

POST/api/instances/stop

Stops the server; billing stops immediately. The server disk is wiped; you can save the working folder.

Request body, JSON

ParameterTypeDescription
idrequiredintegerServer id
saveoptionalbooleantrue: save /workspace first, then stop. Does not work for custom.
forceoptionalbooleantrue: stop right away, even if the folder is still being saved. Saving is interrupted.

Stopping a server that is already stopped returns {"ok":true,"already":true}. It is not an error, so you can put the call in finally or a trap on script exit.

With "save": true the response {"ok":true,"saving":true} comes back immediately, and the server saves the folder and stops on its own, usually within a few minutes. Storage costs €0.03 per GB per month, charged daily. Your next server gets the folder automatically unless you pass "ws_restore": false.

Errors

  • 404No server with this id, or it isn’t yours
  • 409The folder is still being saved; the response has "saving": true. Wait, or pass "force": true
Request
import os, requests

API = "https://vramfarm.com/api"
H = {"Authorization": f"Bearer {os.environ['GF_TOKEN']}"}

r = requests.post(f"{API}/instances/stop", headers=H, json={"id": 1234})
print(r.json())
Response · 200
{
  "ok": true
}

Account

Account

GET/api/account

Balance and readiness to launch. Handy to check before launching that your email is confirmed and an SSH key is added.

No parameters.

emailstringAccount email
verifiedbooleanEmail confirmed. Without it, a launch returns 403
balance_eurnumberBalance, EUR
ssh_key_setbooleanSSH key added. Without a key the server still starts, but you can only get in through Jupyter
active_instancesintegerHow many servers are starting or running now
max_active_instancesintegerHow many you can run at the same time

Errors

  • 401Wrong or revoked token, or the account is blocked
Request
import os, requests

API = "https://vramfarm.com/api"
H = {"Authorization": f"Bearer {os.environ['GF_TOKEN']}"}

acc = requests.get(f"{API}/account", headers=H).json()
assert acc["verified"] and acc["ssh_key_set"], acc
print(acc["balance_eur"], "EUR")
Response · 200
{
  "email": "you@example.com",
  "verified": true,
  "balance_eur": 150.0,
  "ssh_key_set": true,
  "active_instances": 0,
  "max_active_instances": 4
}

Service

Service health

GET/api/health

Checks that the API is up. No token needed.

No parameters.

On a failure the response is 503 with a short reason in why, for example {"ok":false,"why":"…"}.

Errors

  • 503The service is not fully working; the reason is in why
Request
import requests

r = requests.get("https://vramfarm.com/api/health", timeout=10)
print(r.status_code, r.json())
Response · 200
{
  "ok": true,
  "status": "ok"
}

Apps on the server via API

ComfyUI, the text-to-speech apps, the chat and vLLM each have their own API. Use it to generate images, audio and text from a script without opening the interface. Your VRAMfarm token is not needed by these apps and is never sent to them.

Where to find the app address:

  • ComfyUI and vLLM: the access.app field in GET /api/instances/{id}, like https://u.vramfarm.com/api/a/<key>/. For vLLM it already ends in v1.
  • Chat and text to speech: an address like http://<host>:<port> is shown on the server card in the dashboard; it is not in access.app. From a script, an SSH tunnel is more reliable: it works whenever SSH does.
  • Your own image (custom): access.app is filled if the container opens a port other than 22, 8080 and 8888.
SSH tunnel
# SSH port from access.ssh.port; -N: tunnel only, no shell
ssh -p 31001 root@vramfarm.com -N -L 11434:127.0.0.1:11434

Keep the tunnel open while your script runs: requests to localhost:11434 go to the server. For text to speech use port 7860 (-L 7860:127.0.0.1:7860), for ComfyUI 8188, for vLLM 8000. The app starts 5–10 minutes after running; until then the address returns 502 or does not respond.

ComfyUI

Templates comfyui and comfy-*. Build a workflow in the interface and save it with Workflow → Export (API) as workflow_api.json. Then the script changes the prompt text in the workflow, sends it to /prompt, waits for the result in /history/{prompt_id} and downloads the images through /view.

import json, time, requests

APP = "https://u.vramfarm.com/api/a/<key>/"                                   # access.app of the server
wf = json.load(open("workflow_api.json"))        # Workflow → Export (API) in ComfyUI
wf["6"]["inputs"]["text"] = "a cat in a spacesuit, photo"  # node number from your file

pid = requests.post(APP + "prompt", json={"prompt": wf}).json()["prompt_id"]
while pid not in (h := requests.get(APP + f"history/{pid}").json()):
    time.sleep(2)

for node in h[pid]["outputs"].values():
    for img in node.get("images", []):
        data = requests.get(APP + "view", params=img).content  # filename, subfolder, type
        open(img["filename"], "wb").write(data)

Text to speech and other Gradio apps

The tts-qwen3, tts-voxcpm and tts-chatterbox templates are built on Gradio. At the bottom of the app page there is a “Use via API” link listing the methods, their parameters and ready examples. Method names differ between apps, so replace /generate in the example with the name from “Use via API” or from view_api().

# pip install gradio_client
from gradio_client import Client

client = Client("http://localhost:7860/")   # through an SSH tunnel or the address from the dashboard
client.view_api()                           # list of methods and their parameters

# method name and argument order: from view_api() or "Use via API"
audio_path = client.predict("Hello! This is a speech test.", api_name="/generate")
print(audio_path)                           # the result file on your computer

Chat: Ollama and Open WebUI

Templates chat, chat-pro and chat-mimo: Open WebUI runs on top of Ollama. From a script it is simpler to talk to Ollama directly. It listens only inside the server, on 127.0.0.1:11434, so you need an SSH tunnel as in the example above. The model downloads after launch; until it has loaded, Ollama answers that the model is missing.

import requests

r = requests.post("http://localhost:11434/api/chat", json={
    "model": "qwen3:8b",          # chat-pro uses qwen3:32b
    "messages": [{"role": "user", "content": "In one sentence: what is CUDA?"}],
    "stream": False,
})
print(r.json()["message"]["content"])

Ollama also speaks the OpenAI format at http://localhost:11434/v1/chat/completions, so OpenAI libraries work. Open WebUI has its own API too; you issue a key for it in the Open WebUI settings.

vLLM

The vllm template starts an OpenAI-compatible server with Qwen/Qwen2.5-7B-Instruct. The address is access.app, which already ends in /v1. The key is not checked, but the OpenAI library requires a non-empty one, so pass anything. The model list is at GET /v1/models.

# pip install openai
from openai import OpenAI

client = OpenAI(base_url="https://u.vramfarm.com/api/a/<key>/v1",  # access.app
                api_key="EMPTY")                                    # no key needed
r = client.chat.completions.create(
    model="Qwen/Qwen2.5-7B-Instruct",
    messages=[{"role": "user", "content": "Write a haiku about a graphics card"}],
)
print(r.choices[0].message.content)

Need a token? Sign up, confirm your email and create a token in the dashboard settings. If something doesn’t match this page, or you need a higher limit, write to us.