Diamond Careers
APIExemples

Exemples Node.js

Exemples Node.js / TypeScript : authentification, lecture, écriture, téléversement, retry.

Cette page propose des exemples Node.js / TypeScript pour les principaux cas d'usage : authentification, lecture, écriture, téléversement de fichiers, gestion des erreurs.

Les exemples utilisent le fetch natif (Node 18+) ou node-fetch pour les versions antérieures, et TypeScript pour la sécurité de type.

Installation

npm install dotenv
# ou pour Node < 18
npm install node-fetch

Avec un fichier .env à la racine :

DIAMOND_BASE=https://api.diamondcareers.fr/diamond/v1
DIAMOND_EMAIL=integration@votre-etablissement.com
DIAMOND_PASSWORD=VotreMotDePasseTechnique

Client minimal

import 'dotenv/config';

const DIAMOND_BASE = process.env.DIAMOND_BASE!;
let cachedToken: string | null = null;

async function getToken(): Promise<string> {
  if (cachedToken) return cachedToken;
  const res = await fetch(`${DIAMOND_BASE}/auth/login`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      email: process.env.DIAMOND_EMAIL,
      password: process.env.DIAMOND_PASSWORD
    })
  });
  if (!res.ok) throw new Error(`Auth failed: ${res.status}`);
  const data = await res.json() as { token: string };
  cachedToken = data.token;
  return cachedToken;
}

async function api<T>(path: string, init: RequestInit = {}): Promise<T> {
  const token = await getToken();
  const res = await fetch(`${DIAMOND_BASE}${path}`, {
    ...init,
    headers: {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json',
      ...init.headers
    }
  });
  if (!res.ok) {
    const errorBody = await res.json().catch(() => ({ message: res.statusText }));
    const err = new Error(errorBody.message || `HTTP ${res.status}`) as Error & { code?: string; status?: number };
    err.code = errorBody.code;
    err.status = res.status;
    throw err;
  }
  if (res.status === 204) return undefined as unknown as T;
  return res.json() as Promise<T>;
}

Lister vos offres publiées

interface Offer {
  id: number;
  title: string;
  status: string;
  meta: {
    city: string;
    contract_type: string;
  };
}

const offers = await api<Offer[]>('/offres?status=publish&per_page=100');
console.log(`${offers.length} offres publiées`);
offers.forEach(o => console.log(`- ${o.title} (${o.meta.city}) — ${o.meta.contract_type}`));

Synchronisation par delta

async function syncOffersIncremental(since: Date) {
  const sinceIso = since.toISOString();
  let page = 1;
  let processed = 0;

  while (true) {
    const offers = await api<Offer[]>(
      `/offres?filter[modified_after]=${encodeURIComponent(sinceIso)}&per_page=100&page=${page}`
    );
    if (offers.length === 0) break;
    for (const offer of offers) {
      await processOffer(offer);
      processed++;
    }
    page++;
  }

  return processed;
}

Créer une offre

const newOffer = await api<Offer>('/offres', {
  method: 'POST',
  body: JSON.stringify({
    title: 'Chef de partie pâtisserie — restaurant gastronomique',
    meta: {
      contract_type: 'cdi',
      city: 'Paris',
      country: 'France',
      salary_min: 32000,
      salary_max: 38000,
      salary_display: '32 000 € – 38 000 €',
      description: '<p>Au sein d une brigade de 18 personnes...</p>',
      missions: '<ul><li>Encadrer 4 commis</li></ul>',
      profile_required: '<p>3 ans d expérience minimum.</p>',
      start_date: '2026-09-01'
    }
  })
});
console.log('Offre créée :', newOffer.id);

Faire évoluer le statut d'une candidature

interface Application {
  id: number;
  status: string;
}

async function setApplicationStatus(id: number, status: string, comment?: string) {
  return api<Application>(`/candidatures/${id}/status`, {
    method: 'PATCH',
    body: JSON.stringify({ status, ...(comment ? { comment } : {}) })
  });
}

await setApplicationStatus(18472, 'interview', 'Entretien prévu le 18 mai à 14 h.');

Téléverser un visuel d'offre

import { readFileSync } from 'fs';

async function uploadImage(filePath: string, mime = 'image/jpeg') {
  const token = await getToken();
  const fileBuffer = readFileSync(filePath);
  const blob = new Blob([fileBuffer], { type: mime });

  const formData = new FormData();
  formData.append('file', blob, 'visuel.jpg');

  const res = await fetch(`${DIAMOND_BASE}/upload`, {
    method: 'POST',
    headers: { 'Authorization': `Bearer ${token}` },
    body: formData
  });
  if (!res.ok) throw new Error(`Upload failed: ${res.status}`);
  return res.json() as Promise<{ id: number; file_url: string }>;
}

const media = await uploadImage('./visuel-offre.jpg');
console.log('URL publique :', media.file_url);

Récupérer un CV (fichier privé)

import { writeFileSync } from 'fs';

async function downloadSecureFile(path: string, dest: string) {
  const token = await getToken();
  const res = await fetch(`${DIAMOND_BASE}/secure-file?path=${encodeURIComponent(path)}`, {
    headers: { 'Authorization': `Bearer ${token}` }
  });
  if (!res.ok) throw new Error(`Download failed: ${res.status}`);
  const buffer = Buffer.from(await res.arrayBuffer());
  writeFileSync(dest, buffer);
}

await downloadSecureFile('cv/9281/abcd-1234.pdf', './telecharge.pdf');

Gestion des erreurs et retry

async function apiWithRetry<T>(
  path: string,
  init: RequestInit = {},
  maxRetries = 3
): Promise<T> {
  let lastError: Error | null = null;

  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await api<T>(path, init);
    } catch (err) {
      const e = err as Error & { status?: number };
      lastError = e;

      // 4xx (sauf 429) — pas de retry
      if (e.status && e.status >= 400 && e.status < 500 && e.status !== 429) {
        throw e;
      }

      const delay = Math.min(30000, 1000 * Math.pow(2, attempt));
      console.warn(`Tentative ${attempt + 1} échouée (${e.status}), retry dans ${delay}ms`);
      await new Promise(r => setTimeout(r, delay));
    }
  }

  throw lastError ?? new Error('Retry exhausted');
}

Pagination paresseuse avec generator

async function* iterateAll<T>(path: string, perPage = 100): AsyncGenerator<T> {
  let page = 1;
  while (true) {
    const sep = path.includes('?') ? '&' : '?';
    const items = await api<T[]>(`${path}${sep}per_page=${perPage}&page=${page}`);
    if (items.length === 0) return;
    for (const item of items) yield item;
    if (items.length < perPage) return;
    page++;
  }
}

// Utilisation
for await (const offer of iterateAll<Offer>('/offres?status=publish')) {
  await processOffer(offer);
}

Bonnes pratiques

  • Cachez le jeton côté votre intégration et renouvelez-le un peu avant son expiration.
  • Loguez chaque appel avec timestamp, méthode, chemin, statut, durée.
  • Testez en staging avant la production. Notre équipe peut fournir un environnement de test sur demande.
  • Surveillez les erreurs. Mettez une alerte si le taux d'erreur dépasse un seuil (par exemple 5 % sur 15 minutes).
  • Logique idempotente. Une même offre peut revenir entre deux passages de synchronisation ; appliquer deux fois la même mise à jour ne doit pas créer d'incohérence.

On this page