API VRAMfarm
Avvia server GPU da script e CI: trova una macchina disponibile, avviala con un’app pronta o con la tua immagine Docker, aspetta SSH, fermala. Semplice HTTPS e JSON, nessun SDK necessario.
https://vramfarm.com/api- Le risposte sono in JSON, UTF-8. Le richieste POST richiedono l’header
Content-Type: application/json. - Gli importi sono in euro.
priceè all’ora per l’intero server, tutte le schede incluse, come numero decimale con i centesimi (per esempio0.39). - Gli orari nelle risposte sono in ISO 8601, UTC; il campo
updateddel catalogo è in millisecondi Unix. - I messaggi di errore arrivano nella lingua del tuo account o in quella dell’header
Accept-Language, altrimenti in inglese.
Avvio rapido
Lo script avvia un server con RTX 4090, aspetta che sia pronto, stampa il comando SSH e ferma il server, anche se qualcosa va storto a metà.
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})// Node.js 18+, .mjs file
const API = "https://vramfarm.com/api";
const headers = { Authorization: `Bearer ${process.env.GF_TOKEN}`, "Content-Type": "application/json" };
const call = async (path, body) => {
const r = await fetch(API + path, body ? { method: "POST", headers, body: JSON.stringify(body) } : { headers });
const data = await r.json();
if (!r.ok) throw new Error(`${r.status} ${data.code}: ${data.error}`);
return data;
};
const { id } = await call("/instances", { gpu: "rtx-4090", count: 1, template: "pytorch" });
try {
let srv;
while ((srv = await call(`/instances/${id}`)).status !== "running") {
if (srv.status === "stopped") throw new Error(srv.error);
console.log(srv.stage_note ?? srv.stage);
await new Promise((ok) => setTimeout(ok, 10_000));
}
console.log(srv.access.ssh.command);
} finally {
await call("/instances/stop", { id });
}#!/usr/bin/env bash
# needs curl and jq
set -euo pipefail
API=https://vramfarm.com/api; H="Authorization: Bearer $GF_TOKEN"; J="Content-Type: application/json"
ID=$(curl -sf "$API/instances" -H "$H" -H "$J" \
-d '{"gpu":"rtx-4090","count":1,"template":"pytorch"}' | jq -r .id)
trap 'curl -sf "$API/instances/stop" -H "$H" -H "$J" -d "{\"id\":$ID}" >/dev/null' EXIT
for i in $(seq 1 120); do
S=$(curl -sf "$API/instances/$ID" -H "$H")
ST=$(jq -r .status <<<"$S")
[ "$ST" = running ] && break
[ "$ST" = stopped ] && { echo "failed to start: $(jq -r .error <<<"$S")"; exit 1; }
echo " $(jq -r '.stage_note // .stage' <<<"$S")"; sleep 10
done
SSH=$(jq -r .access.ssh.command <<<"$S")
$SSH -o StrictHostKeyChecking=accept-new 'cd /workspace && python train.py'Autenticazione
Crea un token nella dashboard → Impostazioni → Token API. Un token ha la forma gf_live_… e viene mostrato una sola volta: salvalo nei secret della tua CI o nella variabile d’ambiente GF_TOKEN. Dallo stesso punto puoi revocare i token. Puoi avere fino a 10 token attivi.
Invia il token nell’header Authorization: Bearer gf_live_…. Non serve altro: le richieste non vengono firmate, non ci sono timestamp né recvWindow, e per le richieste con token non vengono controllati cookie e Origin.
import os, requests
H = {"Authorization": f"Bearer {os.environ['GF_TOKEN']}"}
print(requests.get("https://vramfarm.com/api/account", headers=H).json())const H = { Authorization: `Bearer ${process.env.GF_TOKEN}` };
console.log(await fetch("https://vramfarm.com/api/account", { headers: H }).then((r) => r.json()));export GF_TOKEN=gf_live_xxxxxxxxxxxxxxxxxxxxxxxx
curl -s https://vramfarm.com/api/account -H "Authorization: Bearer $GF_TOKEN"Un token dà accesso solo ai metodi di questa pagina. Password, chiave SSH ed emissione di nuovi token sono disponibili solo nella dashboard; con un token quegli endpoint restituiscono 403. Chi entra in possesso di un token può avviare e fermare server, ma non gestire il tuo account.
Il catalogo e /health funzionano senza token. Ma se invii un header Authorization con un token errato o revocato, tutti gli endpoint restituiscono 401.
Chiama l’API dal tuo server, da uno script o dalla CI. Le richieste da un browser su un altro sito non vanno a buon fine, e lì qualsiasi visitatore potrebbe vedere il token.
Prima del primo avvio, aggiungi una chiave SSH pubblica nelle impostazioni della dashboard: viene installata su ogni nuovo server. Puoi verificare che sia tutto pronto con GET /api/account.
Errori e limiti
In caso di errore ricevi uno stato HTTP e il body {"error": "message", "code": "snake_case_code"}. Il testo di error si può mostrare così com’è a una persona; nel codice basati su code, mai sul testo. Alcune risposte aggiungono un indizio per i programmi: unverified all’avvio, saving quando fermi il server.
| Stato | Quando |
|---|---|
400 | Parametri non validi: immagine, disco, VRAM insufficiente, limite di server |
401 | Token errato o revocato, oppure account bloccato |
402 | Credito insufficiente; error indica quanto serve |
403 | Email non confermata, oppure metodo non disponibile con un token |
404 | Server non trovato o appartenente a un altro account; endpoint sconosciuto |
409 | Nessuna macchina di questo tipo disponibile; la cartella del server è ancora in fase di salvataggio |
415 | POST senza Content-Type: application/json |
429 | Limite di richieste superato; aspetta i secondi indicati nell’header Retry-After |
500 | Un errore da parte nostra |
502 | Il catalogo è temporaneamente non disponibile |
503 | Il fornitore delle macchine è sovraccarico; riprova l’avvio tra un minuto |
Limiti
- 60 richieste al minuto per token, su tutti i metodi. Il limite di un token non influisce sugli altri.
- 10 avvii al minuto per token. Contano anche gli avvii non riusciti.
- Il catalogo è limitato anche a 60 richieste al minuto per indirizzo IP.
- Fino a 4 server contemporaneamente; il numero esatto è in
max_active_instances. Te ne servono di più? Scrivici. - Body della richiesta fino a 64 KB.
Lo stato del server cambia al massimo ogni pochi secondi, quindi interrogalo ogni 5–10 secondi.
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 rasync function call(url, opts = {}) {
for (let attempt = 0; attempt < 5; attempt++) {
const r = await fetch(url, opts);
if (![429, 502, 503].includes(r.status)) return r;
await new Promise((ok) => setTimeout(ok, (+r.headers.get("Retry-After") || 30) * 1000));
}
throw new Error("API is not responding");
}curl -s --retry 5 --retry-delay 30 --retry-all-errors \
https://vramfarm.com/api/account -H "Authorization: Bearer $GF_TOKEN"Catalogo
Macchine disponibili
GET/api/offers
Un elenco delle macchine disponibili con il prezzo in euro all’ora, dalla più economica. Il catalogo si aggiorna ogni 10 minuti.
Parametri della query
| Parametro | Tipo | Descrizione |
|---|---|---|
gpufacoltativo | string | Modello di GPU, per esempio h200. Senza questo parametro, o con un modello sconosciuto, vengono restituiti tutti i modelli. L’elenco dei modelli è sotto la tabella. |
countfacoltativo | integer | Numero di schede nella macchina: 1, 2, 4 o 8. Gli altri valori vengono ignorati. |
allfacoltativo | string | 1 mostra anche le macchine non verificate. Per impostazione predefinita solo quelle verificate (verified: true). |
Modelli: 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 è da 80 GB, a100-40 da 40 GB.
Campi della macchina
id | integer | Passalo come offer_id all’avvio |
gpu, gpu_name | string | Modello: codice per le richieste e nome visualizzato |
num_gpus | integer | Numero di schede |
vram | integer | VRAM di una scheda, GB |
cpu, cpu_cores, cpu_ghz | string, integer, number | CPU, core disponibili, frequenza |
ram | integer | RAM, GB |
disk, disk_bw | integer | Quanti GB di disco puoi prendere; velocità del disco, MB/s |
net_down, net_up | integer | Rete, Mbit/s |
country | string | Paese, codice ISO |
reliability | number | Affidabilità della macchina da 0 a 1 |
cuda, driver | number, string | Versione massima supportata di CUDA e del driver |
dlperf, tflops, mem_bw, pcie | number | Prestazioni: punteggio DLPerf, TFLOPS, banda di memoria e PCIe |
verified | boolean | La macchina è verificata |
price | number | EUR all’ora per l’intero server, con il disco minimo del template incluso |
disk_eur | number | EUR all’ora per 1 GB di disco oltre la quantità inclusa |
traffic_eur, traffic_up_eur | number | EUR per 1 GB di traffico in download e upload |
duration | integer | Per quanti altri giorni si può noleggiare la macchina |
static_ip, ports | boolean, integer | IP statico, numero di porte aperte |
Se live è false, il catalogo è disattivato e offers è vuoto. updated è l’orario della risposta in millisecondi Unix.
Errori
401Token errato o revocato429Più di 60 richieste al minuto502Il catalogo è temporaneamente non disponibile; riprova tra un minuto
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"])const API = "https://vramfarm.com/api";
const H = { Authorization: `Bearer ${process.env.GF_TOKEN}` };
const r = await fetch(`${API}/offers?gpu=h200&count=4`, { headers: H });
const { offers } = await r.json();
console.log(offers.slice(0, 5).map((o) => [o.id, o.gpu_name, o.price, o.country]));curl -s "https://vramfarm.com/api/offers?gpu=h200&count=4" \
-H "Authorization: Bearer $GF_TOKEN"{
"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
}
]
}Tipi di macchina
GET/api/offer-types
Una riga per ogni coppia “modello × numero di schede”: la macchina più economica con affidabilità pari o superiore a 0,97 e quante macchine di questo tipo sono disponibili. Utile per scegliere gpu e count per un avvio.
Nessun parametro.
I campi sono gli stessi di una macchina del catalogo, più available, quante macchine di questo tipo sono libere, e max_dlperf, il miglior punteggio di prestazioni tra queste. La risposta viene messa in cache per un minuto.
Errori
401Token errato o revocato429Più di 60 richieste al minuto502Il catalogo è temporaneamente non disponibile
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')const API = "https://vramfarm.com/api";
const H = { Authorization: `Bearer ${process.env.GF_TOKEN}` };
const { types } = await fetch(`${API}/offer-types`, { headers: H }).then((r) => r.json());
for (const t of types) console.log(`${t.num_gpus}x ${t.gpu_name}: from ${t.price} EUR/h`);curl -s https://vramfarm.com/api/offer-types -H "Authorization: Bearer $GF_TOKEN"{
"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
}
]
}Server
Avviare un server
POST/api/instances
Noleggia una macchina e ci avvia l’app scelta o la tua immagine Docker. La risposta arriva subito; il server impiega 2–5 minuti per essere pronto, o 8–15 con un’immagine tua.
Body della richiesta, JSON
| Parametro | Tipo | Descrizione |
|---|---|---|
offer_idfacoltativo | integer | id della macchina dal catalogo. Se è già stata presa, ne scegliamo una simile dello stesso tipo. È obbligatorio offer_id oppure gpu. |
gpufacoltativo | string | Modello, se la macchina esatta non ti interessa: prendiamo la più economica affidabile che va bene. |
countfacoltativo | integer | Numero di schede quando scegli per gpu, da 1 a 8. Predefinito 1. |
templatefacoltativo | string | Cosa avviare, pytorch per impostazione predefinita. Vedi la tabella qui sotto. Un valore sconosciuto viene trattato come pytorch. |
imagefacoltativo | string | Obbligatorio per custom: un’immagine pubblica, scritta come in docker pull. |
cmdfacoltativo | string | Per custom: un comando da eseguire all’avvio, fino a 4000 caratteri. |
diskfacoltativo | integer | Disco, GB. Un valore inferiore al minimo del template viene portato al minimo. Non più di 2000 e non più di quanto ha la macchina. |
ws_restorefacoltativo | boolean | Predefinito true: la cartella di lavoro salvata torna sul server. false dà un server pulito. |
Template (template)
| Codice | Cosa contiene | Disco da | Memoria della scheda da |
|---|---|---|---|
pytorch | PyTorch, CUDA, Jupyter | 60 GB | |
jupyter | Jupyter | 40 GB | |
custom | La tua immagine da image | 60 GB | |
comfyui | ComfyUI e Stable Diffusion 1.5 | 100 GB | |
comfy-flux | ComfyUI e Flux schnell | 100 GB | 16 GB |
comfy-sdxl | ComfyUI e SDXL | 100 GB | 12 GB |
comfy-wan | ComfyUI e Wan 2.2 5B, video | 100 GB | 24 GB |
comfy-wan14 | ComfyUI e Wan 2.2 14B, video | 160 GB | 48 GB |
comfy-qwen | ComfyUI e Qwen-Image | 100 GB | 24 GB |
comfy-qedit | ComfyUI e Qwen-Image-Edit | 100 GB | 24 GB |
comfy-hidream | ComfyUI e HiDream-O1 | 60 GB | 24 GB |
chat | Open WebUI e Ollama, modello qwen3:8b | 80 GB | 12 GB |
chat-pro | Open WebUI e Ollama, modello qwen3:32b | 100 GB | 24 GB |
chat-mimo | Open WebUI e Ollama, modello MiMo 9B | 60 GB | 12 GB |
vllm | vLLM, modello Qwen/Qwen2.5-7B-Instruct | 100 GB | |
tts-qwen3 | Sintesi vocale Qwen3-TTS, Gradio | 40 GB | 12 GB |
tts-voxcpm | Sintesi vocale VoxCPM2, Gradio | 40 GB | 12 GB |
tts-chatterbox | Sintesi vocale Chatterbox, Gradio | 40 GB | 12 GB |
La fatturazione è al minuto dal momento in cui il server diventa running, con un minimo di 15 minuti per avvio. Il credito deve coprire almeno un’ora di tutti i tuoi server, compreso quello nuovo. Quando il credito si esaurisce, i server si fermano e i loro dischi vengono cancellati.
Una richiesta identica entro 15 secondi restituisce il server già creato, con "duplicate": true; non noleggiamo una seconda macchina. Se un avvio fallisce per il credito o per un’email non confermata, per le richieste con token non viene messo in coda: riprova tu.
Errori
400Nessuna GPU indicata, immagine non valida, disco troppo grande, VRAM insufficiente per il template o già 4 server attivi402Credito insufficiente per un’ora; l’importo è inerror403Email non confermata; la risposta contiene"unverified": true409Nessuna macchina di questo tipo disponibile429Più di 10 avvii al minuto per token503Il fornitore delle macchine è sovraccarico; riprova tra un minuto
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"])const API = "https://vramfarm.com/api";
const H = { Authorization: `Bearer ${process.env.GF_TOKEN}`, "Content-Type": "application/json" };
const r = await fetch(`${API}/instances`, {
method: "POST", headers: H,
body: JSON.stringify({
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",
}),
});
const data = await r.json();
if (!r.ok) throw new Error(`${r.status} ${data.code}: ${data.error}`);
console.log("server", data.id);curl -s https://vramfarm.com/api/instances \
-H "Authorization: Bearer $GF_TOKEN" \
-H "Content-Type: application/json" \
-d '{"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"}'{
"ok": true,
"id": 1234,
"status": "provisioning"
}Elenco dei server
GET/api/instances
I tuoi server, dai più recenti, fino a 100. Per impostazione predefinita: attivi, in avvio e fermati negli ultimi 7 giorni.
Parametri della query
| Parametro | Tipo | Descrizione |
|---|---|---|
statusfacoltativo | string | provisioning, running o stopped: solo i server in quello stato. |
Ogni elemento di instances ha la stessa struttura della risposta di GET /api/instances/{id}.
Errori
401Token errato o revocato
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")const API = "https://vramfarm.com/api";
const H = { Authorization: `Bearer ${process.env.GF_TOKEN}` };
const { instances } = await fetch(`${API}/instances?status=running`, { headers: H }).then((r) => r.json());
for (const s of instances) console.log(s.id, s.gpu_name, s.cost_eur);curl -s "https://vramfarm.com/api/instances?status=running" \
-H "Authorization: Bearer $GF_TOKEN"{
"instances": [
{ "id": 1234, "status": "running", "gpu": "h200", "gpu_count": 4, "cost_eur": 0.55, "...": "..." }
]
}Server e accesso
GET/api/instances/{id}
Lo stato di un server. Quando status è running, access contiene SSH, Jupyter e l’indirizzo dell’app.
Parametri del percorso
| Parametro | Tipo | Descrizione |
|---|---|---|
idobbligatorio | integer | id dalla risposta dell’avvio |
Campi della risposta
status | string | provisioning: in preparazione; running: attivo e fatturato; stopped: fermo |
stage, stage_note | string | null | Solo durante provisioning: la fase (per esempio pick, loading) e cosa sta succedendo, nella lingua del tuo account |
gpu, gpu_name, gpu_count | string, integer | Modello e numero di schede |
template, image | string | Template e immagine (per custom) |
disk_gb | integer | Disco, GB |
price_eur_hour | number | Prezzo all’ora per l’intero server, EUR |
cost_eur | number | Addebitato per questo avvio, EUR; per un server attivo, fino al minuto corrente |
traffic_eur | number | Addebitato per il traffico, EUR |
specs | object | null | Specifiche della macchina |
access.ssh | object | null | host, port, user e un command pronto |
access.jupyter | string | null | Link a Jupyter con un token |
access.app | string | null | Indirizzo dell’app per ComfyUI e vLLM; per custom, se l’immagine apre una propria porta. Maggiori dettagli in App sul server |
access.workdir | string | Cartella di lavoro, /workspace |
error | string | null | Perché il server si è fermato da solo: download dell’immagine non riuscito, credito esaurito |
created_at, started_at, stopped_at | string | null | Orario in ISO 8601, UTC |
access viene compilato solo per un server attivo. La chiave SSH è quella aggiunta nelle impostazioni della dashboard. Se una macchina non si avvia, ne prendiamo un’altra gratuitamente; l’id del server resta lo stesso.
ssh -p 31001 root@vramfarm.com nvidia-smi
scp -P 31001 root@vramfarm.com:/workspace/out.ckpt ./Errori
404Nessun server con questo id, oppure non è tuo
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"])const API = "https://vramfarm.com/api";
const H = { Authorization: `Bearer ${process.env.GF_TOKEN}` };
const s = await fetch(`${API}/instances/1234`, { headers: H }).then((r) => r.json());
console.log(s.status === "running" ? s.access.ssh.command : s.stage_note ?? s.error);curl -s https://vramfarm.com/api/instances/1234 -H "Authorization: Bearer $GF_TOKEN"{
"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
}Fermare un server
POST/api/instances/stop
Ferma il server; la fatturazione si interrompe subito. Il disco del server viene cancellato; puoi salvare la cartella di lavoro.
Body della richiesta, JSON
| Parametro | Tipo | Descrizione |
|---|---|---|
idobbligatorio | integer | Id del server |
savefacoltativo | boolean | true: prima salva /workspace, poi ferma. Non funziona per custom. |
forcefacoltativo | boolean | true: ferma subito, anche se la cartella è ancora in fase di salvataggio. Il salvataggio viene interrotto. |
Fermare un server già fermo restituisce {"ok":true,"already":true}. Non è un errore, quindi puoi mettere la chiamata in finally o in un trap all’uscita dello script.
Con "save": true la risposta {"ok":true,"saving":true} arriva subito, e il server salva la cartella e si ferma da solo, di solito entro pochi minuti. L’archiviazione costa 0,03 € per GB al mese, con addebito giornaliero. Il tuo prossimo server riceve la cartella in automatico, a meno che tu non passi "ws_restore": false.
Errori
404Nessun server con questo id, oppure non è tuo409La cartella è ancora in fase di salvataggio; la risposta contiene"saving": true. Aspetta oppure passa"force": true
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())const API = "https://vramfarm.com/api";
const H = { Authorization: `Bearer ${process.env.GF_TOKEN}`, "Content-Type": "application/json" };
const r = await fetch(`${API}/instances/stop`, { method: "POST", headers: H, body: JSON.stringify({ id: 1234 }) });
console.log(await r.json());curl -s https://vramfarm.com/api/instances/stop \
-H "Authorization: Bearer $GF_TOKEN" \
-H "Content-Type: application/json" \
-d '{"id":1234}'{
"ok": true
}Account
Account
GET/api/account
Credito e prontezza all’avvio. Utile per verificare prima di un avvio che l’email sia confermata e che sia stata aggiunta una chiave SSH.
Nessun parametro.
email | string | Email dell’account |
verified | boolean | Email confermata. Senza conferma, un avvio restituisce 403 |
balance_eur | number | Credito, EUR |
ssh_key_set | boolean | Chiave SSH aggiunta. Senza chiave il server si avvia lo stesso, ma puoi entrare solo tramite Jupyter |
active_instances | integer | Quanti server sono in avvio o attivi in questo momento |
max_active_instances | integer | Quanti server puoi tenere attivi contemporaneamente |
Errori
401Token errato o revocato, oppure account bloccato
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")const API = "https://vramfarm.com/api";
const H = { Authorization: `Bearer ${process.env.GF_TOKEN}` };
const acc = await fetch(`${API}/account`, { headers: H }).then((r) => r.json());
console.log(acc.balance_eur, acc.verified, acc.ssh_key_set);curl -s https://vramfarm.com/api/account -H "Authorization: Bearer $GF_TOKEN"{
"email": "you@example.com",
"verified": true,
"balance_eur": 150.0,
"ssh_key_set": true,
"active_instances": 0,
"max_active_instances": 4
}Servizio
Stato del servizio
GET/api/health
Verifica che l’API sia attiva. Non serve un token.
Nessun parametro.
In caso di problemi la risposta è 503 con un breve motivo in why, per esempio {"ok":false,"why":"…"}.
Errori
503Il servizio non funziona completamente; il motivo è inwhy
import requests
r = requests.get("https://vramfarm.com/api/health", timeout=10)
print(r.status_code, r.json())const r = await fetch("https://vramfarm.com/api/health");
console.log(r.status, await r.json());curl -s https://vramfarm.com/api/health{
"ok": true,
"status": "ok"
}App sul server tramite API
ComfyUI, le app di sintesi vocale, la chat e vLLM hanno ciascuna la propria API. Usala per generare immagini, audio e testo da uno script senza aprire l’interfaccia. Queste app non hanno bisogno del tuo token VRAMfarm, che non viene mai inviato loro.
Dove trovare l’indirizzo dell’app:
- ComfyUI e vLLM: il campo
access.appin GET /api/instances/{id}, del tipohttps://u.vramfarm.com/api/a/<key>/. Per vLLM termina già conv1. - Chat e sintesi vocale: un indirizzo del tipo
http://<host>:<port>è indicato sulla card del server nella dashboard; non è inaccess.app. Da uno script è più affidabile un tunnel SSH: funziona ogni volta che funziona SSH. - La tua immagine (
custom):access.appviene compilato se il container apre una porta diversa da 22, 8080 e 8888.
# SSH port from access.ssh.port; -N: tunnel only, no shell
ssh -p 31001 root@vramfarm.com -N -L 11434:127.0.0.1:11434Tieni aperto il tunnel mentre gira lo script: le richieste a localhost:11434 arrivano al server. Per la sintesi vocale usa la porta 7860 (-L 7860:127.0.0.1:7860), per ComfyUI la 8188, per vLLM la 8000. L’app si avvia 5–10 minuti dopo running; fino ad allora l’indirizzo restituisce 502 o non risponde.
ComfyUI
Template comfyui e comfy-*. Costruisci un workflow nell’interfaccia e salvalo con Workflow → Export (API) come workflow_api.json. Poi lo script cambia il testo del prompt nel workflow, lo invia a /prompt, aspetta il risultato in /history/{prompt_id} e scarica le immagini tramite /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)import { readFile, writeFile } from "node:fs/promises";
const APP = "https://u.vramfarm.com/api/a/<key>/"; // access.app of the server
const wf = JSON.parse(await readFile("workflow_api.json", "utf8"));
wf["6"].inputs.text = "a cat in a spacesuit, photo";
const { prompt_id } = await fetch(APP + "prompt", {
method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ prompt: wf }),
}).then((r) => r.json());
let h = {};
while (!h[prompt_id]) {
await new Promise((ok) => setTimeout(ok, 2000));
h = await fetch(APP + `history/${prompt_id}`).then((r) => r.json());
}
for (const node of Object.values(h[prompt_id].outputs))
for (const img of node.images ?? []) {
const r = await fetch(APP + "view?" + new URLSearchParams(img));
await writeFile(img.filename, Buffer.from(await r.arrayBuffer()));
}APP="https://u.vramfarm.com/api/a/<key>/"
curl -s "$APP"prompt -H "Content-Type: application/json" \
-d "{\"prompt\": $(cat workflow_api.json)}"
# {"prompt_id":"8f2c…","number":0}
curl -s "$APP"history/8f2c…
curl -s -o out.png "$APP""view?filename=ComfyUI_00001_.png&type=output"Sintesi vocale e altre app Gradio
I template tts-qwen3, tts-voxcpm e tts-chatterbox sono basati su Gradio. In fondo alla pagina dell’app c’è un link “Use via API” con l’elenco dei metodi, i loro parametri ed esempi pronti. I nomi dei metodi cambiano da un’app all’altra, quindi sostituisci /generate nell’esempio con il nome indicato in “Use via API” o in 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// npm install @gradio/client
import { Client } from "@gradio/client";
const client = await Client.connect("http://localhost:7860/");
console.log(await client.view_api());
const res = await client.predict("/generate", ["Hello! This is a speech test."]);
console.log(res.data);# Gradio 5: first the request, then the result by event_id
curl -s -X POST http://localhost:7860/gradio_api/call/generate \
-H "Content-Type: application/json" \
-d '{"data": ["Hello! This is a speech test."]}'
# {"event_id":"3b1f…"}
curl -sN http://localhost:7860/gradio_api/call/generate/3b1f…Chat: Ollama e Open WebUI
Template chat, chat-pro e chat-mimo: Open WebUI gira sopra Ollama. Da uno script è più semplice parlare direttamente con Ollama. È in ascolto solo all’interno del server, su 127.0.0.1:11434, quindi serve un tunnel SSH come nell’esempio qui sopra. Il modello viene scaricato dopo l’avvio; finché non è caricato, Ollama risponde che il modello manca.
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"])const r = await fetch("http://localhost:11434/api/chat", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
model: "qwen3:8b",
messages: [{ role: "user", content: "In one sentence: what is CUDA?" }],
stream: false,
}),
});
console.log((await r.json()).message.content);curl -s http://localhost:11434/api/chat -d '{
"model": "qwen3:8b",
"messages": [{"role": "user", "content": "In one sentence: what is CUDA?"}],
"stream": false
}'Ollama supporta anche il formato OpenAI su http://localhost:11434/v1/chat/completions, quindi le librerie OpenAI funzionano. Anche Open WebUI ha una propria API; la chiave si genera nelle impostazioni di Open WebUI.
vLLM
Il template vllm avvia un server compatibile con OpenAI con Qwen/Qwen2.5-7B-Instruct. L’indirizzo è access.app, che termina già con /v1. La chiave non viene verificata, ma la libreria OpenAI ne richiede una non vuota, quindi passane una qualsiasi. L’elenco dei modelli è in 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)const BASE = "https://u.vramfarm.com/api/a/<key>/v1"; // access.app
const r = await fetch(`${BASE}/chat/completions`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
model: "Qwen/Qwen2.5-7B-Instruct",
messages: [{ role: "user", content: "Write a haiku about a graphics card" }],
}),
});
console.log((await r.json()).choices[0].message.content);BASE="https://u.vramfarm.com/api/a/<key>/v1"
curl -s "$BASE/models"
curl -s "$BASE/chat/completions" -H "Content-Type: application/json" -d '{
"model": "Qwen/Qwen2.5-7B-Instruct",
"messages": [{"role": "user", "content": "Write a haiku about a graphics card"}]
}'Ti serve un token? Registrati, conferma l’email e crea un token nelle impostazioni della dashboard. Se qualcosa non corrisponde a questa pagina o ti serve un limite più alto, scrivici.