API Waxal
Envoyez un audio en wolof, recevez la transcription, la traduction française et l'analyse de l'échange : nature, humeur, contentement, urgence, actions. Pour les centres d'appels, les CRM, les opérateurs et les bots.
- Adresse de production
- https://api.waaxal.com/v1
- Adresse utilisée dans les exemples
- https://api.waaxal.com/v1
- Version du contrat
- 1.0 · 27/09/2026
- Format
- JSON UTF-8, dates ISO 8601 UTC, durées en secondes
Obtenir l'accès
Tout le monde peut lire cette documentation et la spécification OpenAPI. Pour créer des clés, votre organisation doit obtenir un accès, accordé par l'équipe Waxal après examen de votre demande.
- Créez un compte gratuit, ou connectez-vous.
- Demandez l'accès : « Tester l'API » crée un Espace API personnel et envoie la demande ; une organisation existante la fait depuis son Espace développeur. Décrivez votre projet, votre cas d'usage et le volume attendu.
- L'équipe Waxal examine la demande et vous répond par e-mail, avec le motif en cas de refus.
- Bac à sable, puis production : le bac à sable permet les clés
wx_test_(résultats simulés, gratuits). Quand votre intégration est prête, demandez la production pour les cléswx_live_.
Sans accès accordé, la création d'une clé répond 403 api_access_required ; une clé live en bac à sable, 403 production_access_required.
Démarrage en 5 minutes
- Une fois le bac à sable accordé, créez une clé de test
wx_test_…dans l'Espace développeur : elle n'est affichée qu'une seule fois. - Rangez la clé dans une variable d'environnement :
export WAXAL_API_KEY=wx_test_… - Créez une transcription à partir de l'URL https d'un fichier audio. La réponse arrive tout de suite (202), le traitement continue en arrière-plan.
- Suivez le statut, ou mieux, recevez un webhook ; puis lisez le résultat.
curl -X POST https://api.waaxal.com/v1/transcriptions \
-H "Authorization: Bearer $WAXAL_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: appel-1234-v1" \
-d '{
"audio_url": "https://exemple.sn/appels/appel-1234.mp3",
"consent": true,
"outputs": ["transcript", "translation", "analysis"],
"external_id": "appel-1234",
"callback_url": "https://crm.exemple.sn/hooks/waxal"
}'# Statut et progression
curl https://api.waaxal.com/v1/transcriptions/tr_xxxxxxxx \
-H "Authorization: Bearer $WAXAL_API_KEY"
# Résultat, une fois status = "completed" (sinon 409 not_ready)
curl https://api.waaxal.com/v1/transcriptions/tr_xxxxxxxx/result \
-H "Authorization: Bearer $WAXAL_API_KEY"Avec une clé de test, l'audio est vérifié (format, durée) mais pas écouté : le résultat est simulé, réaliste et gratuit, marqué livemode: false.
Authentification et clés
Chaque requête porte l'en-tête Authorization: Bearer <clé>. Une clé appartient à une organisation ; elle se crée, se restreint par adresse IP, se fait tourner et se révoque dans l'Espace développeur de l'application, par l'administrateur de l'organisation. Waxal n'en garde qu'une empreinte : la valeur complète n'est montrée qu'une fois.
| Préfixe | Mode | Résultats | Coût et quota |
|---|---|---|---|
| wx_live_… | Production | Réels | Comptés dans le quota mensuel de l'organisation |
| wx_test_… | Bac à sable | Simulés, déterministes, conservés 7 jours | Gratuit, hors quota |
Portées
| Portée | Autorise |
|---|---|
| transcriptions:write | Créer, réanalyser, demander une URL d'envoi |
| transcriptions:read | Statut, résultat, exports, liste |
| transcriptions:delete | Suppression définitive |
| glossary:read / glossary:write | Glossaire (mémoire) de l'organisation |
| webhooks:manage | Abonnements aux webhooks |
| usage:read | Consommation |
curl https://api.waaxal.com/v1/transcriptions?limit=5 \
-H "Authorization: Bearer $WAXAL_API_KEY"Clé révoquée ou expirée : 401 invalid_api_key. Portée manquante : 403 insufficient_scope (le champ param donne la portée requise). Adresse IP non autorisée : 403 ip_not_allowed.
Envoyer un audio
Trois façons d'envoyer l'audio à POST /v1/transcriptions. Formats : mp3, wav, m4a/mp4, ogg/opus, webm, aac, flac. Durée maximale : 30 minutes, sinon la transcription échoue avec duration_exceeded.
1. Par URL (le plus simple)
Waxal télécharge le fichier depuis audio_url (https uniquement, 500 Mo au plus, 60 s de délai). Les adresses internes ou privées sont refusées : 400 audio_url_forbidden.
curl -X POST https://api.waaxal.com/v1/transcriptions \
-H "Authorization: Bearer $WAXAL_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: appel-1234-v1" \
-d '{
"audio_url": "https://exemple.sn/appels/appel-1234.mp3",
"consent": true,
"outputs": ["transcript", "translation", "analysis"],
"external_id": "appel-1234",
"callback_url": "https://crm.exemple.sn/hooks/waxal"
}'2. Envoi direct (25 Mo au plus)
En multipart/form-data : le fichier dans le champ file, les autres champs en parties du formulaire, metadata en JSON sérialisé.
# Envoi direct (≤ 25 Mo) en multipart/form-data
curl -X POST https://api.waaxal.com/v1/transcriptions \
-H "Authorization: Bearer $WAXAL_API_KEY" \
-F "file=@appel-1234.mp3" \
-F "consent=true" \
-F 'outputs=["transcript","translation"]' \
-F 'metadata={"agent_id":"A12","channel":"phone"}'3. Upload signé (gros fichiers)
Demandez une URL d'envoi à POST /v1/uploads, envoyez le fichier dessus en PUT avec exactement les en-têtes reçus, puis créez la transcription avec upload_id.
# 1. Demander une URL d'envoi
curl -X POST https://api.waaxal.com/v1/uploads \
-H "Authorization: Bearer $WAXAL_API_KEY" \
-H "Content-Type: application/json" \
-d '{"filename": "appel-1234.wav", "content_type": "audio/wav", "size_bytes": 84213760}'
# → {"upload_id": "...", "upload_url": "https://...", "method": "PUT", "headers": {...}, "expires_at": "..."}
# 2. Envoyer le fichier sur upload_url, avec exactement les en-têtes reçus
curl -X PUT "<upload_url>" -H "Content-Type: audio/wav" --upload-file appel-1234.wav
# 3. Créer la transcription
curl -X POST https://api.waaxal.com/v1/transcriptions \
-H "Authorization: Bearer $WAXAL_API_KEY" \
-H "Content-Type: application/json" \
-d '{"upload_id": "<upload_id>", "consent": true}'Champs du corps
| Champ | Rôle |
|---|---|
| consent | Obligatoire, true : vous déclarez avoir informé les personnes enregistrées. Sinon 400 consent_required. |
| legal_basis | consent (défaut), contract, legal_obligation ou legitimate_interest. |
| outputs | Sorties demandées, voir la section suivante. |
| external_id | Votre identifiant (appel, ticket), 200 caractères au plus. Non unique : il sert à relier et à filtrer. |
| metadata | Objet plat, 20 clés au plus, valeurs de 500 caractères au plus ; renvoyé tel quel. |
| tags | 10 étiquettes au plus, de 1 à 40 caractères. |
| title, context, exchange_type_hint | Titre, contexte et type d'échange attendu, pour guider l'analyse. |
| callback_url | https ; reçoit les événements de cette transcription, signés avec le secret d'organisation. |
| quality | standard (défaut) ou high : plus précis, plus lent. |
| training_consent | false par défaut : autorise ou non l'usage de l'audio pour améliorer la reconnaissance du wolof. |
Sorties à la carte
Vous ne payez que ce que vous demandez : sans analysis, pas de coût d'analyse. Une combinaison invalide donne 400 invalid_outputs ; si l'analyse est momentanément désactivée, 409 analysis_unavailable.
// Transcription wolof seule (pas de traduction, pas d'analyse)
{ "outputs": ["transcript"] }
// Transcription + traduction française
{ "outputs": ["transcript", "translation"] }
// Transcription + traduction + analyse (valeur par défaut)
{ "outputs": ["transcript", "translation", "analysis"] }Sans traduction, french vaut null dans chaque segment et cultural_notes est vide. Sans analyse, analysis vaut null. POST /v1/transcriptions/{id}/reanalyze ajoute l'analyse après coup.
Statut et résultat
L'objet Transcription donne l'état : status (queued, processing, completed, failed), l'étape en cours stage (upload, transcription, translation, analysis, done) et progress de 0 à 100, qui ne recule jamais. En cas d'échec, error contient un code et un message en français.
Une fois completed, GET /v1/transcriptions/{id}/result renvoie les segments wolof et français horodatés, les locuteurs, la confiance, les passages incertains, les notes culturelles et l'analyse. Avant la fin : 409 not_ready.
{
"id": "tr_xxxxxxxx",
"object": "transcription.result",
"languages": ["wo", "fr"],
"speakers": [{ "label": "Locuteur 1", "name": null }],
"segments": [
{
"index": 0, "start": 0.0, "end": 4.2, "speaker": "Locuteur 1",
"wolof": "…", "french": "…",
"confidence": 0.93, "uncertain": [], "corrected": false
}
],
"cultural_notes": [],
"analysis": {
"nature": "conversation", "speaker_count": 2,
"summary": "…", "exchange_type": "complaint",
"sentiment": { "label": "negative", "score": -0.4 },
"satisfaction": { "label": "dissatisfied", "score": 0.25, "reason": "…" },
"speakers": [ … ], "timeline": [ … ],
"main_reason": "…", "actions": ["…"],
"urgency": "high", "churn_risk": "medium"
}
}Exports : GET /v1/transcriptions/{id}/export?format=txt|srt|pdf&lang=wolof|french|both ; le PDF exige analysis. Une transcription créée par l'API apparaît aussi dans l'Historique de l'organisation, marquée « API ».
Webhooks et signature
Plutôt que d'interroger le statut, abonnez votre serveur (https) aux événements : transcription.completed, transcription.failed, analysis.completed (après une réanalyse), transcription.deleted et ping (test). L'événement est léger : récupérez ensuite le résultat avec votre clé.
POST /hooks/waxal HTTP/1.1
Content-Type: application/json
Waxal-Signature: t=1790000000,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
Waxal-Event-Id: evt_xxxxxxxx
Waxal-Event-Type: transcription.completed
{
"id": "evt_xxxxxxxx",
"object": "event",
"type": "transcription.completed",
"created_at": "2026-09-27T10:12:00Z",
"livemode": true,
"data": { "transcription": { "id": "tr_xxxxxxxx", "status": "completed", "external_id": "appel-1234", "result_url": "/v1/transcriptions/tr_xxxxxxxx/result", "…": "…" } }
}Vérifier la signature
- Lisez l'en-tête
Waxal-Signature: t=<horodatage unix>,v1=<hex>. Pendant une rotation de secret, il peut contenir plusieursv1. - Calculez
HMAC_SHA256(secret, "<t>.<corps brut>")en hexadécimal, sur le corps brut, avant tout décodage JSON. - Comparez en temps constant avec chaque
v1; acceptez si l'un correspond. - Refusez si
tdate de plus de 5 minutes : c'est la protection contre le rejeu. - Répondez 2xx en moins de 10 s, et dédupliquez sur
Waxal-Event-Id: un événement peut arriver deux fois, et l'ordre n'est pas garanti.
import hashlib, hmac, os, time
from flask import Flask, abort, request
app = Flask(__name__)
SECRET = os.environ["WAXAL_WEBHOOK_SECRET"] # whsec_…
def verify_waxal_signature(payload: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
items = [p.strip().split("=", 1) for p in header.split(",") if "=" in p]
timestamp = next((v for k, v in items if k == "t"), None)
signatures = [v for k, v in items if k == "v1"]
if not timestamp or not timestamp.isdigit() or not signatures:
return False
if abs(time.time() - int(timestamp)) > tolerance: # plus de 5 min : rejeu refusé
return False
signed = timestamp.encode() + b"." + payload
expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
return any(hmac.compare_digest(expected, s) for s in signatures)
@app.post("/hooks/waxal")
def waxal_hook():
payload = request.get_data() # corps BRUT, avant tout décodage JSON
if not verify_waxal_signature(payload, request.headers.get("Waxal-Signature", ""), SECRET):
abort(400)
event = request.get_json()
# Dédupliquez sur event["id"] (= en-tête Waxal-Event-Id) : une livraison peut arriver deux fois.
if event["type"] == "transcription.completed":
... # récupérez le résultat avec votre clé : GET /v1/transcriptions/{id}/result
return "", 204Relances
Sans réponse 2xx, Waxal relance après 1 min, 5 min, 30 min, 2 h, 6 h, 12 h et 24 h (8 tentatives au total). Après 50 échecs consécutifs, l'abonnement est désactivé et l'administrateur de l'organisation est prévenu. Dans l'Espace développeur, chaque livraison affiche son code HTTP, ses tentatives et la prochaine relance, avec un bouton « Rejouer ».
Un callback_url donné à la création reçoit les mêmes événements pour cette transcription, signés avec le secret d'organisation (Espace développeur) au lieu du secret d'un abonnement.
Erreurs et codes
Toute erreur a la même forme. Le code est stable et fait foi pour vos programmes ; le message est en français, affichable tel quel.
{
"error": {
"type": "invalid_request",
"code": "consent_required",
"message": "…",
"param": "consent",
"request_id": "req_…"
}
}| HTTP | type | code | Quand |
|---|---|---|---|
| 400 | invalid_request | consent_required | consent absent ou différent de true |
| 400 | invalid_request | invalid_outputs | combinaison d'outputs invalide |
| 400 | invalid_request | audio_url_forbidden | URL non https, interne ou privée |
| 401 | authentication | invalid_api_key | clé absente, inconnue, révoquée ou expirée |
| 403 | permission | insufficient_scope | portée manquante (param = portée requise) |
| 403 | permission | ip_not_allowed | adresse IP hors de la liste de la clé |
| 403 | permission | org_suspended | organisation suspendue |
| 403 | permission | api_access_required | création de clé sans accès accordé (Espace développeur) |
| 403 | permission | production_access_required | clé live avec un accès bac à sable |
| 404 | not_found | not_available_yet | ressource réservée (/v1/streams, /v1/batches) |
| 409 | conflict | not_ready | résultat demandé avant la fin |
| 409 | conflict | idempotency_conflict | même Idempotency-Key, corps différent |
| 409 | conflict | analysis_unavailable | analyse momentanément désactivée |
| 429 | rate_limit | rate_limited | trop de requêtes : attendre Retry-After |
| 429 | rate_limit | concurrency_limited | trop de transcriptions en cours |
| 429 | quota | quota_exceeded | quota mensuel de l'organisation atteint |
| 503 | server | maintenance | maintenance, avec le message |
Chaque réponse porte l'en-tête Waxal-Request-Id. Donnez-le à l'équipe Waxal pour toute question : il retrouve la requête dans le journal, visible aussi dans l'Espace développeur.
Limites
| Limite | Live | Test |
|---|---|---|
| Débit par clé | 120 requêtes/min | 60 requêtes/min |
| Transcriptions en attente ou en cours, par organisation | 20 | 5 |
| Quota mensuel | Celui de l'organisation | Hors quota |
| Envoi direct | 25 Mo | 25 Mo |
| Téléchargement par URL | 500 Mo, 60 s | 500 Mo, 60 s |
| Durée d'un audio | 30 min | 30 min |
| Abonnements webhook par organisation | 10 | 10 |
En-têtes sur chaque réponse : X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset. Au-delà : 429 avec Retry-After.
Idempotence
Un réseau coupé au mauvais moment, et votre programme renvoie la même demande : sans précaution, l'appel serait traité deux fois. Ajoutez l'en-tête Idempotency-Key (255 caractères au plus) à tout POST. La même clé et le même corps sur 24 h donnent la même réponse ; la même clé avec un autre corps donne 409 idempotency_conflict.
curl -X POST https://api.waaxal.com/v1/transcriptions \
-H "Authorization: Bearer $WAXAL_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 3cx-appel-88213" \
-d '{"audio_url": "https://exemple.sn/appels/appel-1234.mp3", "consent": true}'
# Même clé + même corps sous 24 h → même réponse, aucun doublon.
# Même clé + corps différent → 409 idempotency_conflict.SDK
Deux SDK officiels, dans le dépôt Waxal : @waxal/sdk pour TypeScript et JavaScript (Node 18+ et navigateur, fetch natif), et waxal pour Python. Le SDK TypeScript gère l'authentification, les relances sur 429 et 5xx, une Idempotency-Key automatique à la création, l'attente de fin de traitement et la vérification de signature des webhooks (verifySignature).
# pip install waxal
# Le SDK Python est documenté dans son README (sdk/python du dépôt Waxal).
# Sans SDK, les exemples Python de cette page n'utilisent que la bibliothèque requests.Référence des routes
Base : https://api.waaxal.com/v1. La spécification complète, à importer dans Postman ou dans un générateur de client, est publiée sur /v1/openapi.json (OpenAPI 3.1).
| Méthode | Route | Portée | Réponse |
|---|---|---|---|
| POST | /v1/transcriptions | transcriptions:write | 202 Transcription |
| GET | /v1/transcriptions | transcriptions:read | Liste paginée (status, external_id, tag, created_gte, created_lte) |
| GET | /v1/transcriptions/{id} | transcriptions:read | Transcription |
| GET | /v1/transcriptions/{id}/result | transcriptions:read | TranscriptionResult · 409 not_ready |
| GET | /v1/transcriptions/{id}/export | transcriptions:read | Fichier txt, srt ou pdf (lang = wolof, french, both) |
| POST | /v1/transcriptions/{id}/reanalyze | transcriptions:write | 202 Transcription |
| DELETE | /v1/transcriptions/{id} | transcriptions:delete | 204, suppression définitive |
| POST | /v1/uploads | transcriptions:write | URL d'envoi signée (PUT) |
| GET | /v1/webhooks | webhooks:manage | Liste (sans secret) |
| POST | /v1/webhooks | webhooks:manage | 201 Webhook + secret affiché une fois |
| GET | /v1/webhooks/{id} | webhooks:manage | Webhook + 20 dernières livraisons |
| PATCH | /v1/webhooks/{id} | webhooks:manage | url, events, enabled, description |
| POST | /v1/webhooks/{id}/rotate-secret | webhooks:manage | Nouveau secret (l'ancien : 24 h) |
| POST | /v1/webhooks/{id}/test | webhooks:manage | Envoie un événement ping |
| DELETE | /v1/webhooks/{id} | webhooks:manage | 204 |
| GET | /v1/glossary | glossary:read | Liste (q, status, kind) |
| POST | /v1/glossary | glossary:write | 201 GlossaryEntry, validée |
| PATCH | /v1/glossary/{id} | glossary:write | GlossaryEntry |
| DELETE | /v1/glossary/{id} | glossary:write | 204 |
| GET | /v1/usage | usage:read | Consommation (from, to, group_by = day | key) |
| GET | /v1/openapi.json | aucune | Spécification OpenAPI 3.1 |
Listes : ?limit= (1 à 100, 20 par défaut) et ?starting_after=<id>, réponse {"object": "list", "data": [...], "has_more": …}. Identifiants préfixés : tr_, whk_, evt_, gls_.
Bientôt
Prévus dans l'architecture, pas encore ouverts. Ces routes répondent aujourd'hui 404 not_available_yet.
- Temps réel ·
/v1/streams - À venir
- Lots ·
/v1/batches - À venir
Temps réel : la transcription d'un appel en cours, au fil de la conversation. Lots : l'envoi de nombreux audios en une fois. Les clés, les portées et les webhooks actuels resteront valables.