{"openapi":"3.1.0","info":{"title":"Relay — API publique","description":"API REST de Relay, authentifiée par clé API (`Authorization: Bearer rk_live_...`, ou `X-Relay-Key: rk_live_...` quand un intermédiaire réserve `Authorization`). Les clés se créent dans l'application, sous Administration > Intégrations & API. Le serveur MCP (`POST /api/mcp`) utilise les mêmes clés.\n\n## Erreurs\n\nToutes les erreurs sortent en `application/problem+json` (RFC 7807) : `{type, title, status, detail, code, instance}`. **Basez votre logique sur `code`**, jamais sur `detail` (texte lisible, susceptible de changer).\n\n| `code` | Quand ça arrive |\n|---|---|\n| `invalid_api_key` | Clé absente, invalide ou révoquée. Vérifiez l'en-tête `Authorization: Bearer rk_live_...` (ou `X-Relay-Key: rk_live_...`). |\n| `expired_api_key` | La clé a dépassé sa date d'expiration. Créez-en une nouvelle. |\n| `rate_limited` | Débit du forfait dépassé. Attendez `Retry-After` secondes ; un lot MCP coûte autant que son nombre de messages. |\n| `plan_required` | Fonction absente du forfait. Le message nomme le forfait à prendre. |\n| `sms_not_enabled` | Le forfait n'inclut pas l'envoi de SMS. |\n| `contact_limit_reached` | Plafond de contacts du forfait atteint. La lecture reste possible. |\n| `budget_reached` | Votre plafond de dépenses télécom est atteint (réglage de votre compte, pas du forfait). |\n| `workspace_budget_reached` | Le plafond de dépenses propre à CETTE équipe est atteint (réglage de votre compte). |\n| `recipient_opted_out` | Ce numéro s'est désabonné en répondant STOP. Il se réabonne en répondant START ; vous ne pouvez pas le réabonner à sa place. |\n| `sending_suspended` | Les envois de cette équipe sont suspendus après un emballement détecté. La réception continue ; le propriétaire relance depuis l'application. |\n| `daily_limit_reached` | Plafond quotidien de messages de l'équipe atteint (limite du client, ou limite automatique selon le forfait et le nombre de membres). |\n| `workspace_not_found` | L'équipe n'existe pas, ou n'appartient pas à l'organisation de la clé. |\n| `workspace_locked` | Équipe verrouillée : le forfait n'autorise pas autant de numéros actifs. |\n| `not_configured` | L'équipe n'a pas de compte télécom connecté. |\n| `call_not_found` | Aucun appel avec cet identifiant dans cette équipe. |\n| `transcript_not_found` | Pas de transcription pour cet appel. L'événement `transcription.ready` signale celles qui sont prêtes. |\n| `duplicate_number` | Le numéro appartient déjà à un autre contact de l'équipe. |\n| `invalid_cursor` | Le `cursor` doit venir d'un `next_cursor` renvoyé par l'API. |\n| `idempotency_key_reused` | Cette `Idempotency-Key` a servi pour un contenu différent. Utilisez une clé unique par message. |\n| `send_failed` | Le fournisseur télécom a refusé l'envoi. Le message est marqué `failed`. |\n| `flow_not_found` | Aucun flow avec cet identifiant dans votre organisation. |\n| `flow_exists` | Ce numéro a déjà un flow : modifiez-le au lieu d'en créer un second. |\n| `flow_is_live` | Flow en production : désactivez-le dans l'application avant de le supprimer. |\n| `invalid_flow` | Graphe mal formé : type de bloc inconnu, lien pendant, ou bloc à plusieurs parents (l'arbre est strict). |\n| `flow_too_large` | Trop de blocs ou graphe trop lourd. Un message long va dans un audio, pas dans le graphe. |\n| `unknown_number` | Ce numéro n'appartient pas à votre organisation. |\n| `unknown_template` | Gabarit inconnu : team, hours, menu ou blank. |\n| `no_draft` | Ce flow n'a pas de brouillon à abandonner. |\n| `empty_update` | Aucun champ à modifier dans la requête. |\n| `validation_error` | Un ou plusieurs champs sont invalides ; `errors[]` dit lesquels. |\n| `not_found` | Route ou ressource inexistante. |\n| `bad_request` | Requête malformée. |\n| `forbidden` | Accès refusé pour cette clé. |\n| `internal_error` | Erreur côté Relay. Réessayez ; si ça persiste, écrivez-nous. |","version":"f1d947f57d48"},"paths":{"/api/v1/me":{"get":{"summary":"Votre organisation","description":"Organisation de la cle API, forfait effectif et equipes accessibles. Utile pour verifier qu'une cle fonctionne.","operationId":"v1_me_api_v1_me_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/v1/workspaces":{"get":{"summary":"Lister les equipes","description":"Les equipes (un numero chacune). Leur `id` est exige par tous les autres endpoints.","operationId":"v1_workspaces_api_v1_workspaces_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/v1/workspaces/{workspace_id}/calls":{"get":{"summary":"Historique d'appels","description":"Appels de l'equipe, du plus recent au plus ancien. Pagination par curseur : repassez `next_cursor` dans `cursor`. `has_transcript` indique si une transcription est disponible.","operationId":"v1_calls_api_v1_workspaces__workspace_id__calls_get","parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","title":"Workspace Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":50,"title":"Limit"}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string","title":"Cursor"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/workspaces/{workspace_id}/calls/{call_id}":{"get":{"summary":"Détail d'un appel","description":"Un appel précis, même forme que dans la liste.","operationId":"v1_call_api_v1_workspaces__workspace_id__calls__call_id__get","parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","title":"Workspace Id"}},{"name":"call_id","in":"path","required":true,"schema":{"type":"string","title":"Call Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/workspaces/{workspace_id}/calls/{call_id}/transcript":{"get":{"summary":"Transcription d'un appel","description":"Texte de la transcription, quand elle existe. C'est la contrepartie de l'événement webhook `transcription.ready` : sans cet endpoint, vous étiez prévenu qu'une transcription existait sans pouvoir la lire.","operationId":"v1_transcript_api_v1_workspaces__workspace_id__calls__call_id__transcript_get","parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","title":"Workspace Id"}},{"name":"call_id","in":"path","required":true,"schema":{"type":"string","title":"Call Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/voices":{"get":{"summary":"Voix disponibles","description":"Catalogue des voix offertes par Relay pour la réceptionniste IA et le répondeur (comptes Telnyx) : `group` = qc (accent québécois), fr (France), en (anglais) ; `recommended` = voix mises en avant. Lecture seule.","operationId":"v1_voices_api_v1_voices_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/v1/workspaces/{workspace_id}/voicemail":{"get":{"summary":"Répondeur et voix de l'équipe","description":"Message et voix du répondeur de l'équipe, voix de la réceptionniste IA. `audio_ready` : le message lu par la voix choisie est prêt et joue tel quel ; sinon la voix par défaut le lit. Lecture seule : la configuration se fait dans l'application.","operationId":"v1_voicemail_api_v1_workspaces__workspace_id__voicemail_get","parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","title":"Workspace Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/workspaces/{workspace_id}/messages":{"get":{"summary":"Historique SMS","description":"SMS de l'equipe, du plus recent au plus ancien. `contact` filtre sur un correspondant (E.164) pour obtenir une conversation.","operationId":"v1_messages_api_v1_workspaces__workspace_id__messages_get","parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","title":"Workspace Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":50,"title":"Limit"}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string","title":"Cursor"}},{"name":"contact","in":"query","required":false,"schema":{"type":"string","title":"Contact"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"summary":"Envoyer un SMS","description":"Envoie un SMS depuis le numero de l'equipe. **Envoi reel et facture.** Envoyez un en-tete `Idempotency-Key` (valeur unique par message, un UUID par exemple) : si le reseau coupe et que vous reessayez, le meme message ne partira pas deux fois. Le rejeu renvoie la reponse d'origine avec l'en-tete `Idempotent-Replay: true`. Sans cet en-tete, chaque appel envoie.","operationId":"v1_send_sms_api_v1_workspaces__workspace_id__messages_post","parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","title":"Workspace Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1SmsReq"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/workspaces/{workspace_id}/contacts":{"get":{"summary":"Lister / chercher des contacts","description":"Carnet de l'equipe. `q` cherche dans le prenom, le nom et l'entreprise.","operationId":"v1_contacts_api_v1_workspaces__workspace_id__contacts_get","parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","title":"Workspace Id"}},{"name":"q","in":"query","required":false,"schema":{"type":"string","default":"","title":"Q"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":50,"title":"Limit"}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string","title":"Cursor"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"summary":"Creer un contact","description":"Le numero doit etre unique dans l'equipe (400 `duplicate_number` sinon). Emet l'evenement webhook `contact.created`. Soumis au plafond de contacts du forfait.","operationId":"v1_create_contact_api_v1_workspaces__workspace_id__contacts_post","parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","title":"Workspace Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactReq"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/workspaces/{workspace_id}/contacts/{contact_id}":{"patch":{"summary":"Remplacer un contact","description":"REMPLACE les champs du contact : ce qui n'est pas envoye est efface. Memes regles que l'application (normalisation des numeros, unicite).","operationId":"v1_update_contact_api_v1_workspaces__workspace_id__contacts__contact_id__patch","parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","title":"Workspace Id"}},{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","title":"Contact Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactReq"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"summary":"Supprimer un contact","description":"Suppression definitive. L'historique d'appels et de SMS du numero n'est PAS supprime.","operationId":"v1_delete_contact_api_v1_workspaces__workspace_id__contacts__contact_id__delete","parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","title":"Workspace Id"}},{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","title":"Contact Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/mcp":{"get":{"summary":"Flux SSE (non offert)","description":"Renvoie 405 : ce serveur n'emet aucune notification spontanee, donc aucun flux serveur vers client. Cas prevu par la spec.","operationId":"mcp_get_api_mcp_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"post":{"summary":"Serveur MCP (JSON-RPC)","description":"Endpoint du serveur MCP de Relay, transport Streamable HTTP (spec 2025-06-18). Connexion par le navigateur (OAuth 2.1 : le client decouvre /.well-known/oauth-protected-resource et ouvre la page Autoriser), ou cle API pour un script : `Authorization: Bearer rk_live_...`. Sans session et sans SSE. Methodes : initialize, ping, tools/list, tools/call. Un lot JSON-RPC est accepte jusqu'a 20 messages, et chaque message compte dans votre debit. A brancher dans Claude Desktop ou Claude Code comme connecteur HTTP.","operationId":"mcp_post_api_mcp_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"delete":{"summary":"Fin de session (sans objet)","description":"Renvoie 405 : le serveur est sans session, il n'y a rien a liberer. Cas prevu par la spec.","operationId":"mcp_delete_api_mcp_delete","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/v1/flows":{"get":{"summary":"Lister les flows","description":"Les parcours d'appel de l'organisation. `enabled` dit lequel est en production. `has_draft` signale un brouillon non publié.","operationId":"v1_flows_api_v1_flows_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"post":{"summary":"Créer un flow","description":"Crée le flow d'un numéro à partir d'un gabarit (`team`, `hours`, `menu`, `blank`). Un seul flow par numéro. Créé DÉSACTIVÉ : il ne route aucun appel tant qu'un humain ne l'a pas publié dans l'app.","operationId":"v1_create_flow_api_v1_flows_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FlowCreateReq"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/flow-schema":{"get":{"summary":"Schéma des blocs de flow","description":"Types de blocs disponibles et champs attendus. À lire AVANT d'écrire un graphe : c'est le vocabulaire exact accepté par le moteur.","operationId":"v1_flow_schema_api_v1_flow_schema_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/v1/members":{"get":{"summary":"Lister les membres","description":"Membres de l'organisation et leur `id`. Sans cette liste, impossible de construire un transfert `target_kind: \"member\"` — c'est cet `id` qui va dans le champ `target`.","operationId":"v1_members_api_v1_members_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/v1/flows/{flow_id}/draft":{"delete":{"summary":"Abandonner le brouillon","description":"Jette le brouillon et revient à ce qui est en production. Ne touche pas au parcours que les appelants entendent.","operationId":"v1_discard_draft_api_v1_flows__flow_id__draft_delete","parameters":[{"name":"flow_id","in":"path","required":true,"schema":{"type":"string","title":"Flow Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/flows/{flow_id}":{"get":{"summary":"Lire un flow","description":"Graphe en production (`nodes`/`entry`) et brouillon (`draft`) s'il existe. `problems` liste ce qui empêcherait une publication.","operationId":"v1_flow_api_v1_flows__flow_id__get","parameters":[{"name":"flow_id","in":"path","required":true,"schema":{"type":"string","title":"Flow Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"summary":"Écrire le brouillon","description":"Remplace le BROUILLON. Le parcours entendu par les appelants ne change pas : la publication se fait dans l'application, par un humain. Un brouillon peut être incomplet, mais pas structurellement invalide.","operationId":"v1_update_flow_draft_api_v1_flows__flow_id__put","parameters":[{"name":"flow_id","in":"path","required":true,"schema":{"type":"string","title":"Flow Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FlowDraftReq"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"summary":"Supprimer un flow","description":"Uniquement si le flow n'est PAS en production. Supprimer un flow actif renverrait les appels entrants ailleurs sans prévenir.","operationId":"v1_delete_flow_api_v1_flows__flow_id__delete","parameters":[{"name":"flow_id","in":"path","required":true,"schema":{"type":"string","title":"Flow Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/flows/{flow_id}/check":{"post":{"summary":"Vérifier un brouillon (sans rien écrire)","description":"Renvoie ce qui empêcherait la publication. Envoyez un graphe pour le tester à blanc, ou rien pour vérifier le brouillon enregistré. `can_publish` à vrai = prêt à être publié par un humain.","operationId":"v1_check_flow_api_v1_flows__flow_id__check_post","parameters":[{"name":"flow_id","in":"path","required":true,"schema":{"type":"string","title":"Flow Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FlowCheckReq"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"ContactReq":{"properties":{"first_name":{"type":"string","maxLength":80,"minLength":1,"title":"First Name"},"last_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Name"},"company":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Company"},"job_title":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}],"title":"Job Title"},"website":{"anyOf":[{"type":"string","maxLength":300},{"type":"null"}],"title":"Website"},"avatar_url":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Avatar Url"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"phone_numbers":{"items":{"$ref":"#/components/schemas/PhoneEntry"},"type":"array","minItems":1,"title":"Phone Numbers"},"emails":{"items":{"$ref":"#/components/schemas/EmailEntry"},"type":"array","title":"Emails"},"tags":{"items":{"$ref":"#/components/schemas/TagEntry"},"type":"array","title":"Tags"},"socials":{"items":{"$ref":"#/components/schemas/SocialEntry"},"type":"array","title":"Socials"}},"type":"object","required":["first_name","phone_numbers"],"title":"ContactReq"},"FlowCheckReq":{"properties":{"entry":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Entry"},"nodes":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Nodes"}},"type":"object","title":"FlowCheckReq","description":"Graphe à vérifier. Absent = on vérifie le brouillon enregistré."},"FlowCreateReq":{"properties":{"number":{"type":"string","maxLength":20,"minLength":3,"title":"Number","description":"Numéro E.164 à router."},"name":{"anyOf":[{"type":"string","maxLength":80},{"type":"null"}],"title":"Name"},"template":{"anyOf":[{"type":"string","maxLength":20},{"type":"null"}],"title":"Template","default":"team"}},"type":"object","required":["number"],"title":"FlowCreateReq"},"FlowDraftReq":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":80},{"type":"null"}],"title":"Name"},"entry":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Entry"},"nodes":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Nodes"},"layout":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Layout"}},"type":"object","title":"FlowDraftReq","description":"Écriture d'un BROUILLON. Volontairement sans `enabled` : voir l'en-tête du module."},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"V1SmsReq":{"properties":{"to":{"type":"string","maxLength":30,"minLength":3,"title":"To"},"body":{"type":"string","maxLength":1600,"minLength":1,"title":"Body"}},"type":"object","required":["to","body"],"title":"V1SmsReq"}},"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"rk_live_...","description":"Clé API Relay dans Authorization: Bearer."},"relayKey":{"type":"apiKey","in":"header","name":"X-Relay-Key","description":"Même clé API, pour les intermédiaires qui réservent Authorization."}}},"security":[{"bearerAuth":[]},{"relayKey":[]}]}