openapi: 3.1.0 info: title: Diamond Careers API version: 1.0.0 description: | API REST publique de Diamond Careers, dédiée aux intégrations entre les **établissements** (maisons partenaires) ou les **groupes** et leurs systèmes externes (ATS, jobboards, SIRH, outils RH internes). Le périmètre couvert correspond strictement aux ressources qu'un établissement gère pour son compte : ses offres, les candidatures qu'il reçoit, ses articles éditoriaux, sa fiche établissement, et le téléversement de médias associés. Les groupes accèdent en plus à une vue agrégée de leurs maisons rattachées. Les jetons d'API ne sont délivrés qu'aux comptes établissement et groupe. Toutes les actions sont strictement périmétrées par le compte authentifié : un établissement ne voit ni ne modifie que ses propres ressources, un groupe celles des maisons qui lui sont rattachées. contact: name: Support Diamond Careers email: tech@diamondcareers.fr url: https://docs.diamondcareers.fr license: name: Propriétaire — usage soumis à contrat servers: - url: https://api.diamondcareers.fr/v1 description: Production security: - BearerJWT: [] tags: - name: Authentification description: Connexion, profil, déconnexion. - name: Établissement description: Fiche de votre établissement (maison). - name: Offres description: Offres d'emploi de votre établissement. - name: Candidatures description: Candidatures reçues sur vos offres. - name: Articles description: Articles éditoriaux publiés par votre établissement. - name: Équipe description: Membres gestionnaires de votre établissement. - name: Médias description: Téléversement de fichiers (visuels, CV reçus). - name: Taxonomies description: Listes de référence (types de contrat, catégories, types d'établissement). - name: Groupe description: Vue agrégée pour les comptes groupe (maisons rattachées). paths: /auth/login: post: tags: [Authentification] summary: Obtenir un jeton d'API description: | Échange identifiants contre un jeton Bearer à utiliser sur toutes les autres requêtes. Réservé aux comptes de rôle `etablissement` ou `groupe` disposant d'un accès API. security: [] requestBody: required: true content: application/json: schema: type: object required: [email, password] properties: email: { type: string, format: email } password: { type: string, format: password } responses: '200': description: Jeton émis content: application/json: schema: type: object properties: token: { type: string, description: 'Jeton Bearer à passer dans Authorization.' } user: { $ref: '#/components/schemas/AuthUser' } '401': { $ref: '#/components/responses/Unauthorized' } /auth/me: get: tags: [Authentification] summary: Profil du compte authentifié responses: '200': description: Profil content: application/json: schema: { $ref: '#/components/schemas/AuthUser' } '401': { $ref: '#/components/responses/Unauthorized' } /auth/logout: post: tags: [Authentification] summary: Invalider la session courante responses: '204': { description: Session invalidée } /maisons/{id}: parameters: [{ $ref: '#/components/parameters/IdPath' }] get: tags: [Établissement] summary: Récupérer la fiche de votre établissement description: Le jeton ne donne accès qu'à la maison rattachée au compte (ou aux maisons du groupe). responses: '200': { description: Maison, content: { application/json: { schema: { $ref: '#/components/schemas/Maison' } } } } '403': { $ref: '#/components/responses/Forbidden' } '404': { $ref: '#/components/responses/NotFound' } patch: tags: [Établissement] summary: Mettre à jour la fiche de votre établissement requestBody: required: true content: { application/json: { schema: { $ref: '#/components/schemas/MaisonUpdateInput' } } } responses: '200': { description: Maison mise à jour, content: { application/json: { schema: { $ref: '#/components/schemas/Maison' } } } } '403': { $ref: '#/components/responses/Forbidden' } /offres: get: tags: [Offres] summary: Lister vos offres description: Renvoie uniquement les offres rattachées à votre établissement (ou aux maisons de votre groupe). parameters: - $ref: '#/components/parameters/PerPage' - $ref: '#/components/parameters/Page' - $ref: '#/components/parameters/OrderBy' - $ref: '#/components/parameters/Order' - $ref: '#/components/parameters/Search' - name: status in: query schema: { type: string, enum: [any, mine, pending, publish, draft, private] } responses: '200': description: Liste d'offres content: application/json: schema: type: array items: { $ref: '#/components/schemas/Offer' } post: tags: [Offres] summary: Créer une offre description: | L'offre est créée au statut `pending` et passe en validation interne avant publication. requestBody: required: true content: { application/json: { schema: { $ref: '#/components/schemas/OfferInput' } } } responses: '201': { description: Offre créée, content: { application/json: { schema: { $ref: '#/components/schemas/Offer' } } } } '422': { $ref: '#/components/responses/ValidationError' } /offres/{id}: parameters: [{ $ref: '#/components/parameters/IdPath' }] get: tags: [Offres] summary: Récupérer une offre responses: '200': { description: Offre, content: { application/json: { schema: { $ref: '#/components/schemas/Offer' } } } } '403': { $ref: '#/components/responses/Forbidden' } '404': { $ref: '#/components/responses/NotFound' } patch: tags: [Offres] summary: Mettre à jour une offre requestBody: required: true content: { application/json: { schema: { $ref: '#/components/schemas/OfferInput' } } } responses: '200': { description: Offre mise à jour, content: { application/json: { schema: { $ref: '#/components/schemas/Offer' } } } } '403': { $ref: '#/components/responses/Forbidden' } delete: tags: [Offres] summary: Supprimer une offre responses: '200': { description: Suppression réussie, content: { application/json: { schema: { $ref: '#/components/schemas/Ok' } } } } '403': { $ref: '#/components/responses/Forbidden' } /candidatures: get: tags: [Candidatures] summary: Lister les candidatures reçues description: Renvoie uniquement les candidatures déposées sur vos offres (ou les offres des maisons de votre groupe). parameters: - $ref: '#/components/parameters/PerPage' - $ref: '#/components/parameters/Page' - name: status in: query schema: { type: string, enum: [pending, reviewing, interview, accepted, rejected, withdrawn] } - name: offer_id in: query schema: { type: integer } responses: '200': description: Liste de candidatures content: { application/json: { schema: { type: array, items: { $ref: '#/components/schemas/Application' } } } } /candidatures/{id}/status: parameters: [{ $ref: '#/components/parameters/IdPath' }] patch: tags: [Candidatures] summary: Mettre à jour le statut d'une candidature reçue requestBody: required: true content: application/json: schema: type: object required: [status] properties: status: type: string enum: [pending, reviewing, interview, accepted, rejected] comment: type: string description: 'Commentaire optionnel transmis au candidat.' responses: '200': { description: Statut mis à jour, content: { application/json: { schema: { $ref: '#/components/schemas/Application' } } } } '403': { $ref: '#/components/responses/Forbidden' } '422': { $ref: '#/components/responses/ValidationError' } /articles: get: tags: [Articles] summary: Lister les articles publiés par votre établissement parameters: - $ref: '#/components/parameters/PerPage' - $ref: '#/components/parameters/Page' - name: status in: query schema: { type: string, enum: [any, pending, publish, draft] } responses: '200': { description: Liste, content: { application/json: { schema: { type: array, items: { $ref: '#/components/schemas/Article' } } } } } post: tags: [Articles] summary: Soumettre un article description: L'article est créé au statut `pending` et passe en validation éditoriale avant publication. requestBody: required: true content: { application/json: { schema: { $ref: '#/components/schemas/ArticleInput' } } } responses: '201': { description: Article soumis, content: { application/json: { schema: { $ref: '#/components/schemas/Article' } } } } '422': { $ref: '#/components/responses/ValidationError' } /etablissement/members: get: tags: [Équipe] summary: Lister les membres gestionnaires de votre établissement responses: '200': description: Liste de membres content: application/json: schema: type: object properties: members: type: array items: { $ref: '#/components/schemas/MaisonMember' } my_role: { type: string, enum: [owner, editor, viewer] } post: tags: [Équipe] summary: Inviter un nouveau membre description: Réservé au rôle `owner` au sein de l'établissement. requestBody: required: true content: application/json: schema: type: object required: [email, role] properties: email: { type: string, format: email } role: { type: string, enum: [owner, editor, viewer] } first_name: { type: string } last_name: { type: string } responses: '201': { description: Invitation envoyée, content: { application/json: { schema: { $ref: '#/components/schemas/MaisonMember' } } } } '403': { $ref: '#/components/responses/Forbidden' } /etablissement/members/{user_id}: parameters: - name: user_id in: path required: true schema: { type: integer } patch: tags: [Équipe] summary: Modifier le rôle d'un membre description: Réservé au rôle `owner`. requestBody: required: true content: application/json: schema: type: object required: [role] properties: role: { type: string, enum: [owner, editor, viewer] } responses: '200': { description: Rôle mis à jour } '403': { $ref: '#/components/responses/Forbidden' } '409': { description: Conflit (par exemple dernier owner) } delete: tags: [Équipe] summary: Retirer un membre description: Réservé au rôle `owner`. responses: '200': { description: Membre retiré } '403': { $ref: '#/components/responses/Forbidden' } '409': { description: Conflit (par exemple dernier owner) } /upload: post: tags: [Médias] summary: Téléverser un média public (logo, visuel d'offre, image d'article) requestBody: required: true content: multipart/form-data: schema: type: object properties: file: { type: string, format: binary } responses: '201': description: Média téléversé content: { application/json: { schema: { $ref: '#/components/schemas/UploadedMedia' } } } /secure-upload: post: tags: [Médias] summary: Téléverser un fichier privé (CV reçu, document confidentiel) requestBody: required: true content: multipart/form-data: schema: type: object properties: file: { type: string, format: binary } kind: { type: string, enum: [cv, doc] } responses: '201': description: Fichier téléversé content: application/json: schema: type: object properties: ok: { type: boolean } path: { type: string, description: 'Chemin relatif à utiliser pour récupération ultérieure.' } mime: { type: string } size: { type: integer } original_name: { type: string } /secure-file: get: tags: [Médias] summary: Récupérer un fichier privé téléversé parameters: - name: path in: query required: true schema: { type: string } responses: '200': description: Contenu binaire du fichier content: application/octet-stream: {} '403': { $ref: '#/components/responses/Forbidden' } /taxonomies/{name}: get: tags: [Taxonomies] summary: Lister les termes d'une taxonomie de référence description: | Taxonomies disponibles : - `job_category` — catégories de métiers (cuisine, salle, étage, etc.) - `contract_type` — types de contrat (cdi, cdd, saison, alternance, stage, freelance) - `establishment_type` — types d'établissement (palace, hôtel, restaurant gastronomique, resort, etc.) parameters: - name: name in: path required: true schema: { type: string, enum: [job_category, contract_type, establishment_type] } responses: '200': description: Liste de termes content: application/json: schema: type: array items: type: object properties: id: { type: integer } slug: { type: string } name: { type: string } parent: { type: integer } count: { type: integer } /groupes/{group_id}/maisons: parameters: - name: group_id in: path required: true schema: { type: integer } get: tags: [Groupe] summary: Lister les maisons rattachées à votre groupe description: Réservé aux comptes de rôle `groupe`. L'identifiant doit correspondre à votre groupe. responses: '200': { description: Liste de maisons, content: { application/json: { schema: { type: array, items: { $ref: '#/components/schemas/Maison' } } } } } '403': { $ref: '#/components/responses/Forbidden' } /groupes/{group_id}/offres: parameters: - name: group_id in: path required: true schema: { type: integer } get: tags: [Groupe] summary: Lister les offres de toutes les maisons de votre groupe parameters: - $ref: '#/components/parameters/PerPage' - $ref: '#/components/parameters/Page' responses: '200': { description: Liste, content: { application/json: { schema: { type: array, items: { $ref: '#/components/schemas/Offer' } } } } } '403': { $ref: '#/components/responses/Forbidden' } /groupes/{group_id}/candidatures: parameters: - name: group_id in: path required: true schema: { type: integer } get: tags: [Groupe] summary: Lister les candidatures de toutes les maisons de votre groupe parameters: - $ref: '#/components/parameters/PerPage' - $ref: '#/components/parameters/Page' responses: '200': { description: Liste, content: { application/json: { schema: { type: array, items: { $ref: '#/components/schemas/Application' } } } } } '403': { $ref: '#/components/responses/Forbidden' } /groupes/{group_id}/stats: parameters: - name: group_id in: path required: true schema: { type: integer } get: tags: [Groupe] summary: Statistiques agrégées de votre groupe responses: '200': description: Statistiques agrégées content: application/json: schema: type: object properties: total_maisons: { type: integer } total_offres_active: { type: integer } total_candidatures_30d: { type: integer } by_maison: type: array items: type: object properties: maison_id: { type: integer } maison_name: { type: string } offres: { type: integer } candidatures: { type: integer } '403': { $ref: '#/components/responses/Forbidden' } components: securitySchemes: BearerJWT: type: http scheme: bearer bearerFormat: JWT parameters: IdPath: name: id in: path required: true schema: { type: integer } PerPage: name: per_page in: query schema: { type: integer, default: 10, minimum: 1, maximum: 100 } Page: name: page in: query schema: { type: integer, default: 1, minimum: 1 } OrderBy: name: orderby in: query schema: { type: string, enum: [date, modified, title, id] } Order: name: order in: query schema: { type: string, enum: [asc, desc], default: desc } Search: name: s in: query schema: { type: string } responses: Unauthorized: description: Authentification absente ou invalide content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } Forbidden: description: Authentifié mais sans permission sur cette ressource (hors de votre périmètre) content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } NotFound: description: Ressource introuvable content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } ValidationError: description: Données invalides au regard des règles métier content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } schemas: Ok: type: object properties: ok: { type: boolean } Error: type: object properties: code: { type: string } message: { type: string } data: type: object properties: status: { type: integer } AuthUser: type: object properties: id: { type: integer } email: { type: string, format: email } display_name: { type: string } role: { type: string, enum: [etablissement, groupe] } linked: type: object description: 'Identifiants de la maison ou du groupe rattaché.' properties: maison_id: { type: integer, nullable: true } group_id: { type: integer, nullable: true } member_role: { type: string, enum: [owner, editor, viewer], nullable: true } Maison: type: object properties: id: { type: integer } name: { type: string } slug: { type: string } meta: type: object description: 'Méta-données complètes de la fiche établissement.' properties: type: { type: string } standing: { type: string } description: { type: string } history: { type: string } city: { type: string } country: { type: string } address: { type: string } year_founded: { type: integer } employees_count: { type: integer } capacity_info: { type: string } restaurant_count: { type: integer } opening_period: { type: string } website: { type: string, format: uri } logo: { type: string, format: uri } main_image: { type: string, format: uri } director_name: { type: string } director_title: { type: string } director_photo: { type: string, format: uri } director_message: { type: string } facebook_url: { type: string, format: uri } linkedin_url: { type: string, format: uri } instagram_url: { type: string, format: uri } gallery: type: array items: { type: string, format: uri } video_url: { type: string, format: uri } MaisonUpdateInput: type: object properties: title: { type: string, description: 'Nom de la maison.' } meta: type: object description: 'Sous-ensemble des champs Maison.meta à mettre à jour.' Offer: type: object properties: id: { type: integer } title: { type: string } slug: { type: string } status: { type: string, enum: [draft, pending, publish, filled, closed] } date: { type: string, format: date-time } modified: { type: string, format: date-time } meta: type: object properties: contract_type: { type: string, enum: [cdi, cdd, saison, freelance, stage, alternance] } experience_level: { type: string } job_category: { type: string } city: { type: string } country: { type: string } salary_min: { type: number } salary_max: { type: number } salary_display: { type: string } description: { type: string } missions: { type: string } profile_required: { type: string } benefits: { type: string } start_date: { type: string, format: date } featured: { type: boolean } job_image: { type: string, format: uri } establishment_id: { type: integer } establishment_name: { type: string } establishment_logo: { type: string, format: uri } OfferInput: type: object required: [title] properties: title: { type: string } meta: type: object properties: contract_type: { type: string, enum: [cdi, cdd, saison, freelance, stage, alternance] } experience_level: { type: string } job_category: { type: string } city: { type: string } country: { type: string } salary_min: { type: number } salary_max: { type: number } salary_display: { type: string } description: { type: string } missions: { type: string } profile_required: { type: string } benefits: { type: string } start_date: { type: string, format: date } job_image: { type: string, format: uri } Application: type: object properties: id: { type: integer } offer_id: { type: integer } maison_id: { type: integer } talent_id: { type: integer, nullable: true } intern_id: { type: integer, nullable: true } status: type: string enum: [pending, reviewing, interview, accepted, rejected, withdrawn] motivation: { type: string } cv_url: { type: string, format: uri } is_spontaneous: { type: boolean } date: { type: string, format: date-time } Article: type: object properties: id: { type: integer } title: { type: string } slug: { type: string } status: { type: string, enum: [draft, pending, publish] } date: { type: string, format: date-time } meta: type: object properties: cover_image: { type: string, format: uri } excerpt: { type: string } category: { type: string } author_name: { type: string } author_photo: { type: string, format: uri } reading_time: { type: integer } tags: type: array items: { type: string } published_date: { type: string, format: date-time } related_establishment_id: { type: integer } content: { type: string, description: 'Corps HTML de l''article.' } ArticleInput: type: object required: [title, content] properties: title: { type: string } content: { type: string } meta: type: object properties: cover_image: { type: string, format: uri } excerpt: { type: string } category: { type: string } author_name: { type: string } author_photo: { type: string, format: uri } reading_time: { type: integer } tags: type: array items: { type: string } MaisonMember: type: object properties: user_id: { type: integer } email: { type: string, format: email } first_name: { type: string } last_name: { type: string } role: { type: string, enum: [owner, editor, viewer] } status: { type: string, enum: [active, invited] } invited_at: { type: string, format: date-time } joined_at: { type: string, format: date-time, nullable: true } UploadedMedia: type: object properties: id: { type: integer } file_url: { type: string, format: uri } thumbnail: { type: string, format: uri, nullable: true } mime_type: { type: string } filename: { type: string } date: { type: string, format: date-time }