# Déploiement

Une fois qu'un workflow tourne proprement dans l'éditeur, vous l'exposez à l'extérieur depuis son **espace Production** : barre latérale gauche → **Production** → votre agent → **Deploy**. Trois surfaces, un même workflow :

| Surface | Appelant | Auth |
| ------- | -------- | ---- |
| **API** | Votre backend, scripts, tiers | En-tête `X-Agent-Key` par clé |
| **Widget** | Un navigateur, embarqué sur votre site | Anonyme, limité par session |
| **Shared chat** | Toute personne avec l'URL | Username + mot de passe optionnels |

Chaque surface s'active indépendamment — n'activez que ce dont vous avez besoin. L'agent doit aussi être **actif** (le toggle Live/Draft sur l'onglet Overview ou Settings) pour qu'une surface serve du trafic — voir [Production](#/docs/production).

## API

Endpoint REST appelable depuis votre code. Un workflow peut avoir plusieurs clés ; révocables individuellement.

### Activer

1. Ouvrez l'espace production de l'agent → **Deploy** → **API**.
2. Activez **Enable published API**.
3. Cliquez **Generate key**. Copiez le secret *maintenant* — seul le hash est stocké, vous ne pourrez plus le lire. La page construit aussi un exemple `curl` prêt à l'emploi avec votre clé.

### Endpoint

```
POST /api/v1/published/{workflow_public_id}/chat
```

### Headers

- `X-Agent-Key: <votre-clé>` *(ou)* `Authorization: Bearer <votre-clé>`
- `Content-Type: application/json`

### Body

```json
{
  "input":      "Résume les tickets support du jour",
  "session_id": "id-stable-optionnel",
  "messages":   [{ "role": "user", "content": "..." }],
  "metadata":   { "n'importe": "quoi" }
}
```

Fournissez `input` *ou* un `messages` non-vide. Le dernier message user gagne si les deux sont présents.

### Exemple

```bash
curl -X POST https://app.systalink.io/api/v1/published/wf_abc123/chat \
  -H 'X-Agent-Key: sk_live_xxx' \
  -H 'Content-Type: application/json' \
  -d '{"input":"Bonjour agent"}'
```

### Réponse

```json
{
  "workflow_id": "wf_abc123",
  "session_id":  "...",
  "result":      "Bonjour ! Comment puis-je aider ?",
  "messages":    [ ...transcription complète... ],
  "status":      "completed"
}
```

### Modes d'exécution

L'appel par défaut ci-dessus est **synchrone** : la connexion HTTP reste ouverte jusqu'à la fin du workflow. Parfait pour les workflows courts, mais pas pour ceux qui peuvent dépasser ~30 s — les clients et proxys timeout. Pour les workflows longs, utilisez **async + SSE/polling** (vous streamez la progression ou tirez le résultat) ou un **callback** (le serveur le pousse vers un endpoint que vous exposez).

| Mode | Quand | Comment |
| ---- | ----- | ------- |
| **Sync** (défaut) | Appels rapides (< ~30 s) | `POST .../chat` → `200` avec le résultat complet |
| **Async + SSE/polling** | Workflows longs, sans endpoint callback public | `POST` avec `"async": true` → `202` + `run_id`, puis on stream ou poll le run |
| **Callback (push)** | Workflows longs, vous exposez un endpoint | `POST` avec `callback_url` → le serveur POST le résultat chez vous |

#### Async

Ajoutez `"async": true` au body. L'endpoint renvoie **202** immédiatement et le workflow tourne en arrière-plan :

```json
{
  "run_id":   "run_abc123",
  "status":   "pending",
  "poll_url": "/api/v1/published/{workflow_id}/runs/run_abc123",
  "stream_url": "/api/v1/published/{workflow_id}/runs/run_abc123/stream"
}
```

#### Polling

Faites un `GET` sur le run avec le même en-tête `X-Agent-Key` jusqu'à un `status` terminal :

```
GET /api/v1/published/{workflow_id}/runs/{run_id}
```

```json
{
  "run_id":      "run_abc123",
  "status":      "completed",
  "result":      "...",
  "error":       null,
  "created_at":  "...",
  "finished_at": "..."
}
```

`status` vaut `pending`, `running`, `completed`, `failed` ou `awaiting_approval`. Pollez jusqu'à `completed` ou `failed`.

#### Stream SSE

Utilisez le `stream_url` retourné pour suivre les événements publics en temps réel :

