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 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 ».
================================================================
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.