Référence de l'API publique
Contrat route par route pour le canal API - exécution serveur-à-serveur et client-à-serveur, streaming SSE, signature HMAC, continuité de conversation et gestion des credentials.
Cette référence documente les points d'accès d'intégration publics exposés par le service Core. Les API d'administration (flows, tenants, secrets) ne font pas partie du contrat public et ne sont pas documentées ici.
URL de base
Les points d'accès publics sont servis à la racine de l'origine du service Core (pas de préfixe /api/v1). Remplacez
https://api.example.com par l'origine de votre déploiement.
Modes d'intégration
Le canal API gère les appels serveur-à-serveur et client-à-serveur. Le type de clé détermine le mode ; la route, le corps de requête et la réponse sont identiques.
| Mode | Clé | Appelant typique | Vérification d'origine |
|---|---|---|---|
| Serveur-à-serveur | nx_live_ (prod) · nx_test_ (dev/staging) | Service backend, pipeline CI, script côté serveur | Non vérifiée - ne jamais utiliser depuis un binaire client ou navigateur |
| Client-à-serveur | nx_pub_ | Application mobile (iOS/Android), SPA, client natif | Évaluée par rapport à allowed_origins quand Origin est présent |
OPTIONS /public/v1/api/execute - Préflighting CORS
Pour les clients navigateur et mobile web qui envoient un préflight avant le POST effectif.
Aucune authentification requise.
OPTIONS /public/v1/api/executeEn-têtes de réponse :
| En-tête | Valeur |
|---|---|
Access-Control-Allow-Headers | Authorization, Content-Type |
Access-Control-Allow-Methods | POST, OPTIONS |
Access-Control-Allow-Origin | Reflète l'Origin de la requête |
Access-Control-Allow-Credentials n'est jamais renseigné.
POST /public/v1/api/execute - Exécuter un flow
Exécute le flow lié au canal identifié par la clé d'API. Retourne le résultat en JSON ou sous forme de flux SSE en direct.
POST /public/v1/api/execute
Authorization: Bearer <clé>
Content-Type: application/jsonAuthentification
| Credential | Appelant | Environnement | Comportement de l'origine |
|---|---|---|---|
nx_live_ | Backend de confiance | Canaux de production uniquement | Non évaluée |
nx_test_ | Backend de confiance | Canaux dev / staging uniquement | Non évaluée |
nx_pub_ | Navigateur ou client natif public | Tout canal supportant le mode public | Évaluée par rapport à allowed_origins quand Origin est présent |
Le mode de clé est fixé à la création du canal. Un canal à clé serveur rejette les clés nx_pub_ et vice versa.
Politique d'origine pour les clés publiques (nx_pub_) : Si Origin est présent, il doit correspondre exactement à la liste allowed_origins du canal ou la requête est rejetée avec origin_not_allowed (403). Si Origin est absent (app native ou code côté serveur), la requête est acceptée. Voir les implications de sécurité.
N'intégrez jamais les clés nx_live_ ou nx_test_ dans des bundles navigateur, des binaires d'applications mobiles, ou des dépôts de
code source. Ces clés contournent les contrôles d'origine et s'authentifient en tant que backend de confiance.
En-têtes de requête
| En-tête | Requis | Notes |
|---|---|---|
Authorization: Bearer <clé> | Oui | nx_live_, nx_test_, ou nx_pub_. |
Content-Type: application/json | Oui | multipart/form-data retourne 415. |
Origin | Conditionnel | Envoyé automatiquement par les navigateurs. Les applications mobiles utilisant nx_pub_ doivent l'omettre sauf pour restreindre à des origines spécifiques. |
X-Naixus-Signature | Conditionnel | Requis lorsque require_signature = true sur le canal. Voir Signature HMAC. |
Corps de la requête
| Champ | Type | Requis | Contraintes |
|---|---|---|---|
payload | string | Oui | 1 à 10 000 caractères. Le message ou l'entrée principale de l'utilisateur. |
metadata | object | Non | Données contextuelles libres (ex. {"userId":"u_1","source":"mobile"}). |
conversation_id | string | Non | UUID d'une conversation existante à reprendre. Omettez pour créer une nouvelle conversation. |
stream | boolean | Non | Remplace le paramètre default_stream du canal. Omettez pour utiliser la valeur par défaut du canal. |
stream prend la priorité sur default_stream du canal lorsqu'il est fourni.
Exemple - application mobile, non streamée :
{
"payload": "Quel est le statut de la commande #12345 ?",
"metadata": { "source": "ios-app", "userId": "u_abc" },
"stream": false
}Exemple - service backend, streaming :
{
"payload": "Résume cette demande",
"metadata": { "source": "backend" },
"stream": true
}Exemple - reprise d'une conversation existante :
{
"payload": "Que t'avais-je demandé avant ?",
"conversation_id": "b1f2c3d4-e5f6-7890-abcd-ef1234567890",
"stream": false
}Réponse : JSON (stream = false)
Statut : 200 OK · Content-Type : application/json
| Champ | Type | Notes |
|---|---|---|
run_id | string (UUID) | Identifiant stable d'exécution. |
conversation_id | string (UUID) | Identifiant stable de la conversation. À transmettre pour reprendre la conversation. |
status | string | "success", "error", "pending" ou "cancelled". |
output | object | null | Enveloppe stable décrite ci-dessous. |
usage | object | null | total_nodes (nombre de nœuds exécutés) et duration_ms (durée de l'exécution). |
error | object | null | Présent lorsque status vaut "error". |
Champs de output :
| Champ | Type | Notes |
|---|---|---|
output.result | any | null | Sortie du flow lui-même. La forme dépend du flow. |
output.error | string | null | Message d'erreur du flow, si l'exécution a échoué. |
output.messages | array<{role, content}> | Messages produits par cette exécution uniquement (pas l'historique complet) : réponse de l'assistant, ou question posée si le flow s'est mis en pause. role vaut toujours "assistant" aujourd'hui. Tableau vide si aucun message produit. Pour l'historique complet, voir Conversations. |
output.metadata | object | Métadonnées d'exécution : channel_id, flow_id, flow_name, flow_version, environment, execution_order, started_at/completed_at (ISO 8601), et livechat_mode ("livechat_waiting" uniquement lorsque l'exécution s'est mise en pause sur un transfert livechat, sinon null). |
{
"run_id": "9c1d2e3f-4a5b-6c7d-8e9f-0a1b2c3d4e5f",
"conversation_id": "b1f2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "success",
"output": {
"result": { "text": "Voici le résumé demandé." },
"error": null,
"messages": [{ "role": "assistant", "content": "Voici le résumé demandé." }],
"metadata": {
"channel_id": "6f1e2d3c-4b5a-6978-8f9e-0a1b2c3d4e5f",
"flow_id": "1a2b3c4d-5e6f-7890-abcd-ef1234567890",
"flow_name": "Résumé de demande",
"flow_version": null,
"environment": "prod",
"livechat_mode": null,
"execution_order": ["trigger_from_channel", "agent", "direct_message"],
"started_at": "2026-08-04T10:00:00.000Z",
"completed_at": "2026-08-04T10:00:01.500Z"
}
},
"usage": { "total_nodes": 3, "duration_ms": 1500 },
"error": null
}Réponse : flux SSE (stream = true)
Statut : 200 OK · Content-Type : text/event-stream; charset=utf-8
En-têtes de réponse supplémentaires :
| En-tête | Valeur |
|---|---|
Cache-Control | no-cache, no-transform |
Connection | keep-alive |
X-Accel-Buffering | no |
Le flux suit le protocole Vercel AI SDK UI Message Stream. Chaque événement est une ligne data: {json}\n\n.
Séquence des événements :
data: {"type":"data-run","data":{"run_id":"9c1d…","conversation_id":"b1f2…"}}
data: {"type":"text-start","id":"<message_id>"}
data: {"type":"text-delta","id":"<message_id>","delta":"Voici "}
data: {"type":"text-delta","id":"<message_id>","delta":"le résumé…"}
data: {"type":"text-end","id":"<message_id>"}
data: {"type":"finish","finishReason":"stop"}Types d'événements :
| Type | Émis quand | Champs clés |
|---|---|---|
data-run | Premier événement, avant text-start | data.run_id, data.conversation_id |
text-start | Avant le premier delta de texte | id (identifiant du message) |
text-delta | À chaque token incrémental | id, delta (fragment de texte) |
text-end | Après tous les deltas de texte | id |
finish | Fin normale | finishReason: "stop" |
error | Erreur en cours de flux | errorText (message lisible) |
data-run porte les identifiants stables run_id et conversation_id. Lisez-le avant de traiter les événements text-delta si vous avez besoin de ces valeurs pendant le flux.
Sémantique des erreurs :
- Les erreurs avant l'envoi du premier octet : réponse HTTP d'erreur normale (aucun flux n'est ouvert).
- Les erreurs après le début du streaming : un événement
errorest émis puis le flux se termine. Le statut HTTP est déjà200; l'événementerrorest le signal d'échec. Aucun événementfinishne suit unerror.
Signature des requêtes avec HMAC
Fonctionnalité optionnelle par canal. Principalement pertinente pour les appelants serveur-à-serveur.
Format de l'en-tête de signature :
X-Naixus-Signature: t=<unix_timestamp>,v1=<hex_minuscules>t- horodatage Unix décimal en secondes depuis l'epoch.v1- digest HMAC-SHA256 en hexadécimal minuscule.
Séquence d'octets signée : <chaine_timestamp> + "." + <corps_http_brut_exact>
Signez les octets bruts exactement tels qu'envoyés. Tout changement d'espacement, d'ordre des clés JSON ou d'encodage invalide la signature. Le serveur accepte |maintenant − t| ≤ 300 secondes.
Il n'existe pas de base de données de nonce anti-replay. La fenêtre de 300 secondes est le seul mécanisme de prévention du rejeu. Voir l'explication de l'architecture pour les compromis de sécurité.
Exemple shell / curl :
BODY='{"payload":"Résume cette demande","stream":false}'
SECRET='votre_secret_de_signature'
TS=$(date +%s)
SIG=$(printf '%s.%s' "$TS" "$BODY" | \
openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $2}')
curl -X POST "https://api.example.com/public/v1/api/execute" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-H "X-Naixus-Signature: t=$TS,v1=$SIG" \
-d "$BODY"Exemple TypeScript / Node.js :
import { createHmac } from 'crypto';
function signRequest(body: string, secret: string): string {
const ts = Math.floor(Date.now() / 1000);
const digest = createHmac('sha256', secret).update(`${ts}.${body}`).digest('hex');
return `t=${ts},v1=${digest}`;
}
const body = JSON.stringify({ payload: 'Résume cette demande', stream: false });
await fetch('https://api.example.com/public/v1/api/execute', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.API_KEY}`,
'Content-Type': 'application/json',
'X-Naixus-Signature': signRequest(body, process.env.SIGNING_SECRET!),
},
body,
});Exemple Python :
import hashlib, hmac, time, os
def sign_request(body: bytes, secret: str) -> str:
ts = str(int(time.time()))
signed = ts.encode("ascii") + b"." + body
digest = hmac.new(secret.encode("utf-8"), signed, hashlib.sha256).hexdigest()
return f"t={ts},v1={digest}"
body = b'{"payload":"R\u00e9sume cette demande","stream":false}'
header = sign_request(body, os.environ["SIGNING_SECRET"])Reprise d'une conversation
- L'omission de
conversation_idcrée une nouvelle conversation. L'identifiantconversation_idattribué est retourné dans la réponse JSON ou dans l'événement SSEdata-run. Transmettez-le dans les requêtes suivantes pour maintenir le contexte. - La fourniture de
conversation_idreprend une conversation existante. La conversation doit appartenir au tenant authentifié et au même canal. - La reprise inter-tenant ou inter-canal retourne
conversation_not_found(404) - la même erreur non-énumérative utilisée pour les identifiants inconnus. conversation_idn'est pas une métadonnée de corrélation. Il identifie le contexte de conversation avec état utilisé par le flow (ex. historique de chat, fenêtre de contexte).
État du canal
| État | Exécution | Gestion |
|---|---|---|
active | Autorisée | Autorisée |
inactive | 409 | Autorisée |
archived | 410 | Non autorisée |
Erreurs
Toutes les erreurs utilisent une enveloppe cohérente :
{ "error": { "code": "<code_machine>", "message": "<message_lisible>" } }| HTTP | Code | Signification | Cause fréquente | Action |
|---|---|---|---|---|
| 401 | missing_api_key | Pas d'en-tête Authorization | Clé non envoyée | Ajouter Authorization: Bearer <clé> |
| 401 | invalid_api_key | Clé invalide, révoquée ou expirée | Mauvaise clé ou clé renouvelée | Vérifier la valeur ; renouveler si nécessaire |
| 401 | signature_required | X-Naixus-Signature absent | Signature activée mais en-tête absent | Ajouter l'en-tête de signature |
| 401 | signature_expired | Horodatage hors de ±300 s | Décalage d'horloge ou requête périmée | Synchroniser l'horloge ; re-signer avec l'horodatage actuel |
| 401 | invalid_signature | En-tête mal formé ou digest incorrect | Mauvais secret, mauvais corps d'octets ou encodage incorrect | Vérifier le secret ; signer les octets bruts tels qu'envoyés |
| 403 | environment_mismatch | Environnement de la clé incompatible avec le canal | Clé nx_live_ sur canal dev, ou nx_test_ sur canal prod | Utiliser la bonne clé pour l'environnement du canal |
| 403 | origin_not_allowed | Origin présente mais absente de la liste des origines autorisées | Origine navigateur non dans la liste du canal | Ajouter l'origine dans la console ; ou utiliser un proxy backend |
| 403 | wrong_channel_type | Le canal n'est pas de type API | La clé appartient à un canal non-API | Utiliser une clé d'un canal de type API |
| 403 | conversation_forbidden | conversation_id appartient à un autre canal | Tentative de reprise inter-canal | Reprendre uniquement dans le canal d'origine |
| 404 | conversation_not_found | conversation_id inconnu ou inter-tenant | Mauvais identifiant, mauvais tenant ou inexistant | Vérifier l'identifiant de conversation |
| 409 | channel_inactive | Canal inactif | Canal désactivé dans la console | Réactiver le canal |
| 410 | channel_archived | Canal archivé | Canal retiré définitivement | Utiliser un autre canal |
| 415 | unsupported_media_type | Corps multipart/form-data envoyé | Envoi de fichier vers le point d'accès JSON | Utiliser la variante upload |
| 422 | validation_error | Corps de requête invalide | payload manquant, types incorrects | Vérifier les contraintes de champs dans cette référence |
| 503 | signing_secret_unavailable | Signature requise mais aucun secret configuré | Secret jamais créé ou supprimé | Créer ou renouveler le secret de signature |
| 500 | runtime_error | Erreur d'exécution inattendue | Problème côté serveur | Réessayer ; contacter le support si persistant |
Conversations
Une conversation s'étend sur plusieurs appels à /execute. Le conversation_id retourné par /execute sert de session_id pour le point d'accès ci-dessous. output.messages (voir plus haut) n'expose que les messages produits par le dernier appel ; utilisez cette route pour récupérer l'historique complet de la conversation.
GET /public/v1/api/conversations/{session_id}/messages - Récupérer l'historique d'une conversation
Authentification : même clé bearer que /execute (nx_live_, nx_test_ ou nx_pub_). Aucune signature HMAC requise, quelle que soit la configuration du canal.
GET /public/v1/api/conversations/b1f2c3d4-e5f6-7890-abcd-ef1234567890/messages?limit=50
Authorization: Bearer <clé>Paramètres :
| Paramètre | Type | Emplacement | Requis | Contraintes |
|---|---|---|---|---|
session_id | string | Chemin | Oui | Le conversation_id retourné par /execute. |
limit | int | Requête | Non | 1 à 200. Défaut : 50. |
token_budget | int | Requête | Non | Défaut : 0 (illimité). Tronque l'historique à ce budget de tokens. |
Réponse : 200 OK · Content-Type : application/json
| Champ | Type | Notes |
|---|---|---|
session_id | string | Reprend le session_id de la requête. |
messages | array | Chronologique, du plus ancien au plus récent. Voir le détail des champs ci-dessous. |
total_messages | int | Nombre total de messages retournés. |
total_tokens | int | Estimation du nombre de tokens dans messages. |
mode | string | null | bot, livechat_waiting, livechat_active, livechat_closed, livechat_expired, livechat_cancelled ou livechat_unavailable. |
livechat_status_message | string | null | Message configuré par le tenant pour le mode courant (ex. message d'attente ou de clôture). |
livechat_conversation_id | string | null | Identifiant de la conversation livechat active ou clôturée, si applicable. |
Champs de chaque entrée de messages :
| Champ | Type | Notes |
|---|---|---|
origin | string | "agent" (produit par un run) ou "livechat" (produit par un agent humain). |
role | string | user, assistant, system ou tool. |
content | string | Contenu du message. |
timestamp | string | ISO 8601. |
turn_index | int | Position dans la conversation, à partir de 0. |
token_count | int | Estimation du nombre de tokens du message. |
run_id | string | null | Défini uniquement lorsque origin vaut "agent". |
step_id | string | null | Défini uniquement lorsque origin vaut "agent". |
node_type | string | null | Type de nœud exécuteur, uniquement lorsque origin vaut "agent". |
tool_calls | array | null | Appels d'outils au format OpenAI, si présents. |
tool_call_id | string | null | Appel d'outil auquel ce message répond, si applicable. |
livechat_message_id | string | null | Défini uniquement lorsque origin vaut "livechat". |
sender | string | null | Expéditeur brut côté livechat (user, agent, supervisor, bot ou system). origin="livechat" uniquement. |
Exemple de réponse :
{
"session_id": "b1f2c3d4-e5f6-7890-abcd-ef1234567890",
"messages": [
{
"origin": "agent",
"role": "user",
"content": "Je veux parler à un humain",
"timestamp": "2026-08-04T10:00:00.000Z",
"turn_index": 0,
"token_count": 6,
"run_id": "9c1d2e3f-4a5b-6c7d-8e9f-0a1b2c3d4e5f",
"step_id": "2b3c4d5e-6f70-8192-a3b4-c5d6e7f80912",
"node_type": "trigger_from_channel",
"tool_calls": null,
"tool_call_id": null,
"livechat_message_id": null,
"sender": null
},
{
"origin": "livechat",
"role": "assistant",
"content": "Bonjour, je suis Claire, en quoi puis-je vous aider ?",
"timestamp": "2026-08-04T10:01:12.000Z",
"turn_index": 1,
"token_count": 12,
"run_id": null,
"step_id": null,
"node_type": null,
"tool_calls": null,
"tool_call_id": null,
"livechat_message_id": "3c4d5e6f-7081-92a3-b4c5-d6e7f8091234",
"sender": "agent"
}
],
"total_messages": 2,
"total_tokens": 18,
"mode": "livechat_active",
"livechat_status_message": null,
"livechat_conversation_id": "4d5e6f70-8192-a3b4-c5d6-e7f809123456"
}Erreurs :
| HTTP | Signification | Cause fréquente | Action |
|---|---|---|---|
| 404 | Aucune conversation trouvée pour ce session_id, ou appartenant à un autre canal/tenant | Mauvais session_id, ou tentative inter-canal | Vérifier le session_id et le canal utilisé |
Contrairement aux erreurs de /execute, cette route ne renvoie pas l'enveloppe {"error": {"code", "message"}} de cette référence : elle renvoie le format d'erreur HTTP par défaut, {"detail": "No conversation found for session {session_id}"}.
GET /public/widget/config - Charger la configuration du widget
Alimente le widget de chat navigateur intégrable. Nécessite une clé publique (nx_pub_) et applique les origines autorisées.
GET /public/widget/config
Authorization: Bearer nx_pub_xxxxxxxx
Origin: https://site-client.exampleRetourne la configuration widget du canal (localisation et options de fonctionnalités).
POST /public/widget/chat - Streamer une conversation widget
POST /public/widget/chat
Authorization: Bearer nx_pub_xxxxxxxx
Origin: https://site-client.example
Content-Type: application/jsonDiffuse la réponse du flow en Server-Sent Events avec le même protocole Vercel AI SDK UI Message Stream.
POST /api/v1/channels/{channel_id}/rotate-key - Renouveler la clé d'API
Nécessite un JWT admin tenant (Authorization: Bearer <JWT> avec X-Tenant-ID). Non accessible avec les clés d'API de canal.
Révoque la clé existante et génère une nouvelle clé avec le même préfixe et environnement. La nouvelle clé en clair est retournée une seule fois dans api_key. Stockez-la immédiatement.
{
"api_key": "nx_live_nouvelleClé1a2b3c…",
"channel": { "id": "…", "api_key_hint": "nx_live_••••abcd" }
}POST /api/v1/channels/{channel_id}/rotate-signing-secret - Renouveler le secret de signature
Nécessite un JWT admin tenant.
Génère un nouveau secret HMAC. Le secret précédent est immédiatement invalidé. Le nouveau secret en clair est retourné une seule fois dans signing_secret.
{
"signing_secret": "whs_…",
"channel": { "id": "…", "has_signing_secret": true }
}PATCH /api/v1/channels/{channel_id} - Activer ou désactiver la signature obligatoire
Nécessite un JWT admin tenant.
PATCH /api/v1/channels/{channel_id}{ "config": { "require_signature": true } }Lorsque require_signature passe de false à true, la réponse inclut un signing_secret à usage unique. La désactivation désactive le contrôle sans supprimer le secret stocké.
Confirmé, non spécifié et non supporté
- Confirmé : tous les points d'accès, schémas de requête/réponse, règles d'authentification, codes d'erreur et événements SSE décrits dans cette référence sont vérifiés par rapport à l'implémentation du service Core.
- Non spécifié : les seuils de limitation de débit, le comportement de retry, les clés d'idempotence et la pagination ne font pas partie du contrat vérifié. Traitez
429comme un signal de temporisation. - Non supporté : les points d'accès d'administration (flows, tenants, secrets, gestion des canaux) sont internes et ne doivent pas être appelés par des intégrateurs externes.
Voir aussi
- Construire votre première intégration API - tutoriel pour débutants.
- Signer les requêtes avec HMAC - guide pas à pas pour la signature HMAC.
- Recevoir les réponses en SSE - mise en place du client SSE.
- Renouveler les credentials - procédures de rotation.
- Dépanner les erreurs d'authentification et de signature - checklist de diagnostic.
- Architecture et sécurité du canal API - décisions de conception et modèle de sécurité.
- Prérequis d'intégration - checklist de configuration.
Architecture et sécurité du canal API
Explication - comment le canal API fonctionne, comment l'authentification et la signature fonctionnent, ce qu'elles protègent, et les limitations actuelles.
Changelog
Changelog d'intégration public - changements d'API, de canaux, d'authentification et de compatibilité pour les intégrateurs.