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-fetchAvec un fichier .env à la racine :
DIAMOND_BASE=https://api.diamondcareers.fr/diamond/v1
DIAMOND_EMAIL=integration@votre-etablissement.com
DIAMOND_PASSWORD=VotreMotDePasseTechniqueClient 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.