API de VRAMfarm
Lanza servidores GPU desde scripts y CI: busca una máquina disponible, iníciala con una app lista para usar o con tu propia imagen de Docker, espera al SSH y detenla. HTTPS y JSON sin más, no hace falta ningún SDK.
https://vramfarm.com/api- Las respuestas son JSON en UTF-8. Las peticiones POST necesitan la cabecera
Content-Type: application/json. - Los importes están en euros.
pricees el precio por hora de todo el servidor, con todas las tarjetas incluidas, como decimal con céntimos (por ejemplo,0.39). - Las horas de las respuestas van en ISO 8601 y UTC; el campo
updateddel catálogo va en milisegundos Unix. - Los mensajes de error llegan en el idioma de tu cuenta o en el de la cabecera
Accept-Language, y si no, en inglés.
Inicio rápido
El script lanza un servidor con RTX 4090, espera a que esté listo, muestra el comando SSH y detiene el servidor, aunque algo falle a mitad de camino.
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'Autenticación
Crea un token en panel → Ajustes → Tokens de API. Un token tiene el aspecto gf_live_… y se muestra una sola vez: guárdalo en los secretos de tu CI o en la variable de entorno GF_TOKEN. En el mismo sitio puedes revocar tokens. Puedes tener hasta 10 tokens activos.
Envía el token en la cabecera Authorization: Bearer gf_live_…. No hace falta nada más: las peticiones no se firman, no hay marca de tiempo ni recvWindow, y en las peticiones con token no se comprueban las cookies ni el 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 solo da acceso a los métodos de esta página. Tu contraseña, tu clave SSH y la emisión de tokens nuevos solo están disponibles en el panel; con un token, esos endpoints devuelven 403. Si se filtra un token, alguien podría lanzar y detener servidores, pero no gestionar tu cuenta.
El catálogo y /health funcionan sin token. Pero si envías una cabecera Authorization con un token incorrecto o revocado, todos los endpoints devuelven 401.
Llama a la API desde tu propio servidor, script o CI. Las peticiones desde un navegador en otra web no pasarán, y además cualquier visitante podría ver ahí el token.
Antes del primer lanzamiento, añade una clave SSH pública en los ajustes del panel: se instala en cada servidor nuevo. Puedes comprobar que todo está listo con GET /api/account.
Errores y límites
Si hay un error, recibes un estado HTTP y el cuerpo {"error": "message", "code": "snake_case_code"}. El texto de error se puede mostrar tal cual a una persona; en tu código, decide según code y nunca según el texto. Algunas respuestas añaden una pista para programas: unverified al lanzar y saving al detener.
| Estado | Cuándo |
|---|---|
400 | Parámetros no válidos: imagen, disco, VRAM insuficiente, límite de servidores |
401 | Token incorrecto o revocado, o cuenta bloqueada |
402 | Saldo insuficiente; error indica cuánto hace falta |
403 | Correo sin confirmar, o el método no está disponible con un token |
404 | Servidor no encontrado o de otra cuenta; endpoint desconocido |
409 | No hay máquinas disponibles de este tipo; la carpeta del servidor aún se está guardando |
415 | POST sin Content-Type: application/json |
429 | Límite de peticiones superado; espera los segundos que indique la cabecera Retry-After |
500 | Un error por nuestra parte |
502 | El catálogo no está disponible temporalmente |
503 | El proveedor de máquinas está saturado; vuelve a intentar el lanzamiento en un minuto |
Límites
- 60 peticiones por minuto y token, sumando todos los métodos. El límite de un token no afecta a los demás.
- 10 lanzamientos por minuto y token. Los lanzamientos fallidos también cuentan.
- El catálogo tiene además un límite de 60 peticiones por minuto y dirección IP.
- Hasta 4 servidores a la vez; el número exacto está en
max_active_instances. ¿Necesitas más? Escríbenos. - Cuerpo de la petición de hasta 64 KB.
El estado de un servidor cambia como mucho cada pocos segundos, así que consúltalo cada 5–10 segundos.
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"Catálogo
Máquinas disponibles
GET/api/offers
Una lista de máquinas disponibles con su precio en euros por hora, de la más barata a la más cara. El catálogo se actualiza cada 10 minutos.
Parámetros de consulta
| Parámetro | Tipo | Descripción |
|---|---|---|
gpuopcional | string | Modelo de GPU, por ejemplo h200. Sin él, o con un modelo desconocido, se devuelven todos los modelos. La lista de modelos está debajo de la tabla. |
countopcional | integer | Número de tarjetas de la máquina: 1, 2, 4 u 8. Otros valores se ignoran. |
allopcional | string | 1 muestra también las máquinas sin verificar. Por defecto, solo las verificadas (verified: true). |
Modelos: 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 es la de 80 GB y a100-40, la de 40 GB.
Campos de la máquina
id | integer | Pásalo como offer_id al lanzar |
gpu, gpu_name | string | Modelo: código para las peticiones y nombre visible |
num_gpus | integer | Número de tarjetas |
vram | integer | VRAM de una tarjeta, GB |
cpu, cpu_cores, cpu_ghz | string, integer, number | CPU, núcleos disponibles, frecuencia |
ram | integer | RAM, GB |
disk, disk_bw | integer | Cuántos GB de disco puedes usar; velocidad del disco, MB/s |
net_down, net_up | integer | Red, Mbit/s |
country | string | País, código ISO |
reliability | number | Fiabilidad de la máquina de 0 a 1 |
cuda, driver | number, string | Versión máxima de CUDA admitida y versión del driver |
dlperf, tflops, mem_bw, pcie | number | Rendimiento: puntuación DLPerf, TFLOPS, ancho de banda de memoria y de PCIe |
verified | boolean | La máquina está verificada |
price | number | EUR por hora para todo el servidor, con el disco mínimo de la plantilla incluido |
disk_eur | number | EUR por hora por cada GB de disco por encima de lo incluido |
traffic_eur, traffic_up_eur | number | EUR por cada GB de tráfico descargado y subido |
duration | integer | Cuántos días más se puede alquilar la máquina |
static_ip, ports | boolean, integer | IP fija, número de puertos abiertos |
Si live es false, el catálogo está desactivado y offers viene vacío. updated es la hora de la respuesta en milisegundos Unix.
Errores
401Token incorrecto o revocado429Más de 60 peticiones por minuto502El catálogo no está disponible temporalmente; vuelve a intentarlo en 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
}
]
}Tipos de máquina
GET/api/offer-types
Una fila por cada par «modelo × número de tarjetas»: la máquina más barata con una fiabilidad de 0,97 o más y cuántas máquinas así hay disponibles. Útil para elegir gpu y count para un lanzamiento.
Sin parámetros.
Los campos son los mismos que los de una máquina del catálogo, más available, cuántas máquinas de este tipo están libres, y max_dlperf, la mejor puntuación de rendimiento entre ellas. La respuesta se guarda en caché durante un minuto.
Errores
401Token incorrecto o revocado429Más de 60 peticiones por minuto502El catálogo no está disponible temporalmente
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
}
]
}Servidores
Lanzar un servidor
POST/api/instances
Alquila una máquina e inicia en ella la app elegida o tu imagen de Docker. La respuesta llega al instante; el servidor tarda 2–5 minutos en estar listo, u 8–15 con tu propia imagen.
Cuerpo de la petición, JSON
| Parámetro | Tipo | Descripción |
|---|---|---|
offer_idopcional | integer | id de la máquina del catálogo. Si ya la han cogido, elegimos una similar del mismo tipo. Es obligatorio indicar offer_id o gpu. |
gpuopcional | string | Modelo, si te da igual la máquina concreta: cogemos la más barata y fiable que encaje. |
countopcional | integer | Número de tarjetas al elegir por gpu, de 1 a 8. Por defecto, 1. |
templateopcional | string | Qué ejecutar; por defecto, pytorch. Consulta la tabla de abajo. Un valor desconocido se trata como pytorch. |
imageopcional | string | Obligatorio con custom: una imagen pública, escrita como en docker pull. |
cmdopcional | string | Con custom: un comando que se ejecuta al iniciar, de hasta 4000 caracteres. |
diskopcional | integer | Disco, GB. Si es menor que el mínimo de la plantilla, se sube al mínimo. No más de 2000 ni más de lo que tiene la máquina. |
ws_restoreopcional | boolean | Por defecto, true: tu carpeta de trabajo guardada vuelve al servidor. Con false obtienes un servidor limpio. |
Plantillas (template)
| Código | Qué incluye | Disco desde | Memoria de la tarjeta desde |
|---|---|---|---|
pytorch | PyTorch, CUDA, Jupyter | 60 GB | |
jupyter | Jupyter | 40 GB | |
custom | Tu imagen de image | 60 GB | |
comfyui | ComfyUI y Stable Diffusion 1.5 | 100 GB | |
comfy-flux | ComfyUI y Flux schnell | 100 GB | 16 GB |
comfy-sdxl | ComfyUI y SDXL | 100 GB | 12 GB |
comfy-wan | ComfyUI y Wan 2.2 5B, vídeo | 100 GB | 24 GB |
comfy-wan14 | ComfyUI y Wan 2.2 14B, vídeo | 160 GB | 48 GB |
comfy-qwen | ComfyUI y Qwen-Image | 100 GB | 24 GB |
comfy-qedit | ComfyUI y Qwen-Image-Edit | 100 GB | 24 GB |
comfy-hidream | ComfyUI y HiDream-O1 | 60 GB | 24 GB |
chat | Open WebUI y Ollama, modelo qwen3:8b | 80 GB | 12 GB |
chat-pro | Open WebUI y Ollama, modelo qwen3:32b | 100 GB | 24 GB |
chat-mimo | Open WebUI y Ollama, modelo MiMo 9B | 60 GB | 12 GB |
vllm | vLLM, modelo Qwen/Qwen2.5-7B-Instruct | 100 GB | |
tts-qwen3 | Texto a voz con Qwen3-TTS, Gradio | 40 GB | 12 GB |
tts-voxcpm | Texto a voz con VoxCPM2, Gradio | 40 GB | 12 GB |
tts-chatterbox | Texto a voz con Chatterbox, Gradio | 40 GB | 12 GB |
La facturación es por minuto desde el momento en que el servidor pasa a running, con un mínimo de 15 minutos por lanzamiento. Tu saldo debe cubrir al menos una hora de todos tus servidores, incluido el nuevo. Cuando se acaba el saldo, los servidores se detienen y sus discos se borran.
Una petición idéntica en menos de 15 segundos devuelve el servidor ya creado, con "duplicate": true; no alquilaremos una segunda máquina. Si un lanzamiento falla por el saldo o por no haber confirmado el correo, en las peticiones con token no se deja en cola: vuelve a intentarlo tú.
Errores
400No se indicó GPU, imagen no válida, disco demasiado grande, VRAM insuficiente para la plantilla o ya hay 4 servidores402Saldo insuficiente para una hora; el importe está enerror403Correo sin confirmar; la respuesta incluye"unverified": true409No hay máquinas disponibles de este tipo429Más de 10 lanzamientos por minuto y token503El proveedor de máquinas está saturado; vuelve a intentarlo en 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"
}Lista de servidores
GET/api/instances
Tus servidores, de más reciente a más antiguo, hasta 100. Por defecto: en marcha, iniciándose y detenidos en los últimos 7 días.
Parámetros de consulta
| Parámetro | Tipo | Descripción |
|---|---|---|
statusopcional | string | provisioning, running o stopped: solo los servidores en ese estado. |
Cada elemento de instances tiene la misma forma que la respuesta de GET /api/instances/{id}.
Errores
401Token incorrecto o revocado
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, "...": "..." }
]
}Servidor y acceso
GET/api/instances/{id}
El estado de un servidor. Cuando status es running, access contiene el SSH, Jupyter y la dirección de la app.
Parámetros de ruta
| Parámetro | Tipo | Descripción |
|---|---|---|
idobligatorio | integer | id de la respuesta del lanzamiento |
Campos de la respuesta
status | string | provisioning: preparándose; running: en marcha y facturándose; stopped: detenido |
stage, stage_note | string | null | Solo durante provisioning: la etapa (por ejemplo, pick, loading) y qué está pasando, en el idioma de tu cuenta |
gpu, gpu_name, gpu_count | string, integer | Modelo y número de tarjetas |
template, image | string | Plantilla e imagen (con custom) |
disk_gb | integer | Disco, GB |
price_eur_hour | number | Precio por hora de todo el servidor, EUR |
cost_eur | number | Cobrado por este lanzamiento, EUR; en un servidor en marcha, hasta el minuto actual |
traffic_eur | number | Cobrado por el tráfico, EUR |
specs | object | null | Especificaciones de la máquina |
access.ssh | object | null | host, port, user y un command listo para usar |
access.jupyter | string | null | Enlace de Jupyter con token |
access.app | string | null | Dirección de la app para ComfyUI y vLLM; con custom, si la imagen abre su propio puerto. Más información en Apps del servidor |
access.workdir | string | Carpeta de trabajo, /workspace |
error | string | null | Por qué se detuvo el servidor por sí solo: la imagen no se pudo descargar, se acabó el saldo |
created_at, started_at, stopped_at | string | null | Hora en ISO 8601, UTC |
access solo se rellena en un servidor en marcha. La clave SSH es la que añadiste en los ajustes del panel. Si una máquina no arranca, cogemos otra sin coste; el id del servidor no cambia.
ssh -p 31001 root@vramfarm.com nvidia-smi
scp -P 31001 root@vramfarm.com:/workspace/out.ckpt ./Errores
404No hay ningún servidor con este id, o no es tuyo
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
}Detener un servidor
POST/api/instances/stop
Detiene el servidor; la facturación se para al instante. El disco del servidor se borra; puedes guardar la carpeta de trabajo.
Cuerpo de la petición, JSON
| Parámetro | Tipo | Descripción |
|---|---|---|
idobligatorio | integer | id del servidor |
saveopcional | boolean | true: primero guarda /workspace y después detiene. No funciona con custom. |
forceopcional | boolean | true: detiene en el acto, aunque la carpeta todavía se esté guardando. El guardado se interrumpe. |
Detener un servidor que ya está detenido devuelve {"ok":true,"already":true}. No es un error, así que puedes poner la llamada en finally o en un trap al salir del script.
Con "save": true, la respuesta {"ok":true,"saving":true} llega al instante y el servidor guarda la carpeta y se detiene solo, normalmente en pocos minutos. El almacenamiento cuesta 0,03 € por GB al mes, con cargo diario. Tu siguiente servidor recibe la carpeta automáticamente, salvo que pases "ws_restore": false.
Errores
404No hay ningún servidor con este id, o no es tuyo409La carpeta todavía se está guardando; la respuesta incluye"saving": true. Espera o pasa"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
}Cuenta
Cuenta
GET/api/account
Saldo y disponibilidad para lanzar. Útil para comprobar antes de un lanzamiento que tu correo está confirmado y que has añadido una clave SSH.
Sin parámetros.
email | string | Correo de la cuenta |
verified | boolean | Correo confirmado. Sin él, un lanzamiento devuelve 403 |
balance_eur | number | Saldo, EUR |
ssh_key_set | boolean | Clave SSH añadida. Sin clave el servidor arranca igualmente, pero solo podrás entrar por Jupyter |
active_instances | integer | Cuántos servidores se están iniciando o están en marcha ahora |
max_active_instances | integer | Cuántos puedes tener en marcha a la vez |
Errores
401Token incorrecto o revocado, o cuenta bloqueada
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
}Servicio
Estado del servicio
GET/api/health
Comprueba que la API funciona. No necesita token.
Sin parámetros.
Si hay un fallo, la respuesta es 503 con un motivo breve en why, por ejemplo {"ok":false,"why":"…"}.
Errores
503El servicio no funciona del todo; el motivo está enwhy
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"
}Apps del servidor por API
ComfyUI, las apps de texto a voz, el chat y vLLM tienen cada uno su propia API. Úsala para generar imágenes, audio y texto desde un script sin abrir la interfaz. Estas apps no necesitan tu token de VRAMfarm y nunca se les envía.
Dónde encontrar la dirección de la app:
- ComfyUI y vLLM: el campo
access.appen GET /api/instances/{id}, del tipohttps://u.vramfarm.com/api/a/<key>/. En vLLM ya termina env1. - Chat y texto a voz: en la tarjeta del servidor en el panel aparece una dirección del tipo
http://<host>:<port>; no está enaccess.app. Desde un script es más fiable un túnel SSH: funciona siempre que funcione el SSH. - Tu propia imagen (
custom):access.appse rellena si el contenedor abre un puerto distinto de 22, 8080 y 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:11434Mantén el túnel abierto mientras se ejecuta tu script: las peticiones a localhost:11434 van al servidor. Para texto a voz usa el puerto 7860 (-L 7860:127.0.0.1:7860), para ComfyUI el 8188 y para vLLM el 8000. La app arranca 5–10 minutos después de running; hasta entonces la dirección devuelve 502 o no responde.
ComfyUI
Plantillas comfyui y comfy-*. Monta un workflow en la interfaz y guárdalo con Workflow → Export (API) como workflow_api.json. Después el script cambia el texto del prompt en el workflow, lo envía a /prompt, espera el resultado en /history/{prompt_id} y descarga las imágenes a través de /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"Texto a voz y otras apps de Gradio
Las plantillas tts-qwen3, tts-voxcpm y tts-chatterbox están hechas con Gradio. Al pie de la página de la app hay un enlace «Use via API» con los métodos, sus parámetros y ejemplos listos para usar. Los nombres de los métodos cambian de una app a otra, así que sustituye /generate en el ejemplo por el nombre que aparezca en «Use via API» o en 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 y Open WebUI
Plantillas chat, chat-pro y chat-mimo: Open WebUI funciona sobre Ollama. Desde un script es más sencillo hablar directamente con Ollama. Solo escucha dentro del servidor, en 127.0.0.1:11434, así que necesitas un túnel SSH como en el ejemplo de arriba. El modelo se descarga después del lanzamiento; hasta que termina de cargarse, Ollama responde que el modelo no existe.
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 también habla el formato de OpenAI en http://localhost:11434/v1/chat/completions, así que las librerías de OpenAI funcionan. Open WebUI también tiene su propia API; la clave se genera en los ajustes de Open WebUI.
vLLM
La plantilla vllm inicia un servidor compatible con OpenAI con Qwen/Qwen2.5-7B-Instruct. La dirección es access.app, que ya termina en /v1. La clave no se comprueba, pero la librería de OpenAI exige una que no esté vacía, así que pasa cualquiera. La lista de modelos está en 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"}]
}'¿Necesitas un token? Regístrate, confirma tu correo y crea un token en los ajustes del panel. Si algo no coincide con esta página o necesitas un límite más alto, escríbenos.