API Jobs
Surface unifiée pour toutes les charges exécutées par Outsend — acquisition de sources, enrichissement, vérification, reporting.
API Jobs
L'API Jobs est la surface unifiée pour toutes les charges qu'Outsend exécute pour un tenant : acquisition de sources (scrap) et les modules d'enrichissement, vérification et reporting qui opèrent sur les items résultants. Un job est la seule unité facturable.
Voir aussi :
- Cycle de vie des jobs — pending → running → done | failed | cancelled | expired
- États et événements — référence des payloads SSE
- Limites — quota EF, plafonds par job, rétention
Tous les endpoints requièrent un cookie de session authentifié. Les endpoints qui créent ou mutent des jobs requièrent en plus un utilisateur actif ; POST /api/jobs et POST /api/jobs/resume exigent aussi un email vérifié. Les routes admin (/api/admin/*, /api/jobs/queue) ne sont pas documentées ici.
Conventions
| Élément | Valeur |
|---|---|
| URL de base | https://outsend.xyz |
| Auth | Cookie de session (outsend_session) |
| Content-Type | application/json pour les corps POST |
| Identifiant de job | Chaîne opaque (job.id), stable durant la vie du job |
| Horodatages | ISO 8601 UTC |
L'objet JobPublic
Tout endpoint qui retourne un job retourne la même forme :
{
"id": "j_01HXYZ...",
"job_type": "scrap",
"queries": ["dentiste"],
"zones": ["Paris", "75015"],
"include_reviews": false,
"status": "running",
"grid_points_count": 412,
"processed_points": 87,
"results_count": 64,
"error_count": 0,
"ef_cost": 0.041,
"created_at": "2026-05-27T09:12:03Z",
"started_at": "2026-05-27T09:12:05Z",
"completed_at": null,
"expires_at": "2026-06-26T09:12:03Z",
"error_message": null,
"output_filename": null,
"download_available": false,
"source_job_id": null,
"email_mode": null,
"breakdown": { "by_query": {"dentiste": 64}, "by_zone": {"Paris": 64} },
"dead_queries": [],
"flagged_tiles_count": 0,
"total_attempts_count": 87,
"query_stats": { "dentiste": { "tiles": 87, "with_results": 71 } }
}
status prend l'une des valeurs pending | running | done | failed | cancelled | expired.
Erreurs
Tous les endpoints renvoient {"detail": "..."} (ou {"detail": {"message": ..., "errors": [...]}} pour les erreurs de validation). Codes génériques : 401 non authentifié, 403 non autorisé (autre tenant ou email non vérifié), 404 non trouvé, 422 validation Pydantic. Les causes spécifiques sont listées en ligne.
Créer un job (générique)
POST /api/jobs
Crée un job scrap — la charge canonique d'acquisition qui exécute des requêtes sur une grille géographique. Pour toute autre charge, utiliser le raccourci typé décrit plus bas ; passer un champ type à POST /api/jobs n'est pas supporté.
Corps de requête
{
"queries": ["dentiste", "orthodontiste"],
"zones": ["Paris", "75015", "Lyon 2e"],
"include_reviews": false
}
| Champ | Type | Notes |
|---|---|---|
queries |
string[] (1..20) |
Chaque item ≤ 200 caractères, trimé, dédupliqué |
zones |
string[] (1..50) |
Noms de villes, codes postaux ou arrondissements ; résolus côté serveur |
include_reviews |
boolean |
Si true, récupère les derniers avis par POI (augmente le coût EF) |
Réponse — 200 OK, un JobPublic en statut pending.
Causes spécifiques : 400 parsing de zone échoué / quota EF dépassé / grille vide ; 403 email non vérifié.
Créer un job (raccourci typé)
Chaque module d'enrichissement, vérification et reporting a un endpoint dédié qui accepte les items sur lesquels il opère. Chaque raccourci renvoie un JobPublic dont le job_type est fixé au slug du module.
POST /api/jobs/{type}
type |
Rôle | Doc module |
|---|---|---|
reviews |
Récupérer les derniers avis par POI | reviews |
emails |
Découvrir les emails de contact depuis chaque site | emails |
verify-emails |
Vérification anti-bounce (sans VPN) | verify-emails |
socials |
Détecter les profils sociaux liés | socials |
phones-extra |
Trouver des numéros au-delà du listing Maps | phones-extra |
legal-ids |
Extraire SIRET / SIREN depuis le site | legal-ids |
legal-mentions |
Parser la page mentions légales (capital, RCS, …) | legal-mentions |
legal-data |
Enrichir via SIRENE / INPI (api.gouv.fr) |
legal-data |
pricing |
Extraire les tarifs SaaS / B2B | pricing |
techstack |
Détecter CMS, frameworks, analytics, paiement, CRM | techstack |
pagespeed |
Score via Google PSI API v5 | pagespeed |
ads-intelligence |
Profilage marketing/ads (pixels, CMP, retargeting) | ads-intelligence |
brand-assets |
Logo, favicon, palette, screenshot optionnel | brand-assets |
dead-check |
Marquer les sites morts (DNS, parking, default-server, SSL) | dead-check |
delivery-check |
Test de placement Gmail Inbox / Promotions / Spam | delivery-check |
Corps de requête (forme partagée par tous les modules par item)
{
"items": [
{ "nom": "Cabinet Dupont", "site_web": "https://dupont-dentiste.fr", "ville": "Paris" }
],
"source_job_id": "j_01HXYZ..."
}
| Champ | Type | Notes |
|---|---|---|
items |
dict[] (1..10 000) |
Clés spécifiques au module ; généralement un sous-ensemble du CSV d'un job précédent |
source_job_id |
string? |
Chaîne le nouveau job à un précédent, utilisé pour traçabilité et affichage facturation |
Surcharges spécifiques aux modules
POST /api/jobs/emails— acceptemode: "normal" | "deep"(défautnormal).POST /api/jobs/brand-assets— acceptecapture_screenshot: boolean(défautfalse, ~5× plus lent par item quand activé).POST /api/jobs/delivery-check— ne prend pasitems. Corps :
json
{ "domain": "example.com", "subject_filter": "outsend" }
Réponse — 200 OK, un JobPublic en statut pending. Cause additionnelle : 422 si items est vide, trop grand ou si des clés requises par le module manquent.
Lister les jobs
GET /api/jobs?limit={n}&offset={n}
Renvoie les jobs de l'utilisateur authentifié, les plus récents d'abord.
| Param | Type | Défaut | Plage |
|---|---|---|---|
limit |
int |
100 |
borné à [1, 500] |
offset |
int |
0 |
≥ 0 |
Réponse — 200 OK, JobPublic[].
Récupérer un job
GET /api/jobs/{id}
Réponse — 200 OK, un seul JobPublic. Inclut des compteurs live (processed_points, results_count, query_stats, breakdown) que le tableau de bord interroge entre les événements SSE.
Suivre la progression en direct (SSE)
GET /api/jobs/{id}/stream?since={log_id}
Flux Server-Sent Events qui émet les transitions de statut, lignes de log et mises à jour de compteurs au fur et à mesure de l'avancement. Les reconnexions honorent automatiquement Last-Event-ID ; le paramètre since est un fallback pour clients qui ne parlent pas SSE nativement. La taxonomie d'événements (status, log, progress, done, error) et les payloads sont documentés dans États et événements.
En-têtes renvoyés
Content-Type: text/event-stream
Cache-Control: no-cache
X-Accel-Buffering: no
Lister les items d'un job
GET /api/jobs/{id}/items?offset={n}&limit={n}
Renvoie les lignes du CSV de sortie en JSON, pour chaînage vers un job d'enrichissement. Disponible uniquement pour les jobs dont status == "done" et dont le job_type produit un CSV réutilisable (c.-à-d. ni delivery_check ni viewport_test).
Réponse — 200 OK
{
"count": 412,
"items": [
{ "nom": "Cabinet Dupont", "site_web": "https://...", "telephone": "+33 1 ...", "...": "..." }
]
}
Causes spécifiques : 400 job non terminé ou job_type sans sortie réutilisable ; 410 CSV expiré ou supprimé.
Télécharger le résultat d'un job
GET /api/jobs/{id}/download?format=csv|json|xlsx
Télécharge la sortie du job. Le CSV est l'artefact canonique écrit par le worker (UTF-8 BOM, séparateur ;) ; JSON et XLSX sont dérivés à la volée. Tous les exports passent par un sanitiseur d'injection de formules tableur.
format |
Media type | Nom de fichier |
|---|---|---|
csv (défaut) |
text/csv; charset=utf-8 |
{job.output_filename} |
json |
application/json; charset=utf-8 |
{base}.json |
xlsx |
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet |
{base}.xlsx |
Causes spécifiques : 400 job encore pending/running ou format non supporté ; 410 sortie expirée, manquante, ou job échoué avant la première ligne.
Arrêter un job
POST /api/jobs/{id}/cancel
Arrête un job pending ou running. Les résultats déjà extraits sont conservés (téléchargeables en CSV partiel et réutilisables). Renvoie 400 si le job est déjà terminal.
Si le job appartient à un pipeline, l'arrêt met la pipeline en pause sur cette étape — les étapes suivantes ne sont pas lancées automatiquement (idem en cas de crash). Pour poursuivre la chaîne avec les résultats partiels, l'utilisateur déclenche explicitement POST /api/pipelines/{id}/nodes/{node_id}/continue (bouton « Continuer avec les résultats »). Pour arrêter au contraire la pipeline entière, utiliser POST /api/pipelines/{id}/cancel.
Réponse — 200 OK, {"ok": true}.
Reprendre un job
POST /api/jobs/{id}/resume
Crée un nouveau job qui reprend un scrap cancelled ou failed là où il s'est arrêté. Le nouveau job hérite des queries, zones et CSV partiel de la source ; le worker saute les coordonnées déjà traitées. EF est débité uniquement pour les points restants.
Réponse — 200 OK, un nouveau JobPublic (le job de reprise) en statut pending. Son source_job_id référence l'original.
Causes spécifiques : 400 job source non reprisable (mauvais type, non interrompu, ou déjà entièrement traité) ; 403 email non vérifié.
Supprimer un job
DELETE /api/jobs/{id}
Supprime définitivement le job et son CSV. Refuse de supprimer un job encore en cours — il faut l'arrêter d'abord.
Réponse — 204 No Content. Cause spécifique : 400 job encore en cours.
Estimer le coût EF
POST /api/estimate
Calcule le coût EF d'un job scrap hypothétique sans le créer. Alimente le compteur de coût live du formulaire de lancement. L'estimation est gratuite et non comptée.
Corps de requête — même forme que POST /api/jobs, mais queries et zones peuvent être vides (renvoie valid: false).
Réponse — 200 OK, un JobEstimateResponse :
{
"valid": true,
"grid_points": 412,
"total_requests": 824,
"queries_count": 2,
"ef_cost": 0.041,
"estimated_duration_seconds": 1380,
"errors": [],
"warnings": []
}
| Champ | Signification |
|---|---|
valid |
true ssi errors est vide |
grid_points |
Tuiles GPS distinctes sur l'union des zones |
total_requests |
grid_points × len(queries) — ce que le worker appellera réellement |
queries_count |
Reflète len(queries) pour l'affichage UI |
ef_cost |
Unités équivalent France ; voir Limites |
estimated_duration_seconds |
Estimation au mieux du temps horloge |
errors |
Bloqueurs durs (hors-quota, zones impossibles à parser, grille vide) |
warnings |
Signaux doux (non utilisés actuellement) |
Notes sur les endpoints omis
Les routes suivantes existent mais ne font pas partie de la surface publique :
GET /api/jobs/queue— file globale anonymisée pour le widget public. Sans tenant, périmètre séparé./api/admin/*— réservé opérateur.GET /api/jobs/{id}/breakdown,GET /api/jobs/{id}/map,GET /api/jobs/{id}/output-columns,GET /api/jobs/{id}/delivery-result,POST /api/jobs/parse-list,GET /api/brand-lookup,GET /api/brand-assets/{owner}/{filename},GET /api/delivery-check/seeds— helpers internes UI susceptibles de changer sans préavis.