{"openapi":"3.0.0","info":{"title":"Marvin — API partenaire & intégrations","description":"Référence des API publiques Marvin destinées aux intégrateurs externes (partenaires de sourcing, site carrière). Authentification par clé `X-Marvin-Public-Key`.","version":"1.0.0"},"components":{"securitySchemes":{"cookieAuth":{"type":"apiKey","in":"cookie","name":"session","description":"JWT session cookie"},"partnerApiKey":{"type":"apiKey","in":"header","name":"X-Marvin-Public-Key","description":"Clé `pk_live_…` scopée par tenant, générée par le client depuis Marvin, à droits limités. Droits possibles : `public:careers:read`, `public:careers:apply`, `public:contacts:write`, `partner:projects:read`, `partner:candidates:write`. Droit exigé par chaque opération : https://developers.marvins.ai/docs/authentication."}},"schemas":{"Error":{"type":"object","description":"Erreurs des opérations JSON : un objet `error` qui porte un `code` lisible par une machine (et le plus souvent un `message`). Les flux XML peuvent répondre une erreur en XML, en texte ou dans cette enveloppe JSON, selon l'endroit où elle survient : se fier au statut HTTP et au `Content-Type`.","required":["error"],"properties":{"error":{"type":"object","required":["code"],"properties":{"code":{"type":"string","description":"Code stable, lisible par une machine (`unauthorized`, `insufficient_scope`, `slug_mismatch`, `rate_limited`…)."},"message":{"type":"string","description":"Explication lisible."},"retryable":{"type":"boolean","description":"Présent sur les erreurs du gestionnaire global : `true` si réessayer a un sens."},"retryAfterSec":{"type":"integer","description":"Délai conseillé avant de réessayer, en secondes (débit dépassé)."},"field":{"type":"string","description":"Champ en cause, quand l’erreur porte sur un champ précis."}}}}}}},"paths":{"/v1/public/careers/{slug}":{"get":{"operationId":"listCareersJobs","summary":"Lister les offres ouvertes et les infos du tenant (JSON)","tags":["Careers (Public)"],"security":[{"partnerApiKey":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"slug","required":true}],"responses":{"200":{"description":"Default Response"},"4XX":{"description":"Erreur du client : clé absente ou invalide, droit manquant, requête invalide, ressource introuvable, débit dépassé.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"5XX":{"description":"Erreur du serveur.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/public/careers/{slug}/channel/site-web":{"get":{"operationId":"listSiteWebChannelJobs","summary":"Lister les offres du canal « Site web » (JSON, sélection curée)","tags":["Careers (Public)"],"security":[{"partnerApiKey":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"slug","required":true}],"responses":{"200":{"description":"Default Response"},"4XX":{"description":"Erreur du client : clé absente ou invalide, droit manquant, requête invalide, ressource introuvable, débit dépassé.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"5XX":{"description":"Erreur du serveur.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/public/careers/{slug}/jobs/{id}":{"get":{"operationId":"getCareersJob","summary":"Détail d’une offre (JSON)","tags":["Careers (Public)"],"security":[{"partnerApiKey":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"slug","required":true},{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"Default Response"},"4XX":{"description":"Erreur du client : clé absente ou invalide, droit manquant, requête invalide, ressource introuvable, débit dépassé.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"5XX":{"description":"Erreur du serveur.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/public/careers/{slug}/feed.xml":{"get":{"operationId":"getCareersJobsFeedXml","summary":"Flux XML des offres (auth par header X-Marvin-Public-Key ou query ?key=)","tags":["Careers (Public)"],"security":[{"partnerApiKey":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"slug","required":true}],"responses":{"200":{"description":"Default Response"}}}},"/v1/public/careers/{slug}/jobs/{id}/apply":{"post":{"operationId":"submitJobApplication","summary":"Soumettre une candidature à une offre depuis votre site","tags":["Careers (Public)"],"description":"Reçoit une candidature déposée depuis le formulaire du site web du\nclient et la crée dans Marvin (CV récupéré + parsé, profil rattaché\nou créé, candidature attachée à l’offre, source `site_web`).\n\n`{id}` est l’identifiant de l’offre lu dans le flux d’offres : `<jobid>`\ndu flux Site web, `<id>` de `feed.xml`.\n\nAvec une clé Site web, la candidature est acceptée sur toute offre\nouverte, même si sa diffusion n’est pas activée dans Marvin. Pour un\ndépôt de partenaire (clé partenaire, ou `?origin=` d’un outil de\nsourcing), l’offre doit en plus être diffusée.\n\n#### Authentification\nHeader `X-Marvin-Public-Key: pk_live_…` — clé générée depuis\nConfiguration → Multidiffusion → Site web → Configurer (scope\ncandidatures), ou clé partenaire générée depuis Paramètres →\nIntégrations par un connecteur de sourcing prévu pour ces routes\n(scope `partner:candidates:write`).\nLa clé est un secret côté serveur : ne jamais l’exposer côté\nnavigateur.\n\n#### Attribution de la source (optionnel)\nQuery `?origin=` pour attribuer la provenance. Sans paramètre, la\ncandidature est attribuée à `site_web`. Un outil de sourcing passe\nl'identifiant de provenance que Marvin lui a communiqué : la\ncandidature **et** le candidat lui sont attribués. Avec une clé\npartenaire, la provenance est fixée par la clé et `?origin=` est\nignoré.\n\n#### Corps de la requête\n`multipart/form-data` (avec CV) ou `application/json`. Champs\ncompatibles avec le format historique V1 :\n\n| Champ | Requis | Description |\n| --- | --- | --- |\n| `firstName` | oui | Prénom du candidat |\n| `lastName` | oui | Nom du candidat |\n| `email` | oui | E-mail du candidat |\n| `phoneNumber` | non | Téléphone |\n| `linkedin` | non | URL du profil LinkedIn |\n| `motivationText` | non | Message / lettre de motivation |\n| `resume` (ou `cv`) | non | Fichier CV (PDF, Word ou image), en multipart |\n| `languages` | non | Langues du candidat (voir ci-dessous) |\n\nLes champs personnalisés du formulaire de candidature sont acceptés\nen plus. Un champ à choix multiple s’envoie en chaîne : le libellé d’une\noption, ou un tableau JSON sérialisé (`[\"A\",\"B\"]`). Une valeur qui n’est pas\nune option du champ fait refuser la candidature (`409`, `conflict`).\n\n#### Langues du candidat (optionnel)\n`languages` ajoute les langues du candidat à sa fiche : une ou plusieurs\nlangues, séparées par une virgule ou un point-virgule (en JSON, un tableau\nde chaînes est aussi accepté ; en multipart, le champ peut être répété).\nChaque langue est désignée par son code ISO 639-1 à deux lettres (`fr`,\n`en`, `es`… ; `fra` n’est pas reconnu)\nou par son nom en français ou en anglais, parmi la centaine de langues\ncourantes que Marvin connaît. La candidature ne modifie ni ne retire les\nlangues que la fiche porte déjà. Une valeur non reconnue est ignorée sans\nfaire échouer la candidature. Au plus 10 langues distinctes sont retenues.\n\n#### Exemple\n```bash\ncurl -X POST \\\n  \"https://api.marvins.ai/v1/public/careers/{slug}/jobs/{id}/apply\" \\\n  -H \"X-Marvin-Public-Key: pk_live_…\" \\\n  -F \"firstName=Marie\" \\\n  -F \"lastName=Dupont\" \\\n  -F \"email=marie.dupont@example.com\" \\\n  -F \"phoneNumber=+33612345678\" \\\n  -F \"resume=@/chemin/vers/cv.pdf\"\n```\n\n#### Réponses\n`201` candidature reçue · `401` clé absente/invalide · `403` scope\nmanquant · `404` offre introuvable · `410` offre fermée ou supprimée\n(ou non diffusée, pour un dépôt de partenaire) · `400` `firstName`,\n`lastName` ou `email` manquant ou invalide · `422` autre champ rendu\nobligatoire par le formulaire du client, nommé dans `error.field` ·\n`409` champ personnalisé modifié pendant l’envoi, ou valeur hors des\noptions d’un champ à choix multiple.","security":[{"partnerApiKey":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"slug","required":true},{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"Default Response"},"4XX":{"description":"Erreur du client : clé absente ou invalide, droit manquant, requête invalide, ressource introuvable, débit dépassé.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"5XX":{"description":"Erreur du serveur.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/public/careers/{slug}/jobs/{id}/apply-widget":{"post":{"operationId":"submitJobApplicationFromWidget","summary":"Soumettre une candidature depuis le widget navigateur (reCAPTCHA, sans clé)","tags":["Careers (Public)"],"description":"Endpoint appelé par le **widget careers embarqué** (navigateur), pas\npar un serveur. Le tenant est résolu par le `:slug` de l’URL — aucune\nclé publique n’est requise ni acceptée ici (la clé embarquée dans le\nHTML est en lecture seule).\n\nAnti-abus : reCAPTCHA v3 invisible via le header `X-Recaptcha-Token`\n(généré par `grecaptcha.execute(SITE_KEY, { action: \"apply\" })`) +\nrate-limit par IP. Si `RECAPTCHA_SECRET_KEY` n’est pas configurée côté\nserveur (dev/preview), la vérification est ignorée.\n\nCorps identique au formulaire carrière (`multipart/form-data` avec CV\nou `application/json`) : `firstName`, `lastName`, `email`, `phone`,\n`linkedinLink`, `motivationText`, `cv`.\n\n`201` candidature reçue · `404` tenant/offre introuvable · `422`\nreCAPTCHA échoué ou champ requis manquant.","parameters":[{"schema":{"type":"string"},"in":"path","name":"slug","required":true},{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"Default Response"},"4XX":{"description":"Erreur du client : clé absente ou invalide, droit manquant, requête invalide, ressource introuvable, débit dépassé.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"5XX":{"description":"Erreur du serveur.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/public/careers/{slug}/apply-widget":{"post":{"operationId":"submitSpontaneousApplicationFromWidget","summary":"Soumettre une candidature spontanée depuis le widget navigateur (reCAPTCHA, sans clé)","tags":["Careers (Public)"],"description":"Endpoint appelé par le **widget careers embarqué** (navigateur) pour une\ncandidature **spontanée** (non liée à une offre). Tenant résolu par le\n`:slug` de l’URL — aucune clé publique requise ni acceptée.\n\nAnti-abus : reCAPTCHA v3 invisible via header `X-Recaptcha-Token` (action\n`apply_spontaneous`) + rate-limit par IP + fail-closed en prod (503 si le\nsecret reCAPTCHA n’est pas configuré). Gardé par le toggle\n`allowSpontaneous` (Paramètres → Carrières) : `403` si désactivé.\n\nCorps identique au formulaire carrière (`multipart/form-data` avec CV ou\n`application/json`), sans `{id}` : `firstName`, `lastName`, `email`,\n`phone`, `linkedinLink`, `motivationText`, `cv`.\n\n`201` candidature reçue · `403` candidatures spontanées désactivées · `404`\ntenant introuvable · `422` reCAPTCHA échoué ou champ requis manquant · `503`\nreCAPTCHA indisponible (prod).","parameters":[{"schema":{"type":"string"},"in":"path","name":"slug","required":true}],"responses":{"200":{"description":"Default Response"},"4XX":{"description":"Erreur du client : clé absente ou invalide, droit manquant, requête invalide, ressource introuvable, débit dépassé.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"5XX":{"description":"Erreur du serveur.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/public/careers/{slug}/apply":{"post":{"operationId":"submitSpontaneousApplication","summary":"Soumettre une candidature spontanée (sans offre) depuis votre site","tags":["Careers (Public)"],"description":"Reçoit une candidature **spontanée** (non liée à une offre) déposée\ndepuis le site du client et la crée dans Marvin (CV récupéré + parsé,\nprofil rattaché ou créé, candidature `job_id` null, source `site_web`).\n\n#### Authentification\nHeader `X-Marvin-Public-Key: pk_live_…` (même clé que l’endpoint de\ncandidature à une offre, scope candidatures). Secret côté serveur.\n\n#### Corps de la requête\n`multipart/form-data` (avec CV) ou `application/json`. Champs identiques\nà l’endpoint offre, sans `{id}` :\n\n| Champ | Requis | Description |\n| --- | --- | --- |\n| `firstName` | oui | Prénom du candidat |\n| `lastName` | oui | Nom du candidat |\n| `email` | oui | E-mail du candidat |\n| `phoneNumber` | non | Téléphone |\n| `linkedin` | non | URL du profil LinkedIn |\n| `motivationText` | non | Message / lettre de motivation |\n| `resume` (ou `cv`) | non | Fichier CV (PDF, Word ou image), en multipart |\n| `languages` | non | Langues du candidat (voir ci-dessous) |\n\n#### Langues du candidat (optionnel)\n`languages` ajoute les langues du candidat à sa fiche : une ou plusieurs\nlangues, séparées par une virgule ou un point-virgule (en JSON, un tableau\nde chaînes est aussi accepté ; en multipart, le champ peut être répété).\nChaque langue est désignée par son code ISO 639-1 à deux lettres (`fr`,\n`en`, `es`… ; `fra` n’est pas reconnu)\nou par son nom en français ou en anglais, parmi la centaine de langues\ncourantes que Marvin connaît. La candidature ne modifie ni ne retire les\nlangues que la fiche porte déjà. Une valeur non reconnue est ignorée sans\nfaire échouer la candidature. Au plus 10 langues distinctes sont retenues.\n\n#### Exemple\n```bash\ncurl -X POST \\\n  \"https://api.marvins.ai/v1/public/careers/{slug}/apply\" \\\n  -H \"X-Marvin-Public-Key: pk_live_…\" \\\n  -F \"firstName=Marie\" \\\n  -F \"lastName=Dupont\" \\\n  -F \"email=marie.dupont@example.com\" \\\n  -F \"resume=@/chemin/vers/cv.pdf\"\n```\n\n#### Réponses\n`201` candidature reçue · `400` champ invalide · `401` clé\nabsente/invalide · `403` scope manquant · `404` `slug` d’un autre\nclient que la clé · `422` champ requis manquant · `409` champ\npersonnalisé modifié pendant l’envoi, ou valeur hors des options d’un\nchamp à choix multiple.","security":[{"partnerApiKey":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"slug","required":true}],"responses":{"200":{"description":"Default Response"},"4XX":{"description":"Erreur du client : clé absente ou invalide, droit manquant, requête invalide, ressource introuvable, débit dépassé.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"5XX":{"description":"Erreur du serveur.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/public/crm/{slug}/contact-requests":{"post":{"operationId":"createContactRequest","summary":"Créer une demande de contact (entreprise ou particulier) depuis votre site","tags":["CRM (Public)"],"description":"Reçoit une **demande de contact** déposée sur le site web du client —\nformulaire **entreprise** (audit RH, recrutement, coaching…) ou\n**particulier** (bilan, reconversion…) — et la crée dans Marvin côté CRM :\nentreprise retrouvée ou créée, contact retrouvé ou créé (jamais de\ncandidat, jamais de candidature), demande visible dans la timeline du\ncontact et de l’entreprise, notification aux recruteurs.\n\n#### Authentification\nHeader `X-Marvin-Public-Key: pk_live_…` — clé générée depuis\nConfiguration → Multidiffusion → Site web → Configurer, avec le droit\n**demandes de contact** (`public:contacts:write`). Secret côté serveur :\nne jamais l’exposer côté navigateur.\n\n#### Corps de la requête (`application/json`)\n\n| Champ | Requis | Description |\n| --- | --- | --- |\n| `kind` | oui | `company` (entreprise) ou `individual` (particulier) |\n| `firstName`, `lastName` | oui | Identité du demandeur |\n| `email` | oui | E-mail du demandeur — clé de rapprochement avec une fiche existante (contact distinct si l’e-mail est partagé par plusieurs fiches) |\n| `companyName` | si `kind=company` | Nom de l’entreprise (retrouvée ou créée) |\n| `companyWebsite` | non | Site web de l’entreprise (schéma ajouté si absent ; ignoré si invalide) |\n| `phone` | non | Téléphone (national FR ou E.164 ; ignoré si non reconnu, jamais bloquant) |\n| `jobTitle` | non | Fonction du demandeur |\n| `topic` | non | Objet de la demande (ex. « Audit RH », « Bilan de compétences ») |\n| `message` | non | Message du visiteur (5 000 caractères max) |\n| `formName` | non | Nom du formulaire d’origine (ex. `contact-entreprise`) |\n| `utm_source`, `utm_medium`, `utm_campaign`, `utm_term`, `utm_content` | non | Provenance |\n\nToute autre clé est refusée (`400 validation_error`). La provenance\n(`source`) est toujours **Site web** ; `formName` et les UTM distinguent\nvos formulaires.\n\n#### Exemple\n```bash\ncurl -X POST \\\n  \"https://api.marvins.ai/v1/public/crm/{slug}/contact-requests\" \\\n  -H \"X-Marvin-Public-Key: pk_live_…\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"kind\":\"company\",\"firstName\":\"Marie\",\"lastName\":\"Dupont\",\n       \"email\":\"marie.dupont@acme.example\",\"companyName\":\"Acme\",\n       \"topic\":\"Audit RH\",\"message\":\"Nous souhaitons…\",\"formName\":\"contact-entreprise\"}'\n```\n\n#### Réponses\n`201 { data: { id, personId, companyId } }` demande reçue (`companyId`\nnull pour un particulier) · `400 validation_error` champ invalide (`field`)\n· `401` clé absente/invalide · `403 insufficient_scope` droit manquant ·\n`404` client introuvable · `422 missing_required_field` champ requis\nmanquant (`field`) · `429` trop de requêtes (30/min par clé).\n\nUne même demande soumise deux fois en moins de 10 minutes (même e-mail,\nmême nature, même objet, même entreprise, même message, même formulaire)\nrenvoie `201` avec les identifiants de la première, sans créer de doublon.","security":[{"partnerApiKey":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"slug","required":true}],"responses":{"200":{"description":"Default Response"},"4XX":{"description":"Erreur du client : clé absente ou invalide, droit manquant, requête invalide, ressource introuvable, débit dépassé.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"5XX":{"description":"Erreur du serveur.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/partner/projects":{"get":{"operationId":"listPartnerProjects","summary":"Lister les projets de recrutement ouverts et les étapes du process","tags":["Partner API"],"security":[{"partnerApiKey":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"stages":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"label":{"type":"string"}},"required":["key","label"],"additionalProperties":false}},"projects":{"type":"array","items":{"type":"object","properties":{"projectId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"title":{"type":"string"},"companyName":{"nullable":true,"type":"string"}},"required":["projectId","title","companyName"],"additionalProperties":false}}},"required":["stages","projects"],"additionalProperties":false}}}},"4XX":{"description":"Erreur du client : clé absente ou invalide, droit manquant, requête invalide, ressource introuvable, débit dépassé.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"5XX":{"description":"Erreur du serveur.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/partner/candidates":{"post":{"operationId":"createPartnerCandidate","summary":"Créer un candidat (avec son CV) et le rattacher à un projet à une étape","tags":["Partner API"],"description":"Crée (ou réutilise) le candidat et le rattache au projet ciblé à\nl'étape choisie. Idempotent sur `externalId`.\n\n#### Corps de la requête\n`application/json` ou `multipart/form-data` (pour joindre le CV).\n\n| Champ | Requis | Description |\n| --- | --- | --- |\n| `firstName` | oui | Prénom |\n| `lastName` | oui | Nom |\n| `email` | non \\* | E-mail |\n| `phone` | non | Téléphone (E.164) |\n| `linkedinUrl` | non \\* | URL du profil LinkedIn — clé de déduplication |\n| `projectId` | oui | Projet cible (cf. `GET /v1/partner/projects`) |\n| `stage` | non | Étape du process ; défaut = 1ʳᵉ étape du client |\n| `externalId` | non | Votre identifiant interne — clé d’idempotence |\n| `skipPlacementFee` | non | `true` pour créer le process sur une étape « recruté » sans honoraires (sinon 422 `placement_fee_required` chez un client qui facture dans Marvin). JSON uniquement |\n| `cv` | non | **Fichier CV** (PDF, Word ou image, 10 Mo max), en multipart |\n\n\\* `email` **ou** `linkedinUrl` est requis.\n\nLe CV est rattaché aux documents de la fiche et remplit\nautomatiquement le profil (expériences, compétences, coordonnées)\nsans jamais écraser une donnée déjà saisie. `documentId` le confirme\ndans la réponse. Un même CV n’est jamais versé deux fois sur une\nfiche : renvoyer le même profil est sans effet, et un rejeu rattrape\nau contraire un CV qui manquait.\n\n#### Réponses\n`201` candidat créé ou rattaché · `200` rejeu idempotent · `400` corps\ninvalide, étape inconnue ou fichier non supporté · `401` clé absente\nou révoquée · `403` scope manquant · `404` projet inexistant · `413`\nCV trop lourd · `429` débit dépassé.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"firstName":{"type":"string","minLength":1,"maxLength":100},"lastName":{"type":"string","minLength":1,"maxLength":100},"email":{"anyOf":[{"type":"string"},{"type":"string","nullable":true,"enum":[null]}]},"phone":{"anyOf":[{"type":"string"},{"type":"string","nullable":true,"enum":[null]}]},"linkedinUrl":{"anyOf":[{"type":"string"},{"type":"string","nullable":true,"enum":[null]}]},"projectId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"stage":{"type":"string","minLength":1,"maxLength":64},"externalId":{"type":"string","minLength":1,"maxLength":200},"skipPlacementFee":{"type":"boolean"}},"required":["firstName","lastName","projectId"]}},"multipart/form-data":{"schema":{"type":"object","properties":{"firstName":{"type":"string","minLength":1,"maxLength":100},"lastName":{"type":"string","minLength":1,"maxLength":100},"email":{"anyOf":[{"type":"string"},{"type":"string","nullable":true,"enum":[null]}]},"phone":{"anyOf":[{"type":"string"},{"type":"string","nullable":true,"enum":[null]}]},"linkedinUrl":{"anyOf":[{"type":"string"},{"type":"string","nullable":true,"enum":[null]}]},"projectId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"stage":{"type":"string","minLength":1,"maxLength":64},"externalId":{"type":"string","minLength":1,"maxLength":200},"skipPlacementFee":{"type":"boolean"}},"required":["firstName","lastName","projectId"]}}}},"security":[{"partnerApiKey":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"personId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"processId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"created":{"type":"boolean"},"deduped":{"type":"boolean"},"documentId":{"nullable":true,"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"required":["personId","processId","created","deduped","documentId"],"additionalProperties":false}}}},"201":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"personId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"processId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"created":{"type":"boolean"},"deduped":{"type":"boolean"},"documentId":{"nullable":true,"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"required":["personId","processId","created","deduped","documentId"],"additionalProperties":false}}}},"4XX":{"description":"Erreur du client : clé absente ou invalide, droit manquant, requête invalide, ressource introuvable, débit dépassé.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"5XX":{"description":"Erreur du serveur.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/public/feeds/site-web/{tenantSlug}.xml":{"get":{"operationId":"getSiteWebJobsFeedXml","summary":"Flux XML des offres « Site web » (public par slug, sans clé)","tags":["Public","Career","Careers (Public)"],"description":"Flux XML des offres ouvertes du tenant, à transmettre au webmaster du\nsite client. Public par slug (aucune clé), gaté par l’activation du\ncanal « Site web » (sinon `404`). Racine `<source>`, un `<job>` par\noffre. Le `<jobid>` de chaque offre est la clé à reporter dans l’URL\nde candidature `POST /v1/public/careers/{slug}/jobs/{jobid}/apply`.","parameters":[{"schema":{"type":"string","minLength":1,"maxLength":120},"in":"path","name":"tenantSlug","required":true}],"responses":{"200":{"description":"Default Response"}}}}},"tags":[{"name":"Careers (Public)","description":"Site carrières public"},{"name":"CRM (Public)","description":"Demandes de contact depuis le site web du client (`/v1/public/crm/*`) — auth `X-Marvin-Public-Key`, scope `public:contacts:write` (MAR-9467)."},{"name":"Partner API","description":"API partenaire M2M (`/v1/partner/*`) — auth `X-Marvin-Public-Key` scopée par tenant (MAR-4434)."}],"servers":[{"url":"https://api.marvins.ai"}]}