API REST
A API JSON com escopo por equipe por trás da CLI: aplicações, env, deploys, logs e servidores.
Uma API JSON pequena, com escopo por equipe, para operar o Railyard a partir de uma CLI, de um job de CI ou de um script — listar e criar aplicações, definir variáveis de ambiente, disparar deploys, ler logs de deploy, verificar a frota de servidores.
- URL base — seu control plane, em
/api. Compose local:http://localhost:3000/api - Formato — só JSON. Envie
Content-Type: application/jsonnas escritas. Sem CSRF, sem sessão, sem cookies. - Tudo tem escopo em uma única equipe — a equipe à qual o token pertence.
Autenticação
Toda requisição precisa de um bearer token:
Authorization: Bearer rly_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Para gerar um token: painel → Equipe → Tokens de API → dê um nome, escolha um escopo,
Criar. A string rly_… aparece uma única vez — copie nesse momento. Só admins podem gerar.
Escopos
| Escopo | Pode |
|---|---|
read | todo GET |
write | tudo que read pode, mais POST / PATCH / DELETE |
Um token somente leitura em um endpoint de escrita → 403 {"error":"this API token is read-only"}.
Token ausente / inválido / revogado / expirado → 401 {"error":"missing or invalid API token"}.
Revogue na mesma página da Equipe; um token revogado ou com expires_at no passado
para de funcionar imediatamente.
Convenções
- Erros — respostas fora da faixa 2xx vêm como
{ "error": "<message>" }. Códigos de status:400requisição inválida ·401não autenticado ·403escopo incorreto ·404não encontrado (ou fora da sua equipe) ·409conflito (por exemplo, um deploy já está em execução) ·422falha de validação ·429limite de taxa. - Timestamps — ISO-8601 UTC (
2026-09-02T14:03:11Z). - IDs — inteiros, estáveis por equipe.
- Paginação — endpoints de listagem que aceitam
limitrecebem de1a100(padrão30). Ainda sem cursor; do mais novo para o mais antigo quando ordenado por tempo. - Limite de taxa — a criação de deploys tem um limite por equipe por hora
(
GET /api/me→limits.deploys_per_hour); acima disso →429.
Endpoints
| Método | Caminho | Escopo | Finalidade |
|---|---|---|---|
| GET | /api/me | read | Informações do token e da equipe, limites, contagens |
| GET | /api/applications | read | Listar aplicações |
| POST | /api/applications | write | Criar uma aplicação |
| GET | /api/applications/:id | read | Detalhe da aplicação |
| PATCH | /api/applications/:id | write | Atualizar uma aplicação |
| DELETE | /api/applications/:id | write | Excluir uma aplicação (e seus deploys/logs) |
| POST | /api/applications/:id/archive | write | Parar o container, mantendo o registro |
| POST | /api/applications/:id/unarchive | write | Desarquivar |
| GET | /api/applications/:id/env | read | Ler variáveis de ambiente |
| PATCH | /api/applications/:id/env | write | Mesclar variáveis de ambiente (valor null exclui) |
| DELETE | /api/applications/:id/env?key=KEY | write | Excluir uma variável de ambiente |
| GET | /api/applications/:id/edge | read | Configurações de edge (mesmo formato da seção edge: do manifest, veja Regras de edge) |
| PATCH | /api/applications/:id/edge | write | Mesclar configurações de edge: {"edge":{"access_logs":false,"headers":[…],"cors":{…}}} |
| GET | /api/applications/:id/certificates | read | Certificados enviados (nomes, hosts cobertos, validade, fingerprint; nunca a chave) |
| POST | /api/applications/:id/certificates | write | Enviar {"certificate":{"certificate_pem":"…","private_key_pem":"…"}} |
| DELETE | /api/applications/:id/certificates/:cert_id | write | Remover um certificado (certificados automáticos a partir do próximo deploy) |
| GET | /api/applications/:id/domains | read | Listar domínios personalizados |
| POST | /api/applications/:id/domains | write | Adicionar um domínio personalizado |
| DELETE | /api/applications/:id/domains/:domain_id | write | Remover um domínio personalizado |
| GET | /api/applications/:id/deploys | read | Deploys de uma aplicação |
| POST | /api/applications/:id/deploys | write | Disparar um deploy ({"image": "registry/repo:tag"} faz deploy dessa imagem já buildada uma única vez) |
| GET | /api/applications/:id/registry | write | O registro de build da equipe para o deploy --local: repository, username, password, platform |
| POST | /api/applications/:id/tunnel | write | Abrir um túnel ({"target": "web|db|redis|<process>", "local_port": 5432}): retorna um ticket de 12 horas para o WebSocket do TunnelChannel, a port remota, e uma url em localhost para bancos de dados; registrado como app.tunnel_opened |
| GET | /api/applications/:id/logs | read | Logs do container em execução (?tail=, ?process=) |
| GET | /api/applications/:id/processes | read | Estado de cada container (para o ps) |
| POST | /api/applications/:id/restart | write | Reiniciar todos os containers, ou só um com ?process= |
| GET | /api/applications/:id/process_types | read | Tipos de processo com variáveis de ambiente, encerramento e configurações de tamanho |
| PATCH | /api/applications/:id/process_types/:pt_id | write | Definir env_vars_text, kill_timeout_seconds, stop_signal, size, cpu_limit, memory_limit |
| GET | /api/applications/:id/commands | read | Execuções recentes de comandos avulsos |
| POST | /api/applications/:id/commands | write | Rodar um comando avulso ({"command": "..."}) |
| GET | /api/applications/:id/commands/:cmd_id | read | Uma execução: status, saída, código de saída |
| POST | /api/applications/:id/reindex | write | Reconstruir os índices de busca: roda o search.reindex_command da aplicação em um container avulso (202; 422 sem um comando ou um deploy bem-sucedido, 409 enquanto um já está rodando). Defina reindex_command / auto_reindex com PATCH /api/applications/:id; o GET mostra em search, junto com a library detectada |
| POST | /api/applications/:id/clone | write | Clonar a aplicação em uma cópia de staging |
| POST | /api/applications/:id/promote | write | Promover a release de uma cópia de staging para produção |
| GET | /api/applications/:id/manifest | read | A aplicação como railyard.yml (?secrets=true inclui os valores das variáveis) |
| POST | /api/applications/:id/apply_manifest | write | Aplicar um manifest ({"manifest": "<yaml text>"}) |
| POST | /api/applications/validate_manifest | read | Verificar um manifest em busca de erros sem aplicá-lo — sem :id, o token de qualquer aplicação funciona ({"manifest": "<yaml text>"} → {"problems": [...]}, vazio quando é válido) |
| POST | /api/applications/:id/db_query | read / write | Rodar SQL ({"sql": "...", "write": false}); write: true precisa de um token com escopo de escrita |
| GET/POST | /api/applications/:id/database_backups | read / write | Listar backups / tirar um agora |
| POST | /api/database_backups/:id/restore | write | Restaurar um backup |
| GET | /api/database_backups/:id/download | read | Link de download assinado |
| GET/POST | /api/applications/:id/volume_backups | read / write | Listar backups / fazer backup de um volume ({"volume": "name"}) |
| POST | /api/volume_backups/:id/restore | write | Restaurar um backup de volume |
| GET | /api/applications/:id/traffic | read | Requisições, p95, taxa de erro e endpoints mais lentos (?range=1h|24h|7d|30d) |
| PATCH | /api/applications/:id/autoscale | write | Escalar réplicas web por p95/CPU ({"max":N,"min":N,"p95":ms,"cpu":pct}); {"max":null} desativa |
| POST | /api/applications/:id/data_move | write | Mover os dados da aplicação para um servidor de banco de dados ({"database_server_id": 12}); 202 com o que será copiado |
| POST | /api/applications/:id/external_datastores | write | Apontar o database, redis ou elasticsearch da aplicação para uma URL externa ({"kind","url","copy"}); a URL é verificada antes (502 se inacessível), 202 quando enfileirado |
| DELETE | /api/applications/:id/external_datastores/:kind | write | Trazer esses dados de volta para o Railyard (?copy=true copia os dados de volta) |
| GET | /api/applications/:id/addons/:kind/transfers | read | Exportações/importações recentes de um add-on (redis, mongodb, elasticsearch, storage, influxdb, rabbitmq), com links de download |
| POST | /api/applications/:id/addons/:kind/transfers | write | Exportar os dados do add-on; com file multipart + confirm=<app name>, carregar um arquivo nele |
| GET | /api/applications/:id/storage/objects | read | Navegar no bucket do add-on de storage (?prefix=, ?token=): pastas, arquivos com tamanho, indicador de público e um link assinado |
| POST | /api/applications/:id/storage/objects | write | Enviar um arquivo (file multipart, prefix ou key) |
| DELETE | /api/applications/:id/storage/object?key= | write | Excluir um arquivo, ou uma pasta quando a key termina em / |
| PATCH | /api/applications/:id/storage/visibility | write | Tornar uma pasta pública ou privada ({"prefix":"avatars/","public":true}) |
| GET | /api/applications/:id/redis | read | Onde o Redis da aplicação vive (db N compartilhado ou próprio), seu limite e política, memória usada |
| PATCH | /api/applications/:id/redis | write | {"dedicated":true,"maxmemory_mb":512,"policy":"allkeys-lru"} dá à aplicação seu próprio Redis (chaves copiadas) ou muda o limite; {"dedicated":false} volta ao compartilhado |
| GET | /api/deploys | read | Deploys de toda a equipe (?application_id=, ?limit=) |
| GET | /api/deploys/:id | read | Detalhe do deploy |
| GET | /api/deploys/:id/logs | read | Linhas do log do deploy (mais antigas primeiro, ≤5000) |
| GET | /api/applications/:id/log_search | read | Linhas de log armazenadas: intervalo de tempo, busca, páginas com cursor (veja Logs, métricas e alertas) |
| GET | /api/applications/:id/log_download | read | Linhas de log armazenadas de um intervalo, como texto |
| GET/PATCH/DELETE | /api/applications/:id/otel_export | read / write | Configurações de exportação OpenTelemetry (veja Logs, métricas e alertas) |
| GET/POST | /api/applications/:id/metric_alerts | read / write | Alertas de limite de métricas e seu histórico |
| PATCH/DELETE | /api/applications/:id/metric_alerts/:alert_id | write | Alterar ou remover um alerta |
| GET | /api/applications/:id/job_queue | read | Estatísticas da fila de jobs, jobs com falha, histórico (veja Logs, métricas e alertas) |
| POST | /api/applications/:id/job_queue/failed/:job_id/retry | write | Tentar de novo um job com falha |
| DELETE | /api/applications/:id/job_queue/failed/:job_id | write | Descartar um job com falha |
| GET | /api/servers | read | A frota de servidores |
| GET | /api/servers/:id | read | Detalhe do servidor + estado dos processos em tempo real |
| POST | /api/servers/:id/reboot | /power_cycle | /cordon | /uncordon | /prune_docker | write | Ações do ciclo de vida do servidor |
| PATCH | /api/servers/:id/resize | write | Redimensionar no provedor ({"size": "..."}) |
| POST | /api/servers/:id/trust_ip | write | Desbanir e confiar em um IP ({"ip": "..."}) — o ActiveProtect remove o banimento em ~1 min. Permite que o Watchtower limpe um banimento sem abrir o painel. |
| GET | /api/notification_rules | read | Regras de notificação de toda a equipe |
| GET | /api/webhook_endpoints | read | Endpoints de webhook de toda a equipe |
| GET | /api/config | read | Entradas de configuração de toda a equipe |
GET /api/me
curl -s https://cp.example.com/api/me -H "Authorization: Bearer $RLY"
{
"team": { "id": 1, "name": "Acme", "slug": "acme", "suspended": false },
"token": { "name": "ci", "scope": "write", "last_used_at": "2026-09-02T14:00:00Z" },
"limits": { "servers": 10, "apps": 50, "deploys_per_hour": 30 },
"counts": { "applications": 11, "servers": 3 }
}
Aplicações
Objeto app (na listagem):
{
"id": 11,
"name": "chatwoot-copy",
"environment": "production",
"parent_id": null,
"git_url": "image://chatwoot/chatwoot:v4.17.1",
"git_ref": "main",
"hostname": "chatwoot-copy.apps.example.com",
"url": "https://chatwoot-copy.apps.example.com",
"server": "railyard-8",
"container_port": 3000,
"addons": ["postgres", "redis"],
"archived": false,
"auto_deploy": false,
"latest_deploy": { "id": 31, "status": "succeeded", "sha": null, "created_at": "2026-09-02T13:00:00Z" },
"created_at": "2026-09-01T09:00:00Z"
}
GET /api/applications/:id adiciona: release_command, health_path,
deploy_approval_required, deploy_window {days, start, end, time_zone, open_now, opens_at}, strict_pushes, db_extensions[],
processes[] {name, command, role}, env_keys[] (só os nomes — os valores nunca
são retornados no detalhe da aplicação; use o endpoint de env),
domains[] {hostname, redirect, cert_state}.
Criar — POST /api/applications
curl -s -X POST https://cp.example.com/api/applications \
-H "Authorization: Bearer $RLY" -H "Content-Type: application/json" \
-d '{"application":{
"name":"blog",
"git_url":"https://github.com/acme/blog",
"git_ref":"main",
"container_port":3000,
"server":"railyard-8",
"addons":["postgres"],
"env_vars":{"RAILS_ENV":"production"}
}}'
O corpo é {"application": { … }}. Chaves aceitas: name, git_url,
git_ref, container_port, hostname, health_path, release_command,
build_method (auto | dockerfile | nixpacks), dockerfile_path,
docker_build_target, cpu_limit, memory_limit, process_types_text
(um name: command por linha), scheduled_tasks_text
(name | cron | command por linha), db_extensions,
deploy_approval_required, strict_pushes (um git push cujo build
falha é rejeitado; true por padrão), addons (array), env_vars (objeto).
Em atualizações, também: server_id.
server(ouserver_id) pode ser um id ou hostname de servidor. Se omitido, a aplicação vai para o primeiro servidor sem cordon da equipe.- Um
hostnameem branco é derivado —<name>.<server app_domain>, senãoAPP_DOMAIN_SUFFIX, senão<name>.<server-ip>.sslip.io. - Criar não faz deploy. Siga com
POST …/deploys. - Acima do limite de aplicações da equipe →
422.
201 com o objeto detalhado da aplicação. PATCH → 200. DELETE → 204.
Arquivar / desarquivar — POST /api/applications/:id/archive para o
container em execução e define archived: true; o registro, a configuração e o histórico
permanecem. unarchive remove a marcação (faça o deploy de novo para trazer a aplicação de volta).
Variáveis de ambiente — /api/applications/:id/env
# ler
curl -s https://cp.example.com/api/applications/11/env -H "Authorization: Bearer $RLY"
# {"env":{"RAILS_ENV":"production","LOG_LEVEL":"info"}}
# mesclar (define duas, exclui uma)
curl -s -X PATCH https://cp.example.com/api/applications/11/env \
-H "Authorization: Bearer $RLY" -H "Content-Type: application/json" \
-d '{"env":{"LOG_LEVEL":"debug","FEATURE_X":"1","OLD_KEY":null}}'
# excluir uma
curl -s -X DELETE "https://cp.example.com/api/applications/11/env?key=FEATURE_X" \
-H "Authorization: Bearer $RLY"
PATCH mescla com o conjunto existente; um valor null remove essa chave.
As três chamadas retornam o {"env": { … }} completo. As mudanças valem a partir do próximo
deploy.
Domínios personalizados — /api/applications/:id/domains
curl -s -X POST https://cp.example.com/api/applications/11/domains \
-H "Authorization: Bearer $RLY" -H "Content-Type: application/json" \
-d '{"domain":{"hostname":"support.acme.com","redirect":false}}'
{ "id": 4, "hostname": "support.acme.com", "redirect": false,
"cert_state": "pending", "dns_target": "203.0.113.9", "cert_issuer": null }
redirect: true faz o hostname responder com 301 para o hostname principal da
aplicação, em vez de servi-lo. Aponte o dns_target mostrado no seu DNS e o Caddy
emite o certificado. DELETE …/domains/:domain_id → 204.
Deploys
Objeto deploy:
{
"id": 31, "application_id": 11, "application": "chatwoot-copy",
"status": "succeeded", "ref": "main", "trigger": "manual", "message": null, "sha": null, "rollback": false,
"created_at": "2026-09-02T13:00:00Z",
"started_at": "2026-09-02T13:00:04Z",
"finished_at": "2026-09-02T13:02:31Z"
}
status ∈ queued · pending_approval · running · succeeded ·
failed. GET /api/deploys/:id adiciona error, triggered_by (e-mail),
duration_seconds.
Um deploy canary também tem um objeto canary (null nos demais casos):
"canary": {
"percent": 10, "state": "running", "started_at": "2026-09-29T12:00:00Z",
"promote_at": "2026-09-29T12:15:00Z", "requests": 412, "error_rate": 0.49, "p95_ms": 88.4,
"rest_of_app": { "requests": 3710, "error_rate": 0.4, "p95_ms": 91.0 }
}
state ∈ running · promoted · rolled_back · replaced.
Canary — POST /api/deploys/:id/canary/promote inicia o deploy de
promoção (202, promotion_deploy_id); DELETE /api/deploys/:id/canary
faz o rollback. As duas precisam de um token de escrita e respondem 409 quando o canary já
não está mais ativo.
Estratégia de deploy — PATCH /api/applications/:id/deploy_strategy com qualquer
combinação de deploy_strategy (standard · canary), canary_percent (1–99),
canary_steps_text (porcentagens crescentes separadas por vírgula, por exemplo "5, 25, 50";
em branco = uma etapa), canary_promote_after_minutes (1–1440, vazio = manual),
canary_max_error_rate (0–100). GET /api/applications/:id mostra os mesmos
campos. Com etapas definidas, promover uma etapa inicia a próxima como seu próprio
canary, em vez de ir direto para 100%.
Fazer deploy de novo quando a imagem mudar (só aplicações de imagem, senão 422) —
PATCH /api/applications/:id/image_watch com enabled (true/false);
POST /api/applications/:id/image_watch/check verifica o registro agora
(202). As duas retornam, e GET /api/applications/:id mostra como
image_watch:
{ "enabled": true, "image": "ghcr.io/acme/api:prod",
"webhook_url": "https://cp.example.com/webhooks/image/2cV…",
"digest": "sha256:fb84…", "checked_at": "2026-09-29T12:05:00Z", "error": null }
Registros chamam POST /webhooks/image/<token> (sem token de API; qualquer corpo) para
disparar uma verificação.
Disparar — POST /api/applications/:id/deploys
curl -s -X POST https://cp.example.com/api/applications/11/deploys \
-H "Authorization: Bearer $RLY" -H "Content-Type: application/json" \
-d '{"ref":"v2.1.0"}'
ref(opcional) — branch, tag ou SHA para fazer deploy uma única vez; por padrão usa ogit_refda aplicação, que umrefnunca altera. Otriggerdo deploy érefe seu camporefmostra o que foi usado. Nomes com caracteres que uma ref de git não aceita recebem422. Ignorado em uma aplicaçãoimage://.message(opcional) — uma nota de deploy (até 500 caracteres), retornada comomessagee exibida no painel, na atividade, nas notificações e nos webhooksdeploy.*.at(opcional) — horário ISO 8601 para fazer o deploy, em vez de agora. Responde201comstatus: "scheduled"escheduled_for; o horário precisa estar no futuro e dentro da janela de deploy (senão422). Cancele comPOST /api/deploys/:id/cancel(também cancela um deploy na fila ou em execução).ignore_window(opcional) — faz o deploy mesmo que a aplicação esteja fora da janela de deploy. Sem isso, esse deploy responde409comoutside_window: trueewindow_opens_at.rollback_to(opcional) — id de um deploy anterior bem-sucedido. Refaz o deploy do commit dele e recupera as variáveis com que ele rodava; a resposta adicionarestored_config. Envie"restore_config": falsepara reverter só o código. Releases publicadas antes de o Railyard guardar uma cópia das variáveis sempre revertem apenas o código.202 Accepted+ o objeto do deploy quando ele é colocado na fila e começa a rodar.200 OK+ o objeto comstatus: "pending_approval"se a aplicação exige aprovação de deploy (aprove no painel).409se já existe um deploy ativo ou aguardando aprovação para essa aplicação.429se o limite de deploys por hora da equipe foi atingido.422se a aplicação está arquivada.422commissing_env(lista de nomes) se a aplicação nunca fez um deploy bem-sucedido e credenciais documentadas pelo repositório não foram definidas ou ainda são placeholders. Defina-as pelo endpoint de env, ou envie"force": truepara fazer o deploy mesmo assim — esses nomes não são pedidos de novo depois.
Janela de deploy — PATCH /api/applications/:id/deploy_window
curl -s -X PATCH https://cp.example.com/api/applications/11/deploy_window \
-H "Authorization: Bearer $RLY" -H "Content-Type: application/json" \
-d '{"days":["mon","tue","wed","thu","fri"],"start":"09:00","end":"17:00","time_zone":"America/Toronto"}'
Retorna {days, start, end, time_zone, open_now, opens_at} (também em
deploy_window dentro de GET /api/applications/:id). "days": [] remove a
janela. Pushes, webhooks do GitHub/GitLab e pushes agrupados fora da
janela se tornam um único deploy scheduled que começa quando ela abrir.
Logs — GET /api/deploys/:id/logs
{
"deploy_id": 31,
"status": "succeeded",
"lines": [
{ "stream": "system", "line": "Deploying web (web)", "at": "2026-09-02T13:00:05Z" },
{ "stream": "stdout", "line": "Pulled chatwoot/chatwoot:v4.17.1", "at": "2026-09-02T13:00:40Z" },
{ "stream": "system", "line": "Deployed at https://chatwoot-copy.apps.example.com", "at": "2026-09-02T13:02:30Z" }
]
}
stream ∈ stdout · stderr · system. Limitado a 5000 linhas, das mais antigas
para as mais novas. Consulte repetidamente enquanto status for queued/running.
Listar — GET /api/deploys?application_id=11&limit=50 ou
GET /api/applications/11/deploys.
Runtime — logs, processos, comandos, restart
A metade programática do console da página da aplicação. Tudo aqui passa pelo agente na máquina, por um canal mTLS.
Logs de runtime — GET /api/applications/:id/logs?tail=200&process=web
O que a aplicação em execução está imprimindo agora — não a saída do build (que é
/api/deploys/:id/logs). tail vai de 1 a 5000 (padrão 200); process filtra para
um processo. 409 com {"error": "No running deployment"} quando nada está
no ar ainda, ou o agente está inacessível.
{
"application_id": 11,
"lines": [
{ "stream": "stdout", "process": "web", "line": "GET / 200 12ms", "at": "2026-09-06T12:00:01Z" }
]
}
Ainda não existe acompanhamento por push do servidor — o railyard logs --follow repete
a requisição em intervalos.
Processos — GET /api/applications/:id/processes
Último estado reportado de cada container, atualizado em cada heartbeat do agente. Lista vazia = nada em execução.
{ "processes": [
{ "name": "web", "container": "railyard-app-11-web", "state": "running",
"status": "running", "health": "healthy", "crash_looping": false,
"restarts": 0, "cpu_percent": 1.2, "mem_bytes": 92210000,
"mem_percent": 44.1, "last_seen_at": "2026-09-06T12:00:00Z" } ] }
status ∈ running · unhealthy · crash-looping · exited · dead · …
Restart — POST /api/applications/:id/restart[?process=web]
Reinicia todos os containers, ou só o de um processo. 409 se nada está em execução.
{ "restarted": 2, "processes": ["railyard-app-11-web", "railyard-app-11-worker-1"] }
Tipos de processo — GET /api/applications/:id/process_types,
PATCH /api/applications/:id/process_types/:pt_id
{ "id": 7, "name": "worker", "role": "worker", "command": "bundle exec sidekiq",
"replicas": 2, "env_vars": {}, "kill_timeout_seconds": 30, "stop_signal": "",
"size": "small", "cpu_limit": "0.5", "memory_limit": "512m", "cpus": "0.5", "memory": "512m" }
size é small, medium, large (mapeado a partir de PROCESS_SIZE_*), custom
(nesse caso envie cpu_limit e memory_limit) ou vazio para o limite geral da aplicação.
cpus / memory são os limites efetivos depois desse fallback.
kill_timeout_seconds (1–300, vazio para o padrão) é quanto tempo o processo
tem depois do stop_signal (SIGTERM, SIGINT, SIGQUIT, vazio para o da própria
imagem) antes de ser encerrado à força. As mudanças valem a partir do próximo deploy. Um valor
inválido → 422.
Comandos avulsos — POST /api/applications/:id/commands
curl -s -X POST https://cp.example.com/api/applications/11/commands \
-H "Authorization: Bearer $RLY" -H "Content-Type: application/json" \
-d '{"command":"bin/rails db:migrate"}'
Roda dentro do container web pela RPC RunTask do agente. 202 Accepted
com o registro; consulte GET /api/applications/:id/commands/:cmd_id para o status
(queued → running → succeeded/failed), a output transmitida, e o
exit_code. GET /api/applications/:id/commands lista as execuções recentes.
Um console interativo (um TTY, RPC Exec) ainda não está na API.
Servidores (somente leitura)
curl -s https://cp.example.com/api/servers -H "Authorization: Bearer $RLY"
Objeto server:
{
"id": 5, "hostname": "railyard-8", "name": "railyard-8",
"status": "online",
"agent_version": "20260902-a9821d1",
"cpu_percent": 1.5, "memory_percent": 64.5, "disk_percent": 40.3,
"last_heartbeat_at": "2026-09-02T14:03:00Z",
"cordoned": false
}
status ∈ online (heartbeat < 30 s) · stale (< 5 min) · offline.
GET /api/servers/:id adiciona apps[] (nomes) e
processes[] {container, state, restarts, crash_looping}.
Colocar em cordon, reiniciar, desligar e religar, limpar e redimensionar um servidor existente
são POST/PATCH em /api/servers/:id (veja a tabela acima). Conectar
um servidor novo ainda só é possível pelo painel.
Janela de manutenção de dados — GET/PATCH /api/maintenance_window (equipe) e
GET/PATCH /api/servers/:id/maintenance_window (um servidor; dia em branco = usa o da
equipe). Campos: maintenance_day 0–6 (domingo = 0), maintenance_hour 0–23 UTC,
maintenance_hours 1–12. Veja Servidores de banco de dados.
Upgrades major do Postgres — GET/POST /api/servers/:id/postgres_upgrades
(to_image; criar um roda a verificação de compatibilidade), GET …/:id (com o
log), PATCH …/:id (when=window para esperar a janela de manutenção, senão
começa agora), POST …/:id/rollback, DELETE …/:id (remove a versão antiga mantida,
ou descarta um plano que ainda não começou).
Pooler de conexões — GET/POST/DELETE /api/servers/:id/connection_pooler
(PgBouncer, modo transaction, porta 6432). Por aplicação: PATCH /api/applications/:id/database com app_database[pooled]=true aponta a
DATABASE_URL para o pooler a partir do próximo deploy e mantém a
DATABASE_DIRECT_URL para migrations.
Forks de banco de dados — GET/POST /api/applications/:id/database_forks
(target_server_id para um banco novo, ou target_application_id para um
ambiente de staging/preview, source=live|backup, mask_sql opcional),
GET …/:id (com a URL do fork, quando pronto), DELETE …/:id (remove o fork).
Réplicas standby — GET/POST /api/servers/:id/database_replicas
(replica_server_id), POST …/:id/promote, DELETE …/:id.
Cloud
O Railyard Cloud transforma a conta Railyard de uma equipe na própria nuvem pequena dela: servidores,
imagens, endereços fixos, uma zona de DNS e buckets S3, cobrados pelo custo de nuvem mais
uma margem publicada. É feito para ferramentas como a AliaJS, que esperam a API de um
provedor de nuvem, então os formatos seguem os da Hetzner: toda resposta envolve seu
objeto ({"server": {...}}, {"servers": [...]}). É preciso ter um cartão cadastrado
(adicione na página Cloud), e os endpoints respondem 503 até o operador
ativar o Railyard Cloud.
| Método | Caminho | Escopo | Finalidade |
|---|---|---|---|
| GET | /api/cloud/sizes | read | Tamanhos de servidor com custo por hora, margem e preço |
| GET | /api/cloud/ssh_keys | read | Listar chaves SSH |
| POST | /api/cloud/ssh_keys | write | Adicionar uma chave SSH (201; 200 se a mesma chave já existir) |
| DELETE | /api/cloud/ssh_keys/:id | write | Remover uma chave SSH |
| GET | /api/cloud/servers | read | Listar servidores ativos |
| POST | /api/cloud/servers | write | Lançar um servidor (201, pending) |
| GET | /api/cloud/servers/:id | read | Detalhe do servidor, atualizado na leitura |
| DELETE | /api/cloud/servers/:id | write | Encerrar um servidor (200, terminated) |
| POST | /api/cloud/servers/:id/power | write | Iniciar um servidor parado |
| DELETE | /api/cloud/servers/:id/power | write | Parar um servidor em execução |
| POST | /api/cloud/servers/:id/images | write | Tirar um snapshot do servidor em uma imagem (201, creating) |
| GET | /api/cloud/images | read | Listar imagens (?name= filtra) |
| GET | /api/cloud/images/:id | read | Detalhe da imagem, atualizado na leitura |
| DELETE | /api/cloud/images/:id | write | Excluir uma imagem |
| GET | /api/cloud/addresses | read | Listar endereços fixos |
| POST | /api/cloud/addresses | write | Alocar um endereço fixo |
| GET | /api/cloud/addresses/:id | read | Detalhe do endereço |
| DELETE | /api/cloud/addresses/:id | write | Liberar um endereço fixo |
| POST | /api/cloud/addresses/:id/assignment | write | Anexar a um servidor ({"server_id": 9}) |
| DELETE | /api/cloud/addresses/:id/assignment | write | Desanexar |
| GET | /api/cloud/zone | read | A zona de DNS da equipe |
| GET | /api/cloud/zone/records | read | Listar registros |
| PUT | /api/cloud/zone/records/:type/:name | write | Criar ou substituir um registro |
| DELETE | /api/cloud/zone/records/:type/:name | write | Remover um registro |
| GET | /api/cloud/buckets | read | Listar buckets |
| POST | /api/cloud/buckets | write | Criar um bucket; a resposta traz a chave de storage da equipe |
| DELETE | /api/cloud/buckets/:id | write | Excluir um bucket (precisa estar vazio) |
| GET | /api/cloud/usage | read | Linhas de uso deste mês, com custo, margem e preço |
Os corpos das requisições são JSON e podem ser diretos ({"name": "web"}) ou
envolvidos ({"server": {"name": "web"}}). Tokens com escopo de aplicação são recusados (403).
Erros além dos usuais:
| Status | Quando |
|---|---|
402 | Sem cartão cadastrado. Corpo: {"error": "add a card before using Railyard Cloud", "card_url": "https://app.railyard.run/cloud/card/new"} |
403 | Token somente leitura ou com escopo de aplicação, um limite da equipe atingido ("this team has reached its limit of 3 servers"), ou o limite de gasto mensal seria superado |
409 | Conflito: um nome de chave SSH com uma chave diferente, um nome de bucket já em uso, um bucket que ainda tem objetos, um conflito de CNAME, iniciar um servidor que não está parado |
422 | Entrada inválida: tamanho desconhecido, imagem desconhecida, incompatibilidade de arquitetura entre imagem/tamanho, nome de DNS fora da sua zona, IP privado em um registro A |
502 | A nuvem rejeitou a requisição; a mensagem explica o motivo |
503 | O Railyard Cloud não está ativado |
Tamanhos: GET /api/cloud/sizes
{ "sizes": [
{ "name": "t4g.small", "vcpus": 2, "memory_gb": 2, "disk_gb": 20, "arch": "arm64",
"hourly_cost": "0.023992", "markup_percent": "10.0", "hourly_price": "0.026391", "monthly_price": "19.27" }
] }
hourly_cost é o que o servidor custa para o Railyard (máquina, IPv4 público e
disco); hourly_price é o que a equipe paga: custo × (1 + markup_percent / 100).
Servidores
curl -s -X POST https://app.railyard.run/api/cloud/ssh_keys \
-H "Authorization: Bearer $RLY" -H "Content-Type: application/json" \
-d "{\"name\":\"laptop\",\"public_key\":\"$(cat ~/.ssh/id_ed25519.pub)\"}"
curl -s -X POST https://app.railyard.run/api/cloud/servers \
-H "Authorization: Bearer $RLY" -H "Content-Type: application/json" \
-d '{"name":"n8n-production","size":"t4g.small","image":"ubuntu-24.04","ssh_keys":["laptop"]}'
{ "server": { "id": 9, "name": "n8n-production", "status": "pending", "size": "t4g.small", "arch": "arm64",
"image": "ubuntu-24.04", "disk_gb": 20, "public_ipv4": null, "private_ipv4": null, "public_ipv6": null,
"ssh_keys": ["laptop"], "hourly_price": "0.026391", "failure": null, "created_at": "2026-10-10T12:00:00Z" } }
Consulte GET /api/cloud/servers/9 a cada poucos segundos até o status ser
running e public_ipv4 estar definido (geralmente de 10 a 30 segundos), depois
ssh ubuntu@<public_ipv4>. failed significa que o lançamento foi recusado; failure
explica o motivo. image é ubuntu-24.04 ou o nome de uma das suas próprias imagens
criadas para a mesma arquitetura do tamanho escolhido.
Imagens
curl -s -X POST https://app.railyard.run/api/cloud/servers/9/images \
-H "Authorization: Bearer $RLY" -H "Content-Type: application/json" -d '{"name":"n8n-base"}'
Consulte GET /api/cloud/images/:id até o status ser available (alguns
minutos). Lance servidores novos a partir dela com "image": "n8n-base". Uma
imagem adormecida custa só o seu tamanho armazenado (stored_gb), o que permite que uma cópia
standby de um serviço espere por centavos por mês.
Endereços: um IPv4 fixo que sobrevive à troca do servidor por trás dele.
curl -s -X POST https://app.railyard.run/api/cloud/addresses -H "Authorization: Bearer $RLY"
# {"address":{"id":3,"ip":"198.51.100.9","server_id":null,"created_at":"..."}}
curl -s -X POST https://app.railyard.run/api/cloud/addresses/3/assignment \
-H "Authorization: Bearer $RLY" -H "Content-Type: application/json" -d '{"server_id":9}'
Zona de DNS: toda equipe recebe sua própria zona, então você não precisa de um domínio próprio.
curl -s https://app.railyard.run/api/cloud/zone -H "Authorization: Bearer $RLY"
# {"zone":{"name":"acme.cloud.railyard.run"}}
curl -s -X PUT https://app.railyard.run/api/cloud/zone/records/A/n8n-production \
-H "Authorization: Bearer $RLY" -H "Content-Type: application/json" -d '{"values":["198.51.100.9"],"ttl":300}'
# {"record":{"id":11,"name":"n8n-production","fqdn":"n8n-production.acme.cloud.railyard.run","type":"A","values":["198.51.100.9"],"ttl":300,...}}
nameé relativo à zona (n8n-production) ou totalmente qualificado dentro dela; qualquer coisa fora da sua zona recebe422, e o ápice da zona é reservado.- Tipos:
A,AAAA,CNAME,TXT. TXT funciona para o desafio DNS-01 do certbot:PUT /api/cloud/zone/records/TXT/_acme-challenge.n8n-production. - Valores de
Aprecisam ser endereços públicos. Um CNAME não pode compartilhar o nome com outros registros. PUTsubstitui os valores do registro;DELETEno mesmo caminho o remove (204).
Buckets
curl -s -X POST https://app.railyard.run/api/cloud/buckets \
-H "Authorization: Bearer $RLY" -H "Content-Type: application/json" -d '{"name":"n8n-backups"}'
{ "bucket": { "id": 2, "name": "n8n-backups", "url": "s3:https://s3.us-east-2.amazonaws.com/rly-7-n8n-backups-ab12cd",
"region": "us-east-2", "created_at": "2026-10-10T12:00:00Z", "access_key_id": "AKIA…", "secret_access_key": "…" } }
url está no formato s3: do restic. A access key pertence à equipe e
só funciona nos buckets da equipe; todo bucket que você cria retorna a mesma
chave, e listar os buckets nunca mostra ela. DELETE exige o bucket vazio.
Uso: GET /api/cloud/usage
{ "usage": {
"lines": [ { "description": "Server n8n-production (t4g.small)", "metric": "server_hours", "quantity": "1.0",
"period_start": "2026-10-10T12:00:00Z", "cost_usd": "0.023992", "markup_percent": "10.0",
"price_usd": "0.026391", "invoiced": false } ],
"cost_usd": "0.023992", "price_usd": "0.026391", "our_cut_usd": "0.002399", "daily_price_usd": "2.21",
"spend_limit_usd": "100.0" } }
O uso é medido a cada hora por recurso: horas de servidor, tráfego de saída,
horas de endereço fixo, armazenamento de imagem e de bucket. daily_price_usd
é o custo das últimas 24 horas medidas. Os cartões são cobrados quando o total não faturado
passa de um limite, ou no início de cada mês.
Templates
Aplicações prontas de um clique — o mesmo catálogo que a página “fazer deploy de um template” do painel oferece.
curl -s https://cp.example.com/api/templates -H "Authorization: Bearer $RLY"
{ "templates": [
{ "key": "n8n", "name": "n8n", "category": "automation", "verified": true,
"addons": ["postgres"], "description": "Workflow automation" }
] }
Lançar — POST /api/templates/:key/launch
curl -s -X POST https://cp.example.com/api/templates/n8n/launch \
-H "Authorization: Bearer $RLY" -H "Content-Type: application/json" \
-d '{"server_id":9}'
{ "application_id": 42, "name": "n8n", "url": "https://n8n.apps.example.com", "deploy_id": 101 }
server_id é opcional — se omitido, o Railyard escolhe o primeiro servidor de
aplicação online. 201 com o primeiro deploy já na fila. 422 se a equipe está acima
do limite de aplicações, ou os valores padrão do template falham na validação.
Receitas rápidas
Fazer deploy em todo push (CI):
curl -fsS -X POST "$RAILYARD/api/applications/$APP_ID/deploys" \
-H "Authorization: Bearer $RAILYARD_TOKEN" -H "Content-Type: application/json" \
-d "{\"ref\":\"$GIT_SHA\"}"
Esperar um deploy terminar:
id=$(curl -fsS -X POST "$RAILYARD/api/applications/$APP_ID/deploys" \
-H "Authorization: Bearer $TOKEN" -d '{}' | jq .id)
while :; do
s=$(curl -fsS "$RAILYARD/api/deploys/$id" -H "Authorization: Bearer $TOKEN" | jq -r .status)
case $s in succeeded) exit 0;; failed) exit 1;; esac
sleep 5
done
Sincronizar variáveis de ambiente a partir de um arquivo (linhas KEY=value):
jq -Rn '[inputs | select(test("=")) | split("=") | {(.[0]): (.[1:] | join("="))}] | add | {env: .}' .env.prod \
| curl -fsS -X PATCH "$RAILYARD/api/applications/$APP_ID/env" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d @-
Ainda não está na API
Console interativo (um TTY pela RPC Exec), conectar um servidor novo,
gerenciamento de add-ons além do array addons, criar ou editar regras de
notificação / endpoints de webhook / entradas de configuração (a listagem já é só leitura hoje),
feed de atividade. Por enquanto, isso só existe no painel; abra uma issue se precisar
de algum deles pela API.
MCP (agentes de IA)
POST /api/mcp é um servidor Model Context Protocol (JSON-RPC sobre HTTP com streaming), para que agentes de IA possam usar o Railyard com os mesmos tokens de API.
- Um token de read consegue listar servidores e aplicações, mostrar uma aplicação e ler seus logs.
- Um token de write também consegue fazer deploy, reiniciar e definir variáveis de ambiente.
- Excluir uma aplicação ou remover uma variável de ambiente nunca roda direto: isso cria um pedido de aprovação. Um admin da equipe aprova ou rejeita em Aprovações (também aparece no painel).
Claude Code:
claude mcp add --transport http railyard https://app.railyard.run/api/mcp --header "Authorization: Bearer rly_..."
Cursor (.cursor/mcp.json):
{ "mcpServers": { "railyard": { "url": "https://app.railyard.run/api/mcp", "headers": { "Authorization": "Bearer rly_..." } } } }
Ferramentas: list_servers, list_apps, get_app, app_logs, deploy_app, restart_app, set_env, unset_env (aprovação), delete_app (aprovação).