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.

  1. Créez un compte gratuit, ou connectez-vous.
  2. 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.
  3. L'équipe Waxal examine la demande et vous répond par e-mail, avec le motif en cas de refus.
  4. 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és wx_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

  1. 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.
  2. Rangez la clé dans une variable d'environnement : export WAXAL_API_KEY=wx_test_…
  3. 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.
  4. 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.

Modes de clé
PréfixeModeRésultatsCoût et quota
wx_live_…ProductionRéelsComptés dans le quota mensuel de l'organisation
wx_test_…Bac à sableSimulés, déterministes, conservés 7 joursGratuit, hors quota

Portées

Portées des clés
PortéeAutorise
transcriptions:writeCréer, réanalyser, demander une URL d'envoi
transcriptions:readStatut, résultat, exports, liste
transcriptions:deleteSuppression définitive
glossary:read / glossary:writeGlossaire (mémoire) de l'organisation
webhooks:manageAbonnements aux webhooks
usage:readConsommation
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

Champs de création d'une transcription
ChampRôle
consentObligatoire, true : vous déclarez avoir informé les personnes enregistrées. Sinon 400 consent_required.
legal_basisconsent (défaut), contract, legal_obligation ou legitimate_interest.
outputsSorties demandées, voir la section suivante.
external_idVotre identifiant (appel, ticket), 200 caractères au plus. Non unique : il sert à relier et à filtrer.
metadataObjet plat, 20 clés au plus, valeurs de 500 caractères au plus ; renvoyé tel quel.
tags10 étiquettes au plus, de 1 à 40 caractères.
title, context, exchange_type_hintTitre, contexte et type d'échange attendu, pour guider l'analyse.
callback_urlhttps ; reçoit les événements de cette transcription, signés avec le secret d'organisation.
qualitystandard (défaut) ou high : plus précis, plus lent.
training_consentfalse 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

  1. Lisez l'en-tête Waxal-Signature: t=<horodatage unix>,v1=<hex>. Pendant une rotation de secret, il peut contenir plusieurs v1.
  2. Calculez HMAC_SHA256(secret, "<t>.<corps brut>") en hexadécimal, sur le corps brut, avant tout décodage JSON.
  3. Comparez en temps constant avec chaque v1 ; acceptez si l'un correspond.
  4. Refusez si t date de plus de 5 minutes : c'est la protection contre le rejeu.
  5. 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 "", 204

Relances

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_…"
  }
}
Codes d'erreur
HTTPtypecodeQuand
400invalid_requestconsent_requiredconsent absent ou différent de true
400invalid_requestinvalid_outputscombinaison d'outputs invalide
400invalid_requestaudio_url_forbiddenURL non https, interne ou privée
401authenticationinvalid_api_keyclé absente, inconnue, révoquée ou expirée
403permissioninsufficient_scopeportée manquante (param = portée requise)
403permissionip_not_allowedadresse IP hors de la liste de la clé
403permissionorg_suspendedorganisation suspendue
403permissionapi_access_requiredcréation de clé sans accès accordé (Espace développeur)
403permissionproduction_access_requiredclé live avec un accès bac à sable
404not_foundnot_available_yetressource réservée (/v1/streams, /v1/batches)
409conflictnot_readyrésultat demandé avant la fin
409conflictidempotency_conflictmême Idempotency-Key, corps différent
409conflictanalysis_unavailableanalyse momentanément désactivée
429rate_limitrate_limitedtrop de requêtes : attendre Retry-After
429rate_limitconcurrency_limitedtrop de transcriptions en cours
429quotaquota_exceededquota mensuel de l'organisation atteint
503servermaintenancemaintenance, 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

Limites de l'API
LimiteLiveTest
Débit par clé120 requêtes/min60 requêtes/min
Transcriptions en attente ou en cours, par organisation205
Quota mensuelCelui de l'organisationHors quota
Envoi direct25 Mo25 Mo
Téléchargement par URL500 Mo, 60 s500 Mo, 60 s
Durée d'un audio30 min30 min
Abonnements webhook par organisation1010

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

Routes de l'API v1
MéthodeRoutePortéeRéponse
POST/v1/transcriptionstranscriptions:write202 Transcription
GET/v1/transcriptionstranscriptions:readListe paginée (status, external_id, tag, created_gte, created_lte)
GET/v1/transcriptions/{id}transcriptions:readTranscription
GET/v1/transcriptions/{id}/resulttranscriptions:readTranscriptionResult · 409 not_ready
GET/v1/transcriptions/{id}/exporttranscriptions:readFichier txt, srt ou pdf (lang = wolof, french, both)
POST/v1/transcriptions/{id}/reanalyzetranscriptions:write202 Transcription
DELETE/v1/transcriptions/{id}transcriptions:delete204, suppression définitive
POST/v1/uploadstranscriptions:writeURL d'envoi signée (PUT)
GET/v1/webhookswebhooks:manageListe (sans secret)
POST/v1/webhookswebhooks:manage201 Webhook + secret affiché une fois
GET/v1/webhooks/{id}webhooks:manageWebhook + 20 dernières livraisons
PATCH/v1/webhooks/{id}webhooks:manageurl, events, enabled, description
POST/v1/webhooks/{id}/rotate-secretwebhooks:manageNouveau secret (l'ancien : 24 h)
POST/v1/webhooks/{id}/testwebhooks:manageEnvoie un événement ping
DELETE/v1/webhooks/{id}webhooks:manage204
GET/v1/glossaryglossary:readListe (q, status, kind)
POST/v1/glossaryglossary:write201 GlossaryEntry, validée
PATCH/v1/glossary/{id}glossary:writeGlossaryEntry
DELETE/v1/glossary/{id}glossary:write204
GET/v1/usageusage:readConsommation (from, to, group_by = day | key)
GET/v1/openapi.jsonaucuneSpé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.