Introduction
Oracle ERP Cloud, ou Fusion Cloud ERP, est la suite ERP leader pour les entreprises gérant des flux financiers complexes, RH et supply chain. En 2026, ses REST APIs (version 11.13.18.05+) permettent des intégrations natives sans middleware lourd, idéal pour des microservices ou automatisations custom. Pourquoi c'est crucial ? 80% des déploiements ERP modernes exigent des APIs pour synchroniser avec des CRM comme Salesforce ou des outils BI. Ce tutoriel expert vous guide pas à pas : de l'authentification OAuth 2.0 au traitement de volumes massifs avec pagination et idempotence. À la fin, vous déployez des scripts Node.js production-ready qui lisent/écrivent des factures, balances comptables et plus. Gain temps : 50% sur vos intégrations manuelles. Préparez-vous à scaler vos opérations ERP cloud.
Prérequis
- Compte Oracle ERP Cloud avec rôle Financial Application Administrator et OAuth Client configuré.
- Node.js 20+ et npm installés.
- Variables d'environnement :
ERP_HOST,CLIENT_ID,CLIENT_SECRET,USERNAME,PASSWORD(obtenus via Oracle Identity Cloud Service). - Outil comme Postman pour tester (optionnel).
- Connaissances avancées en TypeScript, async/await et HTTP status codes.
Initialiser le projet Node.js
#!/bin/bash
npm init -y
npm install axios dotenv typescript ts-node @types/node
npm install -D @types/axios
mkdir src
echo '{
"compilerOptions": {
"target": "ES2022",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
}
}' > tsconfig.json
cat > .env << EOF
ERP_HOST=https://your-erp-instance.oraclecloud.com
CLIENT_ID=your_client_id
CLIENT_SECRET=your_client_secret
USERNAME=your_username
PASSWORD=your_password
EOF
cat > src/index.ts << 'EOF'
console.log('Projet prêt pour Oracle ERP Cloud');
EOF
chmod +x setup.shCe script bash initialise un projet Node.js avec TypeScript, installe axios pour les appels HTTP et dotenv pour les secrets. Il crée un tsconfig.json strict et un .env template. Exécutez ./setup.sh puis source .env pour charger les vars. Piège : Vérifiez les permissions Oracle pour éviter 403 Forbidden dès le départ.
Comprendre l'authentification OAuth 2.0
Oracle ERP Cloud utilise OAuth 2.0 Resource Owner Password Credentials (ROPC) pour les scripts server-side, ou Client Credentials pour services. Analogie : comme un badge magnétique d'entreprise, le token JWT (valide 3600s) porte scopes comme urn:opc:idm:__mysc__ + https://your-erp/fscmRestApi. Rafraîchissez-le via refresh_token pour éviter downtime. Configurez votre OAuth Client dans Setup & Maintenance > Manage OAuth Clients.
Obtenir et utiliser le token OAuth
import axios from 'axios';
import * as dotenv from 'dotenv';
dotenv.config();
const ERP_HOST = process.env.ERP_HOST!;
const CLIENT_ID = process.env.CLIENT_ID!;
const CLIENT_SECRET = process.env.CLIENT_SECRET!;
const USERNAME = process.env.USERNAME!;
const PASSWORD = process.env.PASSWORD!;
async function getToken(): Promise<string> {
const tokenUrl = `${ERP_HOST}/ic/api/integration/v1/token`;
const response = await axios.post(tokenUrl, new URLSearchParams({
grant_type: 'password',
username: USERNAME,
password: PASSWORD,
scope: `${CLIENT_ID}/your-erp-instance/*`
}), {
auth: { username: CLIENT_ID, password: CLIENT_SECRET },
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
});
return response.data.access_token;
}
(async () => {
try {
const token = await getToken();
console.log('Token obtenu:', token.substring(0, 20) + '...');
} catch (error) {
console.error('Erreur auth:', error.response?.data || error.message);
}
})();Ce script récupère un bearer token via ROPC flow, essentiel pour tous les appels API. Utilisez axios pour la robustesse. Piège : Scope mal formé cause 401 ; testez d'abord en Postman. Token expire vite : implémentez caching Redis en prod.
Lister les factures avec finder
import axios from 'axios';
import * as dotenv from 'dotenv';
dotenv.config();
const ERP_HOST = process.env.ERP_HOST!;
async function getToken(): Promise<string> {
// Copié de auth.ts
const tokenUrl = `${ERP_HOST}/ic/api/integration/v1/token`;
const response = await axios.post(tokenUrl, new URLSearchParams({
grant_type: 'password',
username: process.env.USERNAME!,
password: process.env.PASSWORD!,
scope: `${process.env.CLIENT_ID!}/your-erp-instance/*`
}), {
auth: { username: process.env.CLIENT_ID!, password: process.env.CLIENT_SECRET! },
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
});
return response.data.access_token;
}
async function listInvoices(token: string, limit: number = 25) {
const url = `${ERP_HOST}/fscmRestApi/resources/11.13.18.05/invoices?q=InvoiceStatusCD=AVAILABLE`;
const response = await axios.get(url, {
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
'REST-Framework-Version': '4'
},
params: { limit, orderBy: 'InvoiceId:desc', finder: 'FindPayablesInvoices' }
});
console.log('Factures:', response.data);
return response.data;
}
(async () => {
const token = await getToken();
await listInvoices(token);
})();Appel GET avec qparam pour filtrer (statut AVAILABLE) et finder pour queries complexes. Limite évite overload. Piège : Sans 'REST-Framework-Version', réponses vides. Utilisez fields=InvoiceId,InvoiceAmount pour optimiser bandwidth.
Gérer la création et mise à jour
POST pour créer : Payload JSON valide contre schéma ERP (use /descriptors). PATCH pour update partiel (idempotent). Analogie : comme upsert en DB, vérifiez existence via GET UUID. Headers obligatoires : If-Match:* pour concurrency.
Créer une nouvelle facture
import axios from 'axios';
import * as dotenv from 'dotenv';
dotenv.config();
// Fonctions getToken et listInvoices copiées/imports possibles
const ERP_HOST = process.env.ERP_HOST!;
async function getToken(): Promise<string> {
// Même impl que précédemment
const tokenUrl = `${ERP_HOST}/ic/api/integration/v1/token`;
// ... (code identique à auth.ts)
return 'token-placeholder'; // Remplacez par code complet
}
async function createInvoice(token: string) {
const url = `${ERP_HOST}/fscmRestApi/resources/11.13.18.05/invoices`;
const payload = {
InvoiceAmount: 1500.00,
InvoiceDate: '2026-01-15',
InvoiceNum: 'INV-2026-001',
SupplierId: 300000001, // UUID d'un supplier existant
InvoiceStatusCD: 'AVAILABLE',
PayGroupName: 'DEFAULT'
};
const response = await axios.post(url, payload, {
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/vnd.oracle.adf.resourceitem+json',
'REST-Framework-Version': '4',
'If-Match': '*'
}
});
console.log('Facture créée:', response.data);
return response.data.InvoiceId;
}
(async () => {
const token = await getToken();
await createInvoice(token);
})();Payload minimal mais valide ; SupplierId doit exister (query d'abord). Content-Type ADF pour nesting. Piège : Montants en devise base, sinon conversion auto échoue. Retournez InvoiceId pour chaining.
Mettre à jour et paginer
import axios from 'axios';
import * as dotenv from 'dotenv';
dotenv.config();
const ERP_HOST = process.env.ERP_HOST!;
// getToken comme avant
async function updateInvoice(token: string, invoiceId: string) {
const url = `${ERP_HOST}/fscmRestApi/resources/11.13.18.05/invoices/${invoiceId}`;
const response = await axios.get(url, {
headers: { Authorization: `Bearer ${token}`, 'REST-Framework-Version': '4' }
});
const etag = response.headers['etag'];
await axios.patch(url, { InvoiceAmount: 2000.00 }, {
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/vnd.oracle.adf.resourceitem+json',
'REST-Framework-Version': '4',
'If-Match': etag
}
});
console.log('Facture mise à jour');
}
async function paginateInvoices(token: string, offset: number = 0, limit: number = 25) {
let allInvoices: any[] = [];
let hasMore = true;
while (hasMore) {
const url = `${ERP_HOST}/fscmRestApi/resources/11.13.18.05/invoices?limit=${limit}&offset=${offset}`;
const res = await axios.get(url, { headers: { Authorization: `Bearer ${token}`, 'REST-Framework-Version': '4' } });
allInvoices.push(...res.data.items);
hasMore = res.data.hasMore;
offset += limit;
}
console.log(`Total factures: ${allInvoices.length}`);
return allInvoices;
}
(async () => {
const token = await getToken();
await paginateInvoices(token);
// await updateInvoice(token, '300000123456789');
})();PATCH utilise ETag pour optimistic locking (anti-race). Pagination boucle sur offset/limit/hasMore, critique pour >10k records. Piège : ETag manquant = 412 Precondition Failed ; toujours GET d'abord.
Gestion d'erreurs globale et retry
import axios, { AxiosError } from 'axios';
import * as dotenv from 'dotenv';
dotenv.config();
class ErpClient {
private token: string;
private host: string;
constructor() {
this.host = process.env.ERP_HOST!;
}
private async getToken(): Promise<void> {
// Impl getToken
this.token = 'token';
}
async request(method: string, endpoint: string, data?: any, retries: number = 3): Promise<any> {
await this.getToken();
for (let i = 0; i < retries; i++) {
try {
const response = await axios({
method,
url: `${this.host}${endpoint}`,
data,
headers: {
Authorization: `Bearer ${this.token}`,
'Content-Type': 'application/vnd.oracle.adf.resourceitem+json',
'REST-Framework-Version': '4',
'If-Match': '*'
}
});
return response.data;
} catch (error) {
const err = error as AxiosError;
if (err.response?.status === 401) {
await this.getToken(); // Retry token
continue;
}
if (err.response?.status >= 500 && i < retries - 1) {
await new Promise(r => setTimeout(r, 1000 * (i + 1))); // Exponential backoff
continue;
}
throw new Error(`ERP Error ${err.response?.status}: ${err.response?.data?.title || err.message}`);
}
}
}
async getInvoices() {
return this.request('GET', '/fscmRestApi/resources/11.13.18.05/invoices');
}
}
const client = new ErpClient();
(async () => {
try {
console.log(await client.getInvoices());
} catch (error) {
console.error(error);
}
})();Wrapper classe avec retry exponentiel, token refresh auto et erreurs typées. Gère 429 RateLimit, 5xx server. Piège : Sans backoff, ban IP ; loggez OData errors pour debug (title/detail).
Bonnes pratiques
- Cachez tokens avec Redis (TTL 3500s) et refresh proactif.
- Validez payloads contre /descriptors endpoint pour schemas.
- Utilisez qparams avancés (ex: q=InvoiceDate>='2026-01-01') et expand=lines pour nested data.
- Implémentez idempotence via InvoiceNum unique.
- Monitorez avec Oracle Logging Analytics ; rate limit à 1000/min.
Erreurs courantes à éviter
- 401/403 : Scope OAuth incomplet ou rôle manquant (ajoutez FSM_APPLICATION_ADMIN).
- 400 Bad Request : Payload sans champs obligatoires (use /childDescriptors).
- Pagination infinie : Oublie hasMore=false après last page.
- ETag mismatch : Toujours refresh ETag avant PATCH en boucle.
Pour aller plus loin
Explorez Oracle Integration Cloud (OIC) pour low-code, Visual Builder pour UIs custom, ou Redwood UX extensions. Consultez la doc officielle REST APIs. Passez au niveau pro avec nos formations Learni Group sur Oracle Cloud certifications.