Quickstart
De zéro à votre premier appel API en moins de 5 minutes.
Prérequis
- Un compte QualiForma actif avec votre slug tenant
- Python 3.9+ avec pip install requests, ou Node.js 18+, ou curl
1
Étape 1 : Obtenir un access token
Authentifiez-vous avec vos identifiants pour recevoir un JWT.
POST /auth/login · Python
import requests
# 1. S'authentifier
response = requests.post(
'https://api.qualiforma.site/api/v1/auth/login',
headers={'X-Tenant-ID': 'votre-tenant'},
json={
'email': 'admin@example.com',
'password': 'votre-mot-de-passe'
}
)
data = response.json()['data']
access_token = data['accessToken']
refresh_token = data['refreshToken']
print(f"Access token: {access_token[:40]}...")
# Access token valide 1h — le rafraîchir avec refreshTokenPOST /auth/login · cURL / Shell
curl -X POST https://api.qualiforma.site/api/v1/auth/login \
-H 'Content-Type: application/json' \
-H 'X-Tenant-ID: votre-tenant' \
-d '{
"email": "admin@example.com",
"password": "votre-mot-de-passe"
}'
# Réponse attendue :
# {
# "data": {
# "accessToken": "eyJhbGciOiJIUzI1NiJ9...",
# "refreshToken": "eyJhbGciOiJIUzI1NiJ9...",
# "expiresIn": 3600
# },
# "meta": {
# "timestamp": "2026-01-01T12:00:00.000Z",
# "requestId": "req_abc123"
# }
# }POST /auth/login · TypeScript
// Typages
interface AuthResponse {
data: {
accessToken: string;
refreshToken: string;
expiresIn: number;
};
meta: { timestamp: string; requestId: string };
}
async function login(email: string, password: string, tenantId: string) {
const response = await fetch('https://api.qualiforma.site/api/v1/auth/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Tenant-ID': tenantId,
},
body: JSON.stringify({ email, password }),
});
if (!response.ok) {
throw new Error(`Login échoué : ${response.status}`);
}
const json: AuthResponse = await response.json();
return json.data;
}
const tokens = await login('admin@example.com', 'mot-de-passe', 'votre-tenant');
console.log('Access token:', tokens.accessToken.slice(0, 40) + '...');POST /auth/login · PHP
<?php
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
$client = new Client(['base_uri' => 'https://api.qualiforma.site/api/v1/']);
try {
$response = $client->post('auth/login', [
'headers' => [
'X-Tenant-ID' => 'votre-tenant',
'Content-Type' => 'application/json',
],
'json' => [
'email' => 'admin@example.com',
'password' => 'votre-mot-de-passe',
],
]);
$body = json_decode($response->getBody()->getContents(), true);
$accessToken = $body['data']['accessToken'];
$refreshToken = $body['data']['refreshToken'];
echo "Access token: " . substr($accessToken, 0, 40) . "...\n";
// Access token valide 1h — le rafraichir avec refreshToken
} catch (GuzzleException $e) {
fwrite(STDERR, "Login failed: " . $e->getMessage() . PHP_EOL);
}Note : Le token expire après 1h. Utilisez refreshToken pour en obtenir un nouveau sans re-login.
2
Étape 2 : Récupérer votre profil
Le login ne retourne que les tokens — appelez /auth/me pour le profil utilisateur.
GET /auth/me · Python
# 2. Récupérer le profil utilisateur
me_response = requests.get(
'https://api.qualiforma.site/api/v1/auth/me',
headers={
'Authorization': f'Bearer {access_token}',
'X-Tenant-ID': 'votre-tenant'
}
)
me = me_response.json()['data']
print(f"Connecté en tant que : {me['firstName']} {me['lastName']}")
print(f"Email : {me['email']}")
print(f"Rôle : {me['role']}")GET /auth/me · cURL / Shell
# Remplacer YOUR_TOKEN par l'accessToken obtenu à l'étape 1
TOKEN="eyJhbGciOiJIUzI1NiJ9..."
curl https://api.qualiforma.site/api/v1/auth/me \
-H "Authorization: Bearer $TOKEN" \
-H 'X-Tenant-ID: votre-tenant'
# Réponse :
# {
# "data": {
# "id": "usr_...",
# "email": "admin@example.com",
# "firstName": "Marie",
# "lastName": "Martin",
# "role": "ADMIN"
# }
# }GET /auth/me · TypeScript
async function getMe(accessToken: string, tenantId: string) {
const response = await fetch('https://api.qualiforma.site/api/v1/auth/me', {
headers: {
'Authorization': `Bearer ${accessToken}`,
'X-Tenant-ID': tenantId,
},
});
if (!response.ok) throw new Error(`Erreur ${response.status}`);
const { data } = await response.json();
return data;
}
const profile = await getMe(tokens.accessToken, 'votre-tenant');
console.log(`Bonjour ${profile.firstName} (${profile.role})`);GET /auth/me · PHP
<?php
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
$client = new Client(['base_uri' => 'https://api.qualiforma.site/api/v1/']);
try {
$response = $client->get('auth/me', [
'headers' => [
'Authorization' => 'Bearer ' . $accessToken,
'X-Tenant-ID' => 'votre-tenant',
],
]);
$body = json_decode($response->getBody()->getContents(), true);
$me = $body['data'];
echo "Connecte en tant que : {$me['firstName']} {$me['lastName']}\n";
echo "Email : {$me['email']}\n";
echo "Role : {$me['role']}\n";
} catch (GuzzleException $e) {
fwrite(STDERR, "Erreur: " . $e->getMessage() . PHP_EOL);
}3
Étape 3 : Lister les formations
Récupérez le catalogue avec pagination, filtres statut, recherche texte.
GET /courses · Python
# 3. Lister les formations
courses_response = requests.get(
'https://api.qualiforma.site/api/v1/courses',
headers={
'Authorization': f'Bearer {access_token}',
'X-Tenant-ID': 'votre-tenant'
},
params={
'page': 1,
'perPage': 10,
'status': 'PUBLISHED' # optionnel
}
)
result = courses_response.json()
courses = result['data']
for course in courses:
print(f"- {course['title']} (id: {course['id']})")GET /courses · cURL / Shell
TOKEN="eyJhbGci..."
curl "https://api.qualiforma.site/api/v1/courses?page=1&perPage=10" \
-H "Authorization: Bearer $TOKEN" \
-H 'X-Tenant-ID: votre-tenant'
# Réponse :
# {
# "data": [
# {
# "id": "crs_abc123",
# "title": "Excel Avancé pour RH",
# "status": "PUBLISHED",
# "price": 49900,
# "currency": "EUR"
# }
# ],
# "meta": {
# "total": 24,
# "page": 1,
# "perPage": 10
# }
# }GET /courses · TypeScript
interface Course {
id: string;
title: string;
status: 'DRAFT' | 'PUBLISHED' | 'ARCHIVED';
price: number;
currency: string;
}
async function listCourses(
accessToken: string,
tenantId: string,
params?: { page?: number; perPage?: number; status?: string }
): Promise<{ data: Course[]; meta: { total: number } }> {
const searchParams = new URLSearchParams({
page: String(params?.page ?? 1),
perPage: String(params?.perPage ?? 10),
...(params?.status ? { status: params.status } : {}),
});
const response = await fetch(
`https://api.qualiforma.site/api/v1/courses?${searchParams}`,
{
headers: {
'Authorization': `Bearer ${accessToken}`,
'X-Tenant-ID': tenantId,
},
}
);
if (!response.ok) throw new Error(`Erreur ${response.status}`);
return response.json();
}
const { data: courses, meta } = await listCourses(tokens.accessToken, 'votre-tenant');
console.log(`${meta.total} formations trouvées :`);
courses.forEach((c) => console.log(` - ${c.title} (${c.id})`));GET /courses · PHP
<?php
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
$client = new Client(['base_uri' => 'https://api.qualiforma.site/api/v1/']);
try {
$response = $client->get('courses', [
'headers' => [
'Authorization' => 'Bearer ' . $accessToken,
'X-Tenant-ID' => 'votre-tenant',
],
'query' => [
'page' => 1,
'perPage' => 10,
'status' => 'PUBLISHED', // optionnel
],
]);
$result = json_decode($response->getBody()->getContents(), true);
$courses = $result['data'];
foreach ($courses as $course) {
echo "- {$course['title']} (id: {$course['id']})\n";
}
} catch (GuzzleException $e) {
fwrite(STDERR, "Erreur: " . $e->getMessage() . PHP_EOL);
}Note : Le champ price est en centimes (49900 = 499,00 €).
4
Étape 4 : Inscrire un apprenant
Créez une inscription directement (FREE) ou via un flux de paiement.
POST /enrollments · Python
# 4. Inscrire un apprenant à une formation
enroll_response = requests.post(
'https://api.qualiforma.site/api/v1/enrollments',
headers={
'Authorization': f'Bearer {access_token}',
'X-Tenant-ID': 'votre-tenant',
'Content-Type': 'application/json'
},
json={
'courseId': 'crs_abc123', # ID de la formation
'userId': 'usr_xyz789', # ID de l'apprenant
'paymentMethod': 'FREE' # FREE | VIVA_ISV | STRIPE
}
)
enrollment = enroll_response.json()['data']
print(f"Inscription créée : {enrollment['id']}")
print(f"Statut : {enrollment['status']}") # ACTIVEPOST /enrollments · cURL / Shell
TOKEN="eyJhbGci..."
curl -X POST https://api.qualiforma.site/api/v1/enrollments \
-H "Authorization: Bearer $TOKEN" \
-H 'X-Tenant-ID: votre-tenant' \
-H 'Content-Type: application/json' \
-d '{
"courseId": "crs_abc123",
"userId": "usr_xyz789",
"paymentMethod": "FREE"
}'
# Réponse 201 :
# {
# "data": {
# "id": "enr_def456",
# "status": "ACTIVE",
# "courseId": "crs_abc123",
# "userId": "usr_xyz789",
# "enrolledAt": "2026-01-01T12:30:00Z"
# }
# }POST /enrollments · TypeScript
interface EnrollmentInput {
courseId: string;
userId: string;
paymentMethod: 'FREE' | 'VIVA_ISV' | 'STRIPE';
}
async function enrollUser(
input: EnrollmentInput,
accessToken: string,
tenantId: string
) {
const response = await fetch('https://api.qualiforma.site/api/v1/enrollments', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${accessToken}`,
'X-Tenant-ID': tenantId,
},
body: JSON.stringify(input),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.message ?? `Erreur ${response.status}`);
}
const { data } = await response.json();
return data;
}
const enrollment = await enrollUser(
{ courseId: 'crs_abc123', userId: 'usr_xyz789', paymentMethod: 'FREE' },
tokens.accessToken,
'votre-tenant'
);
console.log(`Inscription ${enrollment.id} créée — statut : ${enrollment.status}`);POST /enrollments · PHP
<?php
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
$client = new Client(['base_uri' => 'https://api.qualiforma.site/api/v1/']);
try {
$response = $client->post('enrollments', [
'headers' => [
'Authorization' => 'Bearer ' . $accessToken,
'X-Tenant-ID' => 'votre-tenant',
'Content-Type' => 'application/json',
],
'json' => [
'courseId' => 'crs_abc123', // ID de la formation
'userId' => 'usr_xyz789', // ID de l'apprenant
'paymentMethod' => 'FREE', // FREE | VIVA_ISV | STRIPE
],
]);
$enrollment = json_decode($response->getBody()->getContents(), true)['data'];
echo "Inscription creee : {$enrollment['id']}\n";
echo "Statut : {$enrollment['status']}\n";
} catch (GuzzleException $e) {
fwrite(STDERR, "Erreur: " . $e->getMessage() . PHP_EOL);
}Note : Requiert le rôle ADMIN ou CREATOR. LEARNER ne peut s'inscrire que via le formulaire web.