API Yeah4Job

API de recherche et de gestion des offres d'emploi.

Authentification

Toutes les routes nécessitent un token d'organisation passé en en-tête Authorization :

Authorization: Bearer <votre_api_token>
Une organisation ne voit que ses propres offres et les offres publiques (issues des synchronisations, ex. France Travail). Elle ne peut modifier ou supprimer que ses propres offres.

Endpoints

GET /api/offers

Recherche paginée d'offres (publiques + celles de l'organisation).

Paramètre (query)TypeDescription
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.
Localisation : 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).

GET /api/cities

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)TypeDescription
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.

GET /api/offers/{id}

Détail d'une offre (publique ou appartenant à l'organisation).

200 OK   → { "data": { ...offre... } }
404 Not Found  → offre inexistante ou appartenant à une autre organisation
POST /api/offers

Crée une offre rattachée à l'organisation (source direct).

Champ (body JSON)TypeDescription
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). salaryMaxsalaryMin.
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."] }
PUT PATCH /api/offers/{id}

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)
DELETE /api/offers/{id}

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)

Valeurs des énumérations

contractType : cdicddalternancestagefreelanceinterimseasonalother

experienceLevel : beginnerjuniorconfirmedsenior

remotePolicy : on_sitehybridremote

salaryPeriod : hourlydailymonthlyyearly

source : france_travaildirect

workTime : full_timepart_time

educationLevel : nonecap_bepbacbac_2bac_3_4bac_5