Documentation API

L'API Wasate Cloud permet d'intégrer notre plateforme à vos outils. REST classique, JSON, authentification simple par clé API.

👋 Vous cherchez simplement à utiliser Wasate Cloud (envoyer un fichier, créer une galerie, connecter un appareil) ? Cette page est technique, pensée pour les développeurs. Le guide d'utilisation est plus simple et répond à ces questions.

Introduction

L'API Wasate Cloud expose les fonctionnalités suivantes :

Base URL : https://wasate.fr

Table de référence rapide

Vous cherchez « comment on fait X » ? Cette table couvre les besoins les plus fréquents, avec le vrai chemin d'API — et indique honnêtement ce qui n'existe pas encore, pour éviter de chercher un endpoint qui n'a jamais été construit.

BesoinEndpointExiste ?
Mon profilGET /api/me
Mon plan / mon offreinclus dans GET /api/me✅ (pas d'endpoint séparé)
Mes sessions activesGET /api/me/sessions
Double authentification (MFA/TOTP)❌ géré nativement par la plateforme, page /settings/user/security, pas d'API Wasate dédiée
Export RGPD de mes donnéesPOST /api/rgpd/export puis GET /api/rgpd/export/{id}✅ — détail
Mes consentements RGPDGET /api/rgpd/consents
Mes appareils (Wasate Connect)GET /api/workspace/devices✅ l'API existe et fonctionne — détail. ⚠️ pas d'application de bureau prête à l'emploi actuellement, l'enrôlement suppose de faire tourner Syncthing soi-même.
Liste de mes fichiersGET /api/me/files
Fichiers favoris❌ fonctionnalité inexistante sur la plateforme
CorbeilleGET /api/me/trash
Mes galeriesGET /api/galleries✅ — détail
Mes transfertsGET /api/transfers✅ — détail
Mes demandes de signatureGET /api/signatures/requests✅ — détail
Clé API personnelleonglet « API » de votre espace (/api/my/api-keys)✅ tous les comptes, lecture seule — voir Authentification. Couvre /api/me, /api/me/files, /api/me/trash, /api/me/sessions, galeries, transferts, signatures et RGPD (export + consentements) en GET uniquement. Créer/modifier/supprimer (POST/PUT/DELETE) sur ces endpoints reste réservé à la session cookie — voir détail.
Statut antivirus d'un upload❌ scan automatique en tâche de fond, aucun statut consultable par API
Envoyer un feedbackPOST /api/me/feedback✅ (uniquement en POST — un GET renvoie 405)

Authentification

Trois mécanismes d'authentification selon le type d'endpoint :

1. Session utilisateur (cookies)

Pour les endpoints utilisés depuis le frontend web. Connexion via /app/login, cookie de session HttpOnly + Secure.

2. Clé API (header)

Pour les intégrations serveur-à-serveur. Deux types de clés, même format et même en-tête :

curl -H "Authorization: Bearer wsd_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
     https://wasate.fr/api/me

Format de la clé : wsd_live_ (ou wsd_test_ pour une clé de test) suivi d'un secret aléatoire de 32 caractères. La clé n'est jamais affichée dans l'interface — elle arrive par email à la création, et seul un préfixe tronqué reste visible ensuite pour l'identifier (pas pour vous la re-fournir).

3. Webhook HMAC

Pour les webhooks entrants, signature HMAC-SHA256 du body avec le secret configuré.

Démarrage rapide — script complet

Connexion par identifiant/mot de passe, puis appel authentifié à /api/me. Le cookie de session gère l'authentification ; le requesttoken (CSRF) est nécessaire pour toute requête qui modifie un état (POST/PUT/DELETE).

import requests

BASE = "https://wasate.fr"
session = requests.Session()

# 1) Connexion (endpoint natif de la plateforme)
login = session.post(f"{BASE}/app/login", data={
    "user": "mon-identifiant",
    "password": "mon-mot-de-passe",
    "requesttoken": "",  # premier appel : la plateforme accepte un requesttoken vide au login
})
login.raise_for_status()

# 2) Récupérer un requesttoken CSRF valide pour les appels suivants
csrf_page = session.get(f"{BASE}/app/csrftoken")
csrf_token = csrf_page.json()["token"]

# 3) Appel authentifié
me = session.get(f"{BASE}/api/me", headers={"requesttoken": csrf_token})
print(me.json())
const BASE = "https://wasate.fr";

// Dans le contexte du frontend web (déjà connecté), csrfToken est déjà
// disponible en variable globale. Exemple depuis un script externe :
const csrfRes = await fetch(`${BASE}/app/csrftoken`, { credentials: "include" });
const { token: csrfToken } = await csrfRes.json();

const me = await fetch(`${BASE}/api/me`, {
  credentials: "include",
  headers: { "requesttoken": csrfToken, "Accept": "application/json" },
});
console.log(await me.json());
# 1) Connexion (garde le cookie dans un fichier)
curl -c cookies.txt -X POST https://wasate.fr/app/login \
     -d "user=mon-identifiant" -d "password=mon-mot-de-passe"

# 2) Appel authentifié (réutilise le cookie de session)
curl -b cookies.txt https://wasate.fr/api/me

Gestion des erreurs

Toutes les erreurs retournent un JSON avec un champ error contenant un code court :

{
  "error": "invalid_email"
}

Codes HTTP utilisés :

Rate limits

/api/account/request (et l'inscription en général) n'est pas limité par un compteur fixe de requêtes/minute : une IP qui enchaîne les échecs (formulaire refusé, captcha invalide, etc.) est bloquée automatiquement après plusieurs tentatives infructueuses sur une fenêtre glissante de 15 minutes, indépendamment du nombre de requêtes réussies.

Endpoints publics

POST /api/contact

Envoi d'un message via le formulaire de contact public.

Body JSON :

{
  "email": "client@example.com",
  "name": "Marie Dupont",
  "subject": "general",
  "message": "Bonjour, j'aimerais en savoir plus sur..."
}

Réponse : 200 OK avec { "status": "received" }

GET /api/status

État global du service. Aucune information sensible.

Réponse :

{
  "status": "ok",
  "service": "wasate-cloud",
  "time": "2026-05-22T14:30:00+00:00"
}

POST /api/newsletter

Inscription à la newsletter.

{ "email": "contact@example.com" }

POST /api/account/request

Soumet une demande d'inscription. Le compte sera créé après validation manuelle par un administrateur.

{
  "first_name": "Marie",
  "last_name": "Dupont",
  "email": "marie@example.com",
  "phone": "+33 6 12 34 56 78",
  "account_type": "client_pro",
  "plan": "solo_pro",
  "captcha_token": "..."
}

account_type : client, client_pro, ou distributor

Webhook : paiement reçu

Endpoint exposé par Wasate Cloud, appelé par votre passerelle bancaire ou Zapier après réception d'un virement SEPA.

POST /api/webhook/payment

Headers requis : X-Wasate-Signature: <hmac-sha256-hex>

Body :

{
  "reference": "WAS-MARIEDUPONT",
  "amount": 8.00,
  "currency": "EUR",
  "date": "2026-05-22T10:00:00Z"
}

Signature HMAC

Pour les webhooks entrants, la signature est calculée ainsi :

// Node.js
const crypto = require('crypto');
const signature = crypto
    .createHmac('sha256', WEBHOOK_SECRET)
    .update(rawBody)
    .digest('hex');

// Envoyer dans header X-Wasate-Signature

Bureau portable (Wasate Connect)

Important, pour rester honnête : il n'existe aujourd'hui aucune application de bureau Wasate Connect prête à l'emploi à télécharger et installer. Ce qui suit est le contrat d'API réel côté serveur (fonctionnel, testé) — mais côté appareil, connecter une machine suppose de faire tourner votre propre instance Syncthing et d'orchestrer les deux appels ci-dessous vous-même. Ce n'est donc pas « juste un token d'API » : c'est un enrôlement en deux étapes, décrit précisément ci-dessous pour éviter toute ambiguïté.

Vue d'ensemble du flux

  1. Depuis votre session Wasate (compte déjà connecté, cookie de session — pas de clé API) : POST /api/workspace/devices génère un code à usage unique, valable 1 h.
  2. Sur l'appareil à connecter : démarrez une instance Syncthing locale (syncthing serve) et récupérez son identifiant unique via son API REST locale (GET http://127.0.0.1:8384/rest/system/status → champ myID).
  3. Toujours sur l'appareil : échangez le code + cet identifiant Syncthing contre des identifiants de synchronisation via POST /connect/enroll (public, pas de cookie — c'est l'appareil qui appelle directement, pas votre navigateur).
  4. Avec la réponse reçue, configurez votre Syncthing local pour partager le dossier avec le serveur Wasate (folder_id/server_device_id/server_addresses reçus) — c'est Syncthing, pas Wasate, qui gère ensuite le transfert de fichiers lui-même.
  5. L'appareil appelle périodiquement POST /connect/heartbeat pour rester listé comme actif.

Le mot de passe principal du compte n'est jamais transmis à aucune étape. Le agent_token reçu à l'enrôlement n'est utilisé que pour le heartbeat/la déconnexion — pas pour le transfert de fichiers, qui passe entièrement par le protocole natif de Syncthing une fois les deux instances appairées.

POST /connect/enroll

Public (pas de cookie de session — appelé directement par l'appareil, pas par votre navigateur). Échange un code d'enrôlement + l'identifiant Syncthing de l'appareil contre des identifiants de synchronisation.

Body :

{
  "code": "xxxxx",
  "device_name": "PC bureau",
  "os": "linux",
  "syncthing_device_id": "<myID renvoyé par l'API REST locale de VOTRE Syncthing>"
}

Réponse 200 :

{
  "server_url": "https://wasate.cloud/app",
  "device_id": 12,
  "device_name": "PC bureau",
  "agent_token": "<jeton de maintenance — heartbeat/disconnect uniquement>",
  "sync_folder": "Wasate",
  "syncthing": {
    "folder_id": "wsp-xxxxxxxxxxxxxxxx",
    "server_device_id": "<identifiant Syncthing du serveur Wasate, à ajouter comme appareil connu dans VOTRE Syncthing>",
    "server_addresses": ["tcp://wasate.cloud:22000"]
  }
}

Erreurs : 403 invalid_or_expired_code, 400 code_required, 400 syncthing_device_id_required (format d'identifiant Syncthing invalide ou absent), 403 account_unavailable (compte suspendu/supprimé entre la création du code et l'enrôlement), 503 syncthing_unavailable.

POST /connect/heartbeat

Public. L'agent signale qu'il est en ligne, authentifié par possession de son agent_token (lookup direct de jeton, jamais de session cookie ni de mot de passe principal).

{ "password": "<agent_token reçu à l'enrôlement>" }

POST /connect/disconnect

Public, même authentification que le heartbeat. Auto-révocation : l'appareil se déconnecte lui-même par possession de son agent_token, sans session du propriétaire du compte.

{ "password": "<agent_token reçu à l'enrôlement>" }

GET /api/workspace/devices

Session requise. Liste les appareils de l'utilisateur (statut en ligne, OS, dernière activité).

POST /api/workspace/devices

Session requise. Crée un enrôlement et renvoie un code à usage unique (valable 1 h).

{ "name": "PC bureau", "os": "linux" }  →  { "code": "xxxxx", "expires_in": 3600 }

DELETE /api/workspace/devices/{id}

Session requise. Révoque un appareil : son mot de passe d'application est invalidé immédiatement.

Galeries, transferts, signatures, RGPD (mon compte)

Ces endpoints gèrent VOS ressources (session requise). Chaque appel n'agit que sur les ressources de l'utilisateur authentifié.

Galeries

GET /api/galleries  ·  POST

Liste vos galeries, ou en crée une nouvelle. Body POST :

{
  "title": "Mariage Julie & Tom",
  "description": "…",
  "source_path": "/Photos/Mariage",
  "theme": "minimal",
  "is_public": false
}

source_path est confiné à votre propre espace (aucun accès en dehors de votre compte).

GET /api/galleries/{id}  ·  PUT  ·  DELETE

Détail, mise à jour ou suppression d'une galerie.

POST /api/galleries/{id}/share  ·  DELETE /api/galleries/{id}/share/{tokenId}

Génère ou révoque un lien de partage public (/g/{token}, voir plus bas). Body POST : { "ttl": 604800, "password": null } (ttl en secondes).

{
  "token_id": 12,
  "token": "a3f9…",
  "expires_at": "2026-08-01T00:00:00+00:00",
  "url": "/galerie.html?t=a3f9…"
}

Script complet — créer et partager une galerie

import requests

BASE = "https://wasate.fr"
session = requests.Session()
session.post(f"{BASE}/app/login", data={
    "user": "mon-identifiant", "password": "mon-mot-de-passe", "requesttoken": "",
}).raise_for_status()
csrf_token = session.get(f"{BASE}/app/csrftoken").json()["token"]
headers = {"requesttoken": csrf_token}

# 1) Créer la galerie à partir d'un dossier déjà présent dans mon espace
r = session.post(f"{BASE}/api/galleries", headers=headers, json={
    "title": "Mariage Julie & Tom",
    "description": "Séance photo du 12 juillet",
    "source_path": "/Photos/Mariage-JulieTom",
    "theme": "minimal",
})
r.raise_for_status()
gallery = r.json()
print("Galerie créée, id =", gallery["id"])

# 2) Générer un lien de partage public, valable 7 jours, sans mot de passe
share = session.post(f"{BASE}/api/galleries/{gallery['id']}/share", headers=headers,
                      json={"ttl": 604800}).json()
print("Lien à envoyer au client :", BASE + share["url"])
const BASE = "https://wasate.fr";
// Depuis le frontend web (session + csrfToken déjà disponibles) :

const galleryRes = await fetch(`${BASE}/api/galleries`, {
  method: "POST", credentials: "include",
  headers: { "Content-Type": "application/json", "requesttoken": csrfToken },
  body: JSON.stringify({
    title: "Mariage Julie & Tom",
    description: "Séance photo du 12 juillet",
    source_path: "/Photos/Mariage-JulieTom",
    theme: "minimal",
  }),
});
const gallery = await galleryRes.json();
console.log("Galerie créée, id =", gallery.id);

const shareRes = await fetch(`${BASE}/api/galleries/${gallery.id}/share`, {
  method: "POST", credentials: "include",
  headers: { "Content-Type": "application/json", "requesttoken": csrfToken },
  body: JSON.stringify({ ttl: 604800 }),
});
const share = await shareRes.json();
console.log("Lien à envoyer au client :", BASE + share.url);

Transferts

GET /api/transfers  ·  POST

Liste vos transferts, ou en crée un nouveau. Body POST :

{
  "name": "Rushs tournage",
  "ttl": 604800,
  "password": null,
  "max_downloads": 0,
  "recipient_email": "client@example.com"
}

ttl en secondes (7 jours par défaut). Fichiers envoyés ensuite en chunks (POST /api/transfers/{id}/chunks) puis POST /api/transfers/{id}/finalize pour déclencher l'email au destinataire.

GET /api/transfers/{id}  ·  DELETE

Détail ou suppression d'un transfert.

Signatures électroniques

GET /api/signatures/requests  ·  POST

Liste vos demandes de signature, ou en crée une (multipart, champ pdf + title, mode, order_mode, recipients en JSON).

GET /api/signatures/requests/{id}  ·  DELETE

Détail ou annulation d'une demande.

GET /api/signatures/requests/{id}/pdf

Télécharge le PDF (original, ou signé une fois complété).

RGPD

POST /api/rgpd/export

Demande un export complet de vos données (traité en file d'attente).

GET /api/rgpd/export/{id}

Télécharge l'export une fois prêt.

GET /api/rgpd/consents  ·  PUT /api/rgpd/consents/{type}

Consentements RGPD (marketing, cookies, etc.) — lecture et mise à jour.

Corbeille

GET /api/me/trash

Liste les fichiers/dossiers récemment supprimés.

POST /api/me/trash/restore

Restaure un élément. Body : { "id": 1482 }.

DELETE /api/me/trash?id={id}

Supprime définitivement un élément de la corbeille (id requis — pas de vidage global en un appel).

Endpoints publics qui servent une galerie partagée via son token (lien /galerie.html?t={token}, 4 thèmes + lightbox). Galerie protégée par mot de passe → l'envoyer une fois sur /auth pour obtenir un grant à rejouer sur les autres appels.

GET /g/{token}

Métadonnées (titre, description, thème) ou { "status": "password_required" }.

POST /g/{token}/auth

Body password. Renvoie { "grant": "…" } (valable 2 h) pour les galeries protégées.

GET /g/{token}/items

Liste des médias (images/vidéos) de la galerie. Paramètre grant si protégée.

GET /g/{token}/file?name=…&size=thumb|full

Sert un média : aperçu borné (600 px miniature / 1600 px plein écran) ou flux vidéo. Strictement confiné au dossier source de la galerie.

GET /g/{token}/zip

Demande la préparation d'un ZIP complet (traité en file d'attente).

Compte (utilisateur connecté)

GET /api/me/notifications  ·  PUT

Préférences de notification email (consultation / téléchargement de galeries) + destinataires. Body PUT : notify_view, notify_download, recipients (CSV, max 5).

POST /api/me/upgrade-request

Demande d'augmentation de stockage (4 €/To). Body : requested_tb (1-100), message. Traitement manuel par l'équipe, aucun paiement en ligne.

API Distributeur

Réservée aux comptes distributeur : gestion programmatique de vos clients apportés (création, quota, statistiques), en plus de votre espace distributeur habituel.

Gérer vos clés API (session requise)

Depuis votre espace distributeur (section « Clés API »), ou directement :

GET /api/distributor/api-keys

Liste vos clés (préfixe tronqué seulement, jamais le secret) avec leur statut (active/expired/revoked), date de dernière utilisation.

POST /api/distributor/api-keys

Body :

{ "label": "Intégration CRM", "expires_in_days": 365, "test": false }

Réponse (201) : la clé en clair, affichée une seule fois — copiez-la immédiatement.

{
  "status": "created",
  "key": "wsd_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "prefix": "wsd_live_xxx",
  "id": 4,
  "warning": "Copiez cette clé maintenant — elle ne sera plus jamais affichée."
}

DELETE /api/distributor/api-keys/{id}

Révoque une clé immédiatement (irréversible).

Utiliser l'API (clé API requise, header Authorization: Bearer wsd_live_…)

Base : https://wasate.fr/api/v1/distributor. Chaque appel n'agit que sur vos propres clients (ceux créés via votre clé ou inscrits avec un de vos codes affiliés) — toute tentative sur un client qui ne vous appartient pas renvoie 403 not_your_client.

GET /api/v1/distributor/me

Votre fiche distributeur (nom, email, taux de commission, statut).

GET /api/v1/distributor/stats

Statistiques globales : nombre de clients, stockage cumulé utilisé, etc.

GET /api/v1/distributor/clients

Liste de vos clients.

POST /api/v1/distributor/clients

Crée un nouveau compte client, rattaché à vous.

Body :

{
  "uid": "identifiant-unique",
  "email": "client@example.com",
  "display_name": "Marie Dupont",
  "plan": "solo_pro",
  "quota_gb": 200,
  "send_email": true
}

plan : solo_free, solo_pro, solo_max, team_starter ou team_pro. quota_gb optionnel (par défaut, le quota du plan ; 0 = illimité). Si send_email est vrai (par défaut), le client reçoit ses identifiants par email et temp_password n'est pas renvoyé dans la réponse (sécurité) — sinon il est renvoyé une seule fois pour transmission manuelle.

GET /api/v1/distributor/clients/{uid}

Détail d'un de vos clients.

PUT /api/v1/distributor/clients/{uid}

Met à jour un de vos clients (email, nom affiché).

DELETE /api/v1/distributor/clients/{uid}

Sans paramètre : désactive le compte (réversible). Avec ?hard=1 : supprime définitivement le compte et ses fichiers — irréversible.

POST /api/v1/distributor/clients/{uid}/enable

Réactive un compte précédemment désactivé.

GET /api/v1/distributor/clients/{uid}/stats

Usage détaillé d'un client (stockage utilisé, etc.).

POST /api/v1/distributor/clients/{uid}/quota

Body : { "quota_gb": 500 } (0-10000, 0 = illimité).

Script complet — provisionner un client

import requests

BASE = "https://wasate.fr/api/v1/distributor"
HEADERS = {"Authorization": "Bearer wsd_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}

# 1) Créer le client
r = requests.post(f"{BASE}/clients", headers=HEADERS, json={
    "uid": "studio-dupont",
    "email": "contact@studio-dupont.fr",
    "display_name": "Studio Dupont",
    "plan": "solo_pro",
    "quota_gb": 300,
    "send_email": True,
})
r.raise_for_status()
client = r.json()
print("Client créé :", client["uid"], "— email envoyé :", client["email_sent"])

# 2) Vérifier ses statistiques d'usage
stats = requests.get(f"{BASE}/clients/{client['uid']}/stats", headers=HEADERS).json()
print(stats)

# 3) Ajuster son quota plus tard si besoin
requests.post(f"{BASE}/clients/{client['uid']}/quota", headers=HEADERS,
              json={"quota_gb": 500})
const BASE = "https://wasate.fr/api/v1/distributor";
const HEADERS = { "Authorization": "Bearer wsd_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" };

// 1) Créer le client
const createRes = await fetch(`${BASE}/clients`, {
  method: "POST",
  headers: { ...HEADERS, "Content-Type": "application/json" },
  body: JSON.stringify({
    uid: "studio-dupont",
    email: "contact@studio-dupont.fr",
    display_name: "Studio Dupont",
    plan: "solo_pro",
    quota_gb: 300,
    send_email: true,
  }),
});
const client = await createRes.json();
console.log("Client créé :", client.uid, "— email envoyé :", client.email_sent);

// 2) Statistiques d'usage
const stats = await (await fetch(`${BASE}/clients/${client.uid}/stats`, { headers: HEADERS })).json();
console.log(stats);

// 3) Ajuster le quota
await fetch(`${BASE}/clients/${client.uid}/quota`, {
  method: "POST",
  headers: { ...HEADERS, "Content-Type": "application/json" },
  body: JSON.stringify({ quota_gb: 500 }),
});

Connexion avec Google + intégrations

Sign-in OAuth/OIDC via Google, scopes basiques openid email profile (aucun accès aux fichiers, aucun audit). Bouton « Continuer avec Google » sur connexion et inscription. L'id_token est vérifié (signature RS256, claims) et email_verified est exigé avant création/activation du compte.

GET /app/apps/creatif_integrations/api/signin/google/start

Redirige vers Google. Au retour, le compte est créé (Free 50 Go, actif) si l'email est nouveau, puis la session Wasate est ouverte.

GET /api/integrations

Session requise. Liste les services d'import (Dropbox/OneDrive) avec leur statut connecté/configuré. Jetons chiffrés, jamais exposés. La publication sortante vers les réseaux sociaux a été retirée du produit.

Spec OpenAPI

Une spécification OpenAPI 3.1 complète est disponible : /docs/openapi.yaml (à venir).

SDKs

Aucun SDK officiel n'est encore distribué. L'API étant REST classique, tous les clients HTTP fonctionnent (curl, fetch, axios, requests, Guzzle, …).

Changelog