Pular para o conteúdo principal

Visão geral da API

Base​

A API sobe com rc serve e escuta por padrão em http://127.0.0.1:8080. Não há autenticação. O padrão é só loopback; não exponha fora dela sem um proxy autenticado na frente.

MétodoCaminhoO que faz
GET/healthestado e versão
GET/api/v1/languagescatálogo e se a imagem de cada linguagem está pronta
POST/api/v1/runexecuta arquivos, com stdin único ou em lote
POST/api/v1/executeexecuta code solto ou arquivos, com timeout por requisição

GET /health​

{"status": "ok", "version": "dev"}

version é a versão gravada no binário no release; em build local, dev.

GET /api/v1/languages​

[
{"name": "python", "image": "python:3.12-alpine", "ready": true},
{"name": "nodejs", "image": "node:22-alpine", "ready": false}
]

ready diz se o ext4 da linguagem já foi construído neste host. Linguagem com ready: false é recusada com E006, como se não estivesse no catálogo; construa a imagem com image build.

POST /api/v1/run​

{
"language": "python",
"files": [{"name": "main.py", "content": "print(input())"}],
"stdin": "olá"
}
CampoTipoObrigatórioDescrição
languagestringsimnome no catálogo
fileslista de {name, content}simo primeiro arquivo é o principal
stdinstring ou listanãolista roda uma vez por item, cada uma numa VM nova

Resposta com stdin único:

{"status": "success", "stdout": "olá\n", "stderr": null, "exception": null, "executionTime": 33}

Com "stdin": ["a", "b"], a resposta é uma lista, com o stdin ecoado em cada item:

[
{"status": "success", "stdout": "a\n", "stderr": null, "exception": null, "executionTime": 32, "stdin": "a"},
{"status": "success", "stdout": "b\n", "stderr": null, "exception": null, "executionTime": 31, "stdin": "b"}
]

Os valores de executionTime acima são ilustrativos.

POST /api/v1/execute​

{
"language": "python",
"code": "import time; time.sleep(2); print('ok')",
"timeout": 5
}
CampoTipoObrigatórioDescrição
languagestringsimnome no catálogo
codestringnãocódigo do arquivo principal, quando não há files
fileslista de {name, content}nãoarquivos; o primeiro é o principal
stdinstringnãoentrada padrão
timeoutinteiro (segundos)nãopadrão 10, máximo 300

A resposta tem o mesmo formato de /api/v1/run.

POST /api/v1/execute/stream​

Mesmo corpo do /api/v1/execute. A resposta é text/event-stream (Server-Sent Events): a saída chega enquanto o programa roda, e um evento result fecha a resposta.

curl -N -X POST http://127.0.0.1:8080/api/v1/execute/stream -H 'content-type: application/json' \
-d '{"language":"python","code":"import time\nprint(\"early\")\ntime.sleep(1.5)\nprint(\"late\")"}'
event: stdout
data: "early\n"

event: stdout
data: "late\n"

event: result
data: {"status":"success","exitCode":0,"exception":null,"executionTime":1534,"timedOut":false}
Eventodata
stdout, stderro pedaço de saída como string JSON (a quebra de linha vem escapada: SSE não aceita quebra crua no data)
resultstatus, exitCode, exception, executionTime (ms), timedOut, e error quando houver. Não repete stdout/stderr
error{"error": "..."}: falha de infraestrutura depois de o stream ter começado (ex.: conexão com o guest caiu)
  • Job recusado na validação responde 400 JSON, igual ao /execute, antes de abrir o stream. Falha de infraestrutura antes do primeiro evento responde 500 JSON.
  • stdin em lista (lote) não é aceito nesta rota: 400.
  • O cabeçalho X-Accel-Buffering: no desliga o buffer de proxy nginx.
  • Linguagens com stdout em bloco quando a saída é pipe (C, Ruby, Perl) só entregam no flush do programa; o Python roda com PYTHONUNBUFFERED=1. Detalhes em rc run.

Campos da resposta​

CampoDescrição
status"success" quando a API executou o job. Não quer dizer que o programa deu certo: veja stderr e exception
stdoutsaída padrão
stderrsaída de erro, ou null quando vazia
exceptionnull, "Timeout", "Compilation failed" ou a descrição de uma falha de infraestrutura
executionTimetempo de execução dentro da VM, em ms
errorpresente em recusa (E005, E006 ou o motivo) e quando o guest não responde a tempo (E001)
stdinsó no modo lote: o stdin daquele item

Erros e status HTTP​

SituaçãoHTTPCorpo
execução concluída (inclusive programa que falhou ou estourou o tempo)200resposta normal
JSON inválido ou campo obrigatório ausente400{"status": "failed", "error": "requisição inválida: ..."}
stdin acima de 1 MB400"error": "E005: stdin too long"
linguagem fora do catálogo ou sem imagem construída neste host400"error": "E006: unsupported language <nome>"
outro limite do domínio violado (timeout fora de (0, 300 s], job sem código e sem arquivos, arquivo repetido, nome inválido, muitos arquivos, código grande demais)400"error" com o motivo
guest não respondeu dentro do timeout mais a folga do host200{"status": "failed", "error": "E001: operation timed out"}
falha de infraestrutura (VM não subiu, erro no canal com o guest)500{"status": "failed", "exception": "..."}

