L'API Hou.la vous permet d'intégrer la création et la gestion de liens courts directement dans vos applications, scripts ou services.
https://hou.la/apiToutes les requêtes à l'API doivent être authentifiées en utilisant une clé API. Vous pouvez créer et gérer vos clés API depuis votre espace Manager.
Incluez votre clé API dans l'en-tête X-API-Key de chaque requête :
X-API-Key: houla_sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxEn plus de l'authentification par clé API, Hou.la supporte OAuth 2.0 avec le flux Authorization Code + PKCE (S256). Ce mécanisme est idéal pour les applications tierces (plugins WordPress, extensions Chrome, SaaS) qui ont besoin d'agir au nom d'un utilisateur.
| Étape | Endpoint | Description |
|---|---|---|
| 1 | GET /oauth/authorize | Valider les paramètres et afficher la page de consentement (frontend) |
| 2 | POST /oauth/authorize | L'utilisateur accepte ou refuse les permissions demandées |
| 3 | POST /oauth/token | Échanger le code d'autorisation contre un access_token et un refresh_token |
PKCE S256 est obligatoire pour toutes les requêtes OAuth sur Hou.la. Voici comment générer le challenge :
// 1. Générer un code_verifier aléatoire
const codeVerifier = crypto.randomBytes(32).toString('base64url');
// 2. Dériver le code_challenge (SHA-256 + base64url)
const codeChallenge = crypto
.createHash('sha256')
.update(codeVerifier)
.digest('base64url');
// 3. Construire l'URL d'autorisation
const authorizeUrl = `https://hou.la/oauth/authorize?` +
`client_id=YOUR_CLIENT_ID&` +
`redirect_uri=YOUR_REDIRECT_URI&` +
`response_type=code&` +
`scope=links:read links:write&` +
`code_challenge=${codeChallenge}&` +
`code_challenge_method=S256`;Une fois le code d'autorisation reçu, échangez-le contre des tokens :
curl -X POST "https://hou.la/api/oauth/token" \
-H "Content-Type: application/json" \
-d '{
"grant_type": "authorization_code",
"client_id": "YOUR_CLIENT_ID",
"code": "AUTHORIZATION_CODE",
"redirect_uri": "YOUR_REDIRECT_URI",
"code_verifier": "YOUR_CODE_VERIFIER"
}'{
"access_token": "eyJhbGciOiJIUz...",
"refresh_token": "dGhpcyBpcyBh...",
"token_type": "Bearer",
"expires_in": 3600
}Lorsque l'access_token expire, utilisez le refresh_token pour en obtenir un nouveau :
curl -X POST "https://hou.la/api/oauth/token" \
-H "Content-Type: application/json" \
-d '{
"grant_type": "refresh_token",
"client_id": "YOUR_CLIENT_ID",
"refresh_token": "YOUR_REFRESH_TOKEN"
}'| Scope | Permission |
|---|---|
links:read | Lecture des liens, tags et statistiques |
links:write | Création, modification et suppression de liens |
ecommerce:write | Gestion des liens e-commerce et tracking de conversions |
products:sync | Synchronisation du catalogue produits |
orders:create | Création de commandes e-commerce |
orders:update | Modification du statut des commandes |
Les utilisateurs authentifiés (via Bearer token) peuvent lister et révoquer les applications connectées à leur compte :
// Lister les applications connectées
curl "https://hou.la/api/oauth/apps" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
// Révoquer une application
curl -X DELETE "https://hou.la/api/oauth/apps/CLIENT_ID" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"Toutes les ressources (liens, tags, webhooks, domaines, etc.) sont rattachées à un workspace et non directement à un compte utilisateur. Chaque utilisateur dispose d'un workspace personnel créé automatiquement à l'inscription.
Pour cibler un workspace spécifique, ajoutez l'en-tête X-Workspace-Id à vos requêtes :
X-Workspace-Id: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxSi cet en-tête est omis, le workspace personnel par défaut de l'utilisateur est utilisé automatiquement.
curl -X POST "https://hou.la/api/link" \
-H "X-API-Key: houla_sk_xxxxxxxx" \
-H "X-Workspace-Id: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/page"
}'POST/link
| Paramètre | Type | Requis | Description |
|---|---|---|---|
url | string | Oui | L'URL longue à raccourcir |
title | string | Non | Un titre pour identifier le lien |
key | string | Non | Une clé personnalisée (ex: "mon-lien") |
utm_source | string | Non | Source de la campagne (ex: "google", "newsletter", "facebook") |
utm_medium | string | Non | Support marketing (ex: "cpc", "email", "social") |
utm_campaign | string | Non | Nom de la campagne (ex: "soldes-ete-2026") |
utm_term | string | Non | Mots-clés payés (pour les campagnes publicitaires) |
utm_content | string | Non | Contenu spécifique (ex: "bouton-header", "lien-footer") |
utm_id | string | Non | Identifiant de campagne pour le suivi avancé |
ephemeralDuration | string | Non | Durée de vie du lien éphémère. Valeurs : 1h, 6h, 12h, 24h, 48h |
customExpiresAt | string | Non | Date d'expiration personnalisée (ISO 8601, ex: 2026-06-15T23:59:59.000Z). Mutuellement exclusif avec ephemeralDuration. La clé n'est pas recyclée. |
password | string | Non | Mot de passe pour protéger l'accès au lien (1-100 caractères). Hashé avec bcrypt. Sécurité renforcée. |
fbPixelId | string | Non | Facebook Pixel ID (10-20 chiffres, ex: 123456789012345). Le pixel se déclenche lors du clic pour constituer une audience de retargeting. |
googleTagId | string | Non | Google Tag ID (format G-XXX, AW-XXX, DC-XXX ou UA-XXX). Supporte gtag.js. |
tiktokPixelId | string | Non | TikTok Pixel ID (format CXXX..., ex: CABCDEF1234567890). Le pixel se déclenche lors du clic pour le suivi TikTok Ads. |
ogTitle | string | Non | Titre Open Graph personnalisé affiché lors du partage sur les réseaux sociaux (max 200 caractères) |
ogDescription | string | Non | Description Open Graph personnalisée affichée lors du partage (max 500 caractères) |
ogImageUrl | string | Non | URL de l'image Open Graph (doit être une URL valide commençant par http:// ou https://) |
tagIds | string[] | Non | Liste d'identifiants de tags à associer au lien (UUID). Les tags doivent exister au préalable. |
customDomainId | string | Non | UUID du domaine personnalisé à utiliser pour le lien court. Laissez vide pour utiliser le domaine par défaut (hou.la). |
maxHits | number | Non | Nombre maximum de clics autorisés sur le lien (entre 1 et 1 000 000). Une fois atteint, le lien expire automatiquement. |
isCloaked | boolean | Non | Activer le Link Cloaking (<code class="code-inline">true</code>/<code class="code-inline">false</code>). Masque l'URL de destination dans la barre d'adresse du visiteur. |
deepLinkIos | string | Non | Schéma URI iOS pour ouvrir l'app native (ex: instagram://user?username=monprofil). Mobile uniquement. |
deepLinkAndroid | string | Non | Intent URL Android pour ouvrir l'app native (ex: intent://user?username=monprofil#Intent;scheme=instagram;package=com.instagram.android;end). Mobile uniquement. |
deepLinkFallbackUrl | string | Non | URL de repli si l'app n'est pas installée (ex: lien App Store, Play Store ou page web). Si non renseigné, la redirection standard s'applique. |
ephemeralDuration, le lien expirera automatiquement après la durée indiquée. La clé sera alors recyclée et pourra être réutilisée.customExpiresAt (ISO 8601), le lien expirera à cette date précise. La clé n'est pas recyclée. Mutuellement exclusif avec ephemeralDuration.password, le visiteur devra saisir le mot de passe avant d'être redirigé. Le mot de passe est hashé avec bcrypt (jamais stocké en clair). Sécurité renforcée.curl -X POST "https://hou.la/api/link" \
-H "X-API-Key: houla_sk_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/ma-page-tres-longue",
"title": "Ma campagne marketing",
"utm_source": "newsletter",
"utm_medium": "email",
"utm_campaign": "promo-janvier-2026"
}'curl -X POST "https://hou.la/api/link" \
-H "X-API-Key: houla_sk_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/offre-limitee",
"title": "Promo flash 24h",
"ephemeralDuration": "24h"
}'curl -X POST "https://hou.la/api/link" \
-H "X-API-Key: houla_sk_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/evenement",
"title": "Inscription ouverte jusqu'au 15 juin",
"customExpiresAt": "2026-06-15T23:59:59.000Z"
}'curl -X POST "https://hou.la/api/link" \
-H "X-API-Key: houla_sk_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/document-confidentiel",
"title": "Document privé",
"password": "secret123"
}'201 Created
{
"id": "abc123",
"key": "xY7k9",
"url": "https://example.com/ma-page-tres-longue",
"shortUrl": "https://hou.la/xY7k9",
"title": "Ma campagne marketing",
"hasPassword": false,
"isEphemeral": false,
"hitsCount": 0,
"flashsCount": 0,
"createdAt": "2026-01-28T10:30:00Z",
"status": "active"
}{
"id": "def456",
"key": "aB3cD",
"url": "https://example.com/offre-limitee",
"shortUrl": "https://hou.la/aB3cD",
"title": "Promo flash 24h",
"hasPassword": false,
"isEphemeral": true,
"ephemeralDuration": "24h",
"expiresAt": "2026-01-29T10:30:00Z",
"createdAt": "2026-01-28T10:30:00Z",
"status": "active"
}{
"id": "jkl012",
"key": "mN4pQ",
"url": "https://example.com/evenement",
"shortUrl": "https://hou.la/mN4pQ",
"title": "Inscription ouverte jusqu'au 15 juin",
"hasPassword": false,
"isEphemeral": false,
"expiresAt": "2026-06-15T23:59:59.000Z",
"createdAt": "2026-02-12T10:30:00Z",
"status": "active"
}{
"id": "ghi789",
"key": "zK8mP",
"url": "https://example.com/document-confidentiel",
"shortUrl": "https://hou.la/zK8mP",
"title": "Document privé",
"hasPassword": true,
"isEphemeral": false,
"hitsCount": 0,
"flashsCount": 0,
"createdAt": "2026-01-28T10:30:00Z",
"status": "active"
}GET /api/link/{id}/qrcode (voir section 5).GET/link/{key}/availability
curl -X GET "https://hou.la/api/link/mon-lien/availability"200 OK
{
"available": true
}GET/link/{id}
Récupère les détails complets d'un lien par son UUID, incluant les statistiques des 90 derniers jours et les tags.
curl -X GET "https://hou.la/api/link/a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
-H "X-API-Key: houla_sk_xxxxxxxx"200 OK
{
"id": "abc123",
"key": "xY7k9",
"url": "https://example.com/ma-page-tres-longue",
"shortUrl": "https://hou.la/xY7k9",
"title": "Ma campagne marketing",
"hasPassword": false,
"isEphemeral": false,
"hitsCount": 42,
"flashsCount": 5,
"createdAt": "2026-01-28T10:30:00Z",
"status": "active"
}GET/link/{id}/qrcode
| Paramètre | Type | Défaut | Description |
|---|---|---|---|
width | number | 300 | Largeur en pixels (50-1000) |
margin | number | 2 | Marge autour du QR code (0-10) |
darkColor | string | #000000 | Couleur des modules (hex) |
lightColor | string | #FFFFFF | Couleur de fond (hex) |
errorCorrectionLevel | string | M | Niveau de correction : L, M, Q, H |
format | string | png | Format de sortie : png ou svg |
curl -X GET "https://hou.la/api/link/a1b2c3d4-e5f6-7890-abcd-ef1234567890/qrcode?width=500&darkColor=%23FF5722" \
-H "X-API-Key: houla_sk_xxxxxxxx"200 OK
{
"base64": "iVBORw0KGgoAAAANSUhEUgAA...",
"dataUrl": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA..."
}{
"svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 300 300\">...</svg>"
}GET/link
| Paramètre | Type | Défaut | Description |
|---|---|---|---|
page | number | 1 | Numéro de la page |
limit | number | 20 | Nombre de résultats par page (max: 100) |
curl -X GET "https://hou.la/api/link?page=1&limit=10" \
-H "X-API-Key: houla_sk_xxxxxxxx"200 OK
{
"data": [
{
"id": "abc123",
"key": "xY7k9",
"url": "https://example.com/page1",
"shortUrl": "https://hou.la/xY7k9",
"title": "Lien 1",
"hasPassword": false,
"isEphemeral": false,
"hitsCount": 42,
"createdAt": "2026-01-28T10:30:00Z"
},
...
],
"total": 55,
"page": 1,
"pageCount": 6,
"count": 10
}PATCH/link/{id}
| Paramètre | Type | Description |
|---|---|---|
url | string | La nouvelle URL de destination |
title | string | Le nouveau titre du lien |
password | string | Nouveau mot de passe (1-100 caractères), ou null pour retirer la protection |
isEphemeral | boolean | Activer/désactiver le mode éphémère |
ephemeralDuration | string | Durée éphémère : 1h, 6h, 12h, 24h, 48h |
customExpiresAt | string | Date d'expiration personnalisée (ISO 8601), ou null pour retirer l'expiration |
fbPixelId | string | Facebook Pixel ID (10-20 chiffres), ou null pour retirer le pixel |
googleTagId | string | Google Tag ID (G-XXX, AW-XXX, DC-XXX, UA-XXX), ou null pour retirer |
tiktokPixelId | string | TikTok Pixel ID (CXXX...), ou null pour retirer |
ogTitle | string | null | Nouveau titre Open Graph, ou null pour retirer |
ogDescription | string | null | Nouvelle description Open Graph, ou null pour retirer |
ogImageUrl | string | null | Nouvelle URL d'image Open Graph, ou null pour retirer |
tagIds | string[] | null | Liste d'identifiants de tags (UUID) à associer au lien, ou null pour retirer tous les tags |
maxHits | number | null | Nombre maximum de clics autorisés (1 à 1 000 000), ou null pour retirer la limite |
isCloaked | boolean | Activer ou désactiver le Link Cloaking (true/false) |
deepLinkIos | string | null | Schéma URI iOS, ou null pour supprimer |
deepLinkAndroid | string | null | Intent URL Android, ou null pour supprimer |
deepLinkFallbackUrl | string | null | URL de repli, ou <code class="code-inline">null</code> pour supprimer |
curl -X PATCH "https://hou.la/api/link/abc123" \
-H "X-API-Key: houla_sk_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"title": "Nouveau titre",
"url": "https://example.com/nouvelle-destination"
}'200 OK
{
"id": "abc123",
"key": "xY7k9",
"url": "https://example.com/nouvelle-destination",
"shortUrl": "https://hou.la/xY7k9",
"title": "Nouveau titre",
"hasPassword": false,
"updatedAt": "2026-01-28T14:00:00Z"
}DELETE/link/{id}
Supprime un lien (suppression logique). La clé peut être libérée pour réutilisation.
curl -X DELETE "https://hou.la/api/link/a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
-H "X-API-Key: houla_sk_xxxxxxxx"200 OK
{
"success": true,
"message": "Lien supprimé avec succès"
}Uploadez une image personnalisée qui sera optimisée (1200×630, WebP) et stockée sur notre CDN. L'image remplace automatiquement le champ ogImageUrl du lien.
POST/manager/link/{id}/og-image
Le body doit être de type <code class="code-inline">multipart/form-data</code> avec un champ <code class="code-inline">file</code> (JPG, PNG ou WebP, max 8 Mo).
curl -X POST "https://hou.la/api/manager/link/abc123/og-image" \
-H "Authorization: Bearer YOUR_TOKEN" \
-F "file=@my-og-image.jpg"200 OK
{
"ogImageUrl": "https://r2.hou.la/og-images/user123/link456/abc.webp",
"ogImageR2Key": "og-images/user123/link456/abc.webp"
}DELETE/manager/link/{id}/og-image
curl -X DELETE "https://hou.la/api/manager/link/abc123/og-image" \
-H "Authorization: Bearer YOUR_TOKEN"204 No Content
POST/manager/link/{id}/og-crawl
Re-crawle les meta Open Graph depuis l’URL de destination du lien. Utile pour mettre à jour les données OG après modification de la page cible.
curl -X POST "https://hou.la/api/manager/link/abc123/og-crawl" \
-H "Authorization: Bearer YOUR_TOKEN"200 OK
{
"ogTitle": "Page Title",
"ogDescription": "Page description from meta tags",
"ogImageUrl": "https://example.com/og-image.jpg",
"ogCrawlStatus": "success",
"ogCrawledAt": "2026-02-16T10:30:00.000Z"
}POST/manager/link/og-crawl-url
Crawle une URL sans créer de lien, pour prévisualiser les meta OG qui seront extraites.
| Paramètre | Type | Requis | Description |
|---|---|---|---|
url | string | Oui | URL à crawler (ex : https://example.com) |
curl -X POST "https://hou.la/api/manager/link/og-crawl-url" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/my-page"
}'200 OK
{
"ogTitle": "My Page Title",
"ogDescription": "Description from meta tags",
"ogImageUrl": "https://example.com/image.jpg"
}GET/manager/link/{id}/og-preview
Retourne les meta OG stockées pour un lien (titre, description, image, statut du crawl).
curl -X GET "https://hou.la/api/manager/link/abc123/og-preview" \
-H "Authorization: Bearer YOUR_TOKEN"200 OK
{
"ogTitle": "Page Title",
"ogDescription": "Page description",
"ogImageUrl": "https://r2.hou.la/og-images/user/link/abc.webp",
"ogCrawlStatus": "manual",
"ogCrawledAt": "2026-02-16T10:30:00.000Z"
}Le Smart Routing permet de définir des règles de redirection conditionnelle sur vos liens. Chaque règle contient un libellé, une URL de destination, des conditions (pays, appareil, langue, navigateur, OS, première visite) et un poids optionnel pour l'A/B testing.
GET/link/{linkId}/rules
curl -X GET "https://hou.la/api/link/abc123/rules" \
-H "X-API-Key: houla_sk_xxxxxxxx"200 OK
[
{
"id": "rule-1",
"linkId": "abc123",
"label": "Visiteurs français mobile",
"destinationUrl": "https://example.fr/page-fr",
"matchType": "all",
"priority": 0,
"weight": 100,
"isActive": true,
"conditions": [
{
"field": "country",
"operator": "equals",
"value": "FR"
}
]
}
]POST/link/{linkId}/rules
| Paramètre | Type | Requis | Description |
|---|---|---|---|
label | string | Oui | Libellé de la règle (max 100 caractères) |
destinationUrl | string | Oui | URL de destination si la règle correspond |
matchType | string | Non | all (défaut) = toutes les conditions, any = au moins une |
weight | number | Non | Poids pour l'A/B testing (0-100). Si des règles ont des poids, le trafic est réparti proportionnellement. |
isActive | boolean | Non | Activer/désactiver la règle (défaut: true) |
conditions | array | Oui | Liste de conditions (voir ci-dessous) |
| Paramètre | Type | Valeurs possibles |
|---|---|---|
field | string | country, continent, device, os, browser, language, referrer, social_media, day_of_week, hour, date_range, is_bot, is_first_visit |
operator | string | equals, not_equals, contains, not_contains, in, not_in, starts_with, greater_than, less_than, between, not_between |
value | string | Valeur à comparer (ex: FR, mobile, true) |
curl -X POST "https://hou.la/api/link/abc123/rules" \
-H "X-API-Key: houla_sk_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"label": "Visiteurs français mobile",
"destinationUrl": "https://example.fr/page-fr",
"matchType": "all",
"conditions": [
{ "field": "country", "operator": "equals", "value": "FR" },
{ "field": "device", "operator": "equals", "value": "mobile" }
]
}'Pour répartir le trafic entre plusieurs destinations, créez des règles sans conditions mais avec des poids. Le trafic sera distribué proportionnellement aux poids définis.
# Règle A - 70% du trafic
curl -X POST "https://hou.la/api/link/abc123/rules" \
-H "X-API-Key: houla_sk_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"label": "Variante A",
"destinationUrl": "https://example.com/page-a",
"weight": 70,
"conditions": []
}'
# Règle B - 30% du trafic
curl -X POST "https://hou.la/api/link/abc123/rules" \
-H "X-API-Key: houla_sk_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"label": "Variante B",
"destinationUrl": "https://example.com/page-b",
"weight": 30,
"conditions": []
}'PATCH/link/{linkId}/rules/{ruleId}
Mêmes paramètres que la création. Seuls les champs envoyés sont modifiés.
DELETE/link/{linkId}/rules/{ruleId}
PUT/link/{linkId}/rules/reorder
curl -X PUT "https://hou.la/api/link/abc123/rules/reorder" \
-H "X-API-Key: houla_sk_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"ruleIds": ["rule-2", "rule-1", "rule-3"]
}'Les webhooks vous permettent de recevoir des notifications HTTP en temps réel lorsque des événements se produisent sur vos liens ou pages Link-in-Bio. Au lieu de poll l'API régulièrement, configurez un webhook et recevez les données directement sur votre serveur.
profile.visited et profile.link_clicked pour les pages Link-in-Bio.| Événement | Description |
|---|---|
link.clicked | Un lien a été cliqué |
link.created | Un lien a été créé |
link.updated | Un lien a été modifié |
link.deleted | Un lien a été supprimé |
link.health_changed | L'état de santé d'un lien a changé (ex: URL cassée) |
link.safety_changed | Le statut de sécurité d'un lien a changé |
link.expired | Un lien éphémère a expiré |
link.password_attempt | Tentative d'accès à un lien protégé par mot de passe |
profile.visited | Une page Link-in-Bio a été visitée |
profile.link_clicked | Un lien a été cliqué sur une page Link-in-Bio |
POST/manager/webhook
| Paramètre | Type | Requis | Description |
|---|---|---|---|
name | string | Oui | Nom descriptif (max 100 caractères) |
url | string | Oui | URL de destination (HTTPS requis en production, max 2048 caractères) |
events | string[] | Oui | Liste des événements à écouter (au moins 1) |
linkId | string | Non | UUID du lien spécifique à surveiller |
tagId | string | Non | UUID du tag pour filtrer les événements |
batchSize | number | Non | Taille du batch (1-100, défaut: 1 = pas de batching) |
batchDelayMs | number | Non | Délai max avant envoi du batch en ms (0-60000, défaut: 0) |
samplingRate | number | Non | Pourcentage d'événements à envoyer (1-100, défaut: 100) |
anonymizeIp | boolean | Non | Anonymiser l'IP dans les payloads (RGPD) |
excludeGeoCity | boolean | Non | Exclure la ville des données de géolocalisation (RGPD) |
curl -X POST "https://hou.la/api/manager/webhook" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Analytics Integration",
"url": "https://my-app.com/webhooks/houla",
"events": ["link.clicked", "link.created"],
"anonymizeIp": true,
"excludeGeoCity": true
}'201 Created
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "Analytics Integration",
"url": "https://my-app.com/webhooks/houla",
"events": ["link.clicked", "link.created"],
"enabled": true,
"secret": "whsec_xxxxxxxxxxxxxxxxxxxxxxxx",
"anonymizeIp": true,
"excludeGeoCity": true,
"batchSize": 1,
"samplingRate": 100,
"createdAt": "2025-01-15T10:30:00.000Z"
}secret n'est affiché qu'à la création. Conservez-le en lieu sûr pour vérifier les signatures.GET/manager/webhook
curl -X GET "https://hou.la/api/manager/webhook" \
-H "Authorization: Bearer YOUR_JWT_TOKEN"GET/manager/webhook/{id}
curl -X GET "https://hou.la/api/manager/webhook/a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
-H "Authorization: Bearer YOUR_JWT_TOKEN"PATCH/manager/webhook/{id}
Tous les champs de création sont acceptés en paramètre (sauf secret).
curl -X PATCH "https://hou.la/api/manager/webhook/a1b2c3d4-..." \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Updated Integration",
"events": ["link.clicked", "link.created", "link.deleted"]
}'DELETE/manager/webhook/{id}
curl -X DELETE "https://hou.la/api/manager/webhook/a1b2c3d4-..." \
-H "Authorization: Bearer YOUR_JWT_TOKEN"204 No Content
POST/manager/webhook/{id}/enable
POST/manager/webhook/{id}/disable
curl -X POST "https://hou.la/api/manager/webhook/a1b2c3d4-.../enable" \
-H "Authorization: Bearer YOUR_JWT_TOKEN"POST/manager/webhook/{id}/test
Envoie un événement de test à l'URL du webhook pour vérifier que votre serveur répond correctement.
curl -X POST "https://hou.la/api/manager/webhook/a1b2c3d4-.../test" \
-H "Authorization: Bearer YOUR_JWT_TOKEN"200 OK
{
"success": true,
"statusCode": 200,
"responseTimeMs": 142
}GET/manager/webhook/{id}/logs
| Paramètre | Type | Description |
|---|---|---|
page | number | Numéro de page (défaut: 1) |
limit | number | Résultats par page (défaut: 20, max: 100) |
success | boolean | Filtrer par succès (true) ou échec (false) |
curl -X GET "https://hou.la/api/manager/webhook/a1b2c3d4-.../logs?page=1&limit=10&success=true" \
-H "Authorization: Bearer YOUR_JWT_TOKEN"POST/manager/webhook/{id}/regenerate-secret
Génère un nouveau secret de signature. L'ancien secret sera invalidé immédiatement.
curl -X POST "https://hou.la/api/manager/webhook/a1b2c3d4-.../regenerate-secret" \
-H "Authorization: Bearer YOUR_JWT_TOKEN"Chaque requête webhook inclut un en-tête X-Houla-Signature contenant une signature HMAC-SHA256. Vérifiez cette signature pour vous assurer que la requête provient bien de hou.la.
| En-tête | Description |
|---|---|
X-Houla-Signature | Signature HMAC-SHA256 du body |
X-Houla-Event | Type d'événement (ex: link.clicked) |
X-Houla-Delivery | UUID unique de la livraison |
X-Houla-Timestamp | Timestamp ISO 8601 de l'envoi |
const crypto = require('crypto');
function verifyWebhookSignature(body, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(JSON.stringify(body))
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
// Dans votre handler Express
app.post('/webhooks/houla', (req, res) => {
const signature = req.headers['x-houla-signature'];
const event = req.headers['x-houla-event'];
if (!verifyWebhookSignature(req.body, signature, WEBHOOK_SECRET)) {
return res.status(401).json({ error: 'Invalid signature' });
}
switch (event) {
case 'link.clicked':
console.log('Clic sur', req.body.link.shortUrl);
break;
case 'profile.visited':
console.log('Visite page bio', req.body.profile.slug);
break;
}
res.status(200).json({ received: true });
});Gérez des presets de pixels de retargeting pour pré-remplir automatiquement vos liens lors de la création.
GET /api/manager/pixel-preset
Authorization: Bearer <token>POST /api/manager/pixel-preset
Authorization: Bearer <token>
Content-Type: application/json
{
"name": "Mon site e-commerce",
"isDefault": true,
"fbPixelId": "123456789012345",
"googleTagId": "G-AB1CDE2FGH"
}| Paramètre | Type | Requis | Description |
|---|---|---|---|
name | string | Oui | Nom du preset (1-100 caractères) |
isDefault | boolean | Non | Définir comme preset par défaut pour les nouveaux liens |
fbPixelId | string | Non | Facebook Pixel ID (10-20 chiffres) |
googleTagId | string | Non | Google Tag ID (G-XXX, AW-XXX, DC-XXX ou UA-XXX) |
tiktokPixelId | string | Non | TikTok Pixel ID (commence par C, 11-31 caractères) |
PATCH /api/manager/pixel-preset/:id
Authorization: Bearer <token>DELETE /api/manager/pixel-preset/:id
Authorization: Bearer <token>Ajoutez vos propres domaines pour personnaliser vos liens courts. Cette fonctionnalité est réservée aux plans payants.
POST/domains
Ajoute un domaine personnalisé à votre compte. Vous devrez configurer un enregistrement DNS pour vérifier la propriété du domaine.
| Paramètre | Type | Requis | Description |
|---|---|---|---|
domain | string | Oui | Le nom de domaine à ajouter (ex : links.monsite.com) |
curl -X POST "https://hou.la/api/domains" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"domain": "links.monsite.com"
}'201 Created
{
"id": "a1b2c3d4-...",
"domain": "links.monsite.com",
"status": "pending",
"verificationMethod": "cname",
"verificationToken": "houla-verify-abc123...",
"dnsVerified": false,
"sslConfigured": false,
"isActive": false,
"cnameTarget": "custom.hou.la",
"txtRecordName": "_houla-verify.links.monsite.com",
"createdAt": "2025-01-15T10:30:00.000Z",
"updatedAt": "2025-01-15T10:30:00.000Z"
}GET/domains
curl -X GET "https://hou.la/api/domains" \
-H "Authorization: Bearer <token>"200 OK
[
{
"id": "a1b2c3d4-...",
"domain": "links.monsite.com",
"status": "active",
"verificationMethod": "cname",
"dnsVerified": true,
"sslConfigured": false,
"isActive": true,
"cnameTarget": "custom.hou.la",
"txtRecordName": "_houla-verify.links.monsite.com",
"createdAt": "2025-01-15T10:30:00.000Z",
"updatedAt": "2025-01-15T12:00:00.000Z"
}
]GET/domains/{id}
curl -X GET "https://hou.la/api/domains/a1b2c3d4-..." \
-H "Authorization: Bearer <token>"POST/domains/{id}/verify
Déclenche la vérification DNS du domaine. Le système vérifie que l’enregistrement CNAME ou TXT est correctement configuré.
curl -X POST "https://hou.la/api/domains/a1b2c3d4-.../verify" \
-H "Authorization: Bearer <token>"200 OK — Domaine vérifié avec succès
{
"id": "a1b2c3d4-...",
"domain": "links.monsite.com",
"status": "active",
"dnsVerified": true,
"dnsVerifiedAt": "2025-01-15T12:00:00.000Z",
"isActive": true
}PATCH/domains/{id}/verification-method
Change la méthode de vérification DNS du domaine (CNAME ou TXT).
| Paramètre | Type | Requis | Description |
|---|---|---|---|
method | string | Oui | Méthode de vérification : « cname » ou « txt » |
curl -X PATCH "https://hou.la/api/domains/a1b2c3d4-.../verification-method" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"method": "txt"
}'DELETE/domains/{id}
curl -X DELETE "https://hou.la/api/domains/a1b2c3d4-..." \
-H "Authorization: Bearer <token>"200 OK
{
"success": true
}Selon la méthode de vérification choisie, configurez l’un des enregistrements DNS suivants :
links.monsite.com. CNAME custom.hou.la._houla-verify.links.monsite.com. TXT "houla-verify-abc123..."Vendez des produits physiques, digitaux, services ou acceptez des dons directement depuis vos pages Link in Bio. Les paiements sont traités via Stripe Connect.
POST/manager/stripe-connect/onboard
Lance le processus d’onboarding Stripe Connect Express. Retourne une URL de redirection vers le formulaire Stripe.
201 Created
{
"url": "https://connect.stripe.com/setup/s/..."
}GET/manager/stripe-connect/status
Retourne le statut actuel du compte Stripe Connect du vendeur.
200 OK
{
"status": "active",
"chargesEnabled": true,
"payoutsEnabled": true,
"detailsSubmitted": true
}GET/manager/bio-pages/{bioPageId}/pay-links
Retourne tous les Pay Links d’une page Bio, triés par ordre d’affichage.
200 OK
[
{
"id": "uuid",
"title": "E-book SEO Pro",
"productType": "digital",
"priceInCents": 1999,
"currency": "EUR",
"status": "active",
"quantitySold": 42,
"quantityAvailable": 58,
"displayOrder": 0
}
]POST/manager/bio-pages/{bioPageId}/pay-links
Crée un nouveau Pay Link sur une page Bio. Le vendeur doit avoir un compte Stripe Connect actif.
| Paramètre | Type | Requis | Description |
|---|---|---|---|
title | string | Oui | Titre du produit (max 120 caractères) |
priceInCents | integer | Oui | Prix en centimes (ex : 1999 = 19,99 €) |
description | string | Non | Description du produit |
imageUrl | string | Non | URL de l’image du produit |
productType | string | Non | Type de produit : physical, digital, service, donation (défaut : physical) |
currency | string | Non | Devise : EUR, USD, GBP, CAD, CHF (défaut : EUR) |
compareAtPrice | integer | Non | Prix barré en centimes (prix avant promo) |
quantityAvailable | integer | Non | Stock disponible (null = illimité) |
maxPerOrder | integer | Non | Quantité maximum par commande (1-100, défaut : 1) |
shippingCost | integer | Non | Frais de livraison en centimes |
shippingCountries | string[] | Non | Codes ISO pays de livraison (ex : ["FR", "BE", "CH"]) |
digitalFileUrl | string | Non | URL du fichier à télécharger (produits digitaux) |
digitalFileName | string | Non | Nom du fichier affiché à l’acheteur |
ctaStyle | string | Non | Style du CTA : default, featured, minimal |
ctaText | string | Non | Texte du bouton d’achat (max 40 caractères) |
curl -X POST "https://hou.la/api/manager/bio-pages/BIO_ID/pay-links" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"title": "E-book SEO Pro",
"priceInCents": 1999,
"currency": "EUR",
"productType": "digital",
"digitalFileUrl": "https://storage.example.com/ebook.pdf",
"digitalFileName": "ebook-seo-pro.pdf",
"maxPerOrder": 1
}'201 Created
PATCH/manager/pay-links/{id}
Modifie un Pay Link existant. Seuls les champs envoyés sont mis à jour.
Accepte les mêmes paramètres que la création, plus le champ status (active, paused, sold_out, archived).
POST/bio/{username}/shop/shipping-estimate
Calcule les frais de livraison d'un panier et retourne une fenêtre de livraison estimée tenant compte du délai de préparation vendeur, des vacances vendeur, du pays d'expédition et d'un éventuel panier ouvert déjà fusionné.
| Paramètre | Type | Requis | Description |
|---|---|---|---|
items | array | Oui | Lignes du panier. Chaque entrée contient au minimum productId et quantity. |
buyerCountry | string | Non | Code ISO du pays acheteur. Si omis, l'API tente une détection GeoIP puis retombe sur FR. |
buyerEmail | string | Non | Email acheteur pour détecter un panier ouvert existant et recalculer uniquement le complément de livraison. |
curl -X POST "https://hou.la/api/bio/testshop/shop/shipping-estimate" \
-H "Content-Type: application/json" \
-d '{
"items": [
{ "productId": "PRODUCT_ID", "quantity": 2 }
],
"buyerCountry": "FR",
"buyerEmail": "client@example.com"
}'200 OK
La réponse inclut les frais de livraison recalculés, l'état du retrait sur place, et un objet deliveryEstimate avec la fenêtre estimée, le transporteur générique appliqué et l'impact éventuel des vacances vendeur.
{
"shippingCents": 590,
"freeShipping": false,
"buyerCountry": "FR",
"mergeDetected": false,
"localPickupAvailable": true,
"localPickupInstructions": "Retrait du lundi au vendredi, 9h-18h",
"deliveryEstimate": {
"carrierName": "Hou.la Standard",
"carrierServiceName": "Local / National Standard",
"minDeliveryDate": "2026-03-24",
"maxDeliveryDate": "2026-03-27",
"minDeliveryLabel": "24 mars 2026",
"maxDeliveryLabel": "27 mars 2026",
"sellerVacationApplied": false,
"cartCloseDate": "2026-03-24"
}
}DELETE/manager/pay-links/{id}
Supprime un Pay Link (soft delete). Les commandes existantes sont conservées.
204 No Content
GET/manager/pay-orders
Retourne les commandes du vendeur avec pagination et filtres.
| Paramètre | Type | Requis | Description |
|---|---|---|---|
page | integer | Non | Numéro de page (défaut : 1) |
limit | integer | Non | Nombre de résultats par page (max 100, défaut : 20) |
status | string | Non | Filtrer par statut : pending, paid, processing, fulfilled, failed, refunded, partially_refunded |
payLinkId | string | Non | Filtrer par PayLink ID |
dateFrom | string | Non | Date de début (ISO 8601) |
dateTo | string | Non | Date de fin (ISO 8601) |
GET/manager/pay-orders/stats
Retourne un résumé des ventes : chiffre d’affaires total, nombre de commandes, remboursements, et données du mois en cours.
200 OK
{
"totalRevenue": 150000,
"totalOrders": 75,
"totalRefunded": 2,
"thisMonthRevenue": 32000,
"thisMonthOrders": 12
}POST/manager/pay-orders/{id}/refund
Rembourse une commande via Stripe. Par défaut, remboursement total. Spécifiez amountCents pour un remboursement partiel.
| Paramètre | Type | Requis | Description |
|---|---|---|---|
amountCents | integer | Non | Montant à rembourser en centimes (omis = remboursement total) |
reason | string | Non | Motif du remboursement |
Importez vos liens depuis Bitly vers Hou.la. L'import est asynchrone : vous lancez un job et suivez sa progression via SSE ou polling.
Démarre un job d'import asynchrone depuis la source spécifiée. Retourne un objet ImportJob avec un identifiant pour suivre la progression.
POST /api/manager/import/start
Authorization: Bearer JWT_TOKEN
X-Workspace-Id: WORKSPACE_ID
Content-Type: application/json| PAGES.API_DOC.TH_PARAMETER | Type | Requis | Description |
|---|---|---|---|
source | string | Oui | Source de l'import (actuellement : "bitly") |
apiToken | string | Oui | Token d'accès API de la source (Bitly Generic Access Token) |
skipDuplicates | boolean | Non | Ignorer les liens déjà importés (défaut : true) |
preserveTags | boolean | Non | Conserver les tags Bitly en tant que tags Hou.la (défaut : false) |
preserveCustomSlugs | boolean | Non | Tenter de conserver les slugs personnalisés Bitly (défaut : false) |
| Plan | Liens max par import |
|---|---|
| Free | Non disponible |
| Pro | 1 000 |
| Business | 10 000 |
curl -X POST https://api.hou.la/api/manager/import/start \
-H "Authorization: Bearer $JWT_TOKEN" \
-H "X-Workspace-Id: $WORKSPACE_ID" \
-H "Content-Type: application/json" \
-d '{"source":"bitly","apiToken":"YOUR_BITLY_TOKEN","skipDuplicates":true}'Retourne la liste de tous les jobs d'import de l'utilisateur, triés du plus récent au plus ancien.
GET /api/manager/import
Authorization: Bearer JWT_TOKEN
X-Workspace-Id: WORKSPACE_IDRetourne les détails d'un job d'import spécifique, incluant le statut, la progression et les éventuelles erreurs.
GET /api/manager/import/:id
Authorization: Bearer JWT_TOKEN
X-Workspace-Id: WORKSPACE_IDAnnule un job d'import en cours. Seuls les imports au statut "pending" ou "running" peuvent être annulés.
POST /api/manager/import/:id/cancel
Authorization: Bearer JWT_TOKEN
X-Workspace-Id: WORKSPACE_IDFlux Server-Sent Events (SSE) pour suivre la progression de l'import en temps réel. Émet des événements progress, completed ou failed.
GET /api/manager/import/:id/progress
Authorization: Bearer JWT_TOKEN
X-Workspace-Id: WORKSPACE_ID| Code | Description |
|---|---|
| 400 | Requête invalide - Vérifiez les paramètres envoyés |
| 401 | Non autorisé - Clé API manquante ou invalide |
| 403 | Interdit - Clé API révoquée ou accès refusé |
| 404 | Non trouvé - La ressource demandée n'existe pas |
| 429 | Trop de requêtes - Limite de débit atteinte ou protection de sécurité activée |
| 500 | Erreur serveur - Contactez le support |
L'API applique des limites de requêtes selon le type d'authentification utilisé :
| Type d'utilisateur | Limite | TTL |
|---|---|---|
| Anonyme | 10 req/min | 60s |
| JWT authentifié | 100 req/min | 60s |
| Clé API | 1000 req/min | 60s |
| Login / Auth | 5 req/min | 60s |
X-RateLimit-Limit, X-RateLimit-Remaining et X-RateLimit-Reset pour vous permettre de gérer vos quotas.Pour une utilisation optimale de l'API, nous recommandons de :
Le SDK houla-sdk vous permet d'intégrer rapidement l'API Hou.la dans vos projets Node.js/TypeScript.
npm install @houla/sdkimport { HoulaClient } from '@houla/sdk';
const client = new HoulaClient('houla_sk_xxxxxxxx');
// Créer un lien
const link = await client.createLink({
url: 'https://example.com/ma-page',
title: 'Mon lien'
});
console.log(link.shortUrl); // https://hou.la/xY7k9
// Créer un lien protégé par mot de passe
const protectedLink = await client.createLink({
url: 'https://example.com/secret',
title: 'Document confidentiel',
password: 'secret123'
});
// Lister les liens
const result = await client.getLinks({ page: 1, limit: 10 });
// Récupérer un lien par ID
const details = await client.getLinkById('abc123');
// Supprimer un lien
await client.deleteLink('abc123');
// ─── Webhooks ───
// Créer un webhook
const webhook = await client.createWebhook({
name: 'Mon webhook',
url: 'https://my-app.com/webhooks/houla',
events: ['link.clicked', 'link.created'],
anonymizeIp: true
});
console.log(webhook.secret); // À conserver !
// Lister les webhooks
const webhooks = await client.getWebhooks();
// Tester un webhook
const test = await client.testWebhook(webhook.id);
console.log(test.success, test.responseTimeMs);
// Consulter les logs
const logs = await client.getWebhookLogs(webhook.id, 1, 20);L'API Print permet au client desktop Hou.la Print de recevoir et gérer les jobs d'impression en temps réel. Les endpoints de la section /api/print utilisent l'authentification par clé API (header X-API-Key).
GET/api/print/config
Récupère la configuration d'impression du workspace courant.
PATCH/api/manager/print/config
Met à jour la configuration d'impression (endpoint manager, authentification JWT).
| Paramètre | Type | Description |
|---|---|---|
enabled | boolean | Activer/désactiver l'impression automatique |
autoProductLabels | boolean | Impression automatique des étiquettes produits |
autoOrderSummary | boolean | Impression automatique du récapitulatif commande |
autoInvoice | boolean | Impression automatique des factures |
autoInvoiceTrigger | "paid" | "processing" | Déclencheur de la facture : 'paid' (paiement reçu) ou 'processing' (commande en cours) |
autoShippingLabel | boolean | Impression automatique des étiquettes d'expédition |
autoPackingSlip | boolean | Impression automatique des bordereaux de livraison |
productLabelTemplate | "standard" | "minimal" | "detailed" | Style du template d'étiquette : standard, minimal ou detailed |
brandName | string | Nom de marque affiché sur les étiquettes (max 100 caractères) |
GET/api/print/jobs?status=pending
Liste les jobs d'impression du workspace, filtrables par statut et type.
| Paramètre | Type | Description |
|---|---|---|
status | string | Filtrer par statut : pending, sent, printed, failed, cancelled |
type | string | Filtrer par type : product_label, order_summary, invoice, shipping_label, packing_slip |
limit | number | Nombre maximum de résultats (1-100, défaut : 50) |
offset | number | Offset pour la pagination (défaut : 0) |
POST/api/print/jobs/{id}/ack
Confirme qu'un job a été imprimé avec succès ou a échoué.
| Paramètre | Type | Requis | Description |
|---|---|---|---|
status | "printed" | "failed" | Oui | Statut final du job : 'printed' (succès) ou 'failed' (échec) |
error | string | Non | Message d'erreur (si status=failed, max 1000 caractères) |
curl -X POST "https://hou.la/api/print/jobs/{jobId}/ack" \
-H "X-API-Key: houla_sk_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"status": "printed"
}'GET/api/print/jobs/{id}/label
Retourne le contenu de l'étiquette (ZPL, ESC/POS inline) ou l'URL de téléchargement (PDF, PNG).
{
"id": "abc-123",
"type": "product_label",
"labelFormat": "zpl",
"labelData": "^XA^FO50,50^A0N,40,40^FDMon Produit^FS^XZ",
"labelUrl": null
}Pour toute question concernant l'API, contactez-nous à : hello@hou.la
Vous pouvez également consulter notre page de support pour plus d'informations.