{
  "openapi": "3.1.0",
  "info": {
    "title": "M2C Leads API",
    "version": "1.0.0",
    "summary": "Warme leads van Gold Lemon naar M2C.",
    "description": "Gold Lemon stuurt per lead één bericht naar M2C (`POST /v1/leads`) en kan vooraf vragen of een bedrijf bij M2C bekend is (`POST /v1/klantcheck`). Elk verzoek heeft een Bearer-sleutel én een HMAC-SHA256-handtekening over de ruwe body. Zie de documentatie op `/docs`. De API is van Makershouse, de beheerder van het leadportaal van M2C; sleutels en de testomgeving lopen via Makershouse.",
    "contact": {
      "name": "Makershouse (Kees Romijn)"
    }
  },
  "servers": [
    {
      "url": "https://api.m2c.makershouse.nl",
      "description": "Live"
    },
    {
      "url": "https://api-acc.m2c.makershouse.nl",
      "description": "Test (schrijft naar een nagebootste Teamleader)"
    }
  ],
  "security": [
    {
      "sleutel": [],
      "tijdstip": [],
      "handtekening": []
    }
  ],
  "tags": [
    {
      "name": "Leads",
      "description": "Leads en afmeldingen versturen."
    },
    {
      "name": "Klantcheck",
      "description": "Vooraf controleren of een bedrijf bekend is."
    }
  ],
  "paths": {
    "/v1/leads": {
      "post": {
        "tags": [
          "Leads"
        ],
        "operationId": "leadVersturen",
        "summary": "Lead of afmelding versturen",
        "description": "Eén lead per verzoek. Een nieuw signaal voor een bekende lead gaat met hetzelfde `gl_lead_id`. Opnieuw versturen is altijd veilig: het levert nooit een tweede lead op. Verstuur achter elkaar, niet gelijktijdig.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/Lead"
                  },
                  {
                    "$ref": "#/components/schemas/Afmelding"
                  }
                ]
              },
              "examples": {
                "lead": {
                  "summary": "Lead",
                  "value": {
                    "schema_versie": "1.0",
                    "gl_lead_id": "m2c-2026-000123",
                    "verstuurd_op": "2026-11-04T10:12:00+01:00",
                    "contact": {
                      "voornaam": "Sanne",
                      "achternaam": "de Vries",
                      "linkedin_url": "https://www.linkedin.com/in/voorbeeld",
                      "functie": "Operationeel directeur",
                      "dmu_rol": "beslisser"
                    },
                    "bedrijf": {
                      "naam": "Voorbeeld Advies BV",
                      "domein": "voorbeeld.nl",
                      "plaats": "Heemstede",
                      "medewerkers": "25-50"
                    },
                    "signaal": {
                      "bron": "linkedin",
                      "type": "connectie_geaccepteerd",
                      "datum": "2026-11-03",
                      "score": 74,
                      "dienst": "Cyberbewust"
                    },
                    "dossier": {
                      "aanleiding": "Accepteerde gisteren het connectieverzoek van M2C.",
                      "samenvatting": "Adviesbureau, 30 medewerkers, groeit snel (4 vacatures).",
                      "gespreksopener": "Jullie team groeit snel. Hoe neem je nieuwe collega's mee in veilig mailen?"
                    }
                  }
                },
                "afmelding": {
                  "summary": "Afmelding",
                  "value": {
                    "schema_versie": "1.0",
                    "gl_lead_id": "m2c-2026-000123",
                    "verstuurd_op": "2026-11-09T09:30:00+01:00",
                    "afmelding": true,
                    "bedrijf": {
                      "naam": "Voorbeeld Advies BV",
                      "domein": "voorbeeld.nl"
                    },
                    "contact": {
                      "linkedin_url": "https://www.linkedin.com/in/voorbeeld"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Het bedrijf was nog niet bekend; de lead is verwerkt.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LeadAntwoord"
                },
                "example": {
                  "status": "aangemaakt",
                  "gl_lead_id": "m2c-2026-000123"
                }
              }
            }
          },
          "200": {
            "description": "Het bedrijf was al bekend (`bestaat_al`), of een afmelding is verwerkt (`afgemeld`) of er was niets om bij te schrijven (`niet_gevonden`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LeadAntwoord"
                },
                "example": {
                  "status": "bestaat_al",
                  "gl_lead_id": "m2c-2026-000123"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/NietGeautoriseerd"
          },
          "405": {
            "$ref": "#/components/responses/AlleenPost"
          },
          "413": {
            "$ref": "#/components/responses/TeGroot"
          },
          "422": {
            "$ref": "#/components/responses/Ongeldig"
          },
          "429": {
            "$ref": "#/components/responses/TeSnel"
          },
          "503": {
            "$ref": "#/components/responses/Storing"
          }
        }
      }
    },
    "/v1/klantcheck": {
      "post": {
        "tags": [
          "Klantcheck"
        ],
        "operationId": "klantcheck",
        "summary": "Is dit bedrijf bekend?",
        "description": "Antwoordt alleen `bekend` of `onbekend`. Telt niet mee voor het dagmaximum. `bekend` is geen weigering: een lead van een bekend bedrijf wordt gewoon verwerkt.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/KlantcheckVraag"
              },
              "example": {
                "schema_versie": "1.0",
                "bedrijf": {
                  "domein": "voorbeeld.nl",
                  "naam": "Voorbeeld Advies BV",
                  "plaats": "Heemstede"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Antwoord.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "status"
                  ],
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "bekend",
                        "onbekend"
                      ],
                      "description": "Bekend of niet bij M2C."
                    }
                  }
                },
                "example": {
                  "status": "bekend"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/NietGeautoriseerd"
          },
          "405": {
            "$ref": "#/components/responses/AlleenPost"
          },
          "413": {
            "$ref": "#/components/responses/TeGroot"
          },
          "422": {
            "$ref": "#/components/responses/Ongeldig"
          },
          "429": {
            "$ref": "#/components/responses/TeSnel"
          },
          "503": {
            "$ref": "#/components/responses/Storing"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "sleutel": {
        "type": "http",
        "scheme": "bearer",
        "description": "De sleutel van Gold Lemon, eigen per omgeving; je krijgt hem van Makershouse."
      },
      "tijdstip": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Timestamp",
        "description": "Verzendmoment in seconden sinds 1970 (UTC). Meer dan 5 minuten verschil met de tijd van de server geeft `401`."
      },
      "handtekening": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Signature",
        "description": "`sha256=` + hexadecimale HMAC-SHA256 van `<X-Timestamp>.<ruwe body>`, met het signing secret (niet de Bearer-sleutel)."
      }
    },
    "schemas": {
      "Lead": {
        "type": "object",
        "required": [
          "schema_versie",
          "gl_lead_id",
          "verstuurd_op",
          "contact",
          "bedrijf",
          "signaal",
          "dossier"
        ],
        "description": "Onbekende velden worden genegeerd. Laat weg wat je niet weet; een lege tekst telt als afwezig. Platte tekst, geen HTML.",
        "properties": {
          "schema_versie": {
            "type": "string",
            "description": "Nu `1.0`. Een andere hoofdversie wordt geweigerd.",
            "pattern": "^1\\.\\d+$",
            "examples": [
              "1.0"
            ]
          },
          "gl_lead_id": {
            "type": "string",
            "description": "Vast per lead; nooit hergebruiken voor een andere lead.",
            "maxLength": 64,
            "examples": [
              "m2c-2026-000123"
            ]
          },
          "verstuurd_op": {
            "type": "string",
            "description": "ISO 8601 met tijdzone.",
            "format": "date-time",
            "examples": [
              "2026-11-04T10:12:00+01:00"
            ]
          },
          "afmelding": {
            "type": "boolean",
            "const": false,
            "description": "Weglaten of `false` voor een lead."
          },
          "contact": {
            "type": "object",
            "required": [
              "voornaam",
              "achternaam"
            ],
            "description": "`email` of `linkedin_url` is verplicht.",
            "properties": {
              "voornaam": {
                "type": "string",
                "description": "",
                "maxLength": 100
              },
              "achternaam": {
                "type": "string",
                "description": "",
                "maxLength": 100
              },
              "email": {
                "type": "string",
                "description": "",
                "maxLength": 254,
                "format": "email"
              },
              "linkedin_url": {
                "type": "string",
                "description": "Volledige URL.",
                "maxLength": 300,
                "pattern": "^https://(www\\.)?linkedin\\.com/in/[^/?#]+/?$",
                "examples": [
                  "https://www.linkedin.com/in/voorbeeld"
                ]
              },
              "telefoon": {
                "type": "string",
                "description": "Internationaal formaat.",
                "maxLength": 30,
                "examples": [
                  "+31612345678"
                ]
              },
              "functie": {
                "type": "string",
                "description": "",
                "maxLength": 100
              },
              "dmu_rol": {
                "type": "string",
                "description": "Bijvoorbeeld beslisser.",
                "maxLength": 50
              }
            }
          },
          "bedrijf": {
            "type": "object",
            "required": [
              "naam",
              "domein"
            ],
            "properties": {
              "naam": {
                "type": "string",
                "description": "",
                "maxLength": 200
              },
              "domein": {
                "type": "string",
                "description": "Zonder `https://` en `www.`.",
                "maxLength": 253,
                "examples": [
                  "voorbeeld.nl"
                ]
              },
              "kvk": {
                "type": "string",
                "description": "Precies 8 cijfers.",
                "pattern": "^\\d{8}$"
              },
              "plaats": {
                "type": "string",
                "description": "",
                "maxLength": 100
              },
              "medewerkers": {
                "type": "string",
                "description": "Bijvoorbeeld 25-50.",
                "maxLength": 20
              },
              "branche": {
                "type": "string",
                "description": "",
                "maxLength": 100
              }
            }
          },
          "signaal": {
            "type": "object",
            "required": [
              "bron",
              "type",
              "datum"
            ],
            "properties": {
              "bron": {
                "type": "string",
                "enum": [
                  "stairoids",
                  "tam_tracker",
                  "linkedin"
                ]
              },
              "type": {
                "type": "string",
                "description": "Kleine letters, cijfers en `_`.",
                "maxLength": 50,
                "pattern": "^[a-z0-9_]+$",
                "examples": [
                  "connectie_geaccepteerd"
                ]
              },
              "datum": {
                "type": "string",
                "description": "",
                "format": "date"
              },
              "score": {
                "type": "integer",
                "minimum": 0,
                "maximum": 100
              },
              "dienst": {
                "type": "string",
                "description": "",
                "maxLength": 100,
                "examples": [
                  "Cyberbewust"
                ]
              }
            }
          },
          "dossier": {
            "type": "object",
            "required": [
              "samenvatting",
              "aanleiding"
            ],
            "properties": {
              "samenvatting": {
                "type": "string",
                "description": "",
                "maxLength": 1500
              },
              "aanleiding": {
                "type": "string",
                "description": "",
                "maxLength": 500
              },
              "gespreksopener": {
                "type": "string",
                "description": "",
                "maxLength": 300
              }
            }
          }
        }
      },
      "Afmelding": {
        "type": "object",
        "required": [
          "schema_versie",
          "gl_lead_id",
          "verstuurd_op",
          "afmelding",
          "bedrijf",
          "contact"
        ],
        "description": "De persoon wil niet meer benaderd worden. M2C legt dat vast; er wordt niets verwijderd. Stuur daarna geen leads meer voor deze persoon. `contact.email` of `contact.linkedin_url` is verplicht.",
        "properties": {
          "schema_versie": {
            "type": "string",
            "description": "",
            "pattern": "^1\\.\\d+$"
          },
          "gl_lead_id": {
            "type": "string",
            "description": "",
            "maxLength": 64
          },
          "verstuurd_op": {
            "type": "string",
            "description": "",
            "format": "date-time"
          },
          "afmelding": {
            "type": "boolean",
            "const": true
          },
          "bedrijf": {
            "type": "object",
            "required": [
              "naam",
              "domein"
            ],
            "properties": {
              "naam": {
                "type": "string",
                "description": "",
                "maxLength": 200
              },
              "domein": {
                "type": "string",
                "description": "",
                "maxLength": 253
              }
            }
          },
          "contact": {
            "type": "object",
            "properties": {
              "email": {
                "type": "string",
                "description": "",
                "maxLength": 254,
                "format": "email"
              },
              "linkedin_url": {
                "type": "string",
                "description": "",
                "maxLength": 300
              }
            }
          }
        }
      },
      "KlantcheckVraag": {
        "type": "object",
        "required": [
          "schema_versie",
          "bedrijf"
        ],
        "properties": {
          "schema_versie": {
            "type": "string",
            "description": "",
            "pattern": "^1\\.\\d+$"
          },
          "bedrijf": {
            "type": "object",
            "required": [
              "domein"
            ],
            "properties": {
              "domein": {
                "type": "string",
                "description": "",
                "maxLength": 253
              },
              "kvk": {
                "type": "string",
                "description": "",
                "pattern": "^\\d{8}$"
              },
              "naam": {
                "type": "string",
                "description": "Stuur mee als je hem weet.",
                "maxLength": 200
              },
              "plaats": {
                "type": "string",
                "description": "",
                "maxLength": 100
              }
            }
          },
          "contact": {
            "type": "object",
            "properties": {
              "email": {
                "type": "string",
                "description": "",
                "maxLength": 254,
                "format": "email"
              }
            }
          }
        }
      },
      "LeadAntwoord": {
        "type": "object",
        "required": [
          "status",
          "gl_lead_id"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "aangemaakt",
              "bestaat_al",
              "afgemeld",
              "niet_gevonden"
            ]
          },
          "gl_lead_id": {
            "type": "string",
            "description": "Het id uit het bericht."
          }
        }
      },
      "Fout": {
        "type": "object",
        "required": [
          "status"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "niet_geautoriseerd",
              "alleen_post",
              "niet_gevonden",
              "te_groot",
              "ongeldig",
              "te_snel",
              "daglimiet",
              "storing"
            ]
          },
          "gl_lead_id": {
            "type": "string",
            "description": "Alleen als het bericht er een had."
          },
          "fouten": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "veld",
                "reden"
              ],
              "properties": {
                "veld": {
                  "type": "string",
                  "description": "Pad van het veld, bijvoorbeeld `bedrijf.kvk`."
                },
                "reden": {
                  "type": "string",
                  "description": "Korte reden. Bevat nooit de ingestuurde waarde."
                }
              }
            },
            "description": "Alleen bij `ongeldig`."
          }
        }
      }
    },
    "responses": {
      "NietGeautoriseerd": {
        "description": "Sleutel, tijdstip of handtekening onjuist. Niet automatisch herhalen.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Fout"
            },
            "example": {
              "status": "niet_geautoriseerd"
            }
          }
        }
      },
      "AlleenPost": {
        "description": "Alleen `POST`.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Fout"
            },
            "example": {
              "status": "alleen_post"
            }
          }
        }
      },
      "TeGroot": {
        "description": "Body groter dan 64 KB.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Fout"
            },
            "example": {
              "status": "te_groot"
            }
          }
        }
      },
      "Ongeldig": {
        "description": "Een of meer velden kloppen niet. Pas opnieuw versturen na correctie.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Fout"
            },
            "example": {
              "status": "ongeldig",
              "gl_lead_id": "m2c-2026-000123",
              "fouten": [
                {
                  "veld": "bedrijf.kvk",
                  "reden": "moet uit 8 cijfers bestaan"
                }
              ]
            }
          }
        }
      },
      "TeSnel": {
        "description": "`te_snel`: meer dan 10 leads (of 30 klantchecks) per minuut. `daglimiet`: het dagmaximum is bereikt; die dag niets meer sturen.",
        "headers": {
          "Retry-After": {
            "description": "Seconden wachten (bij `daglimiet`: tot middernacht Nederlandse tijd).",
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Fout"
            },
            "example": {
              "status": "te_snel"
            }
          }
        }
      },
      "Storing": {
        "description": "Storing, of er is al een lead bezig. Opnieuw na 1, 5 en 30 minuten (of na `Retry-After`).",
        "headers": {
          "Retry-After": {
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Fout"
            },
            "example": {
              "status": "storing"
            }
          }
        }
      }
    },
    "examples": {
      "lead": {
        "value": {
          "schema_versie": "1.0",
          "gl_lead_id": "m2c-2026-000123",
          "verstuurd_op": "2026-11-04T10:12:00+01:00",
          "contact": {
            "voornaam": "Sanne",
            "achternaam": "de Vries",
            "linkedin_url": "https://www.linkedin.com/in/voorbeeld",
            "functie": "Operationeel directeur",
            "dmu_rol": "beslisser"
          },
          "bedrijf": {
            "naam": "Voorbeeld Advies BV",
            "domein": "voorbeeld.nl",
            "plaats": "Heemstede",
            "medewerkers": "25-50"
          },
          "signaal": {
            "bron": "linkedin",
            "type": "connectie_geaccepteerd",
            "datum": "2026-11-03",
            "score": 74,
            "dienst": "Cyberbewust"
          },
          "dossier": {
            "aanleiding": "Accepteerde gisteren het connectieverzoek van M2C.",
            "samenvatting": "Adviesbureau, 30 medewerkers, groeit snel (4 vacatures).",
            "gespreksopener": "Jullie team groeit snel. Hoe neem je nieuwe collega's mee in veilig mailen?"
          }
        }
      }
    }
  }
}
