{
  "openapi": "3.1.0",
  "info": {
    "title": "API Pignon Digital",
    "version": "1.0.0",
    "summary": "Demander un diagnostic, s'inscrire à la gazette ou demander un audit sectoriel auprès de Pignon Digital.",
    "description": "API publique de Pignon Digital, agence site web, SEO local et référencement IA à Paris (29 rue Tronchet, 75008). Trois actions en JSON, sans clé d'API. N'envoyez une demande qu'avec l'accord explicite de la personne concernée. Toutes les réponses sont en JSON, erreurs comprises : { \"success\": false, \"error\": { \"code\", \"message\", \"hint\" } }. Documentation lisible : https://pignondigital.com/developers",
    "contact": {
      "name": "Pignon Digital",
      "email": "contact@pignondigital.com",
      "url": "https://pignondigital.com/developers"
    },
    "termsOfService": "https://pignondigital.com/legal"
  },
  "servers": [
    {
      "url": "https://pignondigital.com",
      "description": "Production"
    }
  ],
  "externalDocs": {
    "description": "Documentation et exemples curl",
    "url": "https://pignondigital.com/developers"
  },
  "tags": [
    {
      "name": "leads",
      "description": "Demandes de contact, de diagnostic et d'audit."
    },
    {
      "name": "newsletter",
      "description": "Inscription à la gazette mensuelle."
    }
  ],
  "paths": {
    "/api/contact": {
      "post": {
        "operationId": "submitContactRequest",
        "tags": [
          "leads"
        ],
        "summary": "Envoyer une demande de contact ou de diagnostic gratuit",
        "description": "Transmet une demande à Pignon Digital (site sur mesure, fiche Google, SEO local, référencement IA, diagnostic gratuit). Réponse humaine sous 24 h ouvrées et e-mail de confirmation immédiat à l'adresse fournie. Deux usages : formulaire complet (PRENOM, NOM, EMAIL, JOB_TITLE, TYPE_BESOIN, SERVICE, DEMANDE requis) ou demande courte avec TOOL_SOURCE (seuls PRENOM et EMAIL requis).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ContactRequest"
              },
              "examples": {
                "formulaireComplet": {
                  "summary": "Formulaire de contact complet",
                  "value": {
                    "PRENOM": "Marie",
                    "NOM": "Durand",
                    "EMAIL": "marie@atelier.fr",
                    "JOB_TITLE": "Notaire",
                    "TYPE_BESOIN": "Attirer, Convertir",
                    "SERVICE": "Site sur mesure",
                    "DEMANDE": "Refonte du site de l'étude et fiche Google."
                  }
                },
                "diagnosticGratuit": {
                  "summary": "Demande courte de diagnostic gratuit",
                  "value": {
                    "PRENOM": "Paul",
                    "EMAIL": "paul@renov.fr",
                    "TOOL_SOURCE": "diagnostic-gratuit"
                  }
                }
              }
            },
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/ContactRequest"
              }
            }
          },
          "description": "Objet JSON, ou formulaire HTML (application/x-www-form-urlencoded) : un champ répété est joint par « , »."
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/Success"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          },
          "502": {
            "$ref": "#/components/responses/UpstreamError"
          },
          "303": {
            "$ref": "#/components/responses/FormRedirect"
          }
        }
      }
    },
    "/api/subscribe": {
      "post": {
        "operationId": "subscribeNewsletter",
        "tags": [
          "newsletter"
        ],
        "summary": "Inscrire une adresse à la gazette du numérique",
        "description": "Inscrit l'adresse à la gazette mensuelle de Pignon Digital (un conseil concret par mois, sans jargon). Une adresse déjà inscrite renvoie aussi un succès avec existing: true.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SubscribeRequest"
              },
              "examples": {
                "inscription": {
                  "summary": "Inscription simple",
                  "value": {
                    "email": "marie@atelier.fr",
                    "firstName": "Marie",
                    "source": "agent"
                  }
                }
              }
            },
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/SubscribeRequest"
              }
            }
          },
          "description": "Objet JSON, ou formulaire HTML (application/x-www-form-urlencoded) : un champ répété est joint par « , »."
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/Success"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          },
          "502": {
            "$ref": "#/components/responses/UpstreamError"
          },
          "303": {
            "$ref": "#/components/responses/FormRedirect"
          }
        }
      }
    },
    "/api/audit": {
      "post": {
        "operationId": "submitSectorAudit",
        "tags": [
          "leads"
        ],
        "summary": "Demander un audit de visibilité sectoriel",
        "description": "Enregistre une demande d'audit de présence en ligne pour un avocat, une boutique ou une entreprise du BTP, avec les réponses du questionnaire de la page sectorielle.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AuditRequest"
              },
              "examples": {
                "btp": {
                  "summary": "Audit pour une entreprise du BTP",
                  "value": {
                    "email": "paul@renov.fr",
                    "firstName": "Paul",
                    "sector": "btp",
                    "formData": {
                      "siteWeb": "non",
                      "ficheGoogle": "oui"
                    }
                  }
                }
              }
            },
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/AuditRequest"
              }
            }
          },
          "description": "Objet JSON, ou formulaire HTML (application/x-www-form-urlencoded) : un champ répété est joint par « , »."
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/Success"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          },
          "502": {
            "$ref": "#/components/responses/UpstreamError"
          },
          "303": {
            "$ref": "#/components/responses/FormRedirect"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "ContactRequest": {
        "type": "object",
        "description": "Demande de contact. Sans TOOL_SOURCE, NOM, JOB_TITLE, TYPE_BESOIN, SERVICE et DEMANDE sont aussi requis.",
        "required": [
          "PRENOM",
          "EMAIL"
        ],
        "additionalProperties": false,
        "properties": {
          "PRENOM": {
            "type": "string",
            "maxLength": 100,
            "description": "Prénom du demandeur.",
            "example": "Marie"
          },
          "EMAIL": {
            "type": "string",
            "format": "email",
            "maxLength": 254,
            "description": "Adresse e-mail où répondre.",
            "example": "marie@atelier.fr"
          },
          "NOM": {
            "type": "string",
            "maxLength": 100,
            "description": "Nom. Requis sans TOOL_SOURCE.",
            "example": "Durand"
          },
          "JOB_TITLE": {
            "type": "string",
            "maxLength": 200,
            "description": "Métier ou activité. Requis sans TOOL_SOURCE.",
            "example": "Notaire"
          },
          "TYPE_BESOIN": {
            "type": "string",
            "maxLength": 200,
            "description": "Piliers concernés, séparés par des virgules, parmi Attirer, Engager, Convertir. Requis sans TOOL_SOURCE.",
            "example": "Attirer, Convertir"
          },
          "SERVICE": {
            "type": "string",
            "maxLength": 200,
            "description": "Service souhaité, en clair. Requis sans TOOL_SOURCE.",
            "example": "Site sur mesure"
          },
          "DEMANDE": {
            "type": "string",
            "maxLength": 5000,
            "description": "Description du projet. Requis sans TOOL_SOURCE.",
            "example": "Refonte du site de l'étude et fiche Google."
          },
          "SMS": {
            "type": "string",
            "maxLength": 30,
            "description": "Numéro de téléphone sans indicatif.",
            "example": "612345678"
          },
          "SMS__COUNTRY_CODE": {
            "type": "string",
            "maxLength": 6,
            "description": "Indicatif pays du numéro.",
            "example": "+33"
          },
          "TOOL_SOURCE": {
            "type": "string",
            "maxLength": 100,
            "description": "Origine d'une demande courte (outil, diagnostic). Si présent, seuls PRENOM et EMAIL sont requis.",
            "example": "diagnostic-gratuit"
          },
          "TOOL_DATA": {
            "type": "string",
            "maxLength": 20000,
            "description": "Résultats de l'outil, en JSON sérialisé dans une chaîne.",
            "example": "{\"score\":62}"
          }
        }
      },
      "SubscribeRequest": {
        "type": "object",
        "required": [
          "email",
          "firstName"
        ],
        "additionalProperties": false,
        "properties": {
          "email": {
            "type": "string",
            "format": "email",
            "maxLength": 254,
            "description": "Adresse à inscrire.",
            "example": "marie@atelier.fr"
          },
          "firstName": {
            "type": "string",
            "maxLength": 100,
            "description": "Prénom.",
            "example": "Marie"
          },
          "lastName": {
            "type": "string",
            "maxLength": 100,
            "description": "Nom.",
            "example": "Durand"
          },
          "source": {
            "type": "string",
            "maxLength": 100,
            "description": "Origine de l'inscription.",
            "example": "agent"
          }
        }
      },
      "AuditRequest": {
        "type": "object",
        "required": [
          "email",
          "firstName"
        ],
        "additionalProperties": false,
        "properties": {
          "email": {
            "type": "string",
            "format": "email",
            "maxLength": 254,
            "description": "Adresse e-mail du demandeur.",
            "example": "paul@renov.fr"
          },
          "firstName": {
            "type": "string",
            "maxLength": 100,
            "description": "Prénom.",
            "example": "Paul"
          },
          "lastName": {
            "type": "string",
            "maxLength": 100,
            "description": "Nom.",
            "example": "Martin"
          },
          "phone": {
            "type": "string",
            "maxLength": 30,
            "description": "Téléphone.",
            "example": "+33612345678"
          },
          "sector": {
            "type": "string",
            "enum": [
              "avocats",
              "boutiques",
              "btp"
            ],
            "description": "Secteur de l'entreprise.",
            "example": "btp"
          },
          "formData": {
            "type": "object",
            "description": "Réponses du questionnaire, question et réponse en texte.",
            "additionalProperties": {
              "type": "string"
            },
            "example": {
              "siteWeb": "non",
              "ficheGoogle": "oui"
            }
          }
        }
      },
      "SuccessResponse": {
        "type": "object",
        "required": [
          "success"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "const": true,
            "description": "Toujours true en cas de succès."
          },
          "existing": {
            "type": "boolean",
            "description": "true si l'adresse était déjà connue (gazette, audit)."
          }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "required": [
          "success",
          "error"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "const": false,
            "description": "Toujours false en cas d'erreur."
          },
          "error": {
            "$ref": "#/components/schemas/Error"
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "code",
          "message",
          "hint"
        ],
        "properties": {
          "code": {
            "type": "string",
            "enum": [
              "invalid_json",
              "unsupported_media_type",
              "missing_field",
              "invalid_field",
              "method_not_allowed",
              "not_found",
              "server_error",
              "upstream_error"
            ],
            "description": "Code stable, à utiliser dans le code appelant."
          },
          "message": {
            "type": "string",
            "description": "Description lisible de l'erreur, en français."
          },
          "hint": {
            "type": "string",
            "description": "Comment corriger la requête ou que faire ensuite."
          }
        }
      }
    },
    "responses": {
      "Success": {
        "description": "Demande enregistrée.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/SuccessResponse"
            },
            "example": {
              "success": true
            }
          }
        }
      },
      "BadRequest": {
        "description": "Corps invalide : JSON illisible, champ requis manquant ou valeur invalide.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "success": false,
              "error": {
                "code": "missing_field",
                "message": "Champ requis manquant : EMAIL",
                "hint": "Ajoutez EMAIL au corps JSON. Voir https://pignondigital.com/openapi.json"
              }
            }
          }
        }
      },
      "MethodNotAllowed": {
        "description": "Méthode autre que POST ou OPTIONS. L'en-tête Allow liste les méthodes acceptées.",
        "headers": {
          "Allow": {
            "description": "Méthodes acceptées.",
            "schema": {
              "type": "string",
              "example": "POST, OPTIONS"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "success": false,
              "error": {
                "code": "method_not_allowed",
                "message": "Méthode GET non autorisée sur /api/contact.",
                "hint": "Envoyez une requête POST avec un corps JSON. Voir https://pignondigital.com/openapi.json"
              }
            }
          }
        }
      },
      "UnsupportedMediaType": {
        "description": "Corps envoyé ni en JSON (application/json) ni en formulaire HTML (application/x-www-form-urlencoded).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "success": false,
              "error": {
                "code": "unsupported_media_type",
                "message": "Le corps doit être du JSON.",
                "hint": "Ajoutez l'en-tête Content-Type: application/json et envoyez un objet JSON."
              }
            }
          }
        }
      },
      "ServerError": {
        "description": "Erreur interne ou configuration incomplète.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "success": false,
              "error": {
                "code": "server_error",
                "message": "Une erreur interne est survenue.",
                "hint": "Réessayez dans un instant ou écrivez à contact@pignondigital.com."
              }
            }
          }
        }
      },
      "UpstreamError": {
        "description": "Le service d'envoi (Brevo) n'a pas répondu correctement.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "success": false,
              "error": {
                "code": "upstream_error",
                "message": "La demande n'a pas pu être enregistrée.",
                "hint": "Réessayez dans un instant ou écrivez à contact@pignondigital.com."
              }
            }
          }
        }
      },
      "FormRedirect": {
        "description": "Formulaire HTML envoyé sans en-tête Accept: application/json : succès, redirection vers la page de confirmation /contact/merci. Ajoutez Accept: application/json pour recevoir la réponse JSON 200.",
        "headers": {
          "Location": {
            "description": "Page de confirmation.",
            "schema": {
              "type": "string",
              "example": "/contact/merci"
            }
          }
        }
      }
    }
  }
}