```
GET /api/v1/published/{workflow_id}/runs/{run_id}/stream
Accept: text/event-stream
X-Agent-Key: <votre-clé>
```

```bash
curl -N https://app.systalink.io/api/v1/published/wf_abc123/runs/run_abc123/stream \
  -H 'X-Agent-Key: sk_live_xxx' \
  -H 'Accept: text/event-stream'
```

Le stream envoie des frames SSE contenant seulement `data:`. Ignorez les commentaires comme `: keepalive`, parsez chaque payload `data:` en JSON, et arrêtez-vous sur un événement workflow terminal ou `stream_done`.

| Type d'événement | Champs publics |
| ---------------- | -------------- |
| `workflow_started`, `workflow_resumed`, `workflow_retried` | `run_id`, `workflow_id`, `node_count`, `edge_count`, `start_nodes`, `resume_from_node` / `retry_from_node` optionnels |
| `node_started` | `run_id`, `node_id`, `node_type`, `label` redige, `status: "running"` |
| `node_completed`, `node_failed` | `run_id`, `node_id`, `node_type`, `label` redige, `attempts`, `status`, `error` redige optionnel |
| `edge_traversed` | `run_id`, `source`, `target` |
| `workflow_completed` | `run_id`, `workflow_id`, `status: "completed"`, `result` |
| `workflow_awaiting_approval` | `run_id`, `workflow_id`, `status: "awaiting_approval"` |
| `workflow_cancelled`, `workflow_failed` | `run_id`, `workflow_id`, `status` terminal, `error` optionnel |
| `stream_done` | `run_id` ; peut être envoyé après le replay d'un run déjà terminal |

`EventSource` ne permet pas d'ajouter des headers d'auth custom dans un navigateur. Côté navigateur, utilisez `fetch` avec un readable stream (ou le helper frontend `followPublishedRunStream` de `src/api/published.js`) ; côté serveur, gardez `X-Agent-Key` ou `Authorization: Bearer <clé>`. Ne mettez **pas** les clés agent en query string.

Limite de sécurité : le stream utilise les mêmes contrôles API key et appartenance workflow/run que le polling. Une clé invalide renvoie `401` ; un run appartenant à un autre workflow renvoie `404`. Les événements publics sont filtrés avant de sortir de l'API : `input` de nœud, `output` de nœud, contexte complet, `input_data`, secrets de callback, secrets d'environnement, credentials, metadata et snapshots crash/resume ne sont pas exposés. Les labels et erreurs sont redigés/tronqués avant de sortir de l'API.

#### Callback (push)

Ajoutez `callback_url` (et optionnellement `callback_secret`) au body du chat. Le run s'exécute en arrière-plan et, à la fin, le serveur POST vers votre URL :

```json
{
  "input":           "Résume les tickets du jour",
  "callback_url":    "https://your-app.example.com/agent-callback",
  "callback_secret": "whsec_votre_secret_partagé"
}
```

Le payload du callback :

```json
{
  "run_id":      "run_abc123",
  "workflow_id": "wf_abc123",
  "status":      "completed",
  "result":      "...",
  "error":       null
}
```

Si `callback_secret` est défini, la requête porte l'en-tête `X-Signature-256: sha256=<hmac-sha256 du body brut avec votre secret>` — vérifiez-le avant de faire confiance au payload.

### Rate limits

Par clé, configurables sur la page Deploy → API. La plateforme applique également un quota d'exécution par utilisateur — voir [Troubleshooting](#troubleshooting) pour le cas 429. Les clés révoquées renvoient `401` immédiatement.

## Widget

Une page de chat anonyme prête à embarquer sur n'importe quel site via **iframe** — les visiteurs ne se connectent pas.

### Activer

1. **Deploy** → **Widget**.
2. Activez **Enable widget**.
3. Configurez :
   - **Titre du widget** et **message d'accueil**
   - **Allow images** / **Allow files** (resserrez pour le trafic anonyme)
   - **Messages par session** (défaut 20) et **fenêtre de rate limit** (défaut 3600 s)
   - **Theme** (`light` / `dark`)
4. Copiez le snippet **Iframe embed** depuis le panneau (la **Widget URL** est aussi affichée pour un lien direct).

### Embed

```html
<iframe
  src="https://app.systalink.io/published/wf_abc123/widget"
  title="Mon agent"
  style="width:100%;min-height:760px;border:0;"
></iframe>
```

