API HTTP
Endpoints HTTP publics, en lecture seule, exposant vos produits, leur configuration de widget et leurs avis audio (URL audio, notes, transcriptions, métadonnées de l'auteur). Il s'agit des endpoints utilisés par le widget Ekoo lui-même, mis à disposition pour tout scénario d'intégration en lecture : rendu côté serveur, application mobile native, génération statique, outillage interne, synchronisation de données.
L'authentification n'est pas requise. Les requêtes et les réponses sont au format JSON. Les réponses sont cachables au niveau de l'edge Ekoo comme côté client.
Cas d'usage courants
- Afficher les informations produit, notes ou transcriptions dans le HTML avant l'hydratation du widget — pour améliorer l'indexation SEO, l'accessibilité et le first contentful paint.
- Afficher les avis audio dans une application mobile native, à côté de votre catalogue produit.
- Générer des pages au build avec un générateur de site statique (Next.js ISR, Astro, Gatsby, Hugo).
- Synchroniser les métadonnées produit et avis dans votre data warehouse ou votre index de recherche.
Endpoints
| Méthode | Chemin | Retourne |
|---|---|---|
| GET | /ws/websites/{websiteId}/products/{productRef} | Un produit avec ses avis et la config widget. |
| GET | /ws/websites/{websiteId}/products | Une liste paginée de produits. |
URL de base
1https://app.ekoo.coTous les endpoints ci-dessous sont relatifs à cette URL de base. Les réponses sont au format JSON (Content-Type: application/json).
Authentification
Les requêtes ne sont pas authentifiées. Le paramètre websiteId présent dans l'URL identifie le tenant ; seules les ressources qui lui appartiennent sont retournées. Le websiteId est un identifiant public, déjà présent dans le snippet de widget publié sur votre site.
Votre websiteId est disponible dans le backoffice Ekoo sous Paramètres → Site, ou via l'attribut data-ekoo de toute page où le widget est installé.
Récupérer un produit
Retourne un produit avec sa configuration de widget et ses avis audio actifs.
1GET /ws/websites/{websiteId}/products/{productRef}Paramètres de chemin
| Nom | Type | Description |
|---|---|---|
websiteId | string (UUID) | Identifiant de votre site Ekoo (même valeur que data-ekoo). |
productRef | string | Référence produit telle que configurée dans le backoffice. Sensible à la casse. |
Paramètres de requête
| Nom | Type | Requis | Description |
|---|---|---|---|
locale | string | Optionnel | Code de locale BCP-47 (ex. "fr", "en"). Filtre les avis sur la locale demandée ; renvoie tous les avis si aucune correspondance. |
type | string | Optionnel | Type de widget. Vaut "standalone" par défaut. |
variant | string | Optionnel | Référence stable d'une configuration widget. Le produit et ses audios restent résolus par productRef ; seul l'habillage change. Compat legacy: si aucune ref ne correspond, le backend essaie encore un match par nom. |
Utilisez variant pour réutiliser le même produit avec plusieurs habillages de widget sans dupliquer ses audios ni ses contributeurs. Le productRef continue de porter toute la donnée métier; variant ne sélectionne que la configuration visuelle.
Exemples de code
1curl -s "https://app.ekoo.co/ws/websites/{websiteId}/products/{productRef}?locale=fr&variant=homepage"1// Node.js 18+ / Bun / Deno — fetch global2const websiteId = process.env.EKOO_WEBSITE_ID!3const productRef = 'SKU-1234'45const res = await fetch(6 `https://app.ekoo.co/ws/websites/${websiteId}/products/${productRef}?locale=fr&variant=homepage`,7 { headers: { Accept: 'application/json' } }8)910if (!res.ok) throw new Error(`Ekoo API ${res.status}`)11const product = await res.json()1# Python 3.8+ — requests2import os, requests34website_id = os.environ["EKOO_WEBSITE_ID"]5product_ref = "SKU-1234"67res = requests.get(8 f"https://app.ekoo.co/ws/websites/{website_id}/products/{product_ref}",9 params={"locale": "fr", "variant": "homepage"},10 timeout=5,11)12res.raise_for_status()13product = res.json()Exemple de réponse
1{2 "id": "9b0c2f3e-8e4d-4d9b-9c8a-0f3a6b7e1d22",3 "external_id": "SKU-1234",4 "name": "Eau de parfum 50ml",5 "audio_url": "https://cdn.ekoo.co/audios/...",6 "website": {7 "id": "f7c1...",8 "name": "Acme Cosmetics"9 },10 "config": { /* configuration du widget */ },11 "experiment": { /* expérimentation A/B éventuelle */ },12 "reviews": [13 {14 "id": "5d2a...",15 "type": "review",16 "label": "Marie, 34 ans",17 "rating": 5,18 "audio_url": "https://cdn.ekoo.co/audios/abc.mp3",19 "is_active": true,20 "is_default": true,21 "transcript": "J'ai acheté ce parfum pour ma femme...",22 "user": {23 "id": "u_8f...",24 "firstname": "Marie",25 "lastname": "D.",26 "image_url": "https://cdn.ekoo.co/avatars/marie.jpg"27 }28 }29 ]30}Champs de la réponse
| Champ | Type | Description |
|---|---|---|
id | string (UUID) | Identifiant interne du produit. |
external_id | string | Référence produit (identique à productRef). |
name | string | Nom du produit configuré dans le backoffice. |
audio_url | string | URL audio par défaut (champ de commodité ; généralement égal à reviews[0].audio_url). |
website | object | Site propriétaire (id, name). |
config | object | Configuration du widget résolue (thème, textes CTA, animation, etc.). |
experiment | object | undefined | Présent uniquement si une expérimentation A/B est en cours sur ce produit. |
reviews[] | array | Avis et témoignages audio actifs rattachés au produit. |
reviews[].type | "review" | "testimony" | Indique si l'audio est un avis produit ou un témoignage. |
reviews[].rating | number (0–5) | Note en étoiles, le cas échéant. |
reviews[].audio_url | string | URL du fichier audio (mp3). |
reviews[].transcript | string | Transcription textuelle de l'audio. |
reviews[].is_default | boolean | True pour l'avis affiché par défaut par le widget. |
reviews[].is_active | boolean | False pour les avis désactivés ou supprimés en soft-delete. |
reviews[].user | object | Métadonnées de l'auteur (firstname, lastname, image_url, description). |
Types TypeScript
Ajoutez les déclarations suivantes à votre projet pour un typage complet des réponses. Elles couvrent à la fois le endpoint produit unique et le endpoint liste.
1// À copier dans votre projet — typage complet des réponses23export type EkooReviewType = 'review' | 'testimony'45export interface EkooReviewUser {6 id: string7 firstname?: string8 lastname?: string9 email?: string10 image_url?: string11 description?: string12}1314export interface EkooReview {15 id: string16 type: EkooReviewType17 label?: string18 rating?: number19 audio_url?: string20 is_active: boolean21 is_default: boolean22 transcript?: string23 user: EkooReviewUser24}2526export interface EkooExperimentVariation {27 id: string28 name: string29 widgetEnabled: boolean30 widgetConfigId: string | null31 audioSelection: string32 audioId: string | null33 audioPosition: number | null34 audioIdsByLocale?: Record<string, string>35 allocation: number36 position: number37 config?: unknown38}3940export interface EkooExperiment {41 id: string42 name: string43 variations: EkooExperimentVariation[]44}4546export interface EkooProduct {47 id: string48 external_id: string49 name?: string50 audio_url?: string51 website: { id: string; name?: string }52 config?: Record<string, unknown>53 experiment?: EkooExperiment54 reviews: EkooReview[]55}5657export interface EkooProductList {58 limit: number59 offset: number60 count_item: number61 items: EkooProduct[]62}Lister les produits
Retourne une liste paginée des produits d'un site. Chaque élément a la même structure que le endpoint produit unique.
1GET /ws/websites/{websiteId}/productsParamètres de requête
| Nom | Type | Requis | Description |
|---|---|---|---|
limit | integer | Optionnel | Taille de page. Vaut 50 par défaut. Toute valeur ≤ 0 est ramenée à 50. |
offset | integer | Optionnel | Offset des résultats. Vaut 0 par défaut. |
without | string | Optionnel | Liste de références produit (séparées par des virgules) à exclure de la réponse. |
Exemple de requête
1curl -s "https://app.ekoo.co/ws/websites/{websiteId}/products?limit=50&offset=0"Exemple de réponse
1{2 "limit": 50,3 "offset": 0,4 "count_item": 124,5 "items": [6 { /* même format qu'un produit unique */ }7 ]8}Champs de la réponse
| Champ | Type | Description |
|---|---|---|
limit | integer | Écho de la taille de page demandée. |
offset | integer | Écho de l'offset demandé. |
count_item | integer | Nombre total de produits correspondant à la requête (toutes pages confondues). |
items[] | array | Page de produits. Chaque élément a la même structure que la réponse produit unique. |
Pour paginer, incrémentez offset de limit jusqu'à ce que offset + items.length >= count_item.
Exemple de rendu côté serveur (Next.js)
Récupérez le produit côté serveur, rendez les champs nécessaires (note, transcription, auteur, mise en page personnalisée) dans votre HTML, puis laissez le widget Ekoo s'hydrater par-dessus une fois chargé. Le pattern est identique dans Nuxt (useFetch), SvelteKit (+page.server.ts), Astro, Remix, ou tout backend capable d'émettre une requête HTTP.
1// app/products/[ref]/page.tsx — Next.js App Router (Server Component)2import { notFound } from 'next/navigation'3import type { EkooProduct } from '@/lib/ekoo-types'45async function fetchEkooProduct(ref: string, locale: string) {6 const url = `https://app.ekoo.co/ws/websites/${process.env.EKOO_WEBSITE_ID}/products/${ref}?locale=${locale}&variant=homepage`7 const res = await fetch(url, { next: { revalidate: 300 } })8 if (!res.ok) return null9 return (await res.json()) as EkooProduct10}1112export default async function ProductPage({13 params14}: { params: Promise<{ ref: string }> }) {15 const { ref } = await params16 const product = await fetchEkooProduct(ref, 'fr')17 if (!product) notFound()1819 const primary = product.reviews.find((r) => r.is_default && r.is_active)2021 return (22 <article>23 {primary?.transcript && (24 <section aria-label="Transcription de l'avis audio">25 <h2>Ce que disent nos clients</h2>26 <blockquote>{primary.transcript}</blockquote>27 <cite>{primary.user.firstname} {primary.user.lastname}</cite>28 </section>29 )}3031 {/* Le widget Ekoo s'hydrate côté client et prend le relais */}32 <ekoo-widget33 data-ekoo={process.env.EKOO_WEBSITE_ID}34 data-ekoo-product-id={ref}35 data-ekoo-locale="fr"36 data-ekoo-variant="homepage"37 />38 </article>39 )40}CORS
Cache & rate limits
Les réponses sont mises en cache au niveau de l'edge Ekoo et invalidées automatiquement lors de la publication de contenu depuis le backoffice. Nous recommandons d'ajouter un cache court côté client (par exemple revalidate: 300 en Next.js, s-maxage sur un CDN) afin d'absorber les pics de trafic et de mitiger les incidents en amont.
Lorsqu'un contenu doit être servi avant la prochaine revalidation, purgez la page concernée sur votre CDN ; l'edge Ekoo servira déjà le payload mis à jour.
Aucun quota par clé n'est appliqué. Les schémas de trafic abusifs — polling haute fréquence non caché depuis une seule IP, par exemple — peuvent être throttlés au niveau de l'edge. Pour un volume de requêtes élevé soutenu, contactez le support afin que la capacité soit provisionnée en conséquence.
Erreurs
| Statut | Signification | Quand |
|---|---|---|
| 200 | OK | Ressource trouvée et retournée. |
| 404 | Not found | websiteId ou productRef inconnu, ou produit désactivé. |
| 500 | Server error | Erreur base de données ou en amont. Réessayer avec backoff exponentiel est sûr. |
Les réponses d'erreur partagent la même structure :
1{2 "type": "ERR_NOT_FOUND",3 "message": "Product not found"4}Le champ type est un code stable, lisible par machine. Le champ message est destiné aux humains et peut évoluer ; la logique cliente doit s'appuyer sur type, et non sur message.
Versioning & stabilité
Ces endpoints alimentent le widget Ekoo public et sont maintenus comme un contrat stable. De nouveaux champs peuvent être ajoutés aux réponses sans préavis ; les implémentations clientes doivent ignorer les champs inconnus. Les champs existants ne sont ni renommés ni supprimés sans plan de migration documenté et fenêtre de dépréciation.
Les changements cassants, lorsqu'ils sont inévitables, sont publiés sous un nouveau préfixe de chemin ; la version précédente reste disponible pendant au moins six mois.
Ressources liées
- Référence des attributs du widget — attributs lus par le widget côté client.
- Cycle de vie du widget — comment le widget s'hydrate sur votre HTML SSR.
- Locales — valeurs supportées pour le paramètre
locale.