API de recherche et de gestion des offres d'emploi.
Toutes les routes nécessitent un token d'organisation passé en en-tête Authorization :
Authorization: Bearer <votre_api_token>
Recherche paginée d'offres (publiques + celles de l'organisation).
| Paramètre (query) | Type | Description |
|---|---|---|
search |
string | Recherche plein texte, insensible aux accents, sur le titre, l'entreprise, la description et la localisation (ville, nom de département, nom de région). Ex. Lyon, bouches du rhone. |
department[] |
string[] | Filtre par code(s) département. Ex. ?department[]=13&department[]=83 ou ?department=13,83. |
region[] |
string[] | Filtre par code(s) région INSEE (remonte tous les départements de la région). Ex. ?region[]=93 (PACA). |
job_code[] |
string[] | Filtre par code(s) métier ROME (le champ job_code des offres). Ex. ?job_code[]=M1203&job_code[]=M1805 ou ?job_code=M1203,M1805. Insensible à la casse. |
contract_type[] |
string[] | Filtre par type(s) de contrat (voir énumération ci-dessous). Ex. ?contract_type[]=cdi&contract_type[]=cdd ou ?contract_type=cdi,cdd. Valeur inconnue → 400. |
work_time[] |
string[] | Filtre temps plein / temps partiel (voir énumération workTime). Ex. ?work_time[]=part_time. Valeur inconnue → 400. |
source[] |
string[] | Filtre par source(s) d'offres (voir énumération ci-dessous). Ex. ?source[]=france_travail ou ?source=direct pour les offres saisies via l'API. Valeur inconnue → 400. |
mine |
bool | mine=1 : uniquement les offres appartenant à votre organisation (exclut les offres publiques). |
sort |
string | relevance (défaut avec search) ou date (défaut sinon). relevance requiert search. Au-delà de 3 000 résultats, la pertinence n'est plus discriminante : bascule automatique sur la date — meta.sort indique le tri réellement appliqué. Valeur inconnue → 400. |
mine_first |
bool | mine_first=1 : les offres de votre organisation remontent en tête, puis le tri choisi s'applique. Combinable avec tout (contrairement à mine qui filtre, mine_first ordonne). |
city |
string | Code INSEE de la ville (⚠️ pas le code postal), obtenu via /api/cities. Filtre les offres autour du centre de la ville. Ex. ?city=13001 (Aix-en-Provence). Code inconnu → 400. |
radius |
float | Rayon en km autour du centre. Défaut : 10 km quand city ou lat+lng est fourni. Le rayon réellement appliqué est renvoyé dans meta.radius. |
lat, lng |
float | Centre du rayon en coordonnées brutes (alternative à city, ex. géoloc « autour de moi »). |
semantic |
bool | Par défaut, search déclenche une recherche hybride : plein texte (termes exacts) et sémantique (synonymes : « directeur restaurant » trouve « chef de restaurant ») fusionnés en un seul classement. semantic=0 désactive la partie sémantique (plein texte pur). meta.semantic indique si la jambe sémantique a contribué (repli plein texte automatique en cas d'indisponibilité) ; les offres trouvées par le sens portent semantic_distance (plus petit = plus proche). En mode hybride, la pagination est explorable jusqu'à 1 000 résultats (meta.pages en tient compte). |
page |
int | Numéro de page. Défaut : 1. |
limit |
int | Résultats par page. Défaut : 20, max 100. |
department[], region[] et city+radius
se combinent entre eux en OU (une seule zone géographique voulue à la fois, pas leur
intersection). Ex. ?city=04112&radius=10&department[]=2A renvoie les offres autour de
Manosque OU en Corse-du-Sud. Ce bloc reste combiné en ET avec les
autres filtres (contract_type, source, search…).
GET /api/offers?search=infirmier&department[]=13&department[]=83&page=1&limit=20
200 OK
{
"data": [
{
"id": 12, "title": "Infirmier (H/F)", "city": "Marseille",
"department": "13", "department_name": "Bouches-du-Rhône",
"region_code": "93", "region_name": "Provence-Alpes-Côte d'Azur", ...
}
],
"meta": { "total": 137, "total_estimated": null, "capped": false, "radius": null, "city": null, "semantic": true, "sort": "relevance", "page": 1, "limit": 20, "pages": 7 }
}
Filtre department = par code (83) ; ou bien on tape le nom dans search (bouches du rhone). Les deux sont combinables.
Tri : par pertinence quand search est fourni, sinon par date de publication.
Le comptage est plafonné à 10 000 : au-delà, meta.capped = true, meta.total vaut le plafond,
meta.total_estimated fournit une estimation statistique du vrai total (±2 %, pour affichage « environ N offres »),
et le tri bascule sur la date de publication (le score de pertinence n'est pas discriminant sur des recherches aussi larges).
Autocomplétion de villes — route publique, sans token. Sert à alimenter une barre de recherche de ville, puis à passer le code ville à /api/offers.
| Paramètre (query) | Type | Description |
|---|---|---|
q requis |
string | Début du nom de ville (min. 2 caractères, insensible aux accents/casse), ou code postal (5 chiffres, correspondance exacte — utile pour les communes renommées). Ex. ?q=marse, ?q=50580. |
limit |
int | Nombre de résultats. Défaut : 10, max 20. |
GET /api/cities?q=marse
200 OK
{
"data": [
{ "code": "13055", "name": "Marseille", "postal_code": "13001", "department_code": "13",
"latitude": 43.2803, "longitude": 5.3806, "population": 873076 },
...
]
}
Résultats triés par population décroissante (les grandes villes d'abord). Le code retourné se passe ensuite en ?city=…&radius=… sur la recherche d'offres.
Détail d'une offre (publique ou appartenant à l'organisation).
200 OK → { "data": { ...offre... } }
404 Not Found → offre inexistante ou appartenant à une autre organisation
Crée une offre rattachée à l'organisation (source direct).
| Champ (body JSON) | Type | Description |
|---|---|---|
title requis |
string | Intitulé du poste. |
contractType requis |
enum | Type de contrat (voir valeurs ci-dessous). |
description |
string | Description du poste. |
contractLabel |
string | Libellé libre du contrat. |
isAlternance |
bool | Offre en alternance. |
experienceLevel |
enum | Niveau d'expérience. |
experienceLabel |
string | Libellé libre d'expérience. |
experienceMinMonths |
int | Expérience minimale requise, en mois (0 = débutant accepté). |
workTime |
enum | Temps de travail (full_time / part_time). |
workTimeLabel |
string | Détail du temps de travail (ex. « 35H/semaine »). |
educationLevel |
enum | Niveau d'études requis (voir énumération). |
educationLabel |
string | Libellé libre du niveau d'études. |
drivingLicenses |
string[] | Permis requis (libellés libres, ex. ["B - Véhicule léger"]). |
contactName / contactEmail / contactPhone |
string | Contact recruteur (email validé). |
applicationUrl |
string | URL de candidature directe (http/https). |
jobCode |
string | Code métier ROME (ex. M1805) — rend l'offre trouvable via le filtre job_code[]. Format invalide → 422. |
jobCategory / jobAppellation |
string | Libellé et appellation fine du métier. |
isActive |
bool | false = dépublier/archiver l'offre (invisible de la recherche), true = (re)publier. Défaut : true. |
cityCode requis |
string | Code INSEE de la ville (cf. /api/cities). Obligatoire, sauf offre full remote (remotePolicy=remote). Dérive automatiquement : nom de ville, code postal, coordonnées GPS, département, région. Code inconnu → 422. |
city / postalCode |
string | Optionnels : libellé de ville et code postal d'affichage, s'ils doivent différer de ceux dérivés du code INSEE. |
latitude / longitude |
float | Optionnels : coordonnées GPS précises (ex. adresse exacte), sinon celles de la ville. |
remotePolicy |
enum | Politique de télétravail. |
companyName |
string | Nom de l'entreprise. |
salaryMin / salaryMax |
float | Fourchette de salaire (en euros). salaryMax ≥ salaryMin. |
salaryPeriod |
enum | Période du salaire (voir valeurs ci-dessous). |
salaryLabel |
string | Texte affichable (ex. « Selon profil »). Généré automatiquement depuis min/max/période si absent (ex. « De 45 000 € à 55 000 € par an »). |
numberOfPositions |
int | Nombre de postes (≥ 1). |
accessibleForDisabled |
bool | Accessible aux travailleurs handicapés. |
sourceUrl |
string | URL externe de l'offre. |
expiresAt |
string | Date d'expiration (ISO 8601). À l'échéance, l'offre est dépubliée automatiquement (cron quotidien). Absent = n'expire jamais. Republication : PATCH { isActive: true, expiresAt: … }. |
POST /api/offers
{
"title": "Développeur PHP / Symfony",
"contractType": "cdi",
"city": "Marseille",
"remotePolicy": "hybrid",
"numberOfPositions": 2
}
201 Created → { "data": { "id": 5012, ... } }
422 Unprocessable Entity → { "errors": ["Le champ \"title\" est obligatoire."] }
Met à jour une offre de l'organisation. Mise à jour partielle : seuls les champs envoyés sont modifiés. Mêmes champs que la création.
PUT /api/offers/5012
{ "numberOfPositions": 3, "remotePolicy": "remote" }
200 OK → { "data": { ...offre mise à jour... } }
403 Forbidden → l'offre appartient à une autre organisation
404 Not Found → offre inexistante ou publique (non modifiable)
Supprime une offre de l'organisation.
DELETE /api/offers/5012
204 No Content → suppression réussie
403 Forbidden → l'offre appartient à une autre organisation
404 Not Found → offre inexistante ou publique (non supprimable)
contractType : cdicddalternancestagefreelanceinterimseasonalother
experienceLevel : beginnerjuniorconfirmedsenior
remotePolicy : on_sitehybridremote
salaryPeriod : hourlydailymonthlyyearly
source : france_travaildirect
workTime : full_timepart_time
educationLevel : nonecap_bepbacbac_2bac_3_4bac_5