API — Exemples v1¶
Ce catalogue fournit des exemples copiables pour tous les endpoints de l'API SalamBot v1. Chaque exemple respecte l'envelope unifié et inclut l'authentification Bearer complète.
Références :
- API — Référence v1 : Spécifications complètes
- Get Started — Quickstart : Configuration initiale
Variables d'environnement¶
NOTE: Tous les exemples utilisent $BASE_URL sans re-préfixer /v1 (ex:
$BASE_URL/messages/analyze).
Configuration bash¶
## Variables globales
export BASE_URL="https://api.salambot.local/v1"
export TOKEN="YOUR_TOKEN"
export TENANT="acme"
export LOCALE="fr-MA"
export REQUEST_ID="00000000-0000-4000-8000-000000000001"
export IDEMPOTENCY_KEY="00000000-0000-4000-8000-000000000002"
export TIMESTAMP="2025-08-14T10:30:00Z"
Configuration Python¶
import os
import requests
import uuid
from datetime import datetime
# Configuration
BASE_URL = "https://api.salambot.local/v1"
TOKEN = os.getenv("SALAMBOT_TOKEN", "YOUR_TOKEN")
TENANT = "acme"
LOCALE = "fr-MA"
## Headers standards
HEADERS = {
"Authorization": f"Bearer {TOKEN}",
"Content-Type": "application/json",
"X-SalamBot-Tenant": TENANT,
"X-Request-Id": str(uuid.uuid4())
}
def get_timestamp():
return datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")
Configuration JavaScript¶
// Configuration
const BASE_URL = 'https://api.salambot.local/v1';
const TOKEN = process.env.SALAMBOT_TOKEN || 'YOUR_TOKEN';
const TENANT = 'acme';
const LOCALE = 'fr-MA';
// Headers standards
const headers = {
Authorization: `Bearer ${TOKEN}`,
'Content-Type': 'application/json',
'X-SalamBot-Tenant': TENANT,
'X-Request-Id': crypto.randomUUID(),
};
const getTimestamp = () => new Date().toISOString();
Authentification & En-têtes¶
NOTE: Ces en-têtes (Authorization, X-SalamBot-Tenant, X-Request-Id,
Idempotency-Key pour POST) s'appliquent aux endpoints de l'API. Exclusion:
webhooks.
En-têtes recommandés¶
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...
Content-Type: application/json; charset=utf-8
X-SalamBot-Tenant: acme
X-Request-Id: 00000000-0000-4000-8000-000000000001
Idempotency-Key: 00000000-0000-4000-8000-000000000002
Exemple d'URL correcte¶
# Correct: utilisation de $BASE_URL sans double /v1
curl -X POST "$BASE_URL/messages/analyze" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json"
Structure envelope standard¶
{
"schema_version": "1.0",
"tenant": "acme",
"channel": "webchat",
"message_id": "msg_123",
"correlation_id": "corr_abc",
"timestamp": "2025-08-14T10:30:00Z",
"locale": "fr-MA",
"data": {
"NOTE": "payload spécifique à l'endpoint"
}
}
Exemples par endpoint¶
POST /v1/messages/analyze¶
Analyse NLU d'un message utilisateur.
cURL - Analyse NLU¶
curl -X POST "$BASE_URL/messages/analyze" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "X-SalamBot-Tenant: $TENANT" \
-H "X-Request-Id: $REQUEST_ID" \
-H "Idempotency-Key: $IDEMPOTENCY_KEY" \
-d '{
"schema_version": "1.0",
"tenant": "acme",
"channel": "webchat",
"message_id": "msg_123",
"correlation_id": "corr_abc",
"timestamp": "2025-08-14T10:30:00Z",
"locale": "fr-MA",
"data": {
"text": "Je veux annuler ma commande",
"lang": "fr"
}
}'
Python - Analyse NLU¶
import requests
import json
url = f"{BASE_URL}/messages/analyze"
payload = {
"schema_version": "1.0",
"tenant": TENANT,
"channel": "webchat",
"message_id": "msg_123",
"correlation_id": "corr_abc",
"timestamp": get_timestamp(),
"locale": LOCALE,
"data": {
"text": "Je veux annuler ma commande",
"lang": "fr"
}
}
headers_with_idempotency = HEADERS.copy()
headers_with_idempotency["Idempotency-Key"] = str(uuid.uuid4())
response = requests.post(url, json=payload, headers=headers_with_idempotency)
print(json.dumps(response.json(), indent=2))
JavaScript - Analyse NLU¶
const analyzeMessage = async () => {
const url = `${BASE_URL}/messages/analyze`;
const payload = {
schema_version: '1.0',
tenant: TENANT,
channel: 'webchat',
message_id: 'msg_123',
correlation_id: 'corr_abc',
timestamp: getTimestamp(),
locale: LOCALE,
data: {
text: 'Je veux annuler ma commande',
lang: 'fr',
},
};
const headersWithIdempotency = {
...headers,
'Idempotency-Key': crypto.randomUUID(),
};
const response = await fetch(url, {
method: 'POST',
headers: headersWithIdempotency,
body: JSON.stringify(payload),
});
return await response.json();
};
Réponse attendue :
{
"schema_version": "1.0",
"tenant": "acme",
"channel": "webchat",
"message_id": "msg_123",
"correlation_id": "corr_abc",
"timestamp": "2025-08-14T10:30:01Z",
"locale": "fr-MA",
"data": {
"intent": "cancel_order",
"confidence": 0.92,
"entities": [
{ "type": "action", "value": "annuler", "start": 8, "end": 15 }
]
}
}
POST /v1/search/query¶
Recherche RAG dans la base de connaissances.
cURL - Recherche RAG¶
curl -X POST "$BASE_URL/v1/search/query" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "X-SalamBot-Tenant: $TENANT" \
-H "X-Request-Id: $REQUEST_ID" \
-H "Idempotency-Key: $IDEMPOTENCY_KEY" \
-d '{
"schema_version": "1.0",
"tenant": "acme",
"channel": "webchat",
"message_id": "msg_456",
"correlation_id": "corr_def",
"timestamp": "2025-08-14T10:32:00Z",
"locale": "fr-MA",
"data": {
"query": "politique de remboursement",
"filters": {"category": "support"},
"limit": 5
}
}'
Python - Recherche RAG¶
url = f"{BASE_URL}/v1/search/query"
payload = {
"schema_version": "1.0",
"tenant": TENANT,
"channel": "webchat",
"message_id": "msg_456",
"correlation_id": "corr_def",
"timestamp": get_timestamp(),
"locale": LOCALE,
"data": {
"query": "politique de remboursement",
"filters": {"category": "support"},
"limit": 5
}
}
headers_with_idempotency = HEADERS.copy()
headers_with_idempotency["Idempotency-Key"] = str(uuid.uuid4())
response = requests.post(url, json=payload, headers=headers_with_idempotency)
print(json.dumps(response.json(), indent=2))
JavaScript - Recherche RAG¶
const searchQuery = async () => {
const url = `${BASE_URL}/v1/search/query`;
const payload = {
schema_version: '1.0',
tenant: TENANT,
channel: 'webchat',
message_id: 'msg_456',
correlation_id: 'corr_def',
timestamp: getTimestamp(),
locale: LOCALE,
data: {
query: 'politique de remboursement',
filters: { category: 'support' },
limit: 5,
},
};
const headersWithIdempotency = {
...headers,
'Idempotency-Key': crypto.randomUUID(),
};
const response = await fetch(url, {
method: 'POST',
headers: headersWithIdempotency,
body: JSON.stringify(payload),
});
return await response.json();
};
Réponse attendue :
{
"schema_version": "1.0",
"tenant": "acme",
"channel": "webchat",
"message_id": "msg_456",
"correlation_id": "corr_def",
"timestamp": "2025-08-14T10:32:01Z",
"locale": "fr-MA",
"data": {
"passages": [
"Les remboursements sont traités sous 7 jours...",
"Pour demander un remboursement, contactez..."
],
"ids": ["doc_123", "doc_456"],
"scores": [0.89, 0.76]
}
}
POST /v1/generate/reply¶
Génération de réponse via LLM Router.
cURL - Génération LLM¶
curl -X POST "$BASE_URL/v1/generate/reply" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "X-SalamBot-Tenant: $TENANT" \
-H "X-Request-Id: $REQUEST_ID" \
-H "Idempotency-Key: $IDEMPOTENCY_KEY" \
-d '{
"schema_version": "1.0",
"tenant": "acme",
"channel": "webchat",
"message_id": "msg_789",
"correlation_id": "corr_ghi",
"timestamp": "2025-08-14T10:34:00Z",
"locale": "fr-MA",
"data": {
"prompt": "Comment puis-je annuler ma commande ?",
"context": [
"L'utilisateur a une commande active #12345",
"Politique: annulation possible sous 24h"
],
"model": "llama3"
}
}'
Python - Génération LLM¶
url = f"{BASE_URL}/v1/generate/reply"
payload = {
"schema_version": "1.0",
"tenant": TENANT,
"channel": "webchat",
"message_id": "msg_789",
"correlation_id": "corr_ghi",
"timestamp": get_timestamp(),
"locale": LOCALE,
"data": {
"prompt": "Comment puis-je annuler ma commande ?",
"context": [
"L'utilisateur a une commande active #12345",
"Politique: annulation possible sous 24h"
],
"model": "llama3"
}
}
headers_with_idempotency = HEADERS.copy()
headers_with_idempotency["Idempotency-Key"] = str(uuid.uuid4())
response = requests.post(url, json=payload, headers=headers_with_idempotency)
print(json.dumps(response.json(), indent=2))
JavaScript - Génération LLM¶
const generateReply = async () => {
const url = `${BASE_URL}/v1/generate/reply`;
const payload = {
schema_version: '1.0',
tenant: TENANT,
channel: 'webchat',
message_id: 'msg_789',
correlation_id: 'corr_ghi',
timestamp: getTimestamp(),
locale: LOCALE,
data: {
prompt: 'Comment puis-je annuler ma commande ?',
context: [
"L'utilisateur a une commande active #12345",
'Politique: annulation possible sous 24h',
],
model: 'llama3',
},
};
const headersWithIdempotency = {
...headers,
'Idempotency-Key': crypto.randomUUID(),
};
const response = await fetch(url, {
method: 'POST',
headers: headersWithIdempotency,
body: JSON.stringify(payload),
});
return await response.json();
};
Réponse attendue :
{
"schema_version": "1.0",
"tenant": "acme",
"channel": "webchat",
"message_id": "msg_789",
"correlation_id": "corr_ghi",
"timestamp": "2025-08-14T10:34:02Z",
"locale": "fr-MA",
"data": {
"reply_text": "Pour annuler votre commande #12345, vous pouvez...",
"model_used": "llama3",
"tokens": 156,
"confidence": 0.94,
"filtered": false
}
}
POST /v1/incidents¶
Création d'incident système.
cURL - Incidents¶
curl -X POST "$BASE_URL/incidents" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "X-SalamBot-Tenant: $TENANT" \
-H "X-Request-Id: $REQUEST_ID" \
-H "Idempotency-Key: $IDEMPOTENCY_KEY" \
-d '{
"schema_version": "1.0",
"tenant": "acme",
"channel": "system",
"message_id": "msg_inc1",
"correlation_id": "corr_inc",
"timestamp": "2025-08-14T10:36:00Z",
"locale": "fr-MA",
"data": {
"trigger": "Panne serveur principal",
"severity": "high",
"details": {
"affected_services": ["webchat", "api"],
"estimated_impact": "30min"
}
}
}'
Python - Incidents¶
url = f"{BASE_URL}/incidents"
payload = {
"schema_version": "1.0",
"tenant": TENANT,
"channel": "system",
"message_id": "msg_inc1",
"correlation_id": "corr_inc",
"timestamp": get_timestamp(),
"locale": LOCALE,
"data": {
"trigger": "Panne serveur principal",
"severity": "high",
"details": {
"affected_services": ["webchat", "api"],
"estimated_impact": "30min"
}
}
}
headers_with_idempotency = HEADERS.copy()
headers_with_idempotency["Idempotency-Key"] = str(uuid.uuid4())
response = requests.post(url, json=payload, headers=headers_with_idempotency)
print(json.dumps(response.json(), indent=2))
Réponse attendue :
{
"schema_version": "1.0",
"tenant": "acme",
"channel": "system",
"message_id": "msg_inc1",
"correlation_id": "corr_inc",
"timestamp": "2025-08-14T10:36:01Z",
"locale": "fr-MA",
"data": {
"ticket_id": "INC-2025-001",
"status": "assigned",
"assignee": "ops-team"
}
}
POST /v1/broadcasts¶
Diffusion de message système.
cURL - Broadcasts¶
curl -X POST "$BASE_URL/broadcasts" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "X-SalamBot-Tenant: $TENANT" \
-H "X-Request-Id: $REQUEST_ID" \
-H "Idempotency-Key: $IDEMPOTENCY_KEY" \
-d '{
"schema_version": "1.0",
"tenant": "acme",
"channel": "admin",
"message_id": "msg_bcast",
"correlation_id": "corr_bcast",
"timestamp": "2025-08-14T10:38:00Z",
"locale": "fr-MA",
"data": {
"message": "Maintenance programmée ce soir 22h-23h",
"audience": "all_users",
"schedule": "2025-08-14T20:00:00Z"
}
}'
Python - Broadcasts¶
url = f"{BASE_URL}/broadcasts"
payload = {
"schema_version": "1.0",
"tenant": TENANT,
"channel": "admin",
"message_id": "msg_bcast",
"correlation_id": "corr_bcast",
"timestamp": get_timestamp(),
"locale": LOCALE,
"data": {
"message": "Maintenance programmée ce soir 22h-23h",
"audience": "all_users",
"schedule": "2025-08-14T20:00:00Z"
}
}
headers_with_idempotency = HEADERS.copy()
headers_with_idempotency["Idempotency-Key"] = str(uuid.uuid4())
response = requests.post(url, json=payload, headers=headers_with_idempotency)
print(json.dumps(response.json(), indent=2))
Réponse attendue :
{
"schema_version": "1.0",
"tenant": "acme",
"channel": "admin",
"message_id": "msg_bcast",
"correlation_id": "corr_bcast",
"timestamp": "2025-08-14T10:38:01Z",
"locale": "fr-MA",
"data": {
"event": "broadcast_scheduled",
"recipients": 1250,
"status": "pending"
}
}
POST /v1/tickets¶
Création de ticket support.
cURL - Tickets¶
curl -X POST "$BASE_URL/tickets" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "X-SalamBot-Tenant: $TENANT" \
-H "X-Request-Id: $REQUEST_ID" \
-H "Idempotency-Key: $IDEMPOTENCY_KEY" \
-d '{
"schema_version": "1.0",
"tenant": "acme",
"channel": "webchat",
"message_id": "msg_tkt",
"correlation_id": "corr_tkt",
"timestamp": "2025-08-14T10:40:00Z",
"locale": "fr-MA",
"data": {
"incident": "Problème complexe non résolu",
"user_context": {
"id": "user_123",
"history": ["previous_interactions"]
},
"priority": "high"
}
}'
Python - Tickets¶
url = f"{BASE_URL}/tickets"
payload = {
"schema_version": "1.0",
"tenant": TENANT,
"channel": "webchat",
"message_id": "msg_tkt",
"correlation_id": "corr_tkt",
"timestamp": get_timestamp(),
"locale": LOCALE,
"data": {
"incident": "Problème complexe non résolu",
"user_context": {
"id": "user_123",
"history": ["previous_interactions"]
},
"priority": "high"
}
}
headers_with_idempotency = HEADERS.copy()
headers_with_idempotency["Idempotency-Key"] = str(uuid.uuid4())
response = requests.post(url, json=payload, headers=headers_with_idempotency)
print(json.dumps(response.json(), indent=2))
Réponse attendue :
{
"schema_version": "1.0",
"tenant": "acme",
"channel": "webchat",
"message_id": "msg_tkt",
"correlation_id": "corr_tkt",
"timestamp": "2025-08-14T10:40:01Z",
"locale": "fr-MA",
"data": {
"ticket_id": "TKT-2025-456",
"status": "assigned",
"eta_hours": 2
}
}
GET /v1/admin/policies¶
Lecture des politiques tenant.
cURL - GET Policies¶
curl -X GET "$BASE_URL/admin/policies" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "X-SalamBot-Tenant: $TENANT" \
-H "X-Request-Id: $REQUEST_ID"
Python - GET Policies¶
url = f"{BASE_URL}/admin/policies"
response = requests.get(url, headers=HEADERS)
print(json.dumps(response.json(), indent=2))
JavaScript - GET Policies¶
const getPolicies = async () => {
const url = `${BASE_URL}/admin/policies`;
const response = await fetch(url, {
method: 'GET',
headers: headers,
});
return await response.json();
};
Réponse attendue :
{
"schema_version": "1.0",
"tenant": "acme",
"channel": "admin",
"message_id": "msg_pol_get",
"correlation_id": "corr_pol",
"timestamp": "2025-08-14T10:42:00Z",
"locale": "fr-MA",
"data": {
"policies": {
"max_tokens": 1000,
"allowed_models": ["llama3"],
"branding": {
"logo_url": "https://cdn.example.com/logo.png",
"colors": "#123456"
}
}
}
}
PUT /v1/admin/policies¶
Mise à jour des politiques tenant.
cURL - PUT Policies¶
curl -X PUT "$BASE_URL/admin/policies" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "X-SalamBot-Tenant: $TENANT" \
-H "X-Request-Id: $REQUEST_ID" \
-H "Idempotency-Key: $IDEMPOTENCY_KEY" \
-d '{
"schema_version": "1.0",
"tenant": "acme",
"channel": "admin",
"message_id": "msg_pol_put",
"correlation_id": "corr_pol_upd",
"timestamp": "2025-08-14T10:44:00Z",
"locale": "fr-MA",
"data": {
"policies": {
"max_tokens": 1500,
"allowed_models": ["llama3", "gpt-4"]
}
}
}'
Python - PUT Policies¶
url = f"{BASE_URL}/admin/policies"
payload = {
"schema_version": "1.0",
"tenant": TENANT,
"channel": "admin",
"message_id": "msg_pol_put",
"correlation_id": "corr_pol_upd",
"timestamp": get_timestamp(),
"locale": LOCALE,
"data": {
"policies": {
"max_tokens": 1500,
"allowed_models": ["llama3", "gpt-4"]
}
}
}
headers_with_idempotency = HEADERS.copy()
headers_with_idempotency["Idempotency-Key"] = str(uuid.uuid4())
response = requests.put(url, json=payload, headers=headers_with_idempotency)
print(json.dumps(response.json(), indent=2))
JavaScript - PUT Policies¶
const updatePolicies = async () => {
const url = `${BASE_URL}/admin/policies`;
const payload = {
schema_version: '1.0',
tenant: TENANT,
channel: 'admin',
message_id: 'msg_pol_put',
correlation_id: 'corr_pol_upd',
timestamp: getTimestamp(),
locale: LOCALE,
data: {
policies: {
max_tokens: 1500,
allowed_models: ['llama3', 'gpt-4'],
},
},
};
const headersWithIdempotency = {
...headers,
'Idempotency-Key': crypto.randomUUID(),
};
const response = await fetch(url, {
method: 'PUT',
headers: headersWithIdempotency,
body: JSON.stringify(payload),
});
return await response.json();
};
Réponse attendue :
{
"schema_version": "1.0",
"tenant": "acme",
"channel": "admin",
"message_id": "msg_pol_put",
"correlation_id": "corr_pol_upd",
"timestamp": "2025-08-14T10:44:01Z",
"locale": "fr-MA",
"data": {
"ack": true,
"version": "v2.1",
"applied_at": "2025-08-14T10:44:01Z"
}
}
GET /v1/health¶
Vérification de santé de l'API.
cURL - Health¶
curl -X GET "$BASE_URL/health" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json"
JavaScript - Health¶
const checkHealth = async () => {
const url = `${BASE_URL}/health`;
const response = await fetch(url, {
method: 'GET',
headers: {
Authorization: `Bearer ${TOKEN}`,
'Content-Type': 'application/json',
},
});
return await response.json();
};
Réponse attendue :
{
"schema_version": "1.0",
"data": {
"status": "ok",
"time": "2025-08-14T10:46:00Z"
}
}
GET /v1/ready¶
Vérification de disponibilité des dépendances.
cURL - Ready¶
curl -X GET "$BASE_URL/ready" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json"
JavaScript - Ready¶
const checkReadiness = async () => {
const url = `${BASE_URL}/ready`;
const response = await fetch(url, {
method: 'GET',
headers: {
Authorization: `Bearer ${TOKEN}`,
'Content-Type': 'application/json',
},
});
return await response.json();
};
Réponse attendue :
{
"schema_version": "1.0",
"data": {
"status": "ready",
"deps": {
"database": "ok",
"redis": "ok",
"llm_service": "ok"
}
}
}
POST /webhooks/facebook¶
Webhook Facebook Messenger.
cURL - Facebook Webhook¶
curl -X POST "$BASE_URL/webhooks/facebook" \
-H "Content-Type: application/json" \
-H "X-Hub-Signature-256: sha256=abc123..." \
-d '{
"schema_version": "1.0",
"tenant": "acme",
"channel": "facebook",
"message_id": "msg_fb",
"correlation_id": "corr_fb",
"timestamp": "2025-08-14T10:48:00Z",
"locale": "fr-MA",
"data": {
"webhook_data": {
"object": "page",
"entry": [{
"messaging": [{
"sender": {"id": "123"},
"message": {"text": "Bonjour"}
}]
}]
}
}
}'
Réponse attendue: 202 Accepted
La tolérance ±5 min est vérifiée à partir des horodatages natifs du payload canal (data.webhook_data).
Vérification HMAC (pseudo-code)¶
import hmac
import hashlib
def verify_webhook_signature(payload, signature, secret_key):
# Calculer la signature HMAC avec le payload brut
expected_signature = hmac.new(
secret_key.encode('utf-8'),
payload.encode('utf-8'),
hashlib.sha256
).hexdigest()
# Comparer les signatures
return hmac.compare_digest(f"sha256={expected_signature}", signature)
POST /webhooks/whatsapp¶
Webhook WhatsApp Business.
cURL - WhatsApp Webhook¶
curl -X POST "$BASE_URL/webhooks/whatsapp" \
-H "Content-Type: application/json" \
-H "X-Hub-Signature-256: sha256=def456..." \
-d '{
"schema_version": "1.0",
"tenant": "acme",
"channel": "whatsapp",
"message_id": "msg_wa",
"correlation_id": "corr_wa",
"timestamp": "2025-08-14T10:50:00Z",
"locale": "fr-MA",
"data": {
"webhook_data": {
"contacts": [{"profile": {"name": "John"}}],
"messages": [{"text": {"body": "Salut"}}]
}
}
}'
Réponse attendue: 202 Accepted
La tolérance ±5 min est vérifiée à partir des horodatages natifs du payload canal (data.webhook_data).
Erreurs & Rate limiting¶
Exemple d'erreur 429 (Rate Limited)¶
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1642694400
Retry-After: 60
{
"schema_version": "1.0",
"error": "rate_limited",
"http": 429,
"message": "Limite de 1000 requêtes/min dépassée",
"correlation_id": "corr_abc",
"retry_after": 60
}
Gestion des erreurs en Python¶
import time
def make_request_with_retry(url, payload, headers, max_retries=3):
for attempt in range(max_retries):
response = requests.post(url, json=payload, headers=headers)
if response.status_code == 429:
retry_after = int(response.headers.get('Retry-After', 60))
print(f"Rate limited. Waiting {retry_after} seconds...")
time.sleep(retry_after)
continue
if response.status_code < 500:
return response
# Retry on 5xx errors
wait_time = 2 ** attempt
print(f"Server error. Retrying in {wait_time} seconds...")
time.sleep(wait_time)
return response
Idempotency¶
Démonstration Idempotency-Key¶
Les requêtes POST avec la même Idempotency-Key retournent le même résultat :
## Première requête (OK)
curl -X POST "$BASE_URL/messages/analyze" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "X-SalamBot-Tenant: $TENANT" \
-H "Idempotency-Key: 00000000-0000-4000-8000-000000000002" \
-d '{
"schema_version":"1.0",
"tenant":"acme",
"channel":"webchat",
"message_id":"msg_ide1",
"correlation_id":"corr_ide1",
"timestamp":"2025-08-14T10:30:00Z",
"locale":"fr-MA",
"data":{"text":"test","lang":"fr"}
}'
## → HTTP 200 OK
## Requête identique (même body, même Idempotency-Key → réutilisation serveur)
curl -X POST "$BASE_URL/messages/analyze" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "X-SalamBot-Tenant: $TENANT" \
-H "Idempotency-Key: 00000000-0000-4000-8000-000000000002" \
-d '{
"schema_version":"1.0",
"tenant":"acme",
"channel":"webchat",
"message_id":"msg_ide1",
"correlation_id":"corr_ide1",
"timestamp":"2025-08-14T10:30:00Z",
"locale":"fr-MA",
"data":{"text":"test","lang":"fr"}
}'
## → HTTP 200 OK (réponse idempotente)
## Même Idempotency-Key mais body différent → mismatch
curl -X POST "$BASE_URL/messages/analyze" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "X-SalamBot-Tenant: $TENANT" \
-H "Idempotency-Key: 00000000-0000-4000-8000-000000000002" \
-d '{
"schema_version":"1.0",
"tenant":"acme",
"channel":"webchat",
"message_id":"msg_ide1",
"correlation_id":"corr_ide1",
"timestamp":"2025-08-14T10:30:00Z",
"locale":"fr-MA",
"data":{"text":"different","lang":"fr"}
}'
## → HTTP 400 Bad Request (idempotency mismatch)
Pagination & filtres¶
Exemple avec paramètres de pagination¶
curl -X GET "$BASE_URL/admin/policies?page=2&page_size=50&sort=created_at:desc" \
-H "Authorization: Bearer $TOKEN" \
-H "X-SalamBot-Tenant: $TENANT"
Filtres avancés¶
NOTE: L'endpoint /v1/search/query est POST-only.
curl -X POST "$BASE_URL/search/query" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "X-SalamBot-Tenant: $TENANT" \
-H "X-Request-Id: $REQUEST_ID" \
-H "Idempotency-Key: $IDEMPOTENCY_KEY" \
-d '{
"schema_version": "1.0",
"tenant": "acme",
"channel": "webchat",
"message_id": "msg_456",
"correlation_id": "corr_def",
"timestamp": "2025-08-14T10:32:00Z",
"locale": "fr-MA",
"data": {
"query": "remboursement",
"filters": {
"category": "support",
"lang": "fr"
},
"limit": 5
}
}'
Webhooks¶
Vérification signature Facebook (pseudo-code)¶
import hmac
import hashlib
def verify_facebook_signature(payload, signature, secret):
"""
Vérifie la signature X-Hub-Signature-256 de Facebook
"""
expected_signature = 'sha256=' + hmac.new(
secret.encode('utf-8'),
payload.encode('utf-8'),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected_signature, signature)
## Utilisation
payload = request.get_data(as_text=True)
signature = request.headers.get('X-Hub-Signature-256')
secret = os.getenv('FACEBOOK_APP_SECRET')
if not verify_facebook_signature(payload, signature, secret):
return {'error': 'Invalid signature'}, 401
Annexes¶
Snippet complet d'environnement¶
#!/bin/bash
## setup-env.sh
## Configuration SalamBot API
export BASE_URL="https://api.salambot.local/v1"
export TOKEN="YOUR_TOKEN_HERE"
export TENANT="acme"
export LOCALE="fr-MA"
## Génération d'IDs uniques
export REQUEST_ID=$(uuidgen | tr '[:upper:]' '[:lower:]')
export IDEMPOTENCY_KEY=$(uuidgen | tr '[:upper:]' '[:lower:]')
export TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
echo "Environment configured:"
echo " BASE_URL: $BASE_URL"
echo " TENANT: $TENANT"
echo " LOCALE: $LOCALE"
echo " REQUEST_ID: $REQUEST_ID"
echo " IDEMPOTENCY_KEY: $IDEMPOTENCY_KEY"
echo " TIMESTAMP: $TIMESTAMP"
Client Python complet¶
#!/usr/bin/env python3
## salambot_client.py
import os
import requests
import uuid
import json
from datetime import datetime
from typing import Dict, Any, Optional
class SalamBotClient:
def __init__(self, base_url: str, token: str, tenant: str = "acme",
locale: str = "fr-MA"):
self.base_url = base_url.rstrip('/')
self.tenant = tenant
self.locale = locale
self.headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
"X-SalamBot-Tenant": tenant
}
def _make_envelope(self, channel: str, data: Dict[str, Any],
message_id: Optional[str] = None,
correlation_id: Optional[str] = None) -> Dict[str, Any]:
return {
"schema_version": "1.0",
"tenant": self.tenant,
"channel": channel,
"message_id": message_id or f"msg_{uuid.uuid4().hex[:8]}",
"correlation_id": correlation_id or f"corr_{uuid.uuid4().hex[:8]}",
"timestamp": datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ"),
"locale": self.locale,
"data": data
}
def analyze_message(self, text: str, lang: str = "fr") -> Dict[str, Any]:
url = f"{self.base_url}/messages/analyze"
payload = self._make_envelope("webchat", {"text": text, "lang": lang})
headers = self.headers.copy()
headers["X-Request-Id"] = str(uuid.uuid4())
headers["Idempotency-Key"] = str(uuid.uuid4())
response = requests.post(url, json=payload, headers=headers)
return response.json()
def search_query(self, query: str, filters: Optional[Dict] = None,
limit: int = 5) -> Dict[str, Any]:
url = f"{self.base_url}/search/query"
data = {"query": query, "limit": limit}
if filters:
data["filters"] = filters
payload = self._make_envelope("webchat", data)
headers = self.headers.copy()
headers["X-Request-Id"] = str(uuid.uuid4())
headers["Idempotency-Key"] = str(uuid.uuid4())
response = requests.post(url, json=payload, headers=headers)
return response.json()
def generate_reply(self, prompt: str, context: Optional[list] = None,
model: str = "llama3") -> Dict[str, Any]:
url = f"{self.base_url}/generate/reply"
data = {"prompt": prompt, "model": model}
if context:
data["context"] = context
payload = self._make_envelope("webchat", data)
headers = self.headers.copy()
headers["X-Request-Id"] = str(uuid.uuid4())
headers["Idempotency-Key"] = str(uuid.uuid4())
response = requests.post(url, json=payload, headers=headers)
return response.json()
# Utilisation
if __name__ == "__main__":
client = SalamBotClient(
base_url="https://api.salambot.local/v1",
token=os.getenv("SALAMBOT_TOKEN", "YOUR_TOKEN"),
tenant="acme",
locale="fr-MA"
)
# Test NLU
result = client.analyze_message("Je veux annuler ma commande")
print("NLU Result:", json.dumps(result, indent=2))
# Test RAG
result = client.search_query("politique de remboursement")
print("Search Result:", json.dumps(result, indent=2))
# Test LLM
result = client.generate_reply("Comment puis-je vous aider ?")
print("Generated Reply:", json.dumps(result, indent=2))
Client JavaScript/Node.js complet¶
#!/usr/bin/env node
// salambot-client.js
const crypto = require('crypto');
class SalamBotClient {
constructor(baseUrl, token, tenant = 'acme', locale = 'fr-MA') {
this.baseUrl = baseUrl.replace(/\/$/, '');
this.tenant = tenant;
this.locale = locale;
this.headers = {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
'X-SalamBot-Tenant': tenant,
};
}
_makeEnvelope(channel, data, messageId = null, correlationId = null) {
return {
schema_version: '1.0',
tenant: this.tenant,
channel: channel,
message_id: messageId || `msg_${crypto.randomBytes(4).toString('hex')}`,
correlation_id:
correlationId || `corr_${crypto.randomBytes(4).toString('hex')}`,
timestamp: new Date().toISOString(),
locale: this.locale,
data: data,
};
}
async analyzeMessage(text, lang = 'fr') {
const url = `${this.baseUrl}/messages/analyze`;
const payload = this._makeEnvelope('webchat', { text, lang });
const headers = {
...this.headers,
'X-Request-Id': crypto.randomUUID(),
'Idempotency-Key': crypto.randomUUID(),
};
const response = await fetch(url, {
method: 'POST',
headers: headers,
body: JSON.stringify(payload),
});
return await response.json();
}
async searchQuery(query, filters = null, limit = 5) {
const url = `${this.baseUrl}/search/query`;
const data = { query, limit };
if (filters) data.filters = filters;
const payload = this._makeEnvelope('webchat', data);
const headers = {
...this.headers,
'X-Request-Id': crypto.randomUUID(),
'Idempotency-Key': crypto.randomUUID(),
};
const response = await fetch(url, {
method: 'POST',
headers: headers,
body: JSON.stringify(payload),
});
return await response.json();
}
async generateReply(prompt, context = null, model = 'llama3') {
const url = `${this.baseUrl}/generate/reply`;
const data = { prompt, model };
if (context) data.context = context;
const payload = this._makeEnvelope('webchat', data);
const headers = {
...this.headers,
'X-Request-Id': crypto.randomUUID(),
'Idempotency-Key': crypto.randomUUID(),
};
const response = await fetch(url, {
method: 'POST',
headers: headers,
body: JSON.stringify(payload),
});
return await response.json();
}
}
// Utilisation
if (require.main === module) {
const client = new SalamBotClient(
'https://api.salambot.local/v1',
process.env.SALAMBOT_TOKEN || 'YOUR_TOKEN',
'acme',
'fr-MA',
);
(async () => {
try {
// Test NLU
const nluResult = await client.analyzeMessage(
'Je veux annuler ma commande',
);
console.log('NLU Result:', JSON.stringify(nluResult, null, 2));
// Test RAG
const searchResult = await client.searchQuery(
'politique de remboursement',
);
console.log('Search Result:', JSON.stringify(searchResult, null, 2));
// Test LLM
const replyResult = await client.generateReply(
'Comment puis-je vous aider ?',
);
console.log('Generated Reply:', JSON.stringify(replyResult, null, 2));
} catch (error) {
console.error('Error:', error);
}
})();
}
module.exports = SalamBotClient;