Architecture
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
session_entities.session_entities.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
la requête
Bearer token
de référence
(−50% tokens)
Bedrock
+ coûts
à Bubble
Offres d'emploi — champs d'input
| Champ | Type | Description | Obligatoire |
|---|---|---|---|
job_title | string | Métier / intitulé du poste (max 500 car.) | ✅ |
section | SectionName | Section à générer — generate-section uniquement | ✅ (generate-section) |
type_contrat | string | Type 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. | — |
competences | list[string] | Compétences essentielles du candidat | — |
qualifications | list[string] | Diplômes, certifications, habilitations (ex. "CAP Plomberie", "CACES 3") | — |
localisation | string | Ville / région du poste | — |
experience_requise | string | Expérience requise (ex. "2 ans minimum") | — |
date_debut | string | Date de début de mission | — |
date_fin | string | Date de fin de mission | — |
favoris_clients | list[string] | Textes d'offres favorites — référence stylistique | — |
session_id | string | ID de session Bubble pour traçabilité | — |
style | ModifyStyle | Style de rédaction | — |
language_type | LanguageType | Type de langage | — |
translation | TranslationLanguage | Langue de sortie | — |
inclusive | InclusiveOption | Langage inclusif oui/non | — |
user_prompt | string | Instruction 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
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
/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é | Source | Critère métier |
|---|---|---|
| 1 | Agence fille (X-Uid-Agence-Fille) | Même métier |
| 2 | Agence fille | N'importe quel métier |
| 3 | Agence mère (X-Uid-Agence-Mere) | Même métier |
| 4 | Agence mère | N'importe quel métier |
Sections récupérées : description · profil · responsabilites. Format TOON pour économie de tokens.
Candidats — flux synthèse
cv_data (JSON)
+ headers UID
(courte / longue)
Bedrock
CANDIDAT
à Bubble
Candidats — champs d'input
| Champ | Type | Description | Obligatoire |
|---|---|---|---|
cv_data | dict | Données du candidat en JSON (parsing CV ou fiche Bubble). Champs attendus : nom, titre, experience, formation, competences, langues | ✅ |
longueur | SyntheseLongueur | courte — résumé <1 000 car. · longue — synthèse <2 000 car. | ✅ |
source | SyntheseSource | cv_parsing (premier affichage, pas d'updated_at) · fiche_candidat (mise à jour, retourne updated_at) | — |
session_id | string | ID de session Bubble | — |
X-Uid-Candidat — UID Bubble du candidat, tracé dans session_entities avec le type candidat.
Entreprises — flux enrichissement
SIREN / nom
(OFFICIEL)
(si URL fournie)
+ prompt verrouillé
Bedrock
_ONLY
ENTREPRISE
+ summary
Entreprises — champs d'input
| Champ | Type | Description | Obligatoire |
|---|---|---|---|
siren | string | SIREN de l'entreprise (9 chiffres) | Au moins un des trois |
siret | string | SIRET de l'entreprise (14 chiffres) — les 9 premiers sont utilisés comme SIREN | |
raison_sociale | string | Nom de l'entreprise (recherche par texte si SIREN absent) | |
website_url | string | URL 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_existants | dict | Champs déjà renseignés dans Bubble — structure { "web": {...}, "identity": {...}, "recruiting": {...} }. Les champs non-null ici ne seront jamais écrasés (FILL_EMPTY_ONLY). | — |
session_id | string | ID de session Bubble pour traçabilité | — |
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
| Champ | Type valeur | Description |
|---|---|---|
website_url | string | URL du site corporate (ex : https://www.acme.fr) |
linkedin_company_url | string | URL de la page LinkedIn entreprise |
careers_page_url | string | URL de la page carrières / recrutement |
contact_page_url | string | URL de la page contact |
legal_notice_url | string | URL des mentions légales |
Bloc identity
| Champ | Type valeur | Description |
|---|---|---|
description | string | Description textuelle de l'entreprise (max 1 000 car.) |
logo_url | string | URL du logo |
last_known_revenue_amount | integer | Dernier CA connu en EUR (ex. 46394000000) |
last_known_revenue_year | integer | Année du CA (ex. 2023) |
Bloc recruiting
| Champ | Type valeur | Description |
|---|---|---|
active_job_ads_count | number | Nombre d'offres d'emploi actives (V1 : toujours null côté API, non intégré) |
active_job_ads_search_url | string | URL de recherche d'offres (Indeed généré automatiquement si absent) |
sources | string[] | Plateformes de recrutement utilisées (V1 : toujours null côté API) |
top_qualifications | object[] | Top 5 qualifications recherchées — [{"label": "...", "count": N}] (V1 : toujours null côté API) |
"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.
"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
}
null (pas {"value": null, ...}). Cela signifie "ne pas mettre à jour ce champ dans Bubble".
Blocs et types de chaque champ
| Bloc | Champ | Type value | Source |
|---|---|---|---|
| officiel | |||
officiel | siret | string | OFFICIEL |
officiel | siren | string | OFFICIEL |
officiel | siege_adresse | string | OFFICIEL |
officiel | tranche_effectif | string | OFFICIEL |
officiel | forme_juridique | string | OFFICIEL |
officiel | code_naf | string | OFFICIEL |
officiel | dirigeant | string | OFFICIEL |
officiel | annee_creation | string | OFFICIEL |
| web | |||
web | website_url | string | PUBLIC_WEB |
web | linkedin_company_url | string | PUBLIC_WEB |
web | careers_page_url | string | PUBLIC_WEB |
web | contact_page_url | string | PUBLIC_WEB |
web | legal_notice_url | string | PUBLIC_WEB |
| identity | |||
identity | description | string (max 1 000 car.) | PUBLIC_WEB |
identity | logo_url | string | PUBLIC_WEB |
identity | last_known_revenue_amount | integer (EUR) | OFFICIEL |
identity | last_known_revenue_year | integer (ex. 2023) | OFFICIEL |
| recruiting | |||
recruiting | active_job_ads_count | integer — toujours null en V1 | ESTIMATED |
recruiting | active_job_ads_search_url | string (URL Indeed générée) | ESTIMATED |
recruiting | sources | string — toujours null en V1 | ESTIMATED |
recruiting | top_qualifications | string — toujours null en V1 | ESTIMATED |
| enrichment_summary | |||
enrichment_summary.fields_filled | integer | — | |
enrichment_summary.fields_needs_review | integer | — | |
enrichment_summary.status | string : succès | partiel | erreur | — | |
enrichment_summary.sources_used | array — voir ci-dessous | — | |
| tokens | |||
tokens.input_tokens · output_tokens · total_tokens | integer (0 si sans appel Bedrock) | — | |
tokens.cost_usd | float | — | |
model | string (ex. claude-sonnet-4-6) | — | |
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
| Source | Mode | Auth | Donné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 |
| 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.
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.
{
"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
mode + paramètres
+ headers UID
anti-discrimination
verrouillé
Bedrock
sortie LLM
email_ai_log
à Bubble
Emails — paramètres communs (tous modes)
| Champ | Type | Valeurs / Description | Obligatoire |
|---|---|---|---|
mode | string | PROPOSITION_ACTIVE · COMMANDE · EMAIL_LIBRE |
✅ |
tone | string | Direct · Professionnel · Chaleureux · Court & efficace |
✅ |
length | string | Court · Standard · Détaillé |
✅ |
recipients[].type | string | CLIENT — client existant · PROSPECT — pas encore client · CANDIDAT — candidat en base |
✅ |
recipients[].company_name | string | Nom de l'entreprise ou agence destinataire | ✅ |
recipients[].contact_name | string | Nom du contact (optionnel, utilisé pour la personnalisation) | — |
recipients[].contact_role | string | Fonction du contact (ex : Responsable RH) | — |
recipients[].vouvoiement | bool | true = vouvoiement (défaut) · false = tutoiement |
— |
variables | dict | 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_id | string | ID de session Bubble pour traçabilité | — |
X-Uid-User · X-Uid-Agence-Mere · X-Uid-Agence-FilleHeader 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.
| Champ | Type | Description | Obligatoire |
|---|---|---|---|
candidates[].uid | string | UID Bubble du candidat — tracé dans candidate_ids du log | — |
candidates[].first_name | string | Prénom du candidat (utilisé via le placeholder CANDIDATE_FIRSTNAME) | — |
candidates[].last_name | string | Nom du candidat | — |
candidates[].profile_title | string | Intitulé du profil (ex : Soudeur TIG N2) | ✅ |
candidates[].job_main | string | Métier principal (champ jobMainLowercase Bubble) | — |
candidates[].years_exp_or_level | string | Expérience ou niveau (ex : 4 ans, Confirmé) | — |
candidates[].skills[] | string[] | Compétences clés | — |
candidates[].certifications[] | string[] | Certifications et habilitations | — |
candidates[].availability | string | Disponibilité (ex : Immédiate, 01/06/2026) | — |
candidates[].disponibilite_os | string | Disponibilité opérationnelle (champ Bubble complémentaire) | — |
candidates[].mobility_area | string | Zone de mobilité (ex : Grand Lyon) | — |
candidates[].distance_acceptee | string | Distance de déplacement acceptée | — |
candidates[].grand_deplacement | bool | true si le candidat accepte les grands déplacements | — |
candidates[].private_note | string | Note privée du recruteur sur le candidat — exploitée par le LLM pour personnaliser l'email, passée au pré-check anti-discrimination | — |
context_text | string | Contexte 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.
| Champ | Type | Description | Obligatoire |
|---|---|---|---|
| Candidats — mêmes champs que le mode PA | |||
job.job_title | string | Intitulé du poste | — |
job.job_location | string | Lieu de la mission | — |
job.job_contract | string | Type de contrat (CDI, Intérim, CDD…) | — |
job.job_text | string | Descriptif complet du poste. Si absent, le LLM reste générique sans inventer. | — |
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é.
| Champ | Type | Description | Obligatoire |
|---|---|---|---|
context_text | string | Contexte 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é.
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 pertinent3. La réponse contient
#clé# bruts — Bubble fait ses propres remplacements
"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éponse | Type | Description |
|---|---|---|
subject | string | null | Sujet de l'email généré. null si safety.status = BLOCKED |
body_html | string | null | Corps HTML simple (<p>, <br>, <ul>, <li>). null si BLOCKED |
body_text | string | null | Corps texte brut. null si BLOCKED |
placeholders_used | string[] | Placeholders génériques et variables #clé# utilisés dans le contenu généré |
safety.status | string | OK · BLOCKED · NEEDS_REVIEW |
safety.reasons | string[] | Raisons du blocage ou des points à valider |
tokens | object | input_tokens, output_tokens, total_tokens, cost_usd |
model | string | Identifiant du modèle utilisé |
subject et body_* remplis. Appliquer la politique FILL_EMPTY_ONLY avant d'écraser un brouillon existant.subject et body_* sont null. Afficher les reasons à l'utilisateur.reasons.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.
| Champ | Géré par | Description |
|---|---|---|
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 :
X-Uid-Candidat sur /candidates/generate-syntheseX-Uid-Client sur /entreprises/enrichFeedback
Deux types de feedback, disponibles en version globale (toutes fonctionnalités) ou en version rattachée aux offres uniquement.
| Endpoint | Type | Champs |
|---|---|---|
/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 |