API¶
Tout ce que fait l'interface web passe par cette API. Elle est publique et documentée pour que vous puissiez automatiser ce que vous voulez : créer un plan depuis votre dépôt d'infrastructure, déclencher une exécution après une sauvegarde, exporter vos rapports.
Base : https://api.restoreproof.io/api/v1
Les routes ci-dessous sont celles que le serveur enregistre réellement. Le runner ne figure pas ici : il parle un autre protocole (gRPC), sur un autre port.
Authentification¶
Un jeton d'accès JWT dans l'en-tête :
| Jeton | Durée de vie par défaut |
|---|---|
| Accès | 15 minutes |
| Rafraîchissement | 7 jours |
Obtenir un jeton¶
curl -X POST https://api.restoreproof.io/api/v1/auth/login \
-H 'Content-Type: application/json' \
-d '{"email":"vous@exemple.fr","password":"..."}'
Si la double authentification est activée, la réponse est un défi à échanger
contre une session sur POST /auth/two-factor/verify.
Le rafraîchir¶
La connexion pose un cookie rp_refresh, HttpOnly et limité à
/api/v1/auth. Le renvoyer suffit à obtenir un nouveau jeton d'accès ; la
réponse repose un cookie neuf, l'ancien cesse alors de valoir.
curl -X POST https://api.restoreproof.io/api/v1/auth/refresh \
--cookie cookies.txt --cookie-jar cookies.txt
Routes d'authentification¶
| Méthode | Route | Jeton requis |
|---|---|---|
POST |
/auth/register |
non |
POST |
/auth/register/confirm |
non |
POST |
/auth/login |
non |
POST |
/auth/two-factor/verify |
non |
POST |
/auth/refresh |
non |
POST |
/auth/magic-link |
non |
GET |
/auth/verify-magic-link |
non |
POST |
/auth/complete-registration |
non |
GET |
/auth/verify-email |
non |
POST |
/auth/password-reset/request |
non |
POST |
/auth/password-reset/confirm |
non |
POST |
/auth/logout |
non (le cookie suffit) |
GET |
/auth/me |
oui |
PUT |
/auth/password |
oui |
POST |
/auth/verify-email/resend |
oui |
GET |
/auth/two-factor |
oui |
POST |
/auth/two-factor/setup |
oui |
POST |
/auth/two-factor/confirm |
oui |
POST |
/auth/two-factor/disable |
oui |
POST |
/auth/two-factor/recovery-codes |
oui |
PUT /auth/password attend new_password, et current_password en plus dès que
le compte a déjà un mot de passe. Un changement réussi ferme vos autres sessions.
Les routes d'authentification ont leur propre limite de débit, plus stricte que le reste de l'API.
Ce que vous manipulez¶
Toutes les routes qui suivent exigent un jeton, et sont automatiquement limitées à votre organisation : il n'existe aucun moyen de lire les données d'une autre.
Les écritures demandent au moins le rôle operator.
La forme des réponses¶
Une réponse réussie enveloppe son contenu dans data :
Une erreur porte error, et parfois code quand l'interface a besoin de
distinguer les refus entre eux :
{"error": "report not found"}
{"error": "two-factor authentication is required for this action", "code": "TWO_FACTOR_REQUIRED"}
La pagination¶
Toutes les listes — runners, sources, plans, exécutions, rapports,
planifications, déclencheurs, membres, journal d'audit — se paginent avec
limit et offset :
curl -H "Authorization: Bearer $TOKEN" \
'https://api.restoreproof.io/api/v1/runs?limit=50&offset=100'
limit vaut 20 par défaut (50 pour les membres) et 100 au maximum ; une valeur
hors bornes retombe sur la valeur par défaut. La réponse d'une liste ne met pas
son total dans data, elle le pose à côté :
Runners¶
| Méthode | Route | Rôle |
|---|---|---|
GET |
/runners |
|
POST |
/runners |
operator |
GET |
/runners/{id} |
|
GET |
/runners/{id}/secrets |
|
PUT |
/runners/{id} |
operator |
DELETE |
/runners/{id} |
operator |
POST |
/runners/{id}/regenerate-key |
operator |
GET |
/runners/{id}/config |
operator — en lecture aussi |
/runners/{id}/config rend le fichier de configuration prérempli en YAML. C'est
la seule route en lecture qui exige le rôle operator, parce que ce fichier peut
contenir un accès et pas seulement une information.
Par défaut il ne contient pas la clé en clair : le champ api_key vaut
${RESTOREPROOF_API_KEY}, à vous de poser la variable d'environnement du même
nom sur la machine du runner. Ajoutez ?regenerate=true pour obtenir un fichier
avec une clé en clair — mais c'est une nouvelle clé : l'ancienne cesse
aussitôt de fonctionner, comme avec /runners/{id}/regenerate-key.
L'adresse écrite dans ce fichier est celle du canal du runner (gRPC), pas celle
de l'API HTTP. La section secrets.static y est préremplie avec les clés
static:// déclarées par vos sources, valeurs vides : à vous de les compléter.
GET /runners et GET /runners/{id} portent aussi unresolved_secret_refs
(les références que ce runner n'arrive pas à résoudre, groupées par source) et
secrets_reported_at (la date du dernier battement qui les a vérifiées, null
tant qu'aucun n'est arrivé) :
"unresolved_secret_refs": {"9b1e…": ["env://NOT_SET_ANYWHERE"]},
"secrets_reported_at": "2026-09-07T10:00:00Z"
GET /runners/{id}/secrets reprend les références de toutes les sources de
l'organisation et dit, pour chacune, si ce runner sait la résoudre :
{
"runner_id": "…",
"reported_at": "…|null",
"sources": [
{
"id": "…",
"name": "…",
"type": "s3",
"refs": [
{"key": "access_key", "ref": "env://S3_ACCESS_KEY", "scheme": "env", "name": "S3_ACCESS_KEY", "resolved": true},
{"key": "secret_key", "ref": "file://minio-secret", "scheme": "file", "name": "minio-secret", "resolved": false}
]
}
]
}
resolved vaut true, false, ou null tant que reported_at est null —
ce runner ne s'est encore jamais synchronisé. Voir Fournir les secrets au
runner.
Sources¶
| Méthode | Route |
|---|---|
GET |
/sources |
POST |
/sources |
GET |
/sources/{id} |
PUT |
/sources/{id} |
DELETE |
/sources/{id} |
Le corps d'une création porte quatre champs : name, type, config et
secret_refs.
{
"name": "Sauvegardes de production",
"type": "s3",
"config": {"endpoint": "https://s3.fr-par.scw.cloud", "bucket": "backups", "region": "fr-par"},
"secret_refs": {"access_key": "env://S3_ACCESS_KEY", "secret_key": "env://S3_SECRET_KEY"}
}
secret_refs ne contient que des références, jamais une valeur. Chaque
référence prend l'une de ces trois formes :
| Forme | Ce que le runner lit |
|---|---|
env://NOM_DE_VARIABLE |
Une variable d'environnement du runner. Lettres, chiffres et tirets bas, jamais un chiffre en tête. |
file://nom_de_fichier |
Un fichier du dossier des secrets du runner. Un nom seul, sans séparateur de chemin. |
static://cle |
Une clé de la table de secrets statiques du runner. Même écriture que pour un fichier. |
Notez l'absence du secret:// de tête : il s'écrit dans un plan, pas dans une
source.
Les clés acceptées dépendent du type :
| Type | Clés de secret_refs |
|---|---|
s3, minio |
access_key, secret_key |
http, https |
authorization |
local, file, nfs |
aucune |
Pour une URL authentifiée, la référence se pose sur la source, sous
secret_refs.authorization : le runner en fait l'en-tête Authorization. Le
champ headers de l'étape fetch du plan reste disponible pour un autre
en-tête, et un headers.Authorization qu'il porte l'emporte sur celui de la
source. Une source local, file ou nfs ne porte, elle, aucun identifiant.
config ne peut pas contenir access_key, secret_key, password, token,
authorization ni secret : ces champs sont refusés pour empêcher un secret
d'y être écrit en clair.
Tout manquement rend un 400 qui nomme le champ fautif et ne renvoie jamais la
valeur envoyée :
{
"error": "validation failed",
"fields": {
"secret_refs.access_key": "secret_refs.access_key: must be a reference, never the secret itself. Use one of: env://VAR_NAME, file://key or static://key"
}
}
vault:// et sops:// sont reconnus et refusés : ils ne sont pas encore pris
en charge.
Plans¶
| Méthode | Route |
|---|---|
GET |
/plans |
POST |
/plans |
GET |
/plans/{id} |
PUT |
/plans/{id} |
DELETE |
/plans/{id} |
Le mode essai, pour éprouver un plan qui n'est pas encore enregistré :
| Méthode | Route |
|---|---|
POST |
/plans/trial |
GET |
/plans/trial/{id} |
POST |
/plans/trial/{id}/cancel |
Il est monté sous /plans parce que ce qu'il exécute est un document de plan —
en général un plan encore en cours d'écriture, qui n'a pas d'identifiant.
POST /plans/trial demande un runner en ligne ou occupé : un runner occupé
prend l'essai dès qu'il a fini son travail en cours. Sans aucun des deux, la
réponse est un 409.
Exécutions¶
| Méthode | Route | Note |
|---|---|---|
GET |
/runs |
|
POST |
/runs |
Décompte votre quota mensuel. |
GET |
/runs/stats |
|
GET |
/runs/{id} |
|
DELETE |
/runs/{id} |
Essais seulement, et seulement une fois terminés. |
POST |
/runs/{id}/cancel |
|
GET |
/runs/{id}/report |
GET /runs et GET /runs/{id} portent aussi le nom du plan et le résumé du
rapport de l'exécution, pour éviter un appel par ligne. Les trois champs de
rapport sont absents tant qu'aucun rapport n'est arrivé :
"restore_plan_name": "Sauvegarde nocturne",
"report_id": "…",
"report_status": "PASS",
"report_purged_at": "2026-09-08T12:00:00Z"
report_purged_at n'est présent que si la rétention a effacé le contenu du
rapport ; la preuve, elle, reste vérifiable.
GET /runs accepte report_status=PASS|WARN|FAIL, qui ne garde que les
exécutions dont le rapport porte ce verdict. Il se combine avec status et
trial, et total compte la liste filtrée. Toute autre valeur est refusée
en 400.
Rapports¶
| Méthode | Route |
|---|---|
GET |
/reports |
GET |
/reports/{id}/download |
/download rend un fichier autonome, vérifiable hors ligne : c'est
l'artefact destiné à un auditeur.
Planifications et déclencheurs¶
| Méthode | Route |
|---|---|
GET POST |
/schedules |
GET PUT DELETE |
/schedules/{id} |
POST |
/schedules/{id}/trigger |
GET POST |
/webhook-triggers |
GET PUT DELETE |
/webhook-triggers/{id} |
POST |
/webhook-triggers/{id}/regenerate-token |
GET |
/webhook-triggers/{id}/events |
Organisation¶
| Méthode | Route |
|---|---|
GET PUT DELETE |
/organization |
PUT |
/organization/slug |
GET POST |
/organization/members |
PUT DELETE |
/organization/members/{id} |
GET |
/organization/invitations |
DELETE |
/organization/invitations/{id} |
Facturation¶
| Méthode | Route | Rôle |
|---|---|---|
GET |
/billing/subscription |
|
GET |
/billing/plans |
|
GET |
/billing/invoices |
|
GET |
/billing/usage |
|
POST |
/billing/checkout |
admin — premier achat uniquement |
GET |
/billing/checkout/{session_id} |
admin — vérifie le paiement au retour de Stripe |
POST |
/billing/change-plan |
admin — tout changement ensuite |
POST |
/billing/portal |
admin |
Warning
/billing/checkout sert au premier achat. L'appeler sur une organisation
déjà abonnée crée un second abonnement. Pour tout changement, c'est
/billing/change-plan.
Notifications¶
La lecture est ouverte à tous les rôles, l'écriture exige le rôle admin. Les
types de canaux, les événements et le format du webhook sont décrits dans
Notifications.
| Méthode | Route | Rôle |
|---|---|---|
GET |
/notifications/catalog |
|
GET POST |
/notifications/channels |
POST : admin |
GET |
/notifications/channels/{id} |
|
PATCH |
/notifications/channels/{id} |
admin |
DELETE |
/notifications/channels/{id} |
admin |
POST |
/notifications/channels/{id}/test |
admin |
POST |
/notifications/channels/{id}/rotate-secret |
admin — canal webhook seulement |
POST |
/notifications/channels/{id}/recipients/{recipientId}/resend |
admin — canal e-mail seulement |
GET |
/notifications/channels/{id}/deliveries |
|
POST |
/notifications/channels/{id}/deliveries/{deliveryId}/retry |
admin |
Ces routes se paginent avec page et page_size (50 par défaut), et non avec
limit et offset. Le secret ou l'adresse d'un canal ne ressort jamais d'une
réponse : seul un indice masqué (secret_hint) est rendu. Le secret de
signature d'un canal webhook est renvoyé une fois, dans le champ secret
de la réponse de création ou de rotation.
curl -X POST https://api.restoreproof.io/api/v1/notifications/channels \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{
"kind": "slack",
"name": "#astreinte-ops",
"language": "fr",
"secret": "https://hooks.slack.com/services/T0/B0/xxx",
"subscriptions": ["run.failed", "run.recovered", "run.unclaimed"]
}'
kind vaut slack, teams, discord, telegram, matrix, webhook ou
email et ne peut plus changer ensuite. secret est obligatoire sauf pour
webhook et email. Créer ou réactiver un canal d'un type que l'offre n'inclut
pas répond 402 avec quota_type: "channel_kind" et l'offre minimale requise
(required_plan). Un test (/test) est limité à six par
minute et par canal (429).
Une confirmation d'adresse e-mail passe par une route publique, appelée par la
page de confirmation de l'application :
POST /notifications/email-confirmations/{token}.
Journal d'audit¶
| Méthode | Route |
|---|---|
GET |
/audit-logs |
GET |
/audit-logs/filters |
GET |
/audit-logs/export (CSV) |
GET |
/audit-logs/{id} |
Modèles de plan¶
| Méthode | Route |
|---|---|
GET |
/templates |
GET |
/templates/{id} |
Les routes sans jeton¶
Six routes n'en demandent pas, chacune pour une raison précise.
Déclencher une exécution depuis votre système de sauvegarde¶
Le jeton est l'authentification. C'est ce qu'on appelle à la fin d'une tâche de sauvegarde pour vérifier tout de suite ce qui vient d'être écrit :
Vérifier une attestation¶
Publique volontairement : le lecteur est un auditeur qui tient un rapport téléchargé et n'a pas de compte. Elle confirme une empreinte qu'il possède déjà et ne rend aucune donnée client. Son débit est limité à part, à 2 requêtes par seconde, pour que parcourir des identifiants de rapport reste aussi lent que c'est inutile.
Le paramètre fingerprint est obligatoire : sans lui la route répond 400.
C'est ce qui fait de chaque réponse la confirmation d'une valeur que l'appelant
détient déjà, plutôt qu'une divulgation. Un identifiant inconnu répond 404.
Le détail est dans Vérifier un rapport signé.
Lire une invitation¶
Le jeton de l'invitation est l'authentification : la personne invitée n'a pas
encore de compte. La route rend de quoi afficher la page d'acceptation, et rien
d'autre : email, role, organization_id, organization_name,
organization_slug, expires_at.
Une invitation déjà utilisée ou périmée répond 410, un jeton inconnu 404.
Accepter une invitation¶
Corps : name et password (8 caractères au minimum). La route crée
l'utilisateur dans l'organisation qui l'a invité, avec le rôle porté par
l'invitation, ouvre une session et marque l'invitation comme utilisée.
Elle refuse en 409 si l'adresse est déjà enregistrée, quelle que soit
l'organisation : un compte existant ne change pas d'organisation par une
invitation. Une invitation déjà utilisée ou périmée répond 410.
Confirmer une adresse de notification¶
Le jeton reçu par e-mail est l'authentification : la personne qui confirme
n'est pas forcément membre de l'organisation. La route rend l'adresse confirmée
et le nom de l'organisation. Un jeton inconnu, périmé ou déjà utilisé répond
404.
Le webhook Stripe¶
Réservé à Stripe. La signature de chaque événement est vérifiée.
Santé du service¶
| Route | Rend |
|---|---|
GET /health |
Vivant. |
GET /health/detailed |
Détail, dont la base de données. |
GET /ready |
Prêt à servir. |
GET /live |
Sonde de vivacité. |
Ces routes ne sont pas sous /api/v1.
Codes de réponse¶
| Code | Signification |
|---|---|
200 / 201 |
Succès. |
400 |
Requête mal formée. |
401 |
Jeton absent, invalide ou expiré. |
403 |
Rôle insuffisant. |
404 |
Inexistant — ou n'appartenant pas à votre organisation. |
402 |
Quota dépassé. Nombre de plans, de sources, de runners, ou d'exécutions du mois. Aussi : abonnement impayé. |
409 |
Conflit avec l'état actuel : identifiant déjà pris, essai encore en cours, abonnement absent ou déjà sur cette offre. |
410 |
A existé, n'existe plus : invitation utilisée ou périmée, déclencheur supprimé, rapport dont le contenu est passé hors de la durée de consultation. |
429 |
Trop de requêtes. |
500 |
Erreur du service. |
Un 402 n'est pas une erreur technique : c'est votre offre qui est atteinte. La
réponse dit quelle limite, et l'interface propose le changement d'offre.