{"openapi":"3.1.0","info":{"title":"Outsend API","version":"0.1.0-alpha","description":"B2B prospecting data platform — Google Maps extraction, enrichment, pipelines, monitoring. Full docs: https://outsend.xyz/docs. Auth: create an API key in Settings → API keys, then send it as `Authorization: Bearer osk_...` or `X-API-Key: osk_...`."},"servers":[{"url":"https://outsend.xyz"}],"paths":{"/api/auth/me":{"get":{"tags":["auth"],"summary":"Api Me","operationId":"api_me_api_auth_me_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"delete":{"tags":["auth"],"summary":"Api Me Delete","description":"Supprime le compte user + TOUTES les données associées (FK CASCADE en DB).\n\nSécurité :\n  - L'user doit retaper son email pour confirmation (anti-misclick)\n  - On efface la session courante avant de hard-delete\n  - On purge aussi les CSV des jobs sur disque (data/tmp_jobs/<job_id>/)","operationId":"api_me_delete_api_auth_me_delete","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteAccountRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/countries":{"get":{"tags":["jobs"],"summary":"Api Countries","description":"Liste des pays scrapables pour le picker + dernier pays de l'utilisateur\n(pré-sélection) + top-5 épinglés. Alimente le sélecteur de pays du scrap.","operationId":"api_countries_api_countries_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/estimate":{"post":{"tags":["jobs"],"summary":"Api Estimate","description":"Calcule le coût d'un job potentiel sans le créer.\nUtilisé par le frontend pour afficher la barre EF en live.","operationId":"api_estimate_api_estimate_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobEstimateRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobEstimateResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/jobs":{"post":{"tags":["jobs"],"summary":"Api Create Job","operationId":"api_create_job_api_jobs_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobCreateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["jobs"],"summary":"Api List Jobs","description":"Liste les jobs de l'user. limit et offset acceptés en query params.\nlimit clamp [1, 500] pour éviter qu'un client malicieux demande tout\nen mémoire.","operationId":"api_list_jobs_api_jobs_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/JobPublic"},"title":"Response Api List Jobs Api Jobs Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/jobs/queue":{"get":{"tags":["jobs"],"summary":"Api Jobs Queue","description":"File d'attente globale (multi-proxy uniquement), anonymisée.\n\nSeuls les jobs multi-proxy apparaissent : ce sont les seuls qui se\nsérialisent (pool VPN partagé).\n\nETA : vitesse RÉELLE mesurée sur le scraper actif (processed/elapsed),\npas une estimation grossière. Pour les jobs pending, on projette avec\nla même vitesse (puisqu'ils utiliseront le même pool VPN). Fallback\nsur 5s/point si la mesure n'est pas encore exploitable (warm-up).","operationId":"api_jobs_queue_api_jobs_queue_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/jobs/reviews":{"post":{"tags":["jobs"],"summary":"Api Create Reviews Job","operationId":"api_create_reviews_job_api_jobs_reviews_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReviewsJobCreateRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/jobs/emails":{"post":{"tags":["jobs"],"summary":"Api Create Emails Job","operationId":"api_create_emails_job_api_jobs_emails_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailsJobCreateRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/jobs/verify-emails":{"post":{"tags":["jobs"],"summary":"Api Create Verify Emails Job","operationId":"api_create_verify_emails_job_api_jobs_verify_emails_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyEmailsJobCreateRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/jobs/finance":{"post":{"tags":["jobs"],"summary":"Api Create Finance Job","operationId":"api_create_finance_job_api_jobs_finance_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/jobs/socials":{"post":{"tags":["jobs"],"summary":"Api Create Socials Job","operationId":"api_create_socials_job_api_jobs_socials_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SocialsJobCreateRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/jobs/dead-check":{"post":{"tags":["jobs"],"summary":"Api Create Dead Check Job","operationId":"api_create_dead_check_job_api_jobs_dead_check_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeadCheckJobCreateRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/jobs/techstack":{"post":{"tags":["jobs"],"summary":"Api Create Techstack Job","operationId":"api_create_techstack_job_api_jobs_techstack_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TechStackJobCreateRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/jobs/ads-intelligence":{"post":{"tags":["jobs"],"summary":"Api Create Ads Intelligence Job","operationId":"api_create_ads_intelligence_job_api_jobs_ads_intelligence_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdsIntelligenceJobCreateRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/jobs/brand-assets":{"post":{"tags":["jobs"],"summary":"Api Create Brand Assets Job","operationId":"api_create_brand_assets_job_api_jobs_brand_assets_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BrandAssetsJobCreateRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/brand-assets/{owner_user_id}/{filename}":{"get":{"tags":["jobs"],"summary":"Api Serve Brand Asset","description":"Sert un asset (logo/favicon) re-hosté d'un job brand_assets.\n\nIso per-user : seul l'owner peut accéder aux assets de son dossier\n(admin peut tout voir). Le filename est validé contre une regex\nstricte (anti path-traversal).","operationId":"api_serve_brand_asset_api_brand_assets__owner_user_id___filename__get","parameters":[{"name":"owner_user_id","in":"path","required":true,"schema":{"type":"integer","title":"Owner User Id"}},{"name":"filename","in":"path","required":true,"schema":{"type":"string","title":"Filename"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/jobs/legal-ids":{"post":{"tags":["jobs"],"summary":"Api Create Legal Ids Job","operationId":"api_create_legal_ids_job_api_jobs_legal_ids_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LegalIdsJobCreateRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/jobs/legal-mentions":{"post":{"tags":["jobs"],"summary":"Api Create Legal Mentions Job","operationId":"api_create_legal_mentions_job_api_jobs_legal_mentions_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LegalMentionsJobCreateRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/jobs/legal-data":{"post":{"tags":["jobs"],"summary":"Api Create Legal Data Job","description":"Enrichissement Données société via api.gouv.fr (forme juridique,\neffectif, NAF, dirigeants, finances, labels qualité).","operationId":"api_create_legal_data_job_api_jobs_legal_data_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LegalDataJobCreateRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/jobs/pagespeed":{"post":{"tags":["jobs"],"summary":"Api Create Pagespeed Job","description":"Score PageSpeed (Google PSI v5) — performance, SEO, accessibilité,\nbonnes pratiques + Core Web Vitals (LCP, FCP, TBT, CLS, Speed Index).","operationId":"api_create_pagespeed_job_api_jobs_pagespeed_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PageSpeedJobCreateRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/jobs/phone-info":{"post":{"tags":["jobs"],"summary":"Api Create Phone Info Job","operationId":"api_create_phone_info_job_api_jobs_phone_info_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhoneInfoJobCreateRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/jobs/phones-extra":{"post":{"tags":["jobs"],"summary":"Api Create Phones Extra Job","operationId":"api_create_phones_extra_job_api_jobs_phones_extra_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhonesExtraJobCreateRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/jobs/pricing":{"post":{"tags":["jobs"],"summary":"Api Create Pricing Job","operationId":"api_create_pricing_job_api_jobs_pricing_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PricingJobCreateRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/jobs/delivery-check":{"post":{"tags":["jobs"],"summary":"Api Create Delivery Check Job","operationId":"api_create_delivery_check_job_api_jobs_delivery_check_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeliveryCheckJobCreateRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/jobs/parse-list":{"post":{"tags":["jobs"],"summary":"Api Parse List","description":"Parse une liste de POI fournie par l'user.\n\nAccepte deux formats de body :\n- JSON: {\"text\": \"...csv brut...\"} → parse en tant que CSV\n- multipart upload: file=...csv → parse le fichier\nRetourne {count, sample (5 premiers), items} (items à renvoyer tel quel à\n/api/jobs/reviews ou /api/jobs/emails).","operationId":"api_parse_list_api_jobs_parse_list_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/jobs/{job_id}/delivery-result":{"get":{"tags":["jobs"],"summary":"Api Delivery Result","description":"Renvoie le rapport structuré d'un job de test délivrabilité Gmail.\n\nLit results_delivery.csv côté serveur, calcule le récap + le verdict\ncontextuel, et renvoie un JSON consommable par le frontend.\nPermet d'afficher un rapport propre sans avoir à télécharger le CSV.","operationId":"api_delivery_result_api_jobs__job_id__delivery_result_get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/jobs/{job_id}/items":{"get":{"tags":["jobs"],"summary":"Api Get Job Items","description":"Récupère les lignes CSV résultats d'un job done, pour réimport en\njob d'enrichissement / pipeline.\n\nAuparavant restreint aux jobs 'scrap', étendu à tous job types dont\nle CSV est exploitable (`_NON_CHAINABLE_JOB_TYPES` exclu).\n\n`offset`/`limit` paginent le résultat. Les trois connecteurs (Zapier, Make,\nn8n) exposaient déjà ces deux paramètres à leurs utilisateurs alors que la\nroute les ignorait : régler « limit 100 » sur un job de 800 000 lignes\nrenvoyait les 800 000, sans erreur. Les lignes hors fenêtre ne sont même\nplus matérialisées — un client qui pagine ne fait plus tenir tout le CSV\nen RAM côté serveur.\n\n`count` reste le nombre de lignes RENVOYÉES (contrat historique) ;\n`total` donne le nombre de lignes du CSV, pour savoir quand s'arrêter.","operationId":"api_get_job_items_api_jobs__job_id__items_get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"limit","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":100000,"minimum":1},{"type":"null"}],"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/jobs/{job_id}/output-columns":{"get":{"tags":["jobs"],"summary":"Api Get Job Output Columns","description":"Retourne la liste des colonnes du CSV de sortie d'un job done, plus\nle nombre de lignes — sans charger tout le payload. Utilisé par le\npanneau \"Et maintenant ?\" de job_detail.html pour décider quels\nmodules d'enrichissement sont applicables.\n\nLecture optimisée : on parcourt le CSV mais on s ARRETE des que toutes\nles colonnes ont au moins une valeur non-vide (early-exit EXACT — le reste\nne changerait plus non_empty_columns). Sur un CSV de dizaines de milliers\nde lignes aux colonnes pleines, on s arrete vers la 3e ligne. row_count\nvient de la base (results_count) quand on a coupe avant la fin.\n\nRetourne `{columns: [str], non_empty_columns: [str], row_count: int}`\nou `{columns: [], reason: \"...\"}` si pas de CSV exploitable.","operationId":"api_get_job_output_columns_api_jobs__job_id__output_columns_get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/jobs/{job_id}":{"get":{"tags":["jobs"],"summary":"Api Get Job","operationId":"api_get_job_api_jobs__job_id__get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["jobs"],"summary":"Api Delete Job","description":"Suppression définitive d'un job (CSV inclus). Refuse si le job tourne\nencore (l'user doit l'annuler d'abord).","operationId":"api_delete_job_api_jobs__job_id__delete","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/jobs/{job_id}/cancel":{"post":{"tags":["jobs"],"summary":"Api Cancel Job","operationId":"api_cancel_job_api_jobs__job_id__cancel_post","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/jobs/{job_id}/resume":{"post":{"tags":["jobs"],"summary":"Api Resume Job","description":"Crée un nouveau job qui reprend un scrap annulé/échoué là où il s'est arrêté.\n\nHérite des paramètres + CSV partiel + progression du job source. Le worker\nskip les coords déjà traitées (économise les API calls Google Maps).\n\nEF débité proportionnel aux points restants seulement.","operationId":"api_resume_job_api_jobs__job_id__resume_post","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/jobs/{job_id}/breakdown":{"get":{"tags":["jobs"],"summary":"Api Job Breakdown","description":"Endpoint dédié — gardé pour rétrocompat. Le breakdown est aussi inclus\ndans le payload JobPublic des endpoints liste/détail (`job.breakdown`).","operationId":"api_job_breakdown_api_jobs__job_id__breakdown_get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/jobs/{job_id}/map":{"get":{"tags":["jobs"],"summary":"Api Job Map","description":"Carte de progression : pour chaque tuile (rectangle) du scrap,\nretourne ses bounds géographiques + le ratio de queries traitées.\n\nGère le sub-tiling adaptatif :\n  - Tuiles parent (zoom 13) du grid initial\n  - Sous-tuiles dynamiques (zoom 14/15) écrites dans scrap_pending.jsonl\n    quand une parent sature à ≥120 résultats\n  - Une parent qui contient une sous-tuile DANS sa bbox est masquée :\n    on rend la sous-tuile à la place (et récursivement pour les sous-sous-\n    tuiles). Visuellement, la zone parent est \"remplie\" par ses enfants.\n\nFrontend : L.rectangle avec bounds [[lat_min,lon_min],[lat_max,lon_max]].\nIntensité couleur = q_done / q_total. Polling 5s.","operationId":"api_job_map_api_jobs__job_id__map_get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/jobs/{job_id}/download":{"get":{"tags":["jobs"],"summary":"Api Download Job","description":"Télécharge le résultat d'un job.\n\nFormat supportés (paramètre `format`) :\n  - csv   : fichier original tel que produit par le scraper (UTF-8 BOM, ;)\n  - json  : conversion à la volée en tableau d'objets\n  - xlsx  : conversion à la volée en classeur Excel (mieux pour Excel/Sheets)","operationId":"api_download_job_api_jobs__job_id__download_get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}},{"name":"format","in":"query","required":false,"schema":{"type":"string","default":"csv","title":"Format"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/jobs/{job_id}/stream":{"get":{"tags":["jobs"],"summary":"Api Stream Job","description":"SSE: stream les logs et le statut du job en temps réel.\n\nLe paramètre 'since' permet de reprendre à un id de log donné.\nLe paramètre 'tail' (premier chargement uniquement) demande les N derniers\nlogs d'emblée au lieu de rejouer l'historique depuis 0 — évite les ~30s de\ndéfilement de vieux logs sur un gros scrap. Ignoré dès qu'on reprend depuis\nun id (since>0 ou Last-Event-ID).\nLe header `Last-Event-ID` (auto-envoyé par EventSource lors d'un reconnect)\na priorité sur 'since' → évite les doublons après une reconnexion auto.","operationId":"api_stream_job_api_jobs__job_id__stream_get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}},{"name":"since","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Since"}},{"name":"tail","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Tail"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/modules-registry":{"get":{"tags":["registry"],"summary":"Modules Registry","description":"Mirror JSON-sérialisable du registre serveur (app/job_registry.py).\n\nPermet au frontend de découvrir dynamiquement la liste autorisée\nde coming_soon, multi_proxy, parallel, etc. — sans hardcoder côté JS.\nCe endpoint est public (pas de require_user) car la liste de modules\nest elle-même publique (visible sur la landing).","operationId":"modules_registry_api_modules_registry_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Modules Registry Api Modules Registry Get"}}}}}}},"/api/pipelines":{"get":{"tags":["pipelines"],"summary":"Api List Pipelines","operationId":"api_list_pipelines_api_pipelines_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"type":"object"},"type":"array","title":"Response Api List Pipelines Api Pipelines Get"}}}}}},"post":{"tags":["pipelines"],"summary":"Api Create Pipeline","description":"Crée une pipeline + lance les jobs des nodes racines.\n\nLa définition est normalisée d'abord (ids d'edges, x/y auto-layout,\ndéfauts de config, coercition string[]) → un payload minimal venu de\nl'import, de la génération IA ou de l'API est accepté tel quel.","operationId":"api_create_pipeline_api_pipelines_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PipelineCreateRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Api Create Pipeline Api Pipelines Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/pipelines/schema":{"get":{"tags":["pipelines"],"summary":"Api Pipeline Schema","description":"Schéma canonique des blocs pipeline (source de vérité serveur).\n\nContrat UNIQUE consommé par : l'éditeur, l'import, la génération IA, et\nla future API/MCP (`create_pipeline`). Public (pas d'auth) : c'est de la\ndoc de format, identique pour tous, et déjà reflété dans /docs.\n\nForme : `{schema_version, compat, root_types, nodes: {<type>: {category,\nis_root, input, output, needs, produces, config_schema}}}`.","operationId":"api_pipeline_schema_api_pipelines_schema_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Api Pipeline Schema Api Pipelines Schema Get"}}}}}}},"/api/pipelines/validate":{"post":{"tags":["pipelines"],"summary":"Api Validate Pipeline","description":"Normalise + valide une définition SANS créer ni lancer quoi que ce soit.\n\nUtilisé par l'import (relecture avant lancement) et la génération IA\n(vérifier le JSON produit par Claude). Retourne la définition normalisée\n(ids d'edges + x/y auto-layout + défauts de config) prête à charger dans\nl'éditeur, ou la première erreur bloquante.","operationId":"api_validate_pipeline_api_pipelines_validate_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PipelineValidateRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Api Validate Pipeline Api Pipelines Validate Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/pipelines/{pipeline_id}":{"get":{"tags":["pipelines"],"summary":"Api Get Pipeline","operationId":"api_get_pipeline_api_pipelines__pipeline_id__get","parameters":[{"name":"pipeline_id","in":"path","required":true,"schema":{"type":"string","title":"Pipeline Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Api Get Pipeline Api Pipelines  Pipeline Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/pipelines/{pipeline_id}/cancel":{"post":{"tags":["pipelines"],"summary":"Api Cancel Pipeline","description":"Arrête la pipeline ENTIÈRE : stoppe l'étape en cours et n'enchaîne pas les\nétapes restantes. Les résultats partiels déjà scrapés restent téléchargeables.","operationId":"api_cancel_pipeline_api_pipelines__pipeline_id__cancel_post","parameters":[{"name":"pipeline_id","in":"path","required":true,"schema":{"type":"string","title":"Pipeline Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Api Cancel Pipeline Api Pipelines  Pipeline Id  Cancel Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/pipelines/{pipeline_id}/nodes/{node_id}/continue":{"post":{"tags":["pipelines"],"summary":"Api Continue Pipeline Node","description":"« Continuer avec les résultats » : relance la pipeline depuis un step\narrêté/crashé (cancelled/failed) en utilisant ses résultats partiels. Crée le(s)\njob(s) de l'étape suivante. À utiliser quand un scrap a été stoppé (assez de\nrésultats) ou a crashé et qu'on veut poursuivre la chaîne avec ce qu'on a.","operationId":"api_continue_pipeline_node_api_pipelines__pipeline_id__nodes__node_id__continue_post","parameters":[{"name":"pipeline_id","in":"path","required":true,"schema":{"type":"string","title":"Pipeline Id"}},{"name":"node_id","in":"path","required":true,"schema":{"type":"string","title":"Node Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Api Continue Pipeline Node Api Pipelines  Pipeline Id  Nodes  Node Id  Continue Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/pipelines/{pipeline_id}/nodes/{node_id}/input-columns":{"get":{"tags":["pipelines"],"summary":"Api Node Input Columns","description":"Retourne la liste des colonnes en entrée d'un node (CSV produit par\nson prédécesseur) avec détection automatique de type, pour alimenter\nl'UI des filtres avancés.\n\nSi le node n'a pas de prédécesseur OU si le prédécesseur n'est pas\n`done`, retourne `{\"columns\": [], \"reason\": \"no_data_yet\"}`.","operationId":"api_node_input_columns_api_pipelines__pipeline_id__nodes__node_id__input_columns_get","parameters":[{"name":"pipeline_id","in":"path","required":true,"schema":{"type":"string","title":"Pipeline Id"}},{"name":"node_id","in":"path","required":true,"schema":{"type":"string","title":"Node Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Api Node Input Columns Api Pipelines  Pipeline Id  Nodes  Node Id  Input Columns Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/pipelines/{pipeline_id}/nodes/{node_id}/filter-preview":{"post":{"tags":["pipelines"],"summary":"Api Node Filter Preview","description":"Applique les règles de filtre sur le CSV du prédécesseur, en mémoire,\net retourne `{total, matched, samples}` sans créer de job.\n\nPermet à l'UI d'afficher en live combien de lignes vont rester quand\nl'user modifie ses règles, et de voir un échantillon des résultats.\n\nLe CSV est cappé à `_PREVIEW_ROWS_LIMIT` (5000 lignes) — sur un dataset\nplus grand, `total` reflète le nombre lu (pas le vrai total) ; le ratio\nmatched/total reste représentatif.","operationId":"api_node_filter_preview_api_pipelines__pipeline_id__nodes__node_id__filter_preview_post","parameters":[{"name":"pipeline_id","in":"path","required":true,"schema":{"type":"string","title":"Pipeline Id"}},{"name":"node_id","in":"path","required":true,"schema":{"type":"string","title":"Node Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FilterPreviewRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Api Node Filter Preview Api Pipelines  Pipeline Id  Nodes  Node Id  Filter Preview Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/veille":{"get":{"tags":["veille"],"summary":"List Veille","description":"Liste les surveillances actives + pausées de l'user.","operationId":"list_veille_api_veille_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"post":{"tags":["veille"],"summary":"Create Veille","description":"Crée une surveillance depuis un scrap done de l'user.","operationId":"create_veille_api_veille_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VeilleCreate"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/veille/{rs_id}":{"get":{"tags":["veille"],"summary":"Get Veille","operationId":"get_veille_api_veille__rs_id__get","parameters":[{"name":"rs_id","in":"path","required":true,"schema":{"type":"integer","title":"Rs Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["veille"],"summary":"Update Veille","operationId":"update_veille_api_veille__rs_id__patch","parameters":[{"name":"rs_id","in":"path","required":true,"schema":{"type":"integer","title":"Rs Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VeilleUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["veille"],"summary":"Delete Veille","operationId":"delete_veille_api_veille__rs_id__delete","parameters":[{"name":"rs_id","in":"path","required":true,"schema":{"type":"integer","title":"Rs Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/veille/_for-job/{job_id}":{"get":{"tags":["veille"],"summary":"Get Diff For Job","description":"Si ce job est issu d'une veille (clone d'un source_job) et qu'un diff\na été calculé, retourne le run (avec samples). 404 sinon.","operationId":"get_diff_for_job_api_veille__for_job__job_id__get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/veille/_overview/last-runs":{"get":{"tags":["veille"],"summary":"Last Runs Overview","description":"Dernier run stats par surveillance pour l'user (utilisé par la page\n/veille pour afficher la colonne 'dernier diff' sans N+1 queries).\n\nNote : route placée AVANT /{rs_id}/runs pour éviter le shadowing par le\nmatcher (FastAPI évalue dans l'ordre de déclaration).","operationId":"last_runs_overview_api_veille__overview_last_runs_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/veille/{rs_id}/runs":{"get":{"tags":["veille"],"summary":"List Runs","description":"Historique des runs de cette surveillance (chaque entrée = 1 scrap\neffectué + stats diff vs run précédent).","operationId":"list_runs_api_veille__rs_id__runs_get","parameters":[{"name":"rs_id","in":"path","required":true,"schema":{"type":"integer","title":"Rs Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/veille/{rs_id}/runs/{run_id}":{"get":{"tags":["veille"],"summary":"Get Run","description":"Détail d'un run avec samples : nouveaux établissements, fermés, modifiés.","operationId":"get_run_api_veille__rs_id__runs__run_id__get","parameters":[{"name":"rs_id","in":"path","required":true,"schema":{"type":"integer","title":"Rs Id"}},{"name":"run_id","in":"path","required":true,"schema":{"type":"integer","title":"Run Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/veille/{rs_id}/runs/{run_id}/signals":{"get":{"tags":["veille"],"summary":"Get Run Signals","description":"Liste des signaux réputation calculés pour ce run.\nRetourne `{items, total}` (déjà filtré + trié par score décroissant).","operationId":"get_run_signals_api_veille__rs_id__runs__run_id__signals_get","parameters":[{"name":"rs_id","in":"path","required":true,"schema":{"type":"integer","title":"Rs Id"}},{"name":"run_id","in":"path","required":true,"schema":{"type":"integer","title":"Run Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/veille/{rs_id}/runs/{run_id}/signals.{fmt}":{"get":{"tags":["veille"],"summary":"Export Run Signals","description":"Export des signaux en CSV / JSON / XLSX (download direct).","operationId":"export_run_signals_api_veille__rs_id__runs__run_id__signals__fmt__get","parameters":[{"name":"rs_id","in":"path","required":true,"schema":{"type":"integer","title":"Rs Id"}},{"name":"run_id","in":"path","required":true,"schema":{"type":"integer","title":"Run Id"}},{"name":"fmt","in":"path","required":true,"schema":{"type":"string","title":"Fmt"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ef/estimate-enrichment":{"post":{"tags":["ef"],"summary":"Estimate Enrichment","description":"EF estimé pour un job d'enrichissement isolé (1 type, n_pois).","operationId":"estimate_enrichment_api_ef_estimate_enrichment_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnrichEstimateRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ef/estimate-pipeline":{"post":{"tags":["ef"],"summary":"Estimate Pipeline","description":"EF estimé pour une pipeline complète (somme des nodes).","operationId":"estimate_pipeline_api_ef_estimate_pipeline_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PipelineEstimateRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ef/settings":{"get":{"tags":["ef"],"summary":"Get Ef Settings","description":"Seuil EF courant + valeur effective + défaut + plafond dur (UI Réglages).","operationId":"get_ef_settings_api_ef_settings_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Get Ef Settings Api Ef Settings Get"}}}}}},"post":{"tags":["ef"],"summary":"Set Ef Settings","description":"Règle le seuil EF par job d'un compte (admin). `user_id` absent = le\ncompte de l'admin lui-même. null => NULL (défaut selon le rôle) ; sinon\nclamp serveur dans [0.1, EF_MAX_HARD_CEILING].","operationId":"set_ef_settings_api_ef_settings_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EfSettingsAdminRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Set Ef Settings Api Ef Settings Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ef/catalog":{"get":{"tags":["ef"],"summary":"Catalog","description":"Retourne les coûts unitaires (par POI) de chaque job_type d'enrichissement,\npour que le frontend puisse afficher des indicatifs locaux sans aller-retour.","operationId":"catalog_api_ef_catalog_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/keys":{"get":{"tags":["api-keys"],"summary":"List Keys","description":"Liste des clés du compte — préfixes seulement, jamais de secret.","operationId":"list_keys_api_keys_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"post":{"tags":["api-keys"],"summary":"Create Key","description":"Crée une clé. La clé complète n'est renvoyée QU'ICI, une seule fois.","operationId":"create_key_api_keys_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyCreateRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/keys/{key_id}":{"delete":{"tags":["api-keys"],"summary":"Revoke Key","description":"Révoque une clé (logique : la ligne reste en DB pour l'audit).","operationId":"revoke_key_api_keys__key_id__delete","parameters":[{"name":"key_id","in":"path","required":true,"schema":{"type":"integer","title":"Key Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/events":{"get":{"tags":["events"],"summary":"List Events","description":"Événements du compte.\n\nUsage type d'un connecteur : mémoriser `head_id` à l'activation, puis\nrepasser en `?since_id=<last_id>` — chaque événement n'est vu qu'une fois.\n\nTrois champs de sortie, à ne pas confondre :\n  - `last_id` : id du DERNIER événement de CETTE page. C'est le curseur de\n    rattrapage, et lui seul.\n  - `head_id` : id du PLUS RÉCENT événement du compte pour ce filtre, quelle\n    que soit la page. C'est ce qu'il faut mémoriser quand on veut démarrer\n    « à partir de maintenant » sans rejouer l'historique. Sans ce champ, un\n    client qui appelait `?limit=1` pour trouver la tête récupérait en fait\n    l'événement le PLUS ANCIEN (l'ordre est croissant) et repartait donc de\n    zéro au polling suivant — c'est exactement le piège dans lequel étaient\n    tombés les triggers Zapier et Make.\n  - `order=desc` : renvoie les plus récents d'abord, pour peupler les\n    exemples d'un éditeur de scénario avec des données représentatives\n    plutôt qu'avec les premiers jobs jamais lancés.","operationId":"list_events_api_events_get","parameters":[{"name":"since_id","in":"query","required":false,"schema":{"type":"integer","maximum":9007199254740991,"minimum":0,"description":"Ne renvoyer que les événements d'id strictement supérieur","default":0,"title":"Since Id"},"description":"Ne renvoyer que les événements d'id strictement supérieur"},{"name":"types","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filtre CSV, ex: job.completed,pipeline.completed","title":"Types"},"description":"Filtre CSV, ex: job.completed,pipeline.completed"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":50,"title":"Limit"}},{"name":"order","in":"query","required":false,"schema":{"type":"string","pattern":"^(asc|desc)$","description":"asc = chronologique (rattrapage) ; desc = les plus récents d'abord (chargement d'exemples)","default":"asc","title":"Order"},"description":"asc = chronologique (rattrapage) ; desc = les plus récents d'abord (chargement d'exemples)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/webhooks":{"get":{"tags":["webhooks"],"summary":"List Webhooks","description":"Endpoints du compte — sans les secrets.","operationId":"list_webhooks_api_webhooks_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"post":{"tags":["webhooks"],"summary":"Create Webhook","description":"Enregistre un endpoint. Le secret n'est renvoyé QU'ICI — le stocker\ncôté consommateur pour vérifier X-Outsend-Signature.","operationId":"create_webhook_api_webhooks_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookCreateRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/webhooks/{webhook_id}/deliveries":{"get":{"tags":["webhooks"],"summary":"List Deliveries","description":"20 dernières livraisons de l'endpoint (debug d'intégration).","operationId":"list_deliveries_api_webhooks__webhook_id__deliveries_get","parameters":[{"name":"webhook_id","in":"path","required":true,"schema":{"type":"integer","title":"Webhook Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/webhooks/{webhook_id}":{"delete":{"tags":["webhooks"],"summary":"Delete Webhook","description":"Suppression réelle (cycle de vie n8n : delete à la désactivation).\nLes livraisons associées partent avec (FK applicative — nettoyage direct).","operationId":"delete_webhook_api_webhooks__webhook_id__delete","parameters":[{"name":"webhook_id","in":"path","required":true,"schema":{"type":"integer","title":"Webhook Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"AdsIntelligenceJobCreateRequest":{"properties":{"items":{"items":{"type":"object"},"type":"array","maxItems":10000,"title":"Items"},"source_job_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Job Id"}},"type":"object","title":"AdsIntelligenceJobCreateRequest","description":"Job de profilage marketing/ads (pixels FB/Google, retargeting, CMP,\nCRM marketing, chat) basé sur matching uBlock/EasyList + signatures custom."},"ApiKeyCreateRequest":{"properties":{"name":{"type":"string","maxLength":60,"minLength":1,"title":"Name","description":"Nom lisible (ex: 'Zapier', 'n8n prod')"}},"type":"object","required":["name"],"title":"ApiKeyCreateRequest"},"BrandAssetsJobCreateRequest":{"properties":{"items":{"items":{"type":"object"},"type":"array","maxItems":10000,"title":"Items"},"source_job_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Job Id"},"capture_screenshot":{"type":"boolean","title":"Capture Screenshot","default":false}},"type":"object","title":"BrandAssetsJobCreateRequest","description":"Job d'extraction d'identité visuelle (logo, favicon, couleur, palette,\nvariantes de logos). Re-host les images dans data/brand_assets/<user_id>/.\n\n`capture_screenshot` (opt-in) : capture aussi la page principale via\nPlaywright headless. Coûte ~5x plus de temps par item — désactivé par défaut."},"DeadCheckJobCreateRequest":{"properties":{"items":{"items":{"type":"object"},"type":"array","maxItems":10000,"title":"Items"},"source_job_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Job Id"}},"type":"object","title":"DeadCheckJobCreateRequest","description":"Job de détection sites morts (DNS, parking, default-server, SSL, etc.)."},"DeleteAccountRequest":{"properties":{"confirm_email":{"type":"string","maxLength":200,"minLength":3,"title":"Confirm Email"}},"type":"object","required":["confirm_email"],"title":"DeleteAccountRequest","description":"Body : exiger le mot de passe pour confirmation."},"DeliveryCheckJobCreateRequest":{"properties":{"domain":{"type":"string","maxLength":120,"minLength":3,"title":"Domain"},"subject_filter":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}],"title":"Subject Filter","default":""}},"type":"object","required":["domain"],"title":"DeliveryCheckJobCreateRequest","description":"Job de test de délivrabilité Gmail (Inbox / Promotions / Spam)."},"EfSettingsAdminRequest":{"properties":{"ef_max":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ef Max"},"user_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"User Id"}},"type":"object","title":"EfSettingsAdminRequest","description":"Cible du réglage : un admin règle le seuil d'un AUTRE compte."},"EmailsJobCreateRequest":{"properties":{"items":{"items":{"type":"object"},"type":"array","maxItems":10000,"title":"Items"},"mode":{"type":"string","enum":["normal","deep"],"title":"Mode","default":"normal"},"source_job_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Job Id"}},"type":"object","title":"EmailsJobCreateRequest","description":"Job d'enrichissement emails depuis une liste de POI avec site_web."},"EnrichEstimateRequest":{"properties":{"job_type":{"type":"string","title":"Job Type","description":"emails | verify | socials | dead_check | techstack | pagespeed | legal_ids | legal_mentions | phones_extra | pricing | reviews | legal_data"},"n_pois":{"type":"integer","maximum":1000000.0,"minimum":0.0,"title":"N Pois"},"config":{"type":"object","title":"Config"}},"type":"object","required":["job_type","n_pois"],"title":"EnrichEstimateRequest","description":"Estimation pour un job d'enrichissement (post-scrap) sur n_pois."},"FilterPreviewRequest":{"properties":{"rules":{"type":"object","title":"Rules"}},"type":"object","title":"FilterPreviewRequest","description":"Body du POST filter-preview : règles à appliquer (même shape que\n`node.config` côté pipeline_new.html → consommé par scraper_filter._apply_all)."},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"JobCreateRequest":{"properties":{"queries":{"items":{"type":"string"},"type":"array","maxItems":20,"minItems":1,"title":"Queries"},"zones":{"items":{"type":"string"},"type":"array","maxItems":50,"minItems":1,"title":"Zones"},"country":{"type":"string","title":"Country","default":"FRA"},"include_reviews":{"type":"boolean","title":"Include Reviews","default":false},"scrap_mode":{"type":"string","enum":["fast","normal","ultra"],"title":"Scrap Mode","default":"fast"},"extra_columns":{"items":{"type":"string"},"type":"array","title":"Extra Columns"},"stop_at":{"anyOf":[{"type":"integer","maximum":2000000.0,"minimum":1.0},{"type":"null"}],"title":"Stop At"},"max_per_phone":{"type":"integer","maximum":10.0,"minimum":0.0,"title":"Max Per Phone","default":0}},"type":"object","required":["queries","zones"],"title":"JobCreateRequest"},"JobEstimateRequest":{"properties":{"queries":{"items":{"type":"string"},"type":"array","maxItems":20,"title":"Queries"},"zones":{"items":{"type":"string"},"type":"array","maxItems":50,"title":"Zones"},"country":{"type":"string","title":"Country","default":"FRA"},"include_reviews":{"type":"boolean","title":"Include Reviews","default":false},"scrap_mode":{"type":"string","enum":["fast","normal","ultra"],"title":"Scrap Mode","default":"fast"},"stop_at":{"anyOf":[{"type":"integer","maximum":2000000.0,"minimum":1.0},{"type":"null"}],"title":"Stop At"}},"type":"object","title":"JobEstimateRequest","description":"Pour le calcul live d'EF côté frontend (sans création)."},"JobEstimateResponse":{"properties":{"valid":{"type":"boolean","title":"Valid"},"grid_points":{"type":"integer","title":"Grid Points"},"total_requests":{"type":"integer","title":"Total Requests","default":0},"queries_count":{"type":"integer","title":"Queries Count","default":0},"ef_cost":{"type":"number","title":"Ef Cost"},"ef_max":{"type":"number","title":"Ef Max","default":5.0},"estimated_duration_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Estimated Duration Seconds"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"warnings":{"items":{"type":"string"},"type":"array","title":"Warnings"}},"type":"object","required":["valid","grid_points","ef_cost"],"title":"JobEstimateResponse"},"JobPublic":{"properties":{"id":{"type":"string","title":"Id"},"job_type":{"type":"string","title":"Job Type","default":"scrap"},"queries":{"items":{"type":"string"},"type":"array","title":"Queries"},"zones":{"items":{"type":"string"},"type":"array","title":"Zones"},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country"},"include_reviews":{"type":"boolean","title":"Include Reviews"},"status":{"type":"string","enum":["pending","running","done","failed","cancelled","expired"],"title":"Status"},"grid_points_count":{"type":"integer","title":"Grid Points Count"},"processed_points":{"type":"integer","title":"Processed Points"},"results_count":{"type":"integer","title":"Results Count"},"error_count":{"type":"integer","title":"Error Count"},"ef_cost":{"type":"number","title":"Ef Cost"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"started_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Started At"},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At"},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires At"},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message"},"output_filename":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Output Filename"},"download_available":{"type":"boolean","title":"Download Available","default":false},"source_job_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Job Id"},"pipeline_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pipeline Id"},"email_mode":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email Mode"},"breakdown":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Breakdown"},"dead_queries":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Dead Queries"},"flagged_tiles_count":{"type":"integer","title":"Flagged Tiles Count","default":0},"total_attempts_count":{"type":"integer","title":"Total Attempts Count","default":0},"query_stats":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Query Stats"},"scrap_mode":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Scrap Mode"},"sub_tiles_queued":{"type":"integer","title":"Sub Tiles Queued","default":0},"notice":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notice"},"user_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"User Id"},"user_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Email"}},"type":"object","required":["id","queries","zones","include_reviews","status","grid_points_count","processed_points","results_count","error_count","ef_cost","created_at"],"title":"JobPublic"},"LegalDataJobCreateRequest":{"properties":{"items":{"items":{"type":"object"},"type":"array","maxItems":10000,"title":"Items"},"source_job_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Job Id"}},"type":"object","title":"LegalDataJobCreateRequest","description":"Job d'enrichissement Données société (SIRENE/INPI via api.gouv.fr).\nInput : liste d'établissements (nom + adresse minimum, idéalement siren\nsi dispo depuis un legal_ids amont)."},"LegalIdsJobCreateRequest":{"properties":{"items":{"items":{"type":"object"},"type":"array","maxItems":10000,"title":"Items"},"source_job_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Job Id"}},"type":"object","title":"LegalIdsJobCreateRequest","description":"Job de détection SIRET/SIREN depuis une liste de POI avec site_web."},"LegalMentionsJobCreateRequest":{"properties":{"items":{"items":{"type":"object"},"type":"array","maxItems":10000,"title":"Items"},"source_job_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Job Id"}},"type":"object","title":"LegalMentionsJobCreateRequest","description":"Job de parsing des mentions légales (raison sociale, capital, RCS, etc.)."},"PageSpeedJobCreateRequest":{"properties":{"items":{"items":{"type":"object"},"type":"array","maxItems":10000,"title":"Items"},"source_job_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Job Id"}},"type":"object","title":"PageSpeedJobCreateRequest","description":"Job de scoring PageSpeed via Google PSI API v5 (gratuit free-tier, clé\nrequise PAGESPEED_API_KEY)."},"PhoneInfoJobCreateRequest":{"properties":{"items":{"items":{"type":"object"},"type":"array","maxItems":10000,"title":"Items"},"source_job_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Job Id"},"live_mode":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Live Mode","default":"cache_only"}},"type":"object","title":"PhoneInfoJobCreateRequest","description":"Job d'enrichissement \"Informations sur le numéro\" (opérateur d'origine + portabilité)."},"PhonesExtraJobCreateRequest":{"properties":{"items":{"items":{"type":"object"},"type":"array","maxItems":10000,"title":"Items"},"source_job_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Job Id"}},"type":"object","title":"PhonesExtraJobCreateRequest","description":"Job de recherche de téléphones supplémentaires (au-delà du n° Google Maps)."},"PipelineCreateRequest":{"properties":{"name":{"type":"string","maxLength":120,"title":"Name","default":""},"definition":{"type":"object","title":"Definition"}},"type":"object","required":["definition"],"title":"PipelineCreateRequest"},"PipelineEstimateRequest":{"properties":{"definition":{"type":"object","title":"Definition"},"n_input_estimate":{"type":"integer","minimum":0.0,"title":"N Input Estimate","default":0}},"type":"object","required":["definition"],"title":"PipelineEstimateRequest"},"PipelineValidateRequest":{"properties":{"definition":{"type":"object","title":"Definition"},"schema_version":{"type":"integer","title":"Schema Version","default":1}},"type":"object","required":["definition"],"title":"PipelineValidateRequest","description":"Body de POST /validate : une définition à normaliser + valider sans\nrien créer. `schema_version` optionnel (ignoré pour l'instant — réservé\naux migrations futures de format)."},"PricingJobCreateRequest":{"properties":{"items":{"items":{"type":"object"},"type":"array","maxItems":10000,"title":"Items"},"source_job_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Job Id"}},"type":"object","title":"PricingJobCreateRequest","description":"Job d'extraction prix/tarifs SaaS B2B en cascade JSON-LD/Microdata/OG/regex."},"ReviewsJobCreateRequest":{"properties":{"items":{"items":{"type":"object"},"type":"array","maxItems":10000,"title":"Items"},"source_job_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Job Id"}},"type":"object","title":"ReviewsJobCreateRequest","description":"Job d'enrichissement avis depuis une liste de POI."},"SocialsJobCreateRequest":{"properties":{"items":{"items":{"type":"object"},"type":"array","maxItems":10000,"title":"Items"},"source_job_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Job Id"}},"type":"object","title":"SocialsJobCreateRequest","description":"Job de détection des liens réseaux sociaux depuis une liste de POI avec site_web."},"TechStackJobCreateRequest":{"properties":{"items":{"items":{"type":"object"},"type":"array","maxItems":10000,"title":"Items"},"source_job_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Job Id"}},"type":"object","title":"TechStackJobCreateRequest","description":"Job d'analyse tech stack (CMS, frameworks, analytics, CRM, payment, etc.)."},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"VeilleCreate":{"properties":{"source_job_id":{"anyOf":[{"type":"string","maxLength":64,"minLength":8},{"type":"null"}],"title":"Source Job Id"},"source_pipeline_id":{"anyOf":[{"type":"string","maxLength":64,"minLength":8},{"type":"null"}],"title":"Source Pipeline Id"},"name":{"type":"string","maxLength":200,"minLength":2,"title":"Name"},"frequency_days":{"type":"integer","maximum":365.0,"minimum":1.0,"title":"Frequency Days"}},"type":"object","required":["name","frequency_days"],"title":"VeilleCreate"},"VeilleUpdate":{"properties":{"status":{"anyOf":[{"type":"string","pattern":"^(active|paused|deleted)$"},{"type":"null"}],"title":"Status"},"frequency_days":{"anyOf":[{"type":"integer","maximum":365.0,"minimum":1.0},{"type":"null"}],"title":"Frequency Days"},"name":{"anyOf":[{"type":"string","maxLength":200,"minLength":2},{"type":"null"}],"title":"Name"}},"type":"object","title":"VeilleUpdate"},"VerifyEmailsJobCreateRequest":{"properties":{"items":{"items":{"type":"object"},"type":"array","maxItems":10000,"title":"Items"},"source_job_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Job Id"}},"type":"object","title":"VerifyEmailsJobCreateRequest","description":"Job de vérification anti-bounce d'une liste d'emails (sans VPN)."},"WebhookCreateRequest":{"properties":{"url":{"type":"string","maxLength":500,"minLength":12,"title":"Url","description":"URL https:// publique qui recevra les POST"},"events":{"items":{"type":"string"},"type":"array","minItems":1,"title":"Events","description":"Types d'événements souscrits"},"secret":{"anyOf":[{"type":"string","maxLength":128,"minLength":16},{"type":"null"}],"title":"Secret","description":"Secret HMAC. Généré si absent."}},"type":"object","required":["url","events"],"title":"WebhookCreateRequest"}},"securitySchemes":{"BearerKey":{"type":"http","scheme":"bearer","description":"API key as bearer token (osk_...)"},"ApiKeyHeader":{"type":"apiKey","in":"header","name":"X-API-Key"}}},"security":[{"BearerKey":[]},{"ApiKeyHeader":[]}]}