API REST
L'API JSON par équipe derrière le CLI : applications, variables d'environnement, déploiements, logs et serveurs.
Une petite API JSON par équipe pour piloter Railyard depuis un CLI, une tâche CI ou un script : lister et créer des applications, définir des variables d’environnement, déclencher des déploiements, lire les logs de déploiement, vérifier la flotte.
- URL de base : votre plan de contrôle, sous
/api. Compose local :http://localhost:3000/api - Format : JSON seulement. Envoyez
Content-Type: application/jsonpour les écritures. Aucun CSRF, aucune session, aucun cookie. - Tout est limité à une seule équipe : l’équipe à laquelle appartient le jeton.
Authentification
Chaque requête a besoin d’un jeton porteur :
Authorization: Bearer rly_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Obtenir un jeton : tableau de bord → Team → API tokens → nommez-le, choisissez une portée,
Create. La chaîne rly_… ne s’affiche qu’une seule fois : copiez-la à ce moment-là. Réservé aux administrateurs.
Portées
| Portée | Permet |
|---|---|
read | tous les GET |
write | tout ce que permet read, plus POST / PATCH / DELETE |
Un jeton en lecture seule sur un point de terminaison qui modifie des données → 403 {"error":"this API token is read-only"}.
Jeton manquant, invalide, révoqué ou expiré → 401 {"error":"missing or invalid API token"}.
Révoquez-le depuis la même page Team; un jeton révoqué ou dont la date
expires_at est passée arrête de fonctionner immédiatement.
Conventions
- Erreurs : les réponses autres que 2xx sont
{ "error": "<message>" }. Codes de statut :400requête invalide ·401non authentifié ·403mauvaise portée ·404introuvable (ou pas dans votre équipe) ·409conflit (par ex. un déploiement est déjà en cours) ·422validation échouée ·429limite de débit. - Horodatages : ISO-8601 UTC (
2026-09-02T14:03:11Z). - ID : des entiers, stables par équipe.
- Pagination : les points de terminaison de liste qui acceptent
limitacceptent1à100(30par défaut). Pas encore de curseur; du plus récent au plus ancien quand c’est classé par date. - Limite de débit : la création de déploiements est limitée par équipe et par heure
(
GET /api/me→limits.deploys_per_hour); au-delà →429.
Points de terminaison
| Méthode | Chemin | Portée | Description |
|---|---|---|---|
| GET | /api/me | read | Infos sur le jeton et l’équipe, limites, décomptes |
| GET | /api/applications | read | Lister les applications |
| POST | /api/applications | write | Créer une application |
| GET | /api/applications/:id | read | Détail de l’application |
| PATCH | /api/applications/:id | write | Mettre à jour une application |
| DELETE | /api/applications/:id | write | Supprimer une application (et ses déploiements et logs) |
| POST | /api/applications/:id/archive | write | Arrêter le conteneur, conserver l’enregistrement |
| POST | /api/applications/:id/unarchive | write | Désarchiver |
| GET | /api/applications/:id/env | read | Lire les variables d’environnement |
| PATCH | /api/applications/:id/env | write | Fusionner les variables d’environnement (une valeur null supprime) |
| DELETE | /api/applications/:id/env?key=KEY | write | Supprimer une variable d’environnement |
| GET | /api/applications/:id/edge | read | Paramètres de périphérie (même structure que la section edge: du manifeste, voir Règles de périphérie) |
| PATCH | /api/applications/:id/edge | write | Fusionner les paramètres de périphérie : {"edge":{"access_logs":false,"headers":[…],"cors":{…}}} |
| GET | /api/applications/:id/certificates | read | Certificats téléversés (noms, hôtes couverts, validité, empreinte; jamais la clé) |
| POST | /api/applications/:id/certificates | write | Téléverser {"certificate":{"certificate_pem":"…","private_key_pem":"…"}} |
| DELETE | /api/applications/:id/certificates/:cert_id | write | En supprimer un (les certificats automatiques reprennent après le prochain déploiement) |
| GET | /api/applications/:id/domains | read | Lister les domaines personnalisés |
| POST | /api/applications/:id/domains | write | Ajouter un domaine personnalisé |
| DELETE | /api/applications/:id/domains/:domain_id | write | Supprimer un domaine personnalisé |
| GET | /api/applications/:id/deploys | read | Déploiements d’une application |
| POST | /api/applications/:id/deploys | write | Déclencher un déploiement ({"image": "registry/repo:tag"} déploie cette image préconstruite une fois) |
| GET | /api/applications/:id/registry | write | Le registre de build de l’équipe pour deploy --local : repository, username, password, platform |
| POST | /api/applications/:id/tunnel | write | Ouvrir un tunnel ({"target": "web|db|redis|<process>", "local_port": 5432}) : retourne un ticket valide 12 heures pour le WebSocket TunnelChannel, le port distant, et une url localhost pour les bases de données; consigné comme app.tunnel_opened |
| GET | /api/applications/:id/logs | read | Logs du conteneur en exécution (?tail=, ?process=) |
| GET | /api/applications/:id/processes | read | État par conteneur (pour ps) |
| POST | /api/applications/:id/restart | write | Redémarrer tous les conteneurs, ou un seul avec ?process= |
| GET | /api/applications/:id/process_types | read | Types de processus avec les paramètres d’environnement, d’arrêt et de taille |
| PATCH | /api/applications/:id/process_types/:pt_id | write | Définir env_vars_text, kill_timeout_seconds, stop_signal, size, cpu_limit, memory_limit |
| GET | /api/applications/:id/commands | read | Exécutions récentes de commandes ponctuelles |
| POST | /api/applications/:id/commands | write | Exécuter une commande ponctuelle ({"command": "..."}) |
| GET | /api/applications/:id/commands/:cmd_id | read | Une exécution : statut, sortie, code de sortie |
| POST | /api/applications/:id/reindex | write | Reconstruire les index de recherche : exécute le search.reindex_command de l’application dans un conteneur ponctuel (202; 422 sans commande ou sans déploiement réussi, 409 si une réindexation est déjà en cours). Définissez reindex_command / auto_reindex avec PATCH /api/applications/:id; GET les affiche sous search avec la library détectée |
| POST | /api/applications/:id/clone | write | Cloner l’application dans une copie staging |
| POST | /api/applications/:id/promote | write | Promouvoir la version d’une application staging vers la production |
| GET | /api/applications/:id/manifest | read | L’application sous forme de railyard.yml (?secrets=true inclut les valeurs des variables) |
| POST | /api/applications/:id/apply_manifest | write | Appliquer un manifeste ({"manifest": "<yaml text>"}) |
| POST | /api/applications/validate_manifest | read | Vérifier un manifeste sans l’appliquer — sans :id, le jeton de n’importe quelle application fonctionne ({"manifest": "<yaml text>"} → {"problems": [...]}, vide s’il est valide) |
| POST | /api/applications/:id/db_query | read / write | Exécuter du SQL ({"sql": "...", "write": false}); write: true exige un jeton à portée write |
| GET/POST | /api/applications/:id/database_backups | read / write | Lister les sauvegardes ou en faire une maintenant |
| POST | /api/database_backups/:id/restore | write | Restaurer une sauvegarde |
| GET | /api/database_backups/:id/download | read | Lien de téléchargement signé |
| GET/POST | /api/applications/:id/volume_backups | read / write | Lister les sauvegardes ou sauvegarder un volume ({"volume": "name"}) |
| POST | /api/volume_backups/:id/restore | write | Restaurer une sauvegarde de volume |
| GET | /api/applications/:id/traffic | read | Requêtes, p95, taux d’erreur et points de terminaison les plus lents (?range=1h|24h|7d|30d) |
| PATCH | /api/applications/:id/autoscale | write | Ajuster le nombre de répliques web selon p95/CPU ({"max":N,"min":N,"p95":ms,"cpu":pct}); {"max":null} désactive la fonction |
| POST | /api/applications/:id/data_move | write | Déplacer les données de l’application vers un serveur de base de données ({"database_server_id": 12}); 202 avec le détail de ce qui est copié |
| POST | /api/applications/:id/external_datastores | write | Pointer database, redis ou elasticsearch de l’application vers une URL externe ({"kind","url","copy"}); l’URL est d’abord vérifiée (502 si injoignable), 202 une fois mis en file d’attente |
| DELETE | /api/applications/:id/external_datastores/:kind | write | Ramener ces données sous Railyard (?copy=true les copie de retour) |
| GET | /api/applications/:id/addons/:kind/transfers | read | Exports et imports récents d’un module complémentaire (redis, mongodb, elasticsearch, storage, influxdb, rabbitmq), avec liens de téléchargement |
| POST | /api/applications/:id/addons/:kind/transfers | write | Exporter les données du module complémentaire; avec un file multipart et confirm=<app name>, y charger un fichier |
| GET | /api/applications/:id/storage/objects | read | Parcourir le bucket du module complémentaire de stockage (?prefix=, ?token=) : dossiers, fichiers avec taille, indicateur public et lien signé |
| POST | /api/applications/:id/storage/objects | write | Téléverser un fichier (file multipart, prefix ou key) |
| DELETE | /api/applications/:id/storage/object?key= | write | Supprimer un fichier, ou un dossier si la clé se termine par / |
| PATCH | /api/applications/:id/storage/visibility | write | Rendre un dossier public ou privé ({"prefix":"avatars/","public":true}) |
| GET | /api/applications/:id/redis | read | Où réside le Redis de l’application (base partagée N ou la sienne), sa limite et sa politique, mémoire utilisée |
| PATCH | /api/applications/:id/redis | write | {"dedicated":true,"maxmemory_mb":512,"policy":"allkeys-lru"} donne à l’application son propre Redis (les clés sont copiées) ou change sa limite; {"dedicated":false} la ramène en arrière |
| GET | /api/deploys | read | Déploiements de toute l’équipe (?application_id=, ?limit=) |
| GET | /api/deploys/:id | read | Détail du déploiement |
| GET | /api/deploys/:id/logs | read | Lignes de logs du déploiement (du plus ancien au plus récent, ≤5000) |
| GET | /api/applications/:id/log_search | read | Lignes de logs enregistrées : plage de temps, recherche, pages par curseur (voir Logs, métriques et alertes) |
| GET | /api/applications/:id/log_download | read | Lignes de logs enregistrées pour une plage, en texte |
| GET/PATCH/DELETE | /api/applications/:id/otel_export | read / write | Paramètres d’export OpenTelemetry (voir Logs, métriques et alertes) |
| GET/POST | /api/applications/:id/metric_alerts | read / write | Alertes de seuil de métrique et leur historique |
| PATCH/DELETE | /api/applications/:id/metric_alerts/:alert_id | write | Modifier ou supprimer une alerte |
| GET | /api/applications/:id/job_queue | read | Statistiques de la file de tâches, tâches échouées, historique (voir Logs, métriques et alertes) |
| POST | /api/applications/:id/job_queue/failed/:job_id/retry | write | Réessayer une tâche échouée |
| DELETE | /api/applications/:id/job_queue/failed/:job_id | write | Abandonner une tâche échouée |
| GET | /api/servers | read | La flotte |
| GET | /api/servers/:id | read | Détail du serveur, états des processus en direct |
| POST | /api/servers/:id/reboot | /power_cycle | /cordon | /uncordon | /prune_docker | write | Actions sur le cycle de vie du serveur |
| PATCH | /api/servers/:id/resize | write | Redimensionner chez le fournisseur ({"size": "..."}) |
| GET | /api/notification_rules | read | Règles de notification à l’échelle de l’équipe |
| GET | /api/webhook_endpoints | read | Points de terminaison webhook à l’échelle de l’équipe |
| GET | /api/config | read | Entrées de configuration à l’échelle de l’équipe |
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 }
}
Applications
Objet app (sous forme de liste) :
{
"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 ajoute : 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[] (les noms seulement : les
valeurs ne sont jamais retournées dans le détail de l’application; utilisez
le point de terminaison env), domains[] {hostname, redirect, cert_state}.
Créer : 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"}
}}'
Le corps est {"application": { … }}. Clés acceptées : 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
(un name: command par ligne), scheduled_tasks_text
(name | cron | command par ligne), db_extensions,
deploy_approval_required, strict_pushes (un git push dont le build
échoue est rejeté; true par défaut), addons (tableau), env_vars (objet).
Également à la mise à jour : server_id.
server(ouserver_id) peut être un ID de serveur ou un nom d’hôte. Si vous l’omettez, l’application est placée sur le premier serveur non mis en quarantaine de l’équipe.- Si
hostnameest vide, il est déduit :<name>.<server app_domain>, sinonAPP_DOMAIN_SUFFIX, sinon<name>.<server-ip>.sslip.io. - La création ne déclenche pas de déploiement. Enchaînez avec
POST …/deploys. - Au-delà de la limite d’applications de l’équipe →
422.
201 avec l’objet détaillé de l’application. PATCH → 200. DELETE → 204.
Archiver / désarchiver : POST /api/applications/:id/archive arrête le
conteneur en cours d’exécution et met archived: true; l’enregistrement, la
configuration et l’historique restent. unarchive retire l’indicateur
(redéployez pour la faire revenir).
Variables d’environnement — /api/applications/:id/env
# read
curl -s https://cp.example.com/api/applications/11/env -H "Authorization: Bearer $RLY"
# {"env":{"RAILS_ENV":"production","LOG_LEVEL":"info"}}
# merge (set two, delete one)
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}}'
# delete one
curl -s -X DELETE "https://cp.example.com/api/applications/11/env?key=FEATURE_X" \
-H "Authorization: Bearer $RLY"
PATCH fusionne avec l’ensemble existant; une valeur null retire cette
clé. Les trois retournent l’objet complet {"env": { … }}. Les changements
prennent effet au prochain déploiement.
Domaines personnalisés — /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 fait en sorte que le nom d’hôte redirige en 301 vers le nom
d’hôte principal de l’application au lieu de le servir. Pointez le
dns_target affiché vers votre DNS et Caddy émet le certificat.
DELETE …/domains/:domain_id → 204.
Déploiements
Objet 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 ajoute error, triggered_by (courriel),
duration_seconds.
Un déploiement canary a aussi un objet canary (null autrement) :
"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 démarre le déploiement de
promotion (202, promotion_deploy_id); DELETE /api/deploys/:id/canary
effectue un retour arrière. Les deux exigent un jeton write et répondent
409 dès que le canary n’est plus actif.
Stratégie de déploiement : PATCH /api/applications/:id/deploy_strategy
avec un ou plusieurs de deploy_strategy (standard · canary),
canary_percent (1 à 99), canary_steps_text (pourcentages croissants
séparés par des virgules, par ex. "5, 25, 50"; vide = une seule étape),
canary_promote_after_minutes (1 à 1440, vide = manuellement),
canary_max_error_rate (0 à 100). GET /api/applications/:id affiche les
mêmes champs. Avec des étapes définies, promouvoir une étape démarre la
suivante comme son propre canary au lieu de passer directement à 100 %.
Redéployer quand l’image change (applications image seulement, sinon
422) : PATCH /api/applications/:id/image_watch avec enabled
(true/false); POST /api/applications/:id/image_watch/check vérifie le
registre immédiatement (202). Les deux le retournent, et
GET /api/applications/:id l’affiche sous 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 }
Les registres appellent POST /webhooks/image/<token> (aucun jeton d’API;
n’importe quel corps) pour déclencher une vérification.
Déclencher : 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(optionnel) : branche, étiquette ou SHA à déployer une seule fois; par défaut, lagit_refde l’application, qu’unrefne change jamais. Letriggerdu déploiement est alorsrefet son champrefindique ce qui a été utilisé. Les noms contenant des caractères que git n’accepte pas dans une référence reçoivent422. Ignoré pour une applicationimage://.message(optionnel) : une note de déploiement (jusqu’à 500 caractères), retournée dansmessageet affichée dans le tableau de bord, l’activité, les notifications et les webhooksdeploy.*.at(optionnel) : heure ISO 8601 à laquelle déployer au lieu de tout de suite. Répond201avecstatus: "scheduled"etscheduled_for; l’heure doit être dans le futur et à l’intérieur de la fenêtre de déploiement (422autrement). Annulez-le avecPOST /api/deploys/:id/cancel(annule aussi un déploiement en file ou en cours).ignore_window(optionnel) : déployer même si l’application est hors de sa fenêtre de déploiement. Sans ce paramètre, un tel déploiement répond409avecoutside_window: trueetwindow_opens_at.rollback_to(optionnel) : ID d’un déploiement réussi antérieur. Redéploie son commit et rétablit les variables avec lesquelles il s’est exécuté; la réponse ajouterestored_config. Envoyez"restore_config": falsepour revenir en arrière sur le code seulement. Les versions déployées avant que Railyard ne conserve une copie de leurs variables ne reviennent en arrière que sur le code.202 Accepted+ l’objet déploiement quand il est en file d’attente ou en cours d’exécution.200 OK+ l’objet avecstatus: "pending_approval"si l’application exige une approbation de déploiement (approuvez-le dans le tableau de bord).409si un déploiement est déjà actif ou en attente d’approbation pour cette application.429si la limite de déploiements par heure de l’équipe est atteinte.422si l’application est archivée.422avecmissing_env(tableau de noms) si l’application n’a jamais été déployée avec succès et que des identifiants documentés par son dépôt ne sont pas définis ou sont encore des valeurs d’exemple. Définissez-les via le point de terminaison env, ou envoyez"force": truepour déployer quand même : ces noms ne seront plus redemandés.
Fenêtre de déploiement : 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"}'
Retourne {days, start, end, time_zone, open_now, opens_at} (aussi sous
deploy_window dans GET /api/applications/:id). "days": [] retire la
fenêtre. Les push, les webhooks GitHub/GitLab et les push regroupés hors de
la fenêtre deviennent un seul déploiement scheduled qui démarre à
l’ouverture de la fenêtre.
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. Limité à 5000 lignes, les plus
anciennes en premier. Interrogez tant que status est queued/running.
Liste : GET /api/deploys?application_id=11&limit=50 ou
GET /api/applications/11/deploys.
Exécution — logs, processus, commandes, redémarrage
La moitié programmable de la console de la page d’application. Tout ceci passe par l’agent sur la machine via un canal mTLS.
Logs d’exécution : GET /api/applications/:id/logs?tail=200&process=web
Ce que l’application en cours d’exécution imprime en ce moment, pas la
sortie du build (c’est /api/deploys/:id/logs). tail va de 1 à 5000 (200
par défaut); process filtre sur un seul processus. 409 avec
{"error": "No running deployment"} quand rien n’est encore en ligne, ou
que l’agent est injoignable.
{
"application_id": 11,
"lines": [
{ "stream": "stdout", "process": "web", "line": "GET / 200 12ms", "at": "2026-09-06T12:00:01Z" }
]
}
Il n’y a pas encore de suivi par poussée serveur : railyard logs --follow
relance une requête à intervalles réguliers.
Processus : GET /api/applications/:id/processes
Dernier état signalé de chaque conteneur, actualisé à chaque pulsation de l’agent. Liste vide = rien en cours d’exécution.
{ "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 · …
Redémarrage : POST /api/applications/:id/restart[?process=web]
Redémarre tous les conteneurs, ou seulement celui d’un processus. 409 si
rien n’est en cours d’exécution.
{ "restarted": 2, "processes": ["railyard-app-11-web", "railyard-app-11-worker-1"] }
Types de processus : 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 est small, medium, large (mappé depuis PROCESS_SIZE_*),
custom (envoyez alors cpu_limit et memory_limit) ou vide pour la
limite globale de l’application. cpus / memory sont les limites
effectives après ce repli.
kill_timeout_seconds (1 à 300, vide pour la valeur par défaut) est le
temps dont dispose le processus après stop_signal (SIGTERM, SIGINT,
SIGQUIT, vide pour celui de l’image) avant d’être tué. Les changements
s’appliquent au prochain déploiement. Une valeur invalide → 422.
Commandes ponctuelles : 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"}'
S’exécute dans le conteneur web via le RPC RunTask de l’agent.
202 Accepted avec la ligne; interrogez
GET /api/applications/:id/commands/:cmd_id pour le status (queued →
running → succeeded/failed), le output en continu, et le
exit_code. GET /api/applications/:id/commands liste les exécutions
récentes.
La console interactive (un TTY, RPC Exec) n’est pas encore dans l’API.
Serveurs (lecture seulement)
curl -s https://cp.example.com/api/servers -H "Authorization: Bearer $RLY"
Objet 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 (pulsation < 30 s) · stale (< 5 min) · offline.
GET /api/servers/:id ajoute apps[] (noms) et
processes[] {container, state, restarts, crash_looping}.
Mettre en quarantaine, redémarrer, effectuer un cycle d’alimentation, purger
et redimensionner un serveur existant se fait en POST/PATCH sur
/api/servers/:id (voir le tableau plus haut). Attacher un nouveau serveur
n’est possible que depuis le tableau de bord pour l’instant.
Fenêtre de maintenance des données : GET/PATCH /api/maintenance_window
(équipe) et GET/PATCH /api/servers/:id/maintenance_window (un serveur;
jour vide = utiliser celle de l’équipe). Champs : maintenance_day 0 à 6
(dimanche = 0), maintenance_hour 0 à 23 UTC, maintenance_hours 1 à 12.
Voir Serveurs de base de données.
Mises à niveau majeures de Postgres :
GET/POST /api/servers/:id/postgres_upgrades (to_image; la création en
lance la vérification de compatibilité), GET …/:id (avec le log),
PATCH …/:id (when=window pour attendre la fenêtre de maintenance, sinon
elle démarre immédiatement), POST …/:id/rollback, DELETE …/:id (retire
l’ancienne version conservée, ou abandonne un plan non démarré).
Regroupeur de connexions :
GET/POST/DELETE /api/servers/:id/connection_pooler (PgBouncer, mode
transaction, port 6432). Par application : PATCH /api/applications/:id/database avec app_database[pooled]=true pointe
DATABASE_URL vers le regroupeur à partir du prochain déploiement et garde
DATABASE_DIRECT_URL pour les migrations.
Forks de base de données :
GET/POST /api/applications/:id/database_forks (target_server_id pour
une nouvelle base de données ou target_application_id pour un
environnement staging/aperçu, source=live|backup, mask_sql optionnel),
GET …/:id (avec l’URL du fork une fois prêt), DELETE …/:id (le
supprime).
Répliques de secours : GET/POST /api/servers/:id/database_replicas
(replica_server_id), POST …/:id/promote, DELETE …/:id.
Cloud
Railyard Cloud transforme le compte Railyard d’une équipe en son propre
petit cloud : serveurs, images, adresses statiques, une zone DNS et des
buckets S3, facturés au coût cloud plus une majoration publiée. C’est conçu
pour des outils comme AliaJS qui attendent l’API d’un fournisseur cloud,
donc les structures suivent celles de Hetzner : chaque réponse enveloppe son
objet ({"server": {...}}, {"servers": [...]}). Une carte de crédit doit
être enregistrée (ajoutez-la sur la page Cloud), et les points de
terminaison répondent 503 jusqu’à ce que l’opérateur active Railyard
Cloud.
| Méthode | Chemin | Portée | Description |
|---|---|---|---|
| GET | /api/cloud/sizes | read | Tailles de serveur avec coût horaire, majoration et prix |
| GET | /api/cloud/ssh_keys | read | Lister les clés SSH |
| POST | /api/cloud/ssh_keys | write | Ajouter une clé SSH (201; 200 si la même clé existe déjà) |
| DELETE | /api/cloud/ssh_keys/:id | write | Supprimer une clé SSH |
| GET | /api/cloud/servers | read | Lister les serveurs actifs |
| POST | /api/cloud/servers | write | Lancer un serveur (201, pending) |
| GET | /api/cloud/servers/:id | read | Détail du serveur, actualisé à la lecture |
| DELETE | /api/cloud/servers/:id | write | Terminer un serveur (200, terminated) |
| POST | /api/cloud/servers/:id/power | write | Démarrer un serveur arrêté |
| DELETE | /api/cloud/servers/:id/power | write | Arrêter un serveur en cours d’exécution |
| POST | /api/cloud/servers/:id/images | write | Capturer un serveur dans une image (201, creating) |
| GET | /api/cloud/images | read | Lister les images (?name= filtre) |
| GET | /api/cloud/images/:id | read | Détail de l’image, actualisé à la lecture |
| DELETE | /api/cloud/images/:id | write | Supprimer une image |
| GET | /api/cloud/addresses | read | Lister les adresses statiques |
| POST | /api/cloud/addresses | write | Allouer une adresse statique |
| GET | /api/cloud/addresses/:id | read | Détail de l’adresse |
| DELETE | /api/cloud/addresses/:id | write | Libérer une adresse statique |
| POST | /api/cloud/addresses/:id/assignment | write | L’attacher à un serveur ({"server_id": 9}) |
| DELETE | /api/cloud/addresses/:id/assignment | write | La détacher |
| GET | /api/cloud/zone | read | La zone DNS de l’équipe |
| GET | /api/cloud/zone/records | read | Lister les enregistrements |
| PUT | /api/cloud/zone/records/:type/:name | write | Créer ou remplacer un enregistrement |
| DELETE | /api/cloud/zone/records/:type/:name | write | Supprimer un enregistrement |
| GET | /api/cloud/buckets | read | Lister les buckets |
| POST | /api/cloud/buckets | write | Créer un bucket; la réponse contient la clé de stockage de l’équipe |
| DELETE | /api/cloud/buckets/:id | write | Supprimer un bucket (doit être vide) |
| GET | /api/cloud/usage | read | Les lignes d’utilisation du mois avec coût, majoration et prix |
Les corps de requête sont en JSON et peuvent être plats ({"name": "web"})
ou enveloppés ({"server": {"name": "web"}}). Les jetons à portée
application sont refusés (403).
Erreurs en plus des habituelles :
| Statut | Quand |
|---|---|
402 | Aucune carte enregistrée. Corps : {"error": "add a card before using Railyard Cloud", "card_url": "https://app.railyard.run/cloud/card/new"} |
403 | Jeton en lecture seule ou à portée application, une limite d’équipe atteinte ("this team has reached its limit of 3 servers"), ou la limite de dépenses mensuelle serait dépassée |
409 | Conflit : un nom de clé SSH avec une clé différente, un nom de bucket déjà utilisé, un bucket qui contient encore des objets, un conflit de CNAME, démarrer un serveur qui n’est pas arrêté |
422 | Entrée invalide : taille inconnue, image inconnue, incompatibilité d’architecture entre l’image et la taille, nom DNS hors de votre zone, IP privée dans un enregistrement A |
502 | Le cloud a rejeté la requête; le message en indique la raison |
503 | Railyard Cloud n’est pas activé |
Tailles : 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 est ce que le serveur coûte à Railyard (machine, IPv4 publique
et disque); hourly_price est ce que l’équipe paie :
cost × (1 + markup_percent / 100).
Serveurs
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" } }
Interrogez GET /api/cloud/servers/9 toutes les quelques secondes jusqu’à
ce que status soit running et que public_ipv4 soit défini
(habituellement de 10 à 30 secondes), puis ssh ubuntu@<public_ipv4>.
failed signifie que le lancement a été refusé; failure en indique la
raison. image vaut ubuntu-24.04 ou le nom d’une de vos propres images
construites pour la même architecture que la taille.
Images
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"}'
Interrogez GET /api/cloud/images/:id jusqu’à ce que status soit
available (quelques minutes). Lancez de nouveaux serveurs à partir de
celle-ci avec "image": "n8n-base". Une image dormante ne coûte que sa
taille stockée (stored_gb), ce qui permet à une copie de secours d’un
service d’attendre pour quelques cents par mois.
Adresses : une IPv4 statique qui survit au remplacement du serveur derrière elle.
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}'
Zone DNS : chaque équipe obtient sa propre zone, donc aucun domaine personnel n’est requis.
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,...}}
nameest relatif à la zone (n8n-production) ou pleinement qualifié à l’intérieur de celle-ci; tout ce qui est hors de votre zone donne422, et l’apex de la zone est réservé.- Types :
A,AAAA,CNAME,TXT. TXT fonctionne pour le défi DNS-01 de certbot :PUT /api/cloud/zone/records/TXT/_acme-challenge.n8n-production. - Les valeurs
Adoivent être des adresses publiques. Un CNAME ne peut pas partager son nom avec d’autres enregistrements. PUTremplace les valeurs de l’enregistrement;DELETEsur le même chemin le supprime (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 dans le format s3: de restic. La clé d’accès appartient à
l’équipe et ne fonctionne que sur les buckets de l’équipe; chaque bucket que
vous créez retourne la même clé, et la liste des buckets ne l’affiche
jamais. DELETE exige que le bucket soit vide.
Utilisation : 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", "spend_limit_usd": "100.0" } }
L’utilisation est mesurée chaque heure par ressource : heures de serveur, trafic sortant, heures d’adresse statique, stockage d’image et stockage de bucket. Les cartes sont débitées quand le total non facturé dépasse un seuil ou au début de chaque mois.
Modèles
Des applications prêtes à l’emploi en un clic : le même catalogue qu’offre la page « deploy a template » du tableau de bord.
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" }
] }
Lancer : 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 est optionnel : si vous l’omettez, Railyard choisit le premier
hôte d’application en ligne. 201 avec le premier déploiement déjà en
file. 422 si l’équipe dépasse sa limite d’applications ou si les valeurs
par défaut du modèle échouent à la validation.
Recettes rapides
Déployer à chaque 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\"}"
Attendre la fin d’un déploiement :
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
Synchroniser les variables d’environnement depuis un fichier (lignes 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 @-
Pas encore dans l’API
Console interactive (un TTY via le RPC Exec), attacher un nouveau serveur,
la gestion des modules complémentaires au-delà du tableau addons, créer ou
modifier des règles de notification, des points de terminaison webhook ou
des entrées de configuration (le listage est en lecture seule aujourd’hui),
le fil d’activité. Tout ceci n’est offert que depuis le tableau de bord
aujourd’hui; ouvrez un ticket si vous avez besoin de l’un de ces éléments
par l’API.
MCP (agents IA)
POST /api/mcp est un serveur Model Context Protocol (JSON-RPC par HTTP en
streaming), ce qui permet aux agents IA d’utiliser Railyard avec les mêmes
jetons d’API.
- Un jeton read peut lister les serveurs et les applications, afficher une application et lire ses logs.
- Un jeton write peut aussi déployer, redémarrer et définir des variables d’environnement.
- Supprimer une application ou retirer une variable d’environnement ne s’exécute jamais directement : cela crée une demande d’approbation. Un administrateur de l’équipe l’approuve ou la rejette sous Approvals (elle apparaît aussi dans le tableau de bord).
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_..." } } } }
Outils : list_servers, list_apps, get_app, app_logs, deploy_app, restart_app, set_env, unset_env (approbation), delete_app (approbation).