{
  "openapi": "3.1.0",
  "info": {
    "title": "GPChat Platform API",
    "version": "1.1.0",
    "summary": "Bots, messagerie, groupes, stories, sondages et « Login with GPChat ».",
    "description": "API publique de GPChat.\n\n## Authentification\n\n| Jeton | Format | Obtenu via | Utilisé pour |\n|---|---|---|---|\n| **Clé API** | `gpk_live_…` | Console développeur (onglet *Identifiants*) | Agir **au nom de votre bot** : messages, groupes, sondages |\n| **Token d'accès OAuth2** | `gpat_…` | Flux « Login with GPChat » (`/oauth/token`) | Agir **au nom d'un utilisateur** consentant : stories, sondages, groupes, profil |\n\nEnvoyez-le dans l'en-tête `Authorization: Bearer <jeton>`. Cliquez sur **Authorize** ci-dessus pour tester.\n\n## Format des réponses\n\nSuccès : `{ \"status\": \"success\", \"message\": \"…\", \"data\": { … }, \"timestamp\": 1735000000000 }`\n\nErreur : `{ \"status\": \"error\", \"code\": \"invalid-argument\", \"message\": \"…\" }`\n\n## Limites\n\n60 requêtes/minute par défaut, par clé API (ou par application pour les tokens OAuth2) **et par endpoint**. Au-delà : HTTP 429.\n\nDocumentation complète : https://gpchat.app/developers/docs",
    "contact": {
      "name": "Support développeurs GPChat",
      "email": "infos@gpchat.app",
      "url": "https://gpchat.app/developers/docs"
    },
    "termsOfService": "https://terms.gpchat.app",
    "license": {
      "name": "Propriétaire — usage soumis aux conditions GPChat",
      "url": "https://terms.gpchat.app"
    }
  },
  "servers": [
    {
      "url": "https://api.gpchat.app",
      "description": "Production"
    }
  ],
  "tags": [
    {
      "name": "Messages (bots)",
      "description": "Envoi de messages au nom de votre bot. **Clé API** requise (scope `messages:send`)."
    },
    {
      "name": "Groupes & diffusion",
      "description": "Gestion de groupes et de listes de diffusion. **Clé API** (bot) ou **token OAuth2** (scope `groups:manage`)."
    },
    {
      "name": "Stories",
      "description": "Stories de l'utilisateur connecté. **Token OAuth2** requis."
    },
    {
      "name": "Sondages",
      "description": "Sondages au nom de votre bot ou d'un utilisateur connecté. **Clé API** (bot) ou **token OAuth2**, scope `polls:write` dans les deux cas."
    },
    {
      "name": "OAuth2 — Login with GPChat",
      "description": "Authorization Code Grant (RFC 6749) avec rotation des refresh tokens."
    }
  ],
  "paths": {
    "/v1/messages": {
      "post": {
        "tags": [
          "Messages (bots)"
        ],
        "operationId": "sendMessage",
        "summary": "Envoyer un message privé",
        "description": "Envoie un message 1-à-1 au nom du bot rattaché à la clé API.\n\n**Règles (anti-spam)** — refusé si :\n- l'utilisateur n'a **jamais écrit** à votre bot (`400 failed-precondition`) : un bot répond, il ne démarche pas ;\n- l'utilisateur a **bloqué** votre bot (`403`) ;\n- le destinataire est un autre bot (`403`).\n\nLe texte est chiffré au repos comme les messages de l'app mobile.",
        "security": [
          {
            "ApiKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DirectMessageRequest"
              },
              "examples": {
                "text": {
                  "summary": "Texte",
                  "value": {
                    "to": "USER_ID",
                    "type": "text",
                    "content": "Bonjour 👋 Comment puis-je vous aider ?"
                  }
                },
                "reply": {
                  "summary": "Réponse citée",
                  "value": {
                    "to": "USER_ID",
                    "type": "text",
                    "content": "Voici votre facture.",
                    "replyToMsgId": "MSG_ID"
                  }
                },
                "image": {
                  "summary": "Image avec légende",
                  "value": {
                    "to": "USER_ID",
                    "type": "image",
                    "fileUrl": "https://images.unsplash.com/photo-1518791841217-8f162f1e1131?w=800",
                    "caption": "Votre commande est prête 📦"
                  }
                },
                "video": {
                  "summary": "Vidéo",
                  "value": {
                    "to": "USER_ID",
                    "type": "video",
                    "fileUrl": "https://cdn.example.com/demo.mp4",
                    "thumbnailUrl": "https://cdn.example.com/demo.jpg",
                    "caption": "Tutoriel"
                  }
                },
                "audio": {
                  "summary": "Audio",
                  "value": {
                    "to": "USER_ID",
                    "type": "audio",
                    "fileUrl": "https://cdn.example.com/message.m4a"
                  }
                },
                "doc": {
                  "summary": "Document",
                  "value": {
                    "to": "USER_ID",
                    "type": "doc",
                    "fileUrl": "https://cdn.example.com/facture-2026-042.pdf",
                    "caption": "Facture n°042"
                  }
                },
                "gif": {
                  "summary": "GIF",
                  "value": {
                    "to": "USER_ID",
                    "type": "gif",
                    "fileUrl": "https://media.giphy.com/media/l0MYt5jPR6QX5pnqM/giphy.gif"
                  }
                },
                "sticker": {
                  "summary": "Sticker",
                  "value": {
                    "to": "USER_ID",
                    "type": "sticker",
                    "fileUrl": "https://cdn.example.com/stickers/merci.webp"
                  }
                },
                "location": {
                  "summary": "Position",
                  "value": {
                    "to": "USER_ID",
                    "type": "location",
                    "location": {
                      "latitude": 4.0511,
                      "longitude": 9.7679,
                      "name": "Agence Akwa",
                      "address": "Boulevard de la Liberté, Douala"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Message envoyé.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessageSent"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/NoBot"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/v1/messages/group": {
      "post": {
        "tags": [
          "Messages (bots)"
        ],
        "operationId": "sendGroupMessage",
        "summary": "Envoyer un message dans un groupe",
        "description": "Le bot doit être **membre** du groupe. Dans un groupe où seuls les admins peuvent écrire (`sendMessages: false`), le bot doit être administrateur. Dans une diffusion, seul son créateur peut envoyer.\n\n`mentionedUserIds` ne peut contenir que des membres du groupe.",
        "security": [
          {
            "ApiKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GroupMessageRequest"
              },
              "examples": {
                "text": {
                  "summary": "Texte",
                  "value": {
                    "groupId": "GROUP_ID",
                    "type": "text",
                    "content": "Rappel : réunion à 15h."
                  }
                },
                "mention": {
                  "summary": "Texte avec mention",
                  "value": {
                    "groupId": "GROUP_ID",
                    "type": "text",
                    "content": "@Alice ton ticket est résolu ✅",
                    "mentionedUserIds": [
                      "USER_ID_ALICE"
                    ]
                  }
                },
                "location": {
                  "summary": "Position",
                  "value": {
                    "groupId": "GROUP_ID",
                    "type": "location",
                    "location": {
                      "latitude": 3.848,
                      "longitude": 11.502,
                      "name": "Point de rendez-vous"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Message envoyé.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessageSent"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/NoBot"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/v1/groups": {
      "post": {
        "tags": [
          "Groupes & diffusion"
        ],
        "operationId": "createGroup",
        "summary": "Créer un groupe ou une liste de diffusion",
        "description": "Le créateur (bot ou utilisateur) devient l'unique administrateur. `isBroadcast: true` crée une liste de diffusion (seul le créateur y écrit).\n\n**Bot** : chaque membre ajouté doit avoir déjà écrit au bot et ne pas l'avoir bloqué.",
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuthToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateGroupRequest"
              },
              "example": {
                "name": "Clients Premium",
                "description": "Annonces et offres exclusives",
                "members": [
                  "USER_ID_1",
                  "USER_ID_2"
                ],
                "isBroadcast": true,
                "photoUrl": "https://cdn.example.com/logo.png"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Groupe créé.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Success"
                    },
                    {
                      "properties": {
                        "data": {
                          "type": "object",
                          "properties": {
                            "groupId": {
                              "type": "string"
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/v1/groups/delete": {
      "delete": {
        "tags": [
          "Groupes & diffusion"
        ],
        "operationId": "deleteGroup",
        "summary": "Supprimer un groupe",
        "description": "Réservé aux administrateurs du groupe.",
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuthToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GroupRef"
              },
              "example": {
                "groupId": "GROUP_ID"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Groupe supprimé.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Success"
                },
                "example": {
                  "status": "success",
                  "message": "Opération réussie",
                  "data": {
                    "deleted": true
                  },
                  "timestamp": 1735000000000
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/v1/groups/members": {
      "post": {
        "tags": [
          "Groupes & diffusion"
        ],
        "operationId": "addGroupMembers",
        "summary": "Ajouter des membres",
        "description": "Réservé aux administrateurs. 100 utilisateurs maximum par requête ; la taille maximale du groupe est vérifiée côté serveur.\n\n**Bot** : chaque utilisateur doit avoir déjà écrit au bot.",
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuthToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "groupId",
                  "memberIds"
                ],
                "properties": {
                  "groupId": {
                    "type": "string"
                  },
                  "memberIds": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 100,
                    "items": {
                      "$ref": "#/components/schemas/UserId"
                    }
                  }
                }
              },
              "example": {
                "groupId": "GROUP_ID",
                "memberIds": [
                  "USER_ID_1",
                  "USER_ID_2"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Membres ajoutés.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Success"
                },
                "example": {
                  "status": "success",
                  "message": "Opération réussie",
                  "data": {
                    "added": true
                  },
                  "timestamp": 1735000000000
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/v1/groups/members/remove": {
      "post": {
        "tags": [
          "Groupes & diffusion"
        ],
        "operationId": "removeGroupMember",
        "summary": "Retirer un membre (ou quitter le groupe)",
        "description": "Un administrateur peut retirer n'importe quel membre. Avec `memberId` égal à l'acteur lui-même, l'acteur quitte le groupe.",
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuthToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "groupId",
                  "memberId"
                ],
                "properties": {
                  "groupId": {
                    "type": "string"
                  },
                  "memberId": {
                    "$ref": "#/components/schemas/UserId"
                  }
                }
              },
              "example": {
                "groupId": "GROUP_ID",
                "memberId": "USER_ID"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Membre retiré.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Success"
                },
                "example": {
                  "status": "success",
                  "message": "Opération réussie",
                  "data": {
                    "removed": true
                  },
                  "timestamp": 1735000000000
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/v1/groups/members/role": {
      "patch": {
        "tags": [
          "Groupes & diffusion"
        ],
        "operationId": "updateGroupMemberRole",
        "summary": "Promouvoir / rétrograder un administrateur",
        "description": "Réservé aux administrateurs. Non applicable aux listes de diffusion.",
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuthToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "groupId",
                  "memberId",
                  "isAdmin"
                ],
                "properties": {
                  "groupId": {
                    "type": "string"
                  },
                  "memberId": {
                    "$ref": "#/components/schemas/UserId"
                  },
                  "isAdmin": {
                    "type": "boolean",
                    "description": "`true` : promouvoir, `false` : rétrograder."
                  }
                }
              },
              "example": {
                "groupId": "GROUP_ID",
                "memberId": "USER_ID",
                "isAdmin": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Rôle mis à jour.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Success"
                },
                "example": {
                  "status": "success",
                  "message": "Opération réussie",
                  "data": {
                    "updated": true
                  },
                  "timestamp": 1735000000000
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/v1/groups/details": {
      "patch": {
        "tags": [
          "Groupes & diffusion"
        ],
        "operationId": "updateGroupDetails",
        "summary": "Modifier le groupe",
        "description": "Réservé aux administrateurs. Au moins un champ à modifier.",
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuthToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "groupId"
                ],
                "properties": {
                  "groupId": {
                    "type": "string"
                  },
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 100
                  },
                  "description": {
                    "type": "string",
                    "maxLength": 500
                  },
                  "photoUrl": {
                    "type": "string",
                    "format": "uri",
                    "pattern": "^https://"
                  },
                  "sendMessages": {
                    "type": "boolean",
                    "description": "`false` : seuls les administrateurs peuvent écrire."
                  },
                  "editInfoOnlyAdmin": {
                    "type": "boolean",
                    "description": "`true` : seuls les administrateurs modifient les infos du groupe."
                  }
                }
              },
              "example": {
                "groupId": "GROUP_ID",
                "name": "Support VIP",
                "sendMessages": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Groupe modifié.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Success"
                },
                "example": {
                  "status": "success",
                  "message": "Opération réussie",
                  "data": {
                    "updated": true
                  },
                  "timestamp": 1735000000000
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/v1/groups/broadcast": {
      "post": {
        "tags": [
          "Groupes & diffusion"
        ],
        "operationId": "broadcastGroupMessage",
        "summary": "Envoyer dans un groupe / une diffusion (bot ou utilisateur)",
        "description": "Comme `POST /v1/messages/group`, mais accepte aussi un **token OAuth2** : un utilisateur peut ainsi diffuser via sa propre liste de diffusion. `type` vaut `text` par défaut.",
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuthToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GroupMessageRequest"
              },
              "example": {
                "groupId": "GROUP_ID",
                "type": "image",
                "fileUrl": "https://cdn.example.com/promo.jpg",
                "caption": "-20 % ce week-end 🎉"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Message envoyé.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessageSent"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/v1/stories": {
      "post": {
        "tags": [
          "Stories"
        ],
        "operationId": "createStory",
        "summary": "Publier un élément de story",
        "description": "Ajoute un élément (texte, image ou vidéo) à la story de l'utilisateur connecté, ou la crée. Scope `stories:write`.",
        "security": [
          {
            "OAuthToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateStoryRequest"
              },
              "examples": {
                "text": {
                  "value": {
                    "type": "text",
                    "text": "Nouvelle collection disponible !"
                  }
                },
                "image": {
                  "value": {
                    "type": "image",
                    "imageUrl": "https://cdn.example.com/story.jpg"
                  }
                },
                "video": {
                  "value": {
                    "type": "video",
                    "videoUrl": "https://cdn.example.com/story.mp4",
                    "thumbnailUrl": "https://cdn.example.com/story.jpg"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Publié.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Success"
                    },
                    {
                      "properties": {
                        "data": {
                          "type": "object",
                          "properties": {
                            "storyId": {
                              "type": "string"
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/v1/stories/mine": {
      "get": {
        "tags": [
          "Stories"
        ],
        "operationId": "getMyStories",
        "summary": "Lister mes stories",
        "description": "Stories de l'utilisateur connecté uniquement. Scope `stories:read`.",
        "security": [
          {
            "OAuthToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "Stories.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Success"
                    },
                    {
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Story"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/v1/stories/delete": {
      "delete": {
        "tags": [
          "Stories"
        ],
        "operationId": "deleteStory",
        "summary": "Supprimer une story",
        "description": "Uniquement une story de l'utilisateur connecté. Scope `stories:write`.",
        "security": [
          {
            "OAuthToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "storyId"
                ],
                "properties": {
                  "storyId": {
                    "type": "string"
                  }
                }
              },
              "example": {
                "storyId": "STORY_ID"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Supprimée.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Success"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/v1/polls": {
      "post": {
        "tags": [
          "Sondages"
        ],
        "operationId": "createPoll",
        "summary": "Créer un sondage",
        "description": "Publie un sondage au nom d'un bot (clé API) ou d'un utilisateur délégué (OAuth2), dans un groupe (`groupId`, dont l'acteur doit être membre) ou une conversation privée (`receiverId`).",
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuthToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreatePollRequest"
              },
              "example": {
                "groupId": "GROUP_ID",
                "question": "Quel créneau pour la réunion ?",
                "options": [
                  "Lundi 10h",
                  "Mardi 14h",
                  "Jeudi 16h"
                ],
                "allowMultipleAnswers": true,
                "isAnonymous": false
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Créé.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Success"
                    },
                    {
                      "properties": {
                        "data": {
                          "type": "object",
                          "properties": {
                            "msgId": {
                              "type": "string"
                            },
                            "poll": {
                              "$ref": "#/components/schemas/Poll"
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/v1/polls/vote": {
      "post": {
        "tags": [
          "Sondages"
        ],
        "operationId": "votePoll",
        "summary": "Voter (bascule)",
        "description": "Ajoute le vote de l'acteur (bot ou utilisateur) à l'option, ou le retire s'il y était déjà. Refusé sur un sondage clos.",
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuthToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "messageId",
                  "optionId"
                ],
                "properties": {
                  "messageId": {
                    "type": "string",
                    "description": "`msgId` renvoyé à la création."
                  },
                  "optionId": {
                    "type": "string"
                  },
                  "groupId": {
                    "type": "string"
                  },
                  "receiverId": {
                    "type": "string"
                  }
                }
              },
              "example": {
                "messageId": "MSG_ID",
                "optionId": "OPTION_ID",
                "groupId": "GROUP_ID"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sondage à jour.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Success"
                    },
                    {
                      "properties": {
                        "data": {
                          "type": "object",
                          "properties": {
                            "poll": {
                              "$ref": "#/components/schemas/Poll"
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/polls/close": {
      "post": {
        "tags": [
          "Sondages"
        ],
        "operationId": "closePoll",
        "summary": "Clôturer un sondage",
        "description": "Réservé au créateur du sondage (bot ou utilisateur).",
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuthToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "messageId"
                ],
                "properties": {
                  "messageId": {
                    "type": "string"
                  },
                  "groupId": {
                    "type": "string"
                  },
                  "receiverId": {
                    "type": "string"
                  }
                }
              },
              "example": {
                "messageId": "MSG_ID",
                "groupId": "GROUP_ID"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Clôturé.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Success"
                    },
                    {
                      "properties": {
                        "data": {
                          "type": "object",
                          "properties": {
                            "poll": {
                              "$ref": "#/components/schemas/Poll"
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/oauth/authorize": {
      "get": {
        "tags": [
          "OAuth2 — Login with GPChat"
        ],
        "operationId": "oauthAuthorize",
        "summary": "Page de consentement",
        "description": "**À ouvrir dans le navigateur de l'utilisateur** (redirection), pas à appeler depuis votre serveur. L'utilisateur se connecte à GPChat et approuve ; il est ensuite redirigé vers `redirect_uri?code=…&state=…` (ou `?error=access_denied&state=…`).",
        "parameters": [
          {
            "name": "response_type",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "code"
              ]
            }
          },
          {
            "name": "client_id",
            "in": "query",
            "required": true,
            "description": "App ID de votre application.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "redirect_uri",
            "in": "query",
            "required": true,
            "description": "Doit correspondre **exactement** à une URI enregistrée dans la console.",
            "schema": {
              "type": "string",
              "format": "uri"
            }
          },
          {
            "name": "scope",
            "in": "query",
            "required": true,
            "description": "Scopes séparés par des espaces.",
            "schema": {
              "type": "string",
              "example": "profile stories:write"
            }
          },
          {
            "name": "state",
            "in": "query",
            "required": false,
            "description": "Valeur aléatoire anti-CSRF, renvoyée telle quelle. **Fortement recommandée.**",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Page HTML de consentement.",
            "content": {
              "text/html": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "Paramètres invalides (page HTML d'erreur)."
          },
          "404": {
            "description": "Application inconnue ou inactive (page HTML d'erreur)."
          }
        },
        "security": []
      }
    },
    "/oauth/token": {
      "post": {
        "tags": [
          "OAuth2 — Login with GPChat"
        ],
        "operationId": "oauthToken",
        "summary": "Échanger un code / rafraîchir un token",
        "description": "**Appel serveur à serveur uniquement** (le `client_secret` ne doit jamais atteindre un navigateur ou une app mobile).\n\n- `grant_type=authorization_code` : code (usage unique, valide 5 min) → access token (1 h) + refresh token (30 jours).\n- `grant_type=refresh_token` : **rotation** — l'ancien refresh token et son access token sont révoqués, une nouvelle paire est émise.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TokenRequest"
              },
              "examples": {
                "authorization_code": {
                  "value": {
                    "grant_type": "authorization_code",
                    "code": "CODE",
                    "redirect_uri": "https://monapp.com/callback",
                    "client_id": "APP_ID",
                    "client_secret": "CLIENT_SECRET"
                  }
                },
                "refresh_token": {
                  "value": {
                    "grant_type": "refresh_token",
                    "refresh_token": "gprt_…",
                    "client_id": "APP_ID",
                    "client_secret": "CLIENT_SECRET"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Tokens émis (format OAuth2 standard, sans enveloppe).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TokenResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        },
        "security": []
      }
    },
    "/oauth/userinfo": {
      "get": {
        "tags": [
          "OAuth2 — Login with GPChat"
        ],
        "operationId": "oauthUserinfo",
        "summary": "Profil de l'utilisateur connecté",
        "description": "Scope `profile` requis. Format OpenID Connect simplifié, sans enveloppe.",
        "security": [
          {
            "OAuthToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "Profil.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "sub": {
                      "type": "string",
                      "description": "userId GPChat, stable."
                    },
                    "name": {
                      "type": "string"
                    },
                    "picture": {
                      "type": "string",
                      "format": "uri"
                    }
                  }
                },
                "example": {
                  "sub": "aB3dE5fG7hI9",
                  "name": "Alice Ngo",
                  "picture": "https://…/avatar.jpg"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/oauth/revoke": {
      "post": {
        "tags": [
          "OAuth2 — Login with GPChat"
        ],
        "operationId": "oauthRevoke",
        "summary": "Révoquer un token (RFC 7009)",
        "description": "Révoque le token (access ou refresh) **et toute sa famille**. Répond toujours `200`, même pour un token inconnu (ne révèle jamais son existence). Appel serveur à serveur.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "token",
                  "client_id",
                  "client_secret"
                ],
                "properties": {
                  "token": {
                    "type": "string"
                  },
                  "client_id": {
                    "type": "string"
                  },
                  "client_secret": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Révoqué (ou déjà invalide).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Success"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        },
        "security": []
      }
    }
  },
  "webhooks": {
    "message.received": {
      "post": {
        "summary": "Message privé reçu par votre bot",
        "description": "Envoyé à l'URL de webhook de votre application. En-têtes : `X-GPChat-Event: message.received`, `X-GPChat-Signature: t=<epoch>,v1=<hmac_hex>`. Répondez `2xx` en moins de 10 s. `textMsg` est déjà **déchiffré**.",
        "parameters": [
          {
            "name": "X-GPChat-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "message.received"
              ]
            }
          },
          {
            "name": "X-GPChat-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "example": "t=1735000000,v1=5f2b…"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "type": {
                    "const": "message.received"
                  },
                  "data": {
                    "$ref": "#/components/schemas/WebhookMessage"
                  }
                }
              },
              "example": {
                "type": "message.received",
                "data": {
                  "msgId": "8f14e45f-ceea-4e7b-9a4c-1b2c3d4e5f60",
                  "type": "text",
                  "textMsg": "/start",
                  "from": "USER_ID",
                  "chatId": "USER_ID",
                  "sentAt": 1735000000000
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Accusé de réception (tout code 2xx)."
          }
        },
        "security": [],
        "operationId": "webhook_message_received"
      }
    },
    "group_message.received": {
      "post": {
        "summary": "Message posté dans un groupe dont votre bot est membre",
        "description": "Mêmes en-têtes et champs que `message.received`, avec `groupId`, `mentionedUserIds` et `isMentioned` (votre bot est mentionné) à la place de `chatId`.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "type": {
                    "const": "group_message.received"
                  },
                  "data": {
                    "$ref": "#/components/schemas/WebhookMessage"
                  }
                }
              },
              "example": {
                "type": "group_message.received",
                "data": {
                  "msgId": "MSG_ID",
                  "type": "text",
                  "textMsg": "@support_bot j'ai un souci",
                  "from": "USER_ID",
                  "groupId": "GROUP_ID",
                  "mentionedUserIds": [
                    "BOT_USER_ID"
                  ],
                  "isMentioned": true,
                  "sentAt": 1735000000000
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Accusé de réception (tout code 2xx)."
          }
        },
        "security": [],
        "operationId": "webhook_group_message_received"
      }
    },
    "ping": {
      "post": {
        "summary": "Test de connectivité",
        "description": "Envoyé par le bouton « Envoyer un ping » de la console.",
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "type": "ping",
                "data": {
                  "appId": "APP_ID",
                  "sentAt": 1735000000000,
                  "message": "Ping de test depuis la console développeur GPChat"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "security": [],
        "operationId": "webhook_ping"
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKey": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "gpk_live_…",
        "description": "Clé API de votre bot (console → Identifiants)."
      },
      "OAuthToken": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "gpat_…",
        "description": "Token d'accès OAuth2 obtenu via « Login with GPChat »."
      }
    },
    "schemas": {
      "UserId": {
        "type": "string",
        "pattern": "^[A-Za-z0-9_-]{1,128}$",
        "description": "userId GPChat."
      },
      "HttpsUrl": {
        "type": "string",
        "format": "uri",
        "pattern": "^https://",
        "maxLength": 2048
      },
      "Location": {
        "type": "object",
        "required": [
          "latitude",
          "longitude"
        ],
        "properties": {
          "latitude": {
            "type": "number",
            "minimum": -90,
            "maximum": 90
          },
          "longitude": {
            "type": "number",
            "minimum": -180,
            "maximum": 180
          },
          "name": {
            "type": "string",
            "maxLength": 256
          },
          "address": {
            "type": "string",
            "maxLength": 256
          }
        }
      },
      "MessageContent": {
        "type": "object",
        "required": [
          "type"
        ],
        "description": "Champs requis selon `type` :\n\n| type | requis | optionnels |\n|---|---|---|\n| `text` | `content` (≤ 4096) | `replyToMsgId` |\n| `image` | `fileUrl` | `caption` |\n| `video` | `fileUrl`, `thumbnailUrl` | `caption` |\n| `audio` | `fileUrl` | — |\n| `doc` | `fileUrl` | `caption` |\n| `gif` | `fileUrl` | — |\n| `sticker` | `fileUrl` (PNG/WebP) | — |\n| `location` | `location` | — |\n\nToutes les URL doivent être en `https://` et **publiquement accessibles** (l'app mobile les télécharge directement). `replyToMsgId` est disponible pour tous les types.",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "text",
              "image",
              "video",
              "audio",
              "doc",
              "gif",
              "sticker",
              "location"
            ]
          },
          "content": {
            "type": "string",
            "maxLength": 4096
          },
          "fileUrl": {
            "$ref": "#/components/schemas/HttpsUrl"
          },
          "thumbnailUrl": {
            "$ref": "#/components/schemas/HttpsUrl"
          },
          "caption": {
            "type": "string",
            "maxLength": 1024
          },
          "location": {
            "$ref": "#/components/schemas/Location"
          },
          "replyToMsgId": {
            "type": "string",
            "description": "`msgId` d'un message de la même conversation à citer."
          }
        }
      },
      "DirectMessageRequest": {
        "allOf": [
          {
            "type": "object",
            "required": [
              "to"
            ],
            "properties": {
              "to": {
                "$ref": "#/components/schemas/UserId"
              }
            }
          },
          {
            "$ref": "#/components/schemas/MessageContent"
          }
        ]
      },
      "GroupMessageRequest": {
        "allOf": [
          {
            "type": "object",
            "required": [
              "groupId"
            ],
            "properties": {
              "groupId": {
                "type": "string"
              },
              "mentionedUserIds": {
                "type": "array",
                "maxItems": 50,
                "items": {
                  "$ref": "#/components/schemas/UserId"
                },
                "description": "Membres mentionnés (notifiés comme tels)."
              }
            }
          },
          {
            "$ref": "#/components/schemas/MessageContent"
          }
        ]
      },
      "GroupRef": {
        "type": "object",
        "required": [
          "groupId"
        ],
        "properties": {
          "groupId": {
            "type": "string"
          }
        }
      },
      "CreateGroupRequest": {
        "type": "object",
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100
          },
          "description": {
            "type": "string",
            "maxLength": 500
          },
          "members": {
            "type": "array",
            "maxItems": 100,
            "items": {
              "$ref": "#/components/schemas/UserId"
            }
          },
          "isBroadcast": {
            "type": "boolean",
            "default": false
          },
          "photoUrl": {
            "$ref": "#/components/schemas/HttpsUrl"
          }
        }
      },
      "CreateStoryRequest": {
        "type": "object",
        "required": [
          "type"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "text",
              "image",
              "video"
            ]
          },
          "text": {
            "type": "string",
            "maxLength": 1000
          },
          "imageUrl": {
            "$ref": "#/components/schemas/HttpsUrl"
          },
          "videoUrl": {
            "$ref": "#/components/schemas/HttpsUrl"
          },
          "thumbnailUrl": {
            "$ref": "#/components/schemas/HttpsUrl"
          }
        }
      },
      "Story": {
        "type": "object",
        "properties": {
          "storyId": {
            "type": "string"
          },
          "texts": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "images": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "videos": {
            "type": "array",
            "items": {
              "type": "object"
            }
          }
        }
      },
      "CreatePollRequest": {
        "type": "object",
        "required": [
          "question",
          "options"
        ],
        "description": "`groupId` **ou** `receiverId` requis.",
        "properties": {
          "groupId": {
            "type": "string"
          },
          "receiverId": {
            "$ref": "#/components/schemas/UserId"
          },
          "question": {
            "type": "string",
            "minLength": 1,
            "maxLength": 300
          },
          "options": {
            "type": "array",
            "minItems": 2,
            "maxItems": 12,
            "items": {
              "type": "string",
              "maxLength": 100
            }
          },
          "allowMultipleAnswers": {
            "type": "boolean",
            "default": false
          },
          "isAnonymous": {
            "type": "boolean",
            "default": false
          }
        }
      },
      "Poll": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "question": {
            "type": "string"
          },
          "options": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "text": {
                  "type": "string"
                },
                "voterIds": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "allowMultipleAnswers": {
            "type": "boolean"
          },
          "isAnonymous": {
            "type": "boolean"
          },
          "createdBy": {
            "type": "string"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "closedAt": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "TokenRequest": {
        "type": "object",
        "required": [
          "grant_type",
          "client_id",
          "client_secret"
        ],
        "properties": {
          "grant_type": {
            "type": "string",
            "enum": [
              "authorization_code",
              "refresh_token"
            ]
          },
          "code": {
            "type": "string",
            "description": "Requis pour `authorization_code`."
          },
          "redirect_uri": {
            "type": "string",
            "description": "Requis pour `authorization_code` ; identique à celle de `/oauth/authorize`."
          },
          "refresh_token": {
            "type": "string",
            "description": "Requis pour `refresh_token`."
          },
          "client_id": {
            "type": "string"
          },
          "client_secret": {
            "type": "string"
          }
        }
      },
      "TokenResponse": {
        "type": "object",
        "properties": {
          "access_token": {
            "type": "string",
            "example": "gpat_…"
          },
          "refresh_token": {
            "type": "string",
            "example": "gprt_…"
          },
          "token_type": {
            "type": "string",
            "const": "Bearer"
          },
          "expires_in": {
            "type": "integer",
            "example": 3600
          },
          "scope": {
            "type": "string",
            "example": "profile stories:write"
          }
        }
      },
      "WebhookMessage": {
        "type": "object",
        "properties": {
          "msgId": {
            "type": "string",
            "description": "Identifiant unique : utilisez-le pour dédoublonner (une livraison peut être rejouée)."
          },
          "type": {
            "type": "string",
            "enum": [
              "text",
              "image",
              "video",
              "audio",
              "doc",
              "gif",
              "sticker",
              "location",
              "poll"
            ]
          },
          "textMsg": {
            "type": "string",
            "description": "Texte en clair."
          },
          "fileUrl": {
            "type": "string"
          },
          "caption": {
            "type": "string"
          },
          "gifUrl": {
            "type": "string"
          },
          "videoThumbnail": {
            "type": "string"
          },
          "location": {
            "$ref": "#/components/schemas/Location"
          },
          "poll": {
            "$ref": "#/components/schemas/Poll"
          },
          "replyToMsgId": {
            "type": "string",
            "description": "Message cité, le cas échéant."
          },
          "from": {
            "type": "string",
            "description": "userId de l'expéditeur."
          },
          "chatId": {
            "type": "string",
            "description": "Messages privés : conversation (= userId de l'expéditeur). Répondez avec `to: chatId`."
          },
          "groupId": {
            "type": "string",
            "description": "Messages de groupe."
          },
          "mentionedUserIds": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "isMentioned": {
            "type": "boolean"
          },
          "sentAt": {
            "type": "integer",
            "description": "Epoch en millisecondes."
          },
          "sandbox": {
            "type": "boolean",
            "description": "Présent et `true` uniquement pour un événement simulé depuis la console."
          }
        }
      },
      "Success": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "const": "success"
          },
          "message": {
            "type": "string"
          },
          "data": {},
          "timestamp": {
            "type": "integer"
          }
        }
      },
      "MessageSent": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Success"
          },
          {
            "properties": {
              "data": {
                "type": "object",
                "properties": {
                  "msgId": {
                    "type": "string"
                  }
                }
              }
            }
          }
        ],
        "example": {
          "status": "success",
          "message": "Opération réussie",
          "data": {
            "msgId": "8f14e45f-ceea-4e7b-9a4c-1b2c3d4e5f60"
          },
          "timestamp": 1735000000000
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "const": "error"
          },
          "code": {
            "type": "string",
            "examples": [
              "invalid-argument",
              "unauthenticated",
              "permission-denied",
              "not-found",
              "failed-precondition",
              "resource-exhausted",
              "RATE_LIMIT_EXCEEDED"
            ]
          },
          "message": {
            "type": "string"
          }
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Requête invalide (`invalid-argument`) ou précondition non remplie (`failed-precondition`, ex. l'utilisateur n'a jamais écrit au bot).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "status": "error",
              "code": "invalid-argument",
              "message": "Champ 'fileUrl' : seules les URL https:// sont acceptées"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Jeton manquant, invalide, révoqué ou expiré.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "status": "error",
              "code": "unauthenticated",
              "message": "Clé API invalide"
            }
          }
        }
      },
      "Forbidden": {
        "description": "Scope manquant, application désactivée/suspendue, utilisateur ayant bloqué le bot, ou droits insuffisants dans le groupe.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "status": "error",
              "code": "permission-denied",
              "message": "Application développeur suspendue"
            }
          }
        }
      },
      "NotFound": {
        "description": "Destinataire, groupe ou message cité introuvable.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "status": "error",
              "code": "not-found",
              "message": "Groupe introuvable"
            }
          }
        }
      },
      "NoBot": {
        "description": "La clé API n'est rattachée à aucun bot.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "status": "error",
              "code": "failed-precondition",
              "message": "Cette clé API n'est rattachée à aucun bot"
            }
          }
        }
      },
      "TooManyRequests": {
        "description": "Limite de requêtes dépassée : réessayez après quelques secondes (backoff exponentiel conseillé).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "status": "error",
              "code": "RATE_LIMIT_EXCEEDED",
              "message": "Limite de requêtes dépassée"
            }
          }
        }
      }
    }
  }
}