La page widget appelle `POST /api/v1/published/{workflow_id}/widget/chat`. Pas de clé — le workflow doit être widget-enabled, sinon l'endpoint renvoie `403`.

### Pièces jointes

Si **Allow files** est off mais **Allow images** est on, le widget rejette les uploads non-image avec `400 "This published surface accepts image attachments only."` Taille max selon `MAX_UPLOAD_SIZE_MB` (par défaut 20 Mo serveur).

### Rate limits

Par session ID (cookie). En cas de dépassement, l'endpoint renvoie `429 "Rate limit reached for this widget session."` Le bucket se vide auto après la fenêtre configurée (par défaut 3600 s).

## Shared chat

Une URL de chat hébergée autonome — pas d'embed, pas d'appel API. Pratique pour les outils internes ou pour partager un agent ponctuel avec un client.

### Activer

1. **Deploy** → **Shared chat**.
2. Activez **Enable shared chat**.
3. Configurez :
   - **Share slug** (optionnel) — chemin d'URL personnalisé. Par défaut le public id du workflow.
   - **Titre** et **message d'accueil**
   - **Username** (optionnel) — si défini, les visiteurs doivent le saisir au login
   - **Password** (optionnel mais recommandé) — stocké en bcrypt ; saisi par les visiteurs au login
   - **Allow images** / **Allow files**
   - **Messages par session** (par défaut 40 / heure)

### URL

```
https://app.systalink.io/published/<slug-ou-public-id>/chat
```

### Historique visiteur

Chaque visiteur conserve son **historique de conversation par navigateur** : une barre latérale sur la page de chat liste ses sessions précédentes, et en sélectionner une recharge la transcription complète et la reprend. Les mêmes sessions apparaissent (toutes surfaces confondues) dans l'onglet **Conversations** de l'agent.

### Flow de login

Si un mot de passe est défini, la page affiche un formulaire. La soumission appelle :

```
POST /api/v1/published/{workflow_id}/share/login
{ "username": "...", "password": "..." }
```

En cas de succès, le visiteur reçoit un bearer token valable 12 heures, scope sur ce workflow uniquement. Le token est envoyé à chaque appel de chat :

```
POST /api/v1/published/{workflow_id}/share/chat
Authorization: Bearer <share-session-token>
```

Les tokens sont des JWT signés — pas de table de session côté serveur.

### Pièces jointes et rate limits

Mêmes règles que le widget. Les uploads passent par :

```
POST /api/v1/published/{workflow_id}/attachments?surface=share
Authorization: Bearer <share-session-token>
```

## Docs et API lisibles par machine

Toute la documentation est aussi publiée sous forme de fichiers statiques, sans authentification et sans JavaScript, afin qu'un LLM ou un agent puisse la récupérer directement (une URL SPA `/docs/<topic>` ne renvoie que la coquille JS vide). Pointez vos outils vers ces URLs :

| URL | Contenu |
| --- | ------- |
| [`/llms.txt`](/llms.txt) | Index concis de tous les sujets documentés (convention llms.txt). |
| [`/llms-full.txt`](/llms-full.txt) | Tous les sujets concaténés dans un seul fichier anglais. |
| [`/llms-full-fr.txt`](/llms-full-fr.txt) | Tous les sujets concaténés, en français. |
| [`/docs-md/fr/<topic>.md`](/docs-md/fr/overview.md) | Markdown par sujet (aussi `/docs-md/en/<topic>.md`). |
| [`/sitemap.xml`](/sitemap.xml) | Pages humaines et les deux copies Markdown. |
| [`/robots.txt`](/robots.txt) | Politique de crawl (la documentation est publique). |
| [`/api/v1/openapi.json`](/api/v1/openapi.json) | Schéma REST lisible par machine des endpoints publiés ci-dessus. |

Ces fichiers sont régénérés à partir des mêmes sources que la documentation humaine ; ils restent donc synchronisés automatiquement.

## Désactiver

Désactivez la surface dans **Deploy** (ou toutes d'un coup depuis **Settings → Surface authentication**). L'endpoint `/api/v1/published/...` correspondant renvoie immédiatement `403`. Les clés API existantes ne sont *pas* supprimées — réactiver la surface les restaure. Utilisez l'action **Revoke** explicite sur une clé pour la supprimer définitivement.

---

*Source: https://agentbuilder.systalink.sn/docs/deployment — human documentation.*
*Other language: [/docs-md/en/deployment.md](/docs-md/en/deployment.md).*
*Machine-readable index: [/llms.txt](/llms.txt).*
