47 offres d’emploi actives, trois agrégateurs à nourrir, et une équipe qui refuse de ressaisir la même annonce trois fois : voilà le problème posé par un cabinet de recrutement spécialisé dans les profils techniques, que nous appellerons Recrutis. La direction voulait garder WordPress comme outil de saisie quotidienne, avec ses recruteurs habitués à l’éditeur, mais diffuser ces offres vers des plateformes externes qui n’acceptent qu’un format JSON précis.
La tentation aurait été d’installer un plugin d’export tout-en-un. Mais chaque agrégateur exigeait des champs légèrement différents (intitulé, contrat, fourchette de salaire, référence interne) et une fréquence de mise à jour propre. La solution retenue s’appuie sur un type de contenu personnalisé et un endpoint REST sur mesure, bien plus simple à maintenir qu’une jungle de plugins superposés.
Modéliser l’offre d’emploi comme un CPT dédié
Le point de départ est un type de contenu personnalisé offre_emploi, enregistré avec show_in_rest à true pour qu’il apparaisse automatiquement dans l’API REST. Les champs spécifiques (type de contrat, fourchette de salaire, référence interne, date de clôture) sont gérés avec des champs personnalisés classiques, exposés ensuite via register_rest_field(). Cette approche évite de dépendre d’une extension de champs avancés pour un besoin aussi ciblé.
register_post_type( 'offre_emploi', array(
'label' => 'Offres d\'emploi',
'public' => true,
'show_in_rest' => true,
'rest_base' => 'offres',
'supports' => array( 'title', 'editor', 'custom-fields' ),
) );
Construire un endpoint JSON pensé pour les agrégateurs

Exposer le CPT brut dans l’API REST standard ne suffit pas : les agrégateurs attendent un format plat, sans les métadonnées internes de WordPress (identifiants d’auteur, statuts de révision, liens de l’API elle-même). La bonne pratique consiste à créer une route dédiée avec register_rest_route(), qui reconstruit un tableau JSON minimal à partir des offres publiées.
add_action( 'rest_api_init', function () {
register_rest_route( 'recrutis/v1', '/offres-json', array(
'methods' => 'GET',
'callback' => 'recrutis_export_offres',
'permission_callback' => '__return_true',
) );
} );
function recrutis_export_offres() {
$offres = get_posts( array(
'post_type' => 'offre_emploi',
'posts_per_page' => -1,
'post_status' => 'publish',
) );
$donnees = array();
foreach ( $offres as $offre ) {
$donnees[] = array(
'reference' => get_post_meta( $offre->ID, 'reference_interne', true ),
'intitule' => get_the_title( $offre ),
'contrat' => get_post_meta( $offre->ID, 'type_contrat', true ),
'salaire' => get_post_meta( $offre->ID, 'fourchette_salaire', true ),
'url' => get_permalink( $offre ),
);
}
return rest_ensure_response( $donnees );
}
Chaque agrégateur consomme cette route selon son propre calendrier de collecte, sans que Recrutis n’ait besoin de connaître leurs contraintes techniques internes.
Gérer les champs qui varient d’un agrégateur à l’autre
Un des trois agrégateurs demandait un champ supplémentaire, absent des deux autres : un code interne de filière métier. Plutôt que de multiplier les endpoints, l’équipe a ajouté un paramètre de requête optionnel qui active ce champ à la demande :
- Le paramètre
?filiere=1ajoute le code métier dans la réponse. - Sans ce paramètre, la réponse reste minimale, ce qui limite la charge pour les deux autres agrégateurs.
- Le paramètre est déclaré et validé via l’argument
argsderegister_rest_route(), avec unsanitize_callbackqui force un entier.
Surveiller la fraîcheur du flux
Un flux JSON silencieux peut masquer une panne. L’équipe a ajouté un compteur d’offres et un horodatage de dernière mise à jour dans la réponse elle-même, ce qui permet à chaque agrégateur de vérifier que le flux n’est pas figé. Un simple appel wp cron event list en ligne de commande confirme par ailleurs qu’aucune tâche planifiée ne bloque la publication des offres.
Ce que cette architecture évite
En s’en tenant à un CPT et un endpoint dédié, Recrutis évite trois écueils fréquents : la duplication de saisie entre plusieurs outils, la dépendance à une extension généraliste dont les mises à jour cassent parfois le format de sortie, et l’exposition involontaire de données internes (statut de révision, identifiants d’auteur) dans un flux censé rester public et minimal.
Un endpoint JSON dédié aux consommateurs externes vaut toujours mieux qu’un endpoint générique bricolé à coups de filtres : il documente lui-même son contrat de données.
En résumé
Garder WordPress comme back-office et exposer un JSON sur mesure pour les agrégateurs n’exige ni service tiers ni synchronisation complexe : un CPT bien pensé, une route REST dédiée et quelques paramètres optionnels suffisent à couvrir des besoins hétérogènes sans dupliquer la saisie ni fragiliser le format de sortie.