Aller au contenu

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 :

Authorization: Bearer <access_token>
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 :

{"data": { "id": "…", "name": "…" }}

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é :

{"data": [ … ], "total": 137, "limit": 50, "offset": 100}

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

POST /webhooks/trigger/{token}

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 :

curl -X POST https://api.restoreproof.io/api/v1/webhooks/trigger/$RP_TRIGGER_TOKEN

Vérifier une attestation

GET /public/attestations/{id}?fingerprint=<32 caractères hexadécimaux>

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

GET /invitations/{token}

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

POST /invitations/{token}/accept

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

POST /notifications/email-confirmations/{token}

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

POST /webhooks/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.