Image FlowDocumentation publique

Documentation API

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.

Extraits

Authentifier vos appels
export IF_KEY="votre_cle_api"
# chaque appel d'écriture porte l'en-tête :
#   Authorization: Bearer $IF_KEY
Créer un 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"}'
Déposer / remplacer un visuel
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"
Lire un visuel (public, sans clé)
curl https://v2.image-flow.fr/api/read/acme-corp/hero-accueil
Compresser une image en WebP (+ % de compression)
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

Référence complète

# 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.