Limites​

LimiteValor
stdin≤ 1.000.000 bytes
arquivos por jobaté 32
código total (soma dos arquivos)≤ 4 MiB
nome de arquivosem /, sem .., sem começar com ., ≤ 255 bytes
timeoutpadrão 10 s, máximo 300 s

Máquinas​

Ciclo de vida das máquinas persistentes.

MétodoCaminhoCorpo / queryResposta
POST/api/v1/machines{"name","stack"|"image","cpus","memory_mib","disk_gib","gpu","gpu_vram_mib","env","workdir","init","workload","stop_signal","stop_timeout","stop_on_exit","net","ports":["8080:80"],"egress":{"allow_hosts",...},"network":"lab"} ou {"name","runner_code":"<TOML>"} (campos não vazios vencem o arquivo)201 + máquina (state: stopped)
GET/api/v1/machines—lista com state e pid
GET/api/v1/machines/:name—a máquina
DELETE/api/v1/machines/:name?force=true mata se ligada204
POST/api/v1/machines/:name/start—200 + máquina (state: running)
POST/api/v1/machines/:name/stop?timeout=N em segundos (0–300; 0 mata na hora); sem ele, o stop_timeout da máquina (ou 10)204
POST/api/v1/machines/:name/exec{"command":["argv",...],"env":["K=V"],"workdir","stdin","timeout"}{"exitCode","stdout","stderr","timedOut","executionTime"}
POST/api/v1/machines/:name/exec/streammesmo corpoSSE, um evento por linha, texto cru: event: stdout / data: line1; linha que chega partida espera o resto, e o resto sem \n sai antes do exit ({"exitCode","timedOut","executionTime"})
PUT/api/v1/machines/:name/files?path=/abs[&mode=644]corpo cru = conteúdo{"path","bytes"}
GET/api/v1/machines/:name/files?path=/abs—conteúdo cru (application/octet-stream)
PUT/GET/api/v1/machines/:name/files/*pathidem, com o caminho na URL (files/%2Ftmp%2Fa.txt ou files/tmp/a.txt)idem
GET/api/v1/machines/:name/logs?tail=N&source=all|workload—console em texto; workload filtra a saída do workload, sem a marca
PUT/api/v1/machines/:name/egress{"allow_hosts","allow_host_patterns","allow_cidrs","deny_cidrs","block_private","allow_private"} (a política inteira)200 + máquina; com ela ligada vale na próxima conexão. INVALID_STATE se ligada sem placa de rede
POST/api/v1/machines/:name/pause—204; congela a máquina ligada (state: paused), e exec é recusado até o resume
POST/api/v1/machines/:name/resume—204; máquina já rodando não é erro
POST/api/v1/machines/:name/sync—204; devolve ao host as cópias dos volumes staged (INVALID_STATE se parada)
POST/api/v1/machines/:name/branch{"name":"filha"}201 + filha (state: stopped), cópia copy-on-write do disco; a mãe ligada congela só durante o clone
GET/api/v1/machines/:name/screen—200 com a tela em JPEG; tamanho em X-Screen-Width e X-Screen-Height (máquina com display)
POST/api/v1/machines/:name/screen/actions{"actions":[{"action":"click","x":10,"y":20}]}204; ações de Controle de tela (click, move, drag, type, key, scroll, wait), até 500
curl -s -X POST localhost:8080/api/v1/machines -d '{"name":"dev","stack":"python"}'
curl -s -X POST localhost:8080/api/v1/machines/dev/start
curl -s -X POST localhost:8080/api/v1/machines/dev/exec -d '{"command":["python3","-c","print(42)"]}'
# {"exitCode":0,"stdout":"42\n","stderr":"","timedOut":false,"executionTime":31}
curl -s -X PUT --data-binary @app.py 'localhost:8080/api/v1/machines/dev/files?path=/workspace/app.py'

O command é um argv sem shell implícito. Para usar shell, mande ["sh","-c","..."].

Formas alternativas aceitas no create:

Campo alternativoEquivale a
"network": true / falsenet (string continua sendo o nome da rede)
"memoryMb"memory_mib
"cmd"workload
"allowedHosts", "allowedCidrs"egress.allow_hosts, egress.allow_cidrs, e ligam a rede
"nestedVirt" (ou "nested")--nested; recusado com 400 a menos que o servidor suba com rc serve --allow-nested-virt

Campo desconhecido é recusado com 400 (CONFIG_ERROR, a mensagem nomeia o campo), nas rotas de create, exec, egress e branch. Ignorar calado deixava um allowedHost com erro de digitação criar a máquina sem a restrição de saída.

O stream de job (/api/v1/execute/stream) não mudou: lá o data segue como string JSON, porque o pedaço pode carregar bytes sem quebra de linha.

Erros das rotas de máquina:

{"error":{"code":"INVALID_STATE","message":"INVALID_STATE: machine dev is not running; start it first"},"code":"INVALID_STATE"}

O code aparece também no topo do corpo, para o cliente que o procura ali.

CódigoHTTP
NOT_FOUND404
CONFLICT, INVALID_STATE409
CONFIG_ERROR400
RUNNER_ERROR (infraestrutura)500

Protocolo interno​

Entre o host e o guest, o job viaja por vsock em frames de tamanho + JSON. Veja Job request, Job response e Protocolo vsock.