DAM headless multi-clients. Base : https://v2.image-flow.fr. Les routes de lecture /api/read/… sont publiques ; l’écriture demande une clé API admin.
export IF_KEY="votre_cle_api" # chaque appel d'écriture porte l'en-tête : # Authorization: Bearer $IF_KEY
curl -X POST https://v2.image-flow.fr/api/clients \
-H "Authorization: Bearer $IF_KEY" \
-H "Content-Type: application/json" \
-d '{"identifier":"acme-corp","name":"ACME Corp"}'curl -X POST https://v2.image-flow.fr/api/clients/$CLIENT_ID/media \ -H "Authorization: Bearer $IF_KEY" \ -F "identifier=hero-accueil" \ -F "type=SINGLE" \ -F "context=Bannière d'accueil" \ -F "file=@image.jpg"
curl -X POST https://v2.image-flow.fr/api/imports/{client}/{espace} \
-H "Authorization: Bearer $IF_IMPORT_KEY" \
-F "file=@/chemin/vers/cv.pdf"
# la clé d'un espace ne fonctionne sur aucune autre routecurl https://v2.image-flow.fr/api/read/acme-corp/hero-accueil
curl -X POST "https://v2.image-flow.fr/api/compress?quality=80" \ -H "Authorization: Bearer $IF_KEY" \ -H "Content-Type: image/jpeg" \ --data-binary @photo.jpg \ -D - -o compressed.webp # le % est dans l'en-tête X-Compression-Ratio
# Image Flow — Documentation API
Base URL : https://v2.image-flow.fr
Image Flow est un DAM headless multi-clients. Un admin crée des clients ; chaque
média est rangé dans un « emplacement » (slot) identifié et contextualisé ; les
sites clients récupèrent les médias via une API de lecture publique.
================================================================
AUTHENTIFICATION
================================================================
Les routes d'écriture exigent une clé API admin, en en-tête :
Authorization: Bearer if_live_xxxxxxxxxxxxxxxxxxxxxxxx
La clé est générée à la création d'un admin (ou via la rotation) et n'est
affichée qu'une seule fois. Les routes de lecture (/api/read/...) sont PUBLIQUES
et ne demandent aucune clé.
Codes de réponse usuels : 200 OK · 201 créé · 400 requête invalide ·
401 non autorisé · 404 introuvable · 409 conflit (identifiant déjà utilisé).
================================================================
1. CLIENTS
================================================================
# Créer un client
POST /api/clients
Body (JSON) : { "identifier": "acme-corp", "name": "ACME Corp" }
- identifier : slug unique, renommable (l'ancien reste résolvable via alias).
Réponse 201 : l'objet client.
curl -X POST https://v2.image-flow.fr/api/clients \
-H "Authorization: Bearer $IF_KEY" \
-H "Content-Type: application/json" \
-d '{"identifier":"acme-corp","name":"ACME Corp"}'
# Lister les clients
GET /api/clients
# Détail d'un client (+ emplacements + médias)
GET /api/clients/{id}
# Modifier un client
PATCH /api/clients/{id}
Body (JSON), champs optionnels :
{ "identifier": "nouveau-slug", // renommage non destructif (alias conservé)
"name": "Nouveau nom",
"portalLogin": "login-client", // accès portail client
"portalPassword": "motdepasse" // défini/réinitialisé ici
}
# Supprimer un client (et TOUS ses médias)
DELETE /api/clients/{id}
================================================================
2. MÉDIAS — UPLOAD CONTEXTUALISÉ (upsert)
================================================================
POST /api/clients/{id}/media (multipart/form-data)
Champs OBLIGATOIRES :
identifier : identifiant de l'emplacement, unique par client (ex. "hero-accueil")
type : SINGLE (un média) ou LIST (plusieurs médias ordonnés)
context : description de l'emplacement (où s'affiche le média)
file : le(s) fichier(s) image ou vidéo
Champs optionnels :
format : webp | avif | jpeg | png | original (défaut : webp)
quality : 10 à 100 (défaut : 80)
Comportement :
- Si l'emplacement n'existe pas, il est créé ; sinon son contexte est mis à jour.
- SINGLE : le nouveau média REMPLACE l'actuel (ancien fichier supprimé).
- LIST : le(s) fichier(s) sont AJOUTÉS en fin de liste.
- Images : compression Sharp (+ miniature). Vidéos : compression FFmpeg (MP4).
curl -X POST https://v2.image-flow.fr/api/clients/$CLIENT_ID/media \
-H "Authorization: Bearer $IF_KEY" \
-F "identifier=hero-accueil" \
-F "type=SINGLE" \
-F "context=Bannière de la page d'accueil" \
-F "format=webp" \
-F "file=@/chemin/vers/image.jpg"
# Lister les emplacements d'un client (+ médias)
GET /api/clients/{id}/slots
# Modifier un emplacement
PATCH /api/clients/{id}/slots/{slotId}
Body (JSON) : { "context": "...", "identifier": "..." } // champs optionnels
# Supprimer un emplacement (et ses médias)
DELETE /api/clients/{id}/slots/{slotId}
# Retirer un média (ex. un élément d'une LIST)
DELETE /api/clients/{id}/media/{mediaId}
================================================================
3. LECTURE PUBLIQUE (consommation par les sites clients)
================================================================
GET /api/read/{clientSlug}/{slotIdentifier} (aucune authentification)
- clientSlug accepte l'identifiant courant OU un ancien (résolu via alias).
- Chaque média expose 2 versions : "url" = version COMPRESSÉE (WebP), "originalUrl" =
fichier ORIGINAL non compressé (avec "originalWidth"/"originalHeight"). "url" par défaut.
- SINGLE → { "type": "SINGLE", "context", "url", "thumbnailUrl", "width", "height", "format", "duration",
"originalUrl", "originalWidth", "originalHeight" }
- LIST → { "type": "LIST", "context", "items": [ { "url", "thumbnailUrl", "width", "height", "format",
"duration", "originalUrl", "originalWidth", "originalHeight" }, ... ] }
- 404 si le client ou l'emplacement est inconnu.
curl https://v2.image-flow.fr/api/read/acme-corp/hero-accueil
Exemple côté site :
const r = await fetch("https://v2.image-flow.fr/api/read/acme-corp/hero-accueil");
const media = await r.json();
// <img src={media.url} width={media.width} height={media.height} />
Les fichiers médias sont aussi servis directement en CDN public sous /media/...
(cache long, immutable).
================================================================
4. ADMINISTRATEURS
================================================================
# Lister les admins
GET /api/admins
# Créer un admin (renvoie sa clé API UNE SEULE FOIS)
POST /api/admins
Body (JSON) : { "name": "Jean", "email": "jean@agence.fr", "password": "..." }
Réponse 201 : { "id", "name", "email", "apiKeyPrefix", "apiKey" } // apiKey à copier maintenant
# Régénérer la clé API d'un admin (renvoie la nouvelle clé une fois)
POST /api/admins/{id}/rotate-key
# Supprimer un admin
DELETE /api/admins/{id} // impossible sur soi-même ou le dernier admin
================================================================
5. COMPRESSION À LA VOLÉE (sans stockage)
================================================================
POST /api/compress
- Corps : les OCTETS BRUTS de l'image (Content-Type: image/...). Le corps est
streamé sur disque, donc les très gros fichiers passent sans saturer la RAM.
- Query (degré de compression, au choix de l'appelant, optionnel) :
quality 10 à 100 (+ élevé = + de qualité / - compressé) [prioritaire]
compression 0 à 100 (+ élevé = + compressé) [plus parlant]
(sans rien : défaut quality 80)
maxDimension cap en px (défaut 16383, limite du WebP)
response=json renvoie seulement les stats (pas le binaire)
- Réponse (défaut) : l'image WebP compressée, avec en en-têtes :
X-Original-Size, X-Compressed-Size, X-Compression-Ratio (%), X-Width, X-Height, X-Resized
- Les images dépassant la limite WebP sont redimensionnées (fit inside).
# récupère le WebP + le ratio dans les en-têtes
curl -X POST "https://v2.image-flow.fr/api/compress?quality=80" \
-H "Authorization: Bearer $IF_KEY" \
-H "Content-Type: image/jpeg" \
--data-binary @photo.jpg \
-D - -o compressed.webp
# juste le pourcentage de compression (JSON)
curl -X POST "https://v2.image-flow.fr/api/compress?response=json" \
-H "Authorization: Bearer $IF_KEY" \
-H "Content-Type: image/jpeg" \
--data-binary @photo.jpg
================================================================
6. BONNES PRATIQUES — UN ESPACE CLIENT BIEN RANGÉ
================================================================
Principe : un emplacement (slot) = un usage sur le site, PAS un dossier
de fichiers. Le nom (identifier) dit OÙ le média s'affiche, le contexte
le décrit en français. Un non-technicien doit pouvoir remplacer un
visuel sans deviner lequel.
Les 3 règles
------------
1. Nommer d'après la page et la section, pas d'après le fichier.
accueil-savoir-faire-burger ✅ curated / img_04.jpg ❌
2. SINGLE pour un visuel unique, LIST pour une vraie galerie ordonnée.
Galerie Instagram, photos cyclées → LIST.
Logo, hero, image OG → SINGLE.
3. Un contexte explicite, lisible par le client :
« Accueil — galerie "Le Retro sur Instagram" (6 visuels) »
Exemple — retro-pizza-burger (59 emplacements, 72 médias) :
LIST accueil-galerie-instagram 6 méd. « Accueil — galerie Instagram »
LIST accueil-hero-video 2 méd. « Accueil — vidéo hero (desktop + mobile) »
LIST carte-placeholder-burger 6 méd. « Carte — photos cyclées sans photo dédiée »
SINGLE accueil-hero-poster « Accueil — image d'attente de la vidéo »
SINGLE accueil-savoir-faire-pizza « Accueil — section Notre savoir-faire »
SINGLE histoire-fondateurs « Page Histoire — portrait des fondateurs »
SINGLE partage-og « Image de partage Open Graph »
SINGLE pizza-margherita « Carte — Pizza Margherita (détourée) »
SINGLE logo-burger / texture-ardoise / …
Granularité : la carte a un emplacement PAR PLAT (pizza-margherita,
burger-cheesy, tacos-classique). C'est le bon niveau — le restaurateur
change la photo d'un plat, pas celle d'un « dossier ».
================================================================
ESPACES D'IMPORT
================================================================
Pour les sites clients qui doivent RECEVOIR des fichiers de leurs visiteurs
(CV, justificatif, pièce jointe de devis).
À NE PAS CONFONDRE avec les emplacements ci-dessus : un emplacement porte un
visuel permanent du site, à URL stable. Un fichier d'import est ÉPHÉMÈRE — il
est supprimé, fichier et lien, dès que sa durée de vie est écoulée. L'URL
répond alors 404.
Authentification : chaque espace a SA clé, générée par l'admin. Elle ne
fonctionne sur AUCUNE autre route — ni les clients, ni les emplacements, ni
les médias. Une clé API classique, même « manage », ne fonctionne pas ici.
Authorization: Bearer if_live_xxxxxxxxxxxxxxxxxxxxxxxx
L'admin fixe par espace : les types autorisés, la durée de vie, la taille max
par fichier, le quota de stockage, et si la clé a le droit de lister et de
supprimer. Le dépôt, lui, est toujours autorisé.
# Déposer un ou plusieurs fichiers (toujours autorisé)
POST /api/imports/{clientIdentifier}/{spaceIdentifier}
Body : multipart/form-data, un ou plusieurs champs « file ».
Le lot est ATOMIQUE : si un fichier est refusé, aucun n'est enregistré.
curl -X POST https://v2.image-flow.fr/api/imports/acme-corp/candidatures \
-H "Authorization: Bearer $IF_IMPORT_KEY" \
-F "file=@cv-dupont.pdf"
Réponse 201 :
{ "count": 1,
"files": [{ "id": "…", "url": "/media/imports/…/….pdf",
"originalName": "cv-dupont.pdf", "size": 184320,
"mimeType": "application/pdf",
"uploadedAt": "…", "expiresAt": "…" }] }
« url » est RELATIVE : préfixez-la par https://v2.image-flow.fr pour obtenir le lien
complet. Conservez « url » et « expiresAt » de votre côté : passé l'échéance,
le fichier n'existe plus.
# Lister le contenu de l'espace (si l'espace l'autorise)
GET /api/imports/{clientIdentifier}/{spaceIdentifier}
Rend les réglages de l'espace et ses fichiers ENCORE VIVANTS.
curl https://v2.image-flow.fr/api/imports/acme-corp/candidatures \
-H "Authorization: Bearer $IF_IMPORT_KEY"
# Supprimer un fichier avant son échéance (si l'espace l'autorise)
DELETE /api/imports/{clientIdentifier}/{spaceIdentifier}/{fileId}
Réponse 204.
Codes d'erreur propres aux espaces d'import :
401 clé absente ou invalide
403 clé d'un autre espace, ou action désactivée sur cet espace
404 client, espace ou fichier introuvable
413 fichier plus gros que la taille max de l'espace
415 extension non autorisée par la matrice de l'espace
507 quota de stockage de l'espace atteint — rien n'a été écrit
Chaque erreur porte un message en clair dans « error ».
Deux limites à connaître :
- Le fichier est stocké BRUT : aucune compression, aucune miniature. Il
ressort exactement tel qu'il est entré.
- Son URL est PUBLIQUE (non devinable, mais publique). Qui obtient le lien
accède au fichier tant qu'il vit. C'est la durée de vie qui borne la
fenêtre d'exposition.
================================================================
NOTES
================================================================
- Tous les admins voient et gèrent tous les clients (pool partagé).
- Le portail client (/portal) permet au client de remplacer le visuel d'un
emplacement existant — il ne crée/supprime rien.
- Tailles d'upload élevées supportées (vidéos) ; le traitement peut prendre du temps.