API AIGEN

Génération d'offres · Synthèse candidat · Enrichissement entreprise · Claude Sonnet 4.6 · AWS Bedrock

Architecture

graph LR BUBBLE["🫧 Bubble
Application client"] API["⚙️ API AIGEN
FastAPI · Python"] BEDROCK["☁️ AWS Bedrock
Claude Sonnet 4.6"] DBMAIN[("🗄️ DB Main
PostgreSQL
Offres de référence
")] DBLOGS[("📊 DB Logs
PostgreSQL
KPIs · Coûts
")] INSEE["🏛️ INSEE API
Recherche Entreprises
Données officielles
"] WEB["🌐 Site corporate
Fetch httpx
robots.txt respecté
"] BUBBLE -->|"REST / Bearer token"| API API -->|"AWS SigV4 · prompt TOON"| BEDROCK BEDROCK -->|"JSON response"| API API -->|"Lecture offres
de référence"| DBMAIN API -->|"Écriture logs
+ feedbacks"| DBLOGS API -->|"Réponse JSON"| BUBBLE API -->|"Données légales
entreprise"| INSEE API -->|"Contenu corporate
(si URL connue)"| WEB style API fill:#7c3aed,stroke:#6d28d9,color:#fff style BUBBLE fill:#1e40af,stroke:#1d4ed8,color:#fff style BEDROCK fill:#065f46,stroke:#047857,color:#fff style DBMAIN fill:#1a1f2e,stroke:#4a5568,color:#e2e8f0 style DBLOGS fill:#1a1f2e,stroke:#4a5568,color:#e2e8f0 style INSEE fill:#92400e,stroke:#b45309,color:#fff style WEB fill:#1e3a5f,stroke:#2563eb,color:#fff

Endpoints

Méthode Endpoint Description Source externe
Offres d'emploi
POST /api/v1/offers/generate Génère les 4 sections d'une offre complète DB Main (offres de référence)
POST /api/v1/offers/generate-section Génère un seul encart au choix DB Main
POST /api/v1/offers/modify Modifie les 4 sections existantes (style, langue, ton)
POST /api/v1/offers/modify-section Modifie une section (corriger / raccourcir / allonger)
POST /api/v1/offers/feedback Notation 1-5 étoiles — offres uniquement
POST /api/v1/offers/feedback/thumbs Feedback pouce haut/bas — offres uniquement
Candidats
POST /api/v1/candidates/generate-synthese Génère une synthèse candidat HTML (courte <1 000 car. ou longue <2 000 car.)
Entreprises
POST /api/v1/entreprises/enrich Enrichit automatiquement une fiche entreprise (données publiques + IA) — politique FILL_EMPTY_ONLY INSEE · site corporate (optionnel)
Feedback global
POST /api/v1/feedback Notation 1-5 étoiles — toutes fonctionnalités
POST /api/v1/feedback/thumbs Feedback pouce haut/bas — toutes fonctionnalités
Session
GET /api/v1/session/{session_id} Vérifie l'existence d'une session et retourne ses logs
POST /api/v1/session/finalize Rattache une session temporaire à l'ID officiel Bubble
Emails
POST /api/v1/emails/generate Génère sujet + corps d'email via Assist IA — 3 modes : PA, Commande, Email libre
Système
GET /health Vérification de santé de l'API

Headers

Authorization
Bearer <token> — obligatoire sur tous les endpoints
X-Uid-User
UID de l'utilisateur connecté dans Bubble — tous les endpoints
X-Uid-Agence-Mere
UID de l'agence mère — tous les endpoints
X-Uid-Agence-Fille
UID de l'agence fille — tous les endpoints
X-Uid-Candidat
UID Bubble du candidat — candidates uniquement. Tracé dans session_entities.
X-Uid-Client
UID Bubble de l'entreprise — entreprises uniquement. Tracé dans session_entities.
X-Uid-Job
UID Bubble du poste — emails mode COMMANDE uniquement. Tracé dans bubble_job_id du log email pour relier la génération à une fiche poste. Aucun impact sur le contenu généré.

Offres d'emploi — flux /generate

🫧
Bubble envoie
la requête
🔐
Vérification
Bearer token
🗄️
Lecture ≤3 offres
de référence
📝
Prompt TOON
(−50% tokens)
☁️
Appel
Bedrock
📊
Log KPIs
+ coûts
HTML TinyMCE
à Bubble

Offres d'emploi — champs d'input

ChampTypeDescriptionObligatoire
job_titlestringMétier / intitulé du poste (max 500 car.)
sectionSectionNameSection à générer — generate-section uniquement✅ (generate-section)
type_contratstringType de contrat : CDI · CDD · Intérim · Stage · Alternance · … — adapte le ton et le profil cible (ex. Stage → étudiants). Si absent, le type de contrat n'est pas mentionné dans l'offre.
competenceslist[string]Compétences essentielles du candidat
qualificationslist[string]Diplômes, certifications, habilitations (ex. "CAP Plomberie", "CACES 3")
localisationstringVille / région du poste
experience_requisestringExpérience requise (ex. "2 ans minimum")
date_debutstringDate de début de mission
date_finstringDate de fin de mission
favoris_clientslist[string]Textes d'offres favorites — référence stylistique
session_idstringID de session Bubble pour traçabilité
styleModifyStyleStyle de rédaction
language_typeLanguageTypeType de langage
translationTranslationLanguageLangue de sortie
inclusiveInclusiveOptionLangage inclusif oui/non
user_promptstringInstruction libre transmise au modèle (max 1 000 car.). Injectée dans le prompt avec un encadrement de conformité — le modèle l'ignore si elle est discriminatoire, illégale ou hors sujet. Disponible sur generate, generate-section, modify et modify-section.

Offres d'emploi — sections & actions

section
description profil responsabilites elements_complementaires
action (modify-section)
corriger raccourcir allonger

Limites HTML visible : description 1 000 car. · profil 2 000 car. · responsabilites 1 000 car. · elements_complementaires 500 car.

Offres d'emploi — options de ton

style
professionnelinformelinspirantconvivialengageant
language_type
vouvoiementtutoiementneutre
translation
francaisanglais
inclusive
ouinon
Note : sur /offers/generate, le paramètre style est transmis comme label uniquement. Sur tous les autres endpoints, il inclut les instructions détaillées par section.

Offres d'emploi — offres de référence

L'API recherche automatiquement jusqu'à 3 offres de référence dans la DB Main pour guider le style. Priorité :

PrioritéSourceCritère métier
1Agence fille (X-Uid-Agence-Fille)Même métier
2Agence filleN'importe quel métier
3Agence mère (X-Uid-Agence-Mere)Même métier
4Agence mèreN'importe quel métier

Sections récupérées : description · profil · responsabilites. Format TOON pour économie de tokens.

Candidats — flux synthèse

🫧
Bubble envoie
cv_data (JSON)
🔐
Auth Bearer
+ headers UID
📝
Prompt synthèse
(courte / longue)
☁️
Appel
Bedrock
📊
Log + session
CANDIDAT
HTML TinyMCE
à Bubble

Candidats — champs d'input

ChampTypeDescriptionObligatoire
cv_datadictDonnées du candidat en JSON (parsing CV ou fiche Bubble). Champs attendus : nom, titre, experience, formation, competences, langues
longueurSyntheseLongueurcourte — résumé <1 000 car. · longue — synthèse <2 000 car.
sourceSyntheseSourcecv_parsing (premier affichage, pas d'updated_at) · fiche_candidat (mise à jour, retourne updated_at)
session_idstringID de session Bubble
Header requis en plus : X-Uid-Candidat — UID Bubble du candidat, tracé dans session_entities avec le type candidat.

Entreprises — flux enrichissement

🫧
Bubble envoie
SIREN / nom
🏛️
Fetch INSEE
(OFFICIEL)
🌐
Fetch site web
(si URL fournie)
📝
SOURCES_JSON
+ prompt verrouillé
☁️
Appel
Bedrock
🔒
FILL_EMPTY
_ONLY
📊
Log + session
ENTREPRISE
JSON enrichi
+ summary

Entreprises — champs d'input

ChampTypeDescriptionObligatoire
sirenstringSIREN de l'entreprise (9 chiffres)Au moins un
des trois
siretstringSIRET de l'entreprise (14 chiffres) — les 9 premiers sont utilisés comme SIREN
raison_socialestringNom de l'entreprise (recherche par texte si SIREN absent)
website_urlstringURL du site corporate si déjà connue dans Bubble. Si fournie, le contenu de la homepage est fetché (robots.txt vérifié). Si absente, le bloc web est enrichi avec des valeurs null côté API.
champs_existantsdictChamps déjà renseignés dans Bubble — structure { "web": {...}, "identity": {...}, "recruiting": {...} }. Les champs non-null ici ne seront jamais écrasés (FILL_EMPTY_ONLY).
session_idstringID de session Bubble pour traçabilité
Header requis en plus : X-Uid-Client — UID Bubble de l'entreprise, tracé dans session_entities avec le type entreprise.

Entreprises — champs_existants en détail

champs_existants sert à implémenter la politique FILL_EMPTY_ONLY : tout champ passé avec une valeur non-null ne sera jamais écrasé par l'enrichissement. Passer null ou omettre le champ pour laisser l'API l'enrichir.

Les valeurs passées sont des valeurs brutes (string, number…), pas des objets Field.

Bloc web

ChampType valeurDescription
website_urlstringURL du site corporate (ex : https://www.acme.fr)
linkedin_company_urlstringURL de la page LinkedIn entreprise
careers_page_urlstringURL de la page carrières / recrutement
contact_page_urlstringURL de la page contact
legal_notice_urlstringURL des mentions légales

Bloc identity

ChampType valeurDescription
descriptionstringDescription textuelle de l'entreprise (max 1 000 car.)
logo_urlstringURL du logo
last_known_revenue_amountintegerDernier CA connu en EUR (ex. 46394000000)
last_known_revenue_yearintegerAnnée du CA (ex. 2023)

Bloc recruiting

ChampType valeurDescription
active_job_ads_countnumberNombre d'offres d'emploi actives (V1 : toujours null côté API, non intégré)
active_job_ads_search_urlstringURL de recherche d'offres (Indeed généré automatiquement si absent)
sourcesstring[]Plateformes de recrutement utilisées (V1 : toujours null côté API)
top_qualificationsobject[]Top 5 qualifications recherchées — [{"label": "...", "count": N}] (V1 : toujours null côté API)
Exemple minimal : Bubble a déjà le site et le logo, veut enrichir le reste.

"champs_existants": { "web": { "website_url": "https://www.acme.fr" }, "identity": { "logo_url": "https://www.acme.fr/logo.png" }, "recruiting": {} }
Résultat : website_url et logo_url ne seront pas écrasés. Tous les autres champs sont enrichis normalement.

Entreprises — structure de la réponse

Chaque champ enrichi est un objet Field avec ses métadonnées de traçabilité. Le type de value est fixe par champ — il ne change jamais.

// Champ texte (StringField) — value toujours string ou null
"nom_du_champ": {
  "value": string | null, // la donnée extraite, null si absente des sources
  "mode": "OFFICIEL" | "PUBLIC_WEB" | "ESTIMATED",
  "confidence": float, // 0.0 → 1.0
  "source_label": string | null,
  "source_url": string | null,
  "retrieved_at": string, // ISO 8601 UTC
  "needs_review": bool
}

// Champ numérique (IntField) — value toujours integer ou null
"nom_du_champ": {
  "value": integer | null,
  // … mêmes métadonnées que StringField
}
Champ entier = null : lorsque FILL_EMPTY_ONLY s'applique, le Field entier est null (pas {"value": null, ...}). Cela signifie "ne pas mettre à jour ce champ dans Bubble".

Blocs et types de chaque champ

BlocChampType valueSource
officiel
officielsiretstringOFFICIEL
officielsirenstringOFFICIEL
officielsiege_adressestringOFFICIEL
officieltranche_effectifstringOFFICIEL
officielforme_juridiquestringOFFICIEL
officielcode_nafstringOFFICIEL
officieldirigeantstringOFFICIEL
officielannee_creationstringOFFICIEL
web
webwebsite_urlstringPUBLIC_WEB
weblinkedin_company_urlstringPUBLIC_WEB
webcareers_page_urlstringPUBLIC_WEB
webcontact_page_urlstringPUBLIC_WEB
weblegal_notice_urlstringPUBLIC_WEB
identity
identitydescriptionstring (max 1 000 car.)PUBLIC_WEB
identitylogo_urlstringPUBLIC_WEB
identitylast_known_revenue_amountinteger (EUR)OFFICIEL
identitylast_known_revenue_yearinteger (ex. 2023)OFFICIEL
recruiting
recruitingactive_job_ads_countinteger — toujours null en V1ESTIMATED
recruitingactive_job_ads_search_urlstring (URL Indeed générée)ESTIMATED
recruitingsourcesstring — toujours null en V1ESTIMATED
recruitingtop_qualificationsstring — toujours null en V1ESTIMATED
enrichment_summary
enrichment_summary.fields_filledinteger
enrichment_summary.fields_needs_reviewinteger
enrichment_summary.statusstring : succès | partiel | erreur
enrichment_summary.sources_usedarray — voir ci-dessous
tokens
tokens.input_tokens · output_tokens · total_tokensinteger (0 si sans appel Bedrock)
tokens.cost_usdfloat
modelstring (ex. claude-sonnet-4-6)
needs_review = true lorsque mode ∈ {PUBLIC_WEB, ESTIMATED} ou confidence < 0.75. Ces champs doivent être présentés avec un badge "À valider" dans l'interface Bubble avant tout usage.

Entreprises — sources de données

SourceModeAuthDonnées
INSEE / Recherche Entreprises OFFICIEL Aucune clé requise Nom, SIREN, SIRET siège, code NAF, secteur, forme juridique, effectifs, adresse, date création, dirigeant principal
Site corporate PUBLIC_WEB Aucune — robots.txt vérifié avant fetch Description, URL carrières, contact, mentions légales — extrait si website_url est fourni dans la requête
Offres d'emploi (France Travail…) ESTIMATED Non intégré en V1 active_job_ads_count et top_qualifications retournés null en V1
LinkedIn ESTIMATED Non disponible sans API payante URL LinkedIn non extraite automatiquement en V1

Entreprises — politique FILL_EMPTY_ONLY

L'enrichissement ne remplace jamais une valeur déjà saisie manuellement dans Bubble.

Comment ça marche : Bubble passe les valeurs existantes dans champs_existants. Pour chaque champ non-null dans ce dict, l'API annule la valeur LLM correspondante (mise à null) avant de retourner la réponse. Les autres champs sont enrichis normalement.
// Exemple : website_url déjà renseigné, ne pas toucher
{
  "champs_existants": {
    "web": { "website_url": "https://acme.fr" },
    "identity": {},
    "recruiting": {}
  }
}

→ Dans la réponse, web.website_url sera null. Tous les autres champs sont enrichis normalement.

Emails — flux génération

🫧
Bubble envoie
mode + paramètres
🔐
Auth Bearer
+ headers UID
🛡️
Pré-check
anti-discrimination
📝
Prompt email
verrouillé
☁️
Appel
Bedrock
🛡️
Post-check
sortie LLM
📊
Log complet
email_ai_log
Sujet + Corps
à Bubble
Aucun envoi automatique. L'utilisateur relit et valide toujours le contenu avant envoi. Le badge « ✳️ Contenu généré par IA – à valider » doit rester visible.

Emails — paramètres communs (tous modes)

ChampTypeValeurs / DescriptionObligatoire
modestring PROPOSITION_ACTIVE · COMMANDE · EMAIL_LIBRE
tonestring Direct · Professionnel · Chaleureux · Court & efficace
lengthstring Court · Standard · Détaillé
recipients[].typestring CLIENT — client existant · PROSPECT — pas encore client · CANDIDAT — candidat en base
recipients[].company_namestring Nom de l'entreprise ou agence destinataire
recipients[].contact_namestring Nom du contact (optionnel, utilisé pour la personnalisation)
recipients[].contact_rolestring Fonction du contact (ex : Responsable RH)
recipients[].vouvoiementbool true = vouvoiement (défaut) · false = tutoiement
variablesdict Variables Bubble disponibles. Clé = nom de la variable, valeur = description optionnelle pour l'IA (ou null). Ex. {"nom_contact": "Civilité et nom", "signature_agence": null}. L'IA place les #clé# dans l'email ; Bubble fait le remplacement. Voir section Variables Bubble.
session_idstring ID de session Bubble pour traçabilité
Headers requis en plus : X-Uid-User · X-Uid-Agence-Mere · X-Uid-Agence-Fille
Header optionnel (mode COMMANDE uniquement) : X-Uid-Job — UID Bubble du poste. Tracé dans bubble_job_id du log pour relier l'email à une fiche poste Bubble. Ne pas envoyer pour les modes PA et EMAIL_LIBRE.

Emails — mode Proposition Active (PA)

Objectif : proposer un ou plusieurs candidats à un client ou prospect hors commande. Envoyer recipients[].type = CLIENT ou PROSPECT.

ChampTypeDescriptionObligatoire
candidates[].uidstringUID Bubble du candidat — tracé dans candidate_ids du log
candidates[].first_namestringPrénom du candidat (utilisé via le placeholder CANDIDATE_FIRSTNAME)
candidates[].last_namestringNom du candidat
candidates[].profile_titlestringIntitulé du profil (ex : Soudeur TIG N2)
candidates[].job_mainstringMétier principal (champ jobMainLowercase Bubble)
candidates[].years_exp_or_levelstringExpérience ou niveau (ex : 4 ans, Confirmé)
candidates[].skills[]string[]Compétences clés
candidates[].certifications[]string[]Certifications et habilitations
candidates[].availabilitystringDisponibilité (ex : Immédiate, 01/06/2026)
candidates[].disponibilite_osstringDisponibilité opérationnelle (champ Bubble complémentaire)
candidates[].mobility_areastringZone de mobilité (ex : Grand Lyon)
candidates[].distance_accepteestringDistance de déplacement acceptée
candidates[].grand_deplacementbooltrue si le candidat accepte les grands déplacements
candidates[].private_notestringNote privée du recruteur sur le candidat — exploitée par le LLM pour personnaliser l'email, passée au pré-check anti-discrimination
context_textstringContexte libre complémentaire (max 800 car.)

Emails — mode Commande

Objectif : répondre à une commande en valorisant le match candidat / poste. Même structure que le mode PA, plus un objet job.

ChampTypeDescriptionObligatoire
Candidats — mêmes champs que le mode PA
job.job_titlestringIntitulé du poste
job.job_locationstringLieu de la mission
job.job_contractstringType de contrat (CDI, Intérim, CDD…)
job.job_textstringDescriptif complet du poste. Si absent, le LLM reste générique sans inventer.
Header X-Uid-Job : UID Bubble du poste. Tracé dans bubble_job_id du log pour relier l'email à la fiche poste.

Emails — mode Email libre

Rédaction assistée par l'IA à partir d'un contexte libre. Utiliser context_text pour décrire l'objectif et le contenu souhaité.

ChampTypeDescriptionObligatoire
context_textstringContexte libre décrivant l'objectif de l'email (max 800 car.)Recommandé

Emails — variables Bubble

Bubble gère ses propres variables. L'API ne connaît pas les valeurs — elle place les #clé# dans le texte, Bubble effectue le remplacement de son côté.

Workflow :
1. Bubble passe dans variables les noms de ses variables (+ description optionnelle pour le contexte IA)
2. L'IA génère l'email en utilisant #clé# là où c'est pertinent
3. La réponse contient #clé# bruts — Bubble fait ses propres remplacements
// Exemple de payload variables
"variables": {
  "nom_contact": "Civilité et nom du contact destinataire", // description pour l'IA
  "signature_agence": null, // sans description
  "nom_agence": "Raison sociale de l'agence"
}

Les noms de clés sont libres — Bubble définit ses propres variables. La description (valeur du dict) est optionnelle : elle aide l'IA à comprendre le contexte pour bien placer le #clé#. Passer null si inutile.

Emails — réponse & statuts de sécurité

Champ réponseTypeDescription
subjectstring | nullSujet de l'email généré. null si safety.status = BLOCKED
body_htmlstring | nullCorps HTML simple (<p>, <br>, <ul>, <li>). null si BLOCKED
body_textstring | nullCorps texte brut. null si BLOCKED
placeholders_usedstring[]Placeholders génériques et variables #clé# utilisés dans le contenu généré
safety.statusstringOK · BLOCKED · NEEDS_REVIEW
safety.reasonsstring[]Raisons du blocage ou des points à valider
tokensobjectinput_tokens, output_tokens, total_tokens, cost_usd
modelstringIdentifiant du modèle utilisé
✅ OK
Contenu conforme. subject et body_* remplis. Appliquer la politique FILL_EMPTY_ONLY avant d'écraser un brouillon existant.
🚫 BLOCKED
Contenu risqué détecté (discrimination, donnée sensible, illégal). subject et body_* sont null. Afficher les reasons à l'utilisateur.
⚠️ NEEDS_REVIEW
Contenu à valider manuellement. Champs remplis en brouillon avec badge « à valider ». Afficher les reasons.
Codes HTTP : 200 même si BLOCKED (le statut est dans le body) · 401 token invalide · 422 payload invalide · 502 erreur Bedrock ou JSON LLM non parsable

Traçabilité session

Tous les appels sont tracés via deux identifiants complémentaires, stockés dans generation_logs et session_entities.

ChampGéré parDescription
session_id Bubble ID temporaire généré par Bubble en début de recrutement. À passer sur tous les appels (generate, modify, synthèse, enrich, feedback).
bubble_offer_id Bubble via POST /session/finalize ID officiel de l'offre dans Bubble, rattaché a posteriori à tous les logs de la session en un seul appel.

Table session_entities

Chaque session peut être liée à une entité Bubble identifiée :

candidat
Via header X-Uid-Candidat sur /candidates/generate-synthese
entreprise
Via header X-Uid-Client sur /entreprises/enrich
offre
Futur usage — génération d'offres
client
Futur usage — fiche client agence

Feedback

Deux types de feedback, disponibles en version globale (toutes fonctionnalités) ou en version rattachée aux offres uniquement.

EndpointTypeChamps
/api/v1/feedback
/api/v1/offers/feedback
Étoiles (1-5) rating (1-5, obligatoire) · comment (max 250 car.) · session_id
/api/v1/feedback/thumbs
/api/v1/offers/feedback/thumbs
Pouce haut/bas thumb (up|down, obligatoire) · comment (max 250 car.) · session_id