Download OpenAPI specification:
API REST pour la gestion des comptes SFTP et des fichiers MediaGrade.
L'API utilise trois patterns différents selon la nature de l'opération. Chaque endpoint déclare lequel s'applique. Le client doit en tenir compte pour ne pas se faire piéger par les limites d'API Gateway (timeout 30 s, payload request 10 MB, réponse Lambda 6 MB).
Appel HTTP classique, réponse 200 immédiate avec un JSON paginé. Aucune
contrainte particulière côté client.
Exemple :
GET /api/files/{accountId}?path=/folder&limit=1000
Le client demande au backend une URL signée S3, puis échange les bytes directement avec S3 (pas via API Gateway). Contourne la limite 10 MB et permet l'affichage de progression réelle côté navigateur.
POST /api/files/{accountId}/upload-url → { uploadUrl, s3Key }
PUT <uploadUrl> → bytes vers S3 directement
GET /api/files/{accountId}/download-url → { downloadUrl }
GET <downloadUrl> → bytes depuis S3 directement
Pour les téléchargements multi-fichiers, voir download-batch qui suit
le pattern async (un ZIP est construit en arrière-plan).
Le backend ne fait pas le travail dans le cycle HTTP : il écrit un job en
base, l'enqueue en SQS, et retourne immédiatement 202 Accepted avec un
jobId et un statusUrl. Le client poll statusUrl jusqu'à ce que le
statut soit terminal (completed, partial ou error).
Toutes les requêtes async doivent porter un header Idempotency-Key
(UUID v4 généré côté client par action utilisateur). Une retry avec la
même clé renvoie le même jobId — le serveur dédupe par
(account_id, idempotency_key). Cela permet au client de retry sans
risque de doublon.
DELETE /api/files/{accountId}/delete → 202 { jobId, statusUrl }
PUT /api/files/{accountId}/rename → 202 { jobId, statusUrl }
PUT /api/files/{accountId}/move → 202 { jobId, statusUrl }
GET {statusUrl} → 200 { status, progress, errors }
Le statut partial est utilisé quand l'opération a globalement abouti
mais qu'une partie des objets a échoué (collision S3, autorisation, etc.).
Dans ce cas, errors[] liste les { path, reason } non-traités — le
client peut proposer un retry ciblé.
Cadence de polling recommandée : 500 ms initial, backoff exponentiel plafonné à 5 s. Pas plus rapide — la Lambda worker met à jour la progression toutes les 50 items ou 1 s, donc des polls plus serrés ne rapportent rien.
Tous les endpoints (sauf /health) nécessitent une authentification via clé API. Deux méthodes sont supportées :
X-API-Key : X-API-Key: your-api-secret-keyAuthorization : Authorization: Bearer your-api-secret-keyLes limites de taux suivantes s'appliquent par clé API :
En cas de dépassement, une réponse 429 Too Many Requests est retournée avec les headers suivants :
X-RateLimit-Limit : Limite totaleX-RateLimit-Remaining : Requêtes restantesX-RateLimit-Reset : Timestamp de réinitialisationLes endpoints retournant des listes supportent la pagination via les paramètres de requête :
limit : Nombre d'éléments par page (défaut: 50, max: 1000)offset : Nombre d'éléments à ignorer (défaut: 0)La réponse inclut un objet pagination avec :
total : Nombre total d'élémentslimit : Limite utiliséeoffset : Offset utiliséreturned : Nombre d'éléments retournés dans cette pageLes paramètres de requête contenant des caractères spéciaux (accents, apostrophes, espaces) doivent être encodés en URL :
path=files/Capture d'écran.png → path=files%2FCapture%20d%27%C3%A9cran.pngImportant : macOS utilise NFD (Normalization Form Decomposed) par défaut, mais l'API normalise systématiquement en NFC pour garantir la cohérence avec Linux et Windows.
En cas d'erreur temporaire (codes 5xx, 429), il est recommandé d'implémenter une stratégie de retry avec backoff exponentiel :
Ne pas retry sur les codes 4xx (sauf 429) car ce sont des erreurs client.
async function retryRequest(fn, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
if (i === maxRetries - 1) throw error;
if ([429, 500, 502, 503, 504].includes(error.status)) {
await new Promise(resolve => setTimeout(resolve, Math.pow(2, i) * 1000));
continue;
}
throw error;
}
}
}
Retourne la liste de tous les comptes SFTP.
Peut être filtrée par une liste d'account IDs via le paramètre accountIds.
| accountIds | string Example: accountIds=123e4567-e89b-12d3-a456-426614174000,987fcdeb-51a2-4d6c-b543-678901234567 Liste d'account IDs séparés par des virgules pour filtrer les résultats |
{- "success": true,
- "data": [
- {
- "id": "550e8400-e29b-41d4-a716-446655440000",
- "accountId": "123e4567-e89b-12d3-a456-426614174000",
- "username": "mediagrade-testcompany-a1b2c3d4",
- "companyName": "Test Company",
- "isEnabled": true,
- "quotaBytes": 10737418240,
- "usedBytes": 1048576,
- "accountStatus": "active",
- "createdAt": "2025-09-24T10:00:00Z",
- "updatedAt": "2025-09-24T15:30:00Z"
}
]
}Crée un nouveau compte SFTP pour un compte MediaGrade.
Le client est responsable de la génération du username et du password.
| accountId required | string^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89... UUID du compte MediaGrade |
| username required | string >= 3 characters SFTP username choisi par le client. Convention: |
| password required | string >= 8 characters Mot de passe SFTP en clair. Sera bcrypt-hashé côté serveur. |
{- "accountId": "123e4567-e89b-12d3-a456-426614174000",
- "username": "mediagrade-masociete-a1b2c3d4",
- "password": "Xk9m$pR7#vB2nQ8z"
}{- "success": true,
- "data": {
- "id": "550e8400-e29b-41d4-a716-446655440000",
- "accountId": "123e4567-e89b-12d3-a456-426614174000",
- "username": "mediagrade-masociete-a1b2c3d4",
- "isEnabled": true
}
}Retourne les informations détaillées d'un compte SFTP spécifique
| accountId required | string^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89... Example: 123e4567-e89b-12d3-a456-426614174000 UUID du compte MediaGrade |
{- "success": true,
- "data": {
- "id": "550e8400-e29b-41d4-a716-446655440000",
- "accountId": "123e4567-e89b-12d3-a456-426614174000",
- "username": "mediagrade-masociete-a1b2c3d4",
- "isEnabled": true,
- "storage": {
- "quotaBytes": 10737418240,
- "usedBytes": 1048576,
- "availableBytes": 10736369664,
- "usagePercentage": 0.01
}, - "createdAt": "2025-09-24T10:00:00Z",
- "updatedAt": "2025-09-24T15:30:00Z"
}
}Supprime définitivement un compte SFTP et tous ses fichiers. ⚠️ Attention : Cette opération est irréversible.
| accountId required | string^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89... Example: 123e4567-e89b-12d3-a456-426614174000 UUID du compte MediaGrade |
{- "success": true,
- "message": "SFTP account deleted successfully"
}Change le mot de passe SFTP d'un compte. Le client doit fournir le nouveau mot de passe.
| accountId required | string^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89... Example: 123e4567-e89b-12d3-a456-426614174000 UUID du compte MediaGrade |
| password required | string >= 8 characters Nouveau mot de passe |
{- "password": "NouveauMotDePasse123!"
}{- "success": true,
- "message": "Password updated"
}Modifie la limite de stockage d'un compte.
| accountId required | string^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89... Example: 123e4567-e89b-12d3-a456-426614174000 UUID du compte MediaGrade |
| quotaBytes required | integer [ 1073741824 .. 1099511627776 ] Nouvelle limite en bytes |
{- "quotaBytes": 21474836480
}{- "success": true,
- "message": "Quota updated"
}Active ou désactive l'accès SFTP d'un compte
| accountId required | string^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89... Example: 123e4567-e89b-12d3-a456-426614174000 UUID du compte MediaGrade |
| enabled required | boolean État souhaité (true = activé, false = désactivé) |
{- "enabled": false
}{- "success": true,
- "message": "Account disabled"
}Retourne la liste des fichiers et répertoires d'un compte avec pagination. Supporte le tri par nom, taille, date de création/modification.
| accountId required | string^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89... Example: 123e4567-e89b-12d3-a456-426614174000 UUID du compte MediaGrade |
| path | string Default: "/" Example: path=/documents Chemin du répertoire à lister.
Doit être encodé en URL si contenant des caractères spéciaux.
Exemple encodé : |
| limit | integer [ 1 .. 1000 ] Default: 1000 Example: limit=50 Nombre maximum de fichiers à retourner |
| offset | integer >= 0 Default: 0 Décalage pour la pagination |
| sortBy | string Default: "name" Enum: "name" "size" "createdAt" "updatedAt" Example: sortBy=createdAt Champ de tri |
| sortOrder | string Default: "asc" Enum: "asc" "desc" Example: sortOrder=desc Sens du tri |
{- "success": true,
- "data": {
- "path": "/",
- "files": [
- {
- "id": "uuid-123",
- "name": "document.pdf",
- "path": "/document.pdf",
- "size": 1048576,
- "isDirectory": false,
- "mimeType": "application/pdf",
- "antivirusStatus": "clean",
- "createdAt": "2025-09-25T10:00:00Z",
- "updatedAt": "2025-09-25T10:00:00Z"
}
], - "storage": {
- "quotaBytes": 10737418240,
- "usedBytes": 1048576,
- "availableBytes": 10736369664,
- "usagePercentage": 0.01
}, - "pagination": {
- "total": 150,
- "offset": 0,
- "limit": 50,
- "returned": 50,
- "sortBy": "name",
- "sortOrder": "asc"
}
}
}Pattern presigned URL. Le client demande une URL S3 signée puis
fait un PUT direct vers cette URL avec les bytes du fichier — aucun
byte ne transite par l'API Gateway, ce qui contourne la limite 10 MB.
const { data } = await fetch('/api/files/{accountId}/upload-url', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-Api-Key': apiKey },
body: JSON.stringify({ fileName: 'photo.jpg', path: '/folder', contentType: 'image/jpeg' })
}).then(r => r.json());
await fetch(data.uploadUrl, { method: 'PUT', body: file, headers: { 'Content-Type': data.contentType } });
L'URL signée expire après 1 h. Une fois l'upload réussi, la Lambda file-sync est déclenchée par S3 et met à jour le listing du compte.
| accountId required | string^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89... Example: 123e4567-e89b-12d3-a456-426614174000 UUID du compte MediaGrade |
| fileName required | string |
| path | string Chemin du dossier de destination (défaut: |
| contentType | string MIME (défaut: déduit de l'extension) |
{- "fileName": "photo.jpg",
- "path": "/documents",
- "contentType": "image/jpeg"
}{- "success": true,
- "data": {
- "s3Key": "string",
- "contentType": "string",
- "expiresIn": 3600
}
}Pattern presigned URL. Retourne une URL S3 signée vers laquelle
le client fait un GET pour récupérer les bytes directement, sans
passer par l'API Gateway.
Pour un téléchargement multi-fichiers, voir download-batch qui
construit un ZIP en arrière-plan.
| accountId required | string^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89... Example: 123e4567-e89b-12d3-a456-426614174000 UUID du compte MediaGrade |
| path required | string Example: path=/documents/file.pdf |
{- "success": true,
}Retourne un redirect HTTP 302 vers une URL S3 signée. Le client suit la redirection et télécharge directement depuis S3 ; aucun byte ne transite par l'API. Pas de support des requêtes Range côté API (S3 gère les Range sur l'URL signée).
Pour récupérer l'URL signée sans suivre la redirection, utiliser plutôt
GET /api/files/{accountId}/download-url.
| accountId required | string^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89... Example: 123e4567-e89b-12d3-a456-426614174000 UUID du compte MediaGrade |
| path required | string Example: path=/documents/file.pdf Chemin du fichier à télécharger.
Doit être encodé en URL si contenant des caractères spéciaux.
Exemple encodé : |
{- "error": "Bad Request",
- "message": "Invalid request parameters"
}Pattern async job + polling propre au download-batch — antérieur
au pattern unifié des delete/rename/move et donc légèrement
différent : le statut se poll sur
GET /api/files/{accountId}/download-batch/status/{jobId} (et non
sur /api/jobs/{jobId}/status). Le résultat final est une URL
signée S3 vers l'archive ZIP générée par un service Fargate.
| accountId required | string^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89... Example: 123e4567-e89b-12d3-a456-426614174000 UUID du compte MediaGrade |
| paths required | Array of strings |
| archiveName | string Default: "download.zip" Nom de l'archive (alias: |
| name | string Alias historique de |
{- "paths": [
- "/folder1",
- "/file.pdf"
], - "archiveName": "export.zip",
- "name": "string"
}{- "success": true,
- "data": {
- "jobId": "string",
- "archiveName": "string",
- "statusUrl": "string",
- "message": "string"
}
}| accountId required | string^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89... Example: 123e4567-e89b-12d3-a456-426614174000 UUID du compte MediaGrade |
| jobId required | string |
{- "success": true,
- "data": {
- "status": "processing",
- "downloadUrl": "string",
- "size": 0,
- "expiresIn": 0,
- "code": "string",
- "message": "string",
- "timestamp": "2019-08-24T14:15:22Z"
}
}Crée un nouveau répertoire
| accountId required | string^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89... Example: 123e4567-e89b-12d3-a456-426614174000 UUID du compte MediaGrade |
| path required | string Chemin du répertoire à créer |
{- "path": "/nouveau-dossier"
}{- "success": true,
- "message": "Directory created"
}Pattern async job + polling. Retourne immédiatement 202 Accepted
avec un jobId (préfixe dl-) et un statusUrl. Le client poll
statusUrl jusqu'à un statut terminal.
Une retry avec le même Idempotency-Key renvoie le même jobId.
Sur status partial, errors[] liste les objets non-supprimés (avec
leur motif S3) — le client peut proposer un retry ciblé.
| accountId required | string^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89... Example: 123e4567-e89b-12d3-a456-426614174000 UUID du compte MediaGrade |
| Idempotency-Key required | string [ 1 .. 128 ] characters Example: 8a4f5b2c-9d1e-4f3a-b7c8-1e2d3f4a5b6c Clé d'idempotence générée par le client (UUID v4 recommandé) pour
une action utilisateur donnée. Une retry avec la même clé renvoie
le même |
| paths required | Array of strings Chemins à supprimer (fichiers ou dossiers) |
{- "paths": [
- "/dossier-a-supprimer",
- "/fichier.txt"
]
}{- "success": true,
- "data": {
- "jobId": "rn-7c1f5a3b8e2d4f6a9b0c1d2e3f4a5b6c",
- "statusUrl": "/api/jobs/rn-7c1f5a3b8e2d4f6a9b0c1d2e3f4a5b6c/status"
}
}Pattern async job + polling. Pour un fichier seul, l'opération est rapide (~1 s perçu). Pour un dossier contenant beaucoup de fichiers, le worker copie chaque objet S3 puis supprime — peut prendre plusieurs secondes selon le volume.
Une retry avec le même Idempotency-Key renvoie le même jobId
(préfixe rn-).
| accountId required | string^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89... Example: 123e4567-e89b-12d3-a456-426614174000 UUID du compte MediaGrade |
| Idempotency-Key required | string [ 1 .. 128 ] characters Example: 8a4f5b2c-9d1e-4f3a-b7c8-1e2d3f4a5b6c Clé d'idempotence générée par le client (UUID v4 recommandé) pour
une action utilisateur donnée. Une retry avec la même clé renvoie
le même |
| oldPath required | string |
| newPath required | string |
{- "oldPath": "/ancien-nom.txt",
- "newPath": "/nouveau-nom.txt"
}{- "success": true,
- "data": {
- "jobId": "rn-7c1f5a3b8e2d4f6a9b0c1d2e3f4a5b6c",
- "statusUrl": "/api/jobs/rn-7c1f5a3b8e2d4f6a9b0c1d2e3f4a5b6c/status"
}
}Pattern async job + polling. Chaque source peut être un fichier
ou un dossier; le worker copie sous destination/basename(source)
puis supprime l'original. Préfixe du jobId : mv-.
| accountId required | string^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89... Example: 123e4567-e89b-12d3-a456-426614174000 UUID du compte MediaGrade |
| Idempotency-Key required | string [ 1 .. 128 ] characters Example: 8a4f5b2c-9d1e-4f3a-b7c8-1e2d3f4a5b6c Clé d'idempotence générée par le client (UUID v4 recommandé) pour
une action utilisateur donnée. Une retry avec la même clé renvoie
le même |
| sourcePaths required | Array of strings Chemins à déplacer |
| destination required | string Dossier de destination |
{- "sourcePaths": [
- "/fichier1.txt",
- "/dossier1"
], - "destination": "/destination"
}{- "success": true,
- "data": {
- "jobId": "rn-7c1f5a3b8e2d4f6a9b0c1d2e3f4a5b6c",
- "statusUrl": "/api/jobs/rn-7c1f5a3b8e2d4f6a9b0c1d2e3f4a5b6c/status"
}
}Pattern async job + polling. Réconcilie la table file_storage
avec le contenu réel S3 dans le scope demandé : insert/update des
fichiers présents en S3, suppression des rows orphelines (fichiers
ou dossiers qui n'existent plus en S3), borné au sous-arbre
spécifié.
path → resync complet du compte. Le worker recalcule
used_bytes à la fin.path → resync limité au sous-arbre. used_bytes
n'est pas recalculé (les events S3 + file-sync le maintiennent
à jour pendant le fonctionnement normal).Préfixe du jobId : rf-. Le worker utilise des opérations bulk
SQL (jsonb_to_recordset + DELETE … NOT EXISTS) pour rester
rapide quel que soit le volume — un compte de 50 K fichiers passe
en quelques secondes côté worker.
Cas d'usage typique côté browser : appeler avec
path: "<dossier courant>" à chaque clic refresh, pour ne resync
que la vue affichée (~10 fois plus rapide que le refresh global).
| accountId required | string^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89... Example: 123e4567-e89b-12d3-a456-426614174000 UUID du compte MediaGrade |
| Idempotency-Key required | string [ 1 .. 128 ] characters Example: 8a4f5b2c-9d1e-4f3a-b7c8-1e2d3f4a5b6c Clé d'idempotence générée par le client (UUID v4 recommandé) pour
une action utilisateur donnée. Une retry avec la même clé renvoie
le même |
| path | string Sous-arbre à resync (chemin utilisateur, ex.
|
| recursive | boolean Default: true
|
{- "path": "/Mirakulous",
- "recursive": true
}{- "success": true,
- "data": {
- "jobId": "rn-7c1f5a3b8e2d4f6a9b0c1d2e3f4a5b6c",
- "statusUrl": "/api/jobs/rn-7c1f5a3b8e2d4f6a9b0c1d2e3f4a5b6c/status"
}
}Pattern sync direct. Recherche par sous-chaîne (ILIKE) sur
file_name, optionnellement scopée à un dossier. Le résultat est
paginé via limit / offset.
Le terme de recherche q doit faire au moins 2 caractères.
| accountId required | string^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89... Example: 123e4567-e89b-12d3-a456-426614174000 UUID du compte MediaGrade |
| q required | string >= 2 characters Example: q=document Sous-chaîne à chercher dans le nom (ILIKE), 2 caractères minimum |
| path | string Example: path=/documents Restreindre à un sous-arbre (chemin utilisateur) |
| limit | integer [ 1 .. 500 ] Default: 100 |
| offset | integer >= 0 Default: 0 |
{- "success": true,
- "data": {
- "query": "string",
- "files": [
- {
- "id": "string",
- "name": "string",
- "path": "string",
- "size": 0,
- "isDirectory": true,
- "mimeType": "string",
- "antivirusStatus": "string",
- "createdAt": "2019-08-24T14:15:22Z",
- "updatedAt": "2019-08-24T14:15:22Z"
}
], - "pagination": {
- "total": 0,
- "offset": 0,
- "limit": 0,
- "returned": 0
}
}
}Retourne l'état courant d'un job. Le préfixe du jobId (dl-,
rn-, mv-, rf-) identifie la table sous-jacente — le client
n'a pas à s'en préoccuper.
Pour un job refresh, la réponse inclut en plus data.scopePath
(chemin du sous-arbre resync ou null si compte complet),
data.syncedCount (fichiers upserted) et data.removedCount
(orphelins supprimés).
Cadence de polling recommandée : 500 ms initial, backoff exponentiel plafonné à 5 s. La progression est mise à jour côté worker toutes les 50 items ou 1 s, donc poller plus vite n'apporte rien.
const url = `/api/jobs/${jobId}/status`;
let delay = 500;
while (true) {
const r = await fetch(url, { headers: { 'X-Api-Key': apiKey } });
const { data } = await r.json();
updateProgressBar(data.progress.percent);
if (['completed', 'partial', 'error'].includes(data.status)) return data;
await new Promise(r => setTimeout(r, delay));
delay = Math.min(delay * 2, 5000);
}
| jobId required | string^(dl|rn|mv|rf)-[0-9a-f]{32}$ Example: rn-7c1f5a3b8e2d4f6a9b0c1d2e3f4a5b6c |
{- "success": true,
- "data": {
- "jobId": "rn-7c1f5a3b8e2d4f6a9b0c1d2e3f4a5b6c",
- "type": "rename",
- "accountId": "3d07c219-0a88-45be-9cfc-91e9d095a1e9",
- "status": "pending",
- "progress": {
- "done": 234,
- "total": 1000,
- "percent": 23.4
}, - "errors": [
- {
- "path": "user1/files/folder/file.jpg",
- "reason": "InternalError: We encountered an internal error"
}
], - "errorCode": "string",
- "errorMessage": "string",
- "createdAt": "2019-08-24T14:15:22Z",
- "startedAt": "2019-08-24T14:15:22Z",
- "finishedAt": "2019-08-24T14:15:22Z",
- "scopePath": "/Mirakulous",
- "syncedCount": 142,
- "removedCount": 3
}
}Retourne les statistiques globales du serveur SFTP
{- "success": true,
- "data": {
- "summary": {
- "totalAccounts": 187,
- "totalStorageGB": 138.95,
- "enabledSftpAccounts": 187
}, - "accounts": [
- {
- "accountId": "123e4567-e89b-12d3-a456-426614174000",
- "accountName": "Ma Société",
- "sftpEnabled": true,
- "totalStorageGB": 1.2,
- "quotaGB": 30,
- "usagePercentage": 4
}
]
}
}Retourne le nom du service, sa version, l'environnement et l'architecture courante.
{- "success": true,
- "data": {
- "name": "mediagrade-sftp-api",
- "version": "1.0.0",
- "environment": "production",
- "architecture": "AWS Transfer Family + Aurora + S3",
- "uptime": 142.5
}
}