658 lines
12 KiB
Markdown
658 lines
12 KiB
Markdown
|
|
# Frontend Integration Guide — Veza Backend API
|
||
|
|
|
||
|
|
**Version**: 1.2.0
|
||
|
|
**Date**: 2025-01-27
|
||
|
|
**Base URL**: `http://localhost:8080/api/v1` (configurable via `VITE_API_URL`)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Table des Matières
|
||
|
|
|
||
|
|
1. [Configuration](#configuration)
|
||
|
|
2. [Authentification](#authentification)
|
||
|
|
3. [Format des Réponses](#format-des-réponses)
|
||
|
|
4. [Gestion des Erreurs](#gestion-des-erreurs)
|
||
|
|
5. [Codes d'Erreur](#codes-derreur)
|
||
|
|
6. [Exemples de Requêtes](#exemples-de-requêtes)
|
||
|
|
7. [Health Checks](#health-checks)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Configuration
|
||
|
|
|
||
|
|
### Variables d'Environnement Frontend
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# .env.local ou .env.production
|
||
|
|
VITE_API_URL=http://localhost:8080/api/v1
|
||
|
|
VITE_WS_URL=ws://localhost:8081/ws
|
||
|
|
VITE_STREAM_URL=ws://localhost:8082/stream
|
||
|
|
```
|
||
|
|
|
||
|
|
### Headers Requis
|
||
|
|
|
||
|
|
Toutes les requêtes authentifiées doivent inclure :
|
||
|
|
|
||
|
|
```javascript
|
||
|
|
{
|
||
|
|
"Authorization": "Bearer <token>",
|
||
|
|
"Content-Type": "application/json"
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Authentification
|
||
|
|
|
||
|
|
### Format du Token JWT
|
||
|
|
|
||
|
|
Le token JWT est fourni dans le header `Authorization` :
|
||
|
|
|
||
|
|
```
|
||
|
|
Authorization: Bearer <token>
|
||
|
|
```
|
||
|
|
|
||
|
|
### Endpoints d'Authentification
|
||
|
|
|
||
|
|
#### POST `/auth/login`
|
||
|
|
|
||
|
|
**Request** :
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"email": "user@example.com",
|
||
|
|
"password": "password123",
|
||
|
|
"remember_me": false
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Response Success (200)** :
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"success": true,
|
||
|
|
"data": {
|
||
|
|
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||
|
|
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||
|
|
"expires_in": 3600,
|
||
|
|
"token_type": "Bearer",
|
||
|
|
"user": {
|
||
|
|
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||
|
|
"email": "user@example.com",
|
||
|
|
"username": "username",
|
||
|
|
"role": "user"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Response Error (401)** :
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"success": false,
|
||
|
|
"data": null,
|
||
|
|
"error": {
|
||
|
|
"code": 1000,
|
||
|
|
"message": "Invalid credentials",
|
||
|
|
"request_id": "req-123",
|
||
|
|
"timestamp": "2025-01-27T10:00:00Z"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
#### POST `/auth/register`
|
||
|
|
|
||
|
|
**Request** :
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"email": "user@example.com",
|
||
|
|
"password": "password123",
|
||
|
|
"username": "username"
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
#### POST `/auth/refresh`
|
||
|
|
|
||
|
|
**Request** :
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
#### GET `/auth/me`
|
||
|
|
|
||
|
|
**Headers** :
|
||
|
|
```
|
||
|
|
Authorization: Bearer <token>
|
||
|
|
```
|
||
|
|
|
||
|
|
**Response Success (200)** :
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"success": true,
|
||
|
|
"data": {
|
||
|
|
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||
|
|
"email": "user@example.com",
|
||
|
|
"username": "username",
|
||
|
|
"role": "user",
|
||
|
|
"created_at": "2025-01-27T10:00:00Z"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Format des Réponses
|
||
|
|
|
||
|
|
### Réponse Succès
|
||
|
|
|
||
|
|
Toutes les réponses de succès suivent ce format :
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"success": true,
|
||
|
|
"data": { ... },
|
||
|
|
"message": "Optional success message"
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Exemples** :
|
||
|
|
|
||
|
|
- **GET** `/tracks/:id` :
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"success": true,
|
||
|
|
"data": {
|
||
|
|
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||
|
|
"title": "Track Title",
|
||
|
|
"artist": "Artist Name",
|
||
|
|
"duration": 180.5,
|
||
|
|
"status": "ready"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
- **POST** `/tracks` (201 Created) :
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"success": true,
|
||
|
|
"data": {
|
||
|
|
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||
|
|
"title": "New Track",
|
||
|
|
"status": "processing"
|
||
|
|
},
|
||
|
|
"message": "Track uploaded successfully"
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Réponse Erreur
|
||
|
|
|
||
|
|
Toutes les réponses d'erreur suivent ce format standardisé :
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"success": false,
|
||
|
|
"data": null,
|
||
|
|
"error": {
|
||
|
|
"code": 2000,
|
||
|
|
"message": "Validation failed",
|
||
|
|
"details": [
|
||
|
|
{
|
||
|
|
"field": "email",
|
||
|
|
"message": "Email is required"
|
||
|
|
}
|
||
|
|
],
|
||
|
|
"request_id": "req-123",
|
||
|
|
"timestamp": "2025-01-27T10:00:00Z",
|
||
|
|
"context": {
|
||
|
|
"user_id": "550e8400-e29b-41d4-a716-446655440000"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Champs** :
|
||
|
|
- `code` : Code d'erreur numérique (voir [Codes d'Erreur](#codes-derreur))
|
||
|
|
- `message` : Message d'erreur lisible
|
||
|
|
- `details` : Détails de validation (optionnel, pour erreurs 400)
|
||
|
|
- `request_id` : ID de requête pour corrélation des logs (optionnel)
|
||
|
|
- `timestamp` : Timestamp ISO 8601
|
||
|
|
- `context` : Contexte additionnel (optionnel)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Gestion des Erreurs
|
||
|
|
|
||
|
|
### TypeScript Interface
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
interface APIError {
|
||
|
|
code: number;
|
||
|
|
message: string;
|
||
|
|
details?: Array<{
|
||
|
|
field: string;
|
||
|
|
message: string;
|
||
|
|
}>;
|
||
|
|
request_id?: string;
|
||
|
|
timestamp: string;
|
||
|
|
context?: Record<string, any>;
|
||
|
|
}
|
||
|
|
|
||
|
|
interface APIResponse<T> {
|
||
|
|
success: boolean;
|
||
|
|
data: T | null;
|
||
|
|
error?: APIError;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Helper Function
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
async function apiRequest<T>(
|
||
|
|
endpoint: string,
|
||
|
|
options?: RequestInit
|
||
|
|
): Promise<T> {
|
||
|
|
const token = localStorage.getItem('access_token');
|
||
|
|
|
||
|
|
const response = await fetch(`${import.meta.env.VITE_API_URL}${endpoint}`, {
|
||
|
|
...options,
|
||
|
|
headers: {
|
||
|
|
'Content-Type': 'application/json',
|
||
|
|
...(token && { Authorization: `Bearer ${token}` }),
|
||
|
|
...options?.headers,
|
||
|
|
},
|
||
|
|
});
|
||
|
|
|
||
|
|
const data: APIResponse<T> = await response.json();
|
||
|
|
|
||
|
|
if (!data.success || data.error) {
|
||
|
|
throw new APIError(data.error!);
|
||
|
|
}
|
||
|
|
|
||
|
|
return data.data!;
|
||
|
|
}
|
||
|
|
|
||
|
|
class APIError extends Error {
|
||
|
|
constructor(public error: APIError) {
|
||
|
|
super(error.message);
|
||
|
|
this.name = 'APIError';
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Gestion des Codes HTTP
|
||
|
|
|
||
|
|
| Code HTTP | Signification | Action Frontend |
|
||
|
|
|-----------|---------------|-----------------|
|
||
|
|
| 200 | Succès | Traiter `data` |
|
||
|
|
| 201 | Créé | Traiter `data` |
|
||
|
|
| 400 | Bad Request | Afficher `error.message` + `error.details` |
|
||
|
|
| 401 | Unauthorized | Rediriger vers login, rafraîchir token |
|
||
|
|
| 403 | Forbidden | Afficher erreur, vérifier permissions |
|
||
|
|
| 404 | Not Found | Afficher "Ressource non trouvée" |
|
||
|
|
| 409 | Conflict | Afficher `error.message` |
|
||
|
|
| 422 | Unprocessable Entity | Afficher `error.message` + `error.details` |
|
||
|
|
| 429 | Too Many Requests | Afficher "Trop de requêtes", attendre |
|
||
|
|
| 500 | Internal Server Error | Logger `request_id`, afficher message générique |
|
||
|
|
| 502 | Bad Gateway | Afficher "Service temporairement indisponible" |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Codes d'Erreur
|
||
|
|
|
||
|
|
### Authentication & Authorization (1000-1999)
|
||
|
|
|
||
|
|
| Code | Message | HTTP Status |
|
||
|
|
|------|---------|-------------|
|
||
|
|
| 1000 | Invalid credentials | 401 |
|
||
|
|
| 1001 | Token expired | 401 |
|
||
|
|
| 1002 | Token invalid | 401 |
|
||
|
|
| 1003 | Forbidden | 403 |
|
||
|
|
| 1004 | Unauthorized | 401 |
|
||
|
|
|
||
|
|
### Validation (2000-2999)
|
||
|
|
|
||
|
|
| Code | Message | HTTP Status |
|
||
|
|
|------|---------|-------------|
|
||
|
|
| 2000 | Validation failed | 400 |
|
||
|
|
| 2001 | Required field | 400 |
|
||
|
|
| 2002 | Invalid format | 400 |
|
||
|
|
| 2003 | Out of range | 400 |
|
||
|
|
|
||
|
|
### Resource (3000-3999)
|
||
|
|
|
||
|
|
| Code | Message | HTTP Status |
|
||
|
|
|------|---------|-------------|
|
||
|
|
| 3000 | Not found | 404 |
|
||
|
|
| 3001 | Already exists | 409 |
|
||
|
|
| 3002 | Conflict | 409 |
|
||
|
|
|
||
|
|
### Business Logic (4000-4999)
|
||
|
|
|
||
|
|
| Code | Message | HTTP Status |
|
||
|
|
|------|---------|-------------|
|
||
|
|
| 4000 | Operation not allowed | 422 |
|
||
|
|
| 4005 | Quota exceeded | 403 |
|
||
|
|
|
||
|
|
### Rate Limiting (5000-5099)
|
||
|
|
|
||
|
|
| Code | Message | HTTP Status |
|
||
|
|
|------|---------|-------------|
|
||
|
|
| 5000 | Rate limit exceeded | 429 |
|
||
|
|
|
||
|
|
### Internal (9000-9999)
|
||
|
|
|
||
|
|
| Code | Message | HTTP Status |
|
||
|
|
|------|---------|-------------|
|
||
|
|
| 9000 | Internal error | 500 |
|
||
|
|
| 9001 | Database error | 500 |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Exemples de Requêtes
|
||
|
|
|
||
|
|
### Health Check
|
||
|
|
|
||
|
|
```bash
|
||
|
|
curl -X GET http://localhost:8080/api/v1/health
|
||
|
|
```
|
||
|
|
|
||
|
|
**Response** :
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"status": "ok"
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Login
|
||
|
|
|
||
|
|
```bash
|
||
|
|
curl -X POST http://localhost:8080/api/v1/auth/login \
|
||
|
|
-H "Content-Type: application/json" \
|
||
|
|
-d '{
|
||
|
|
"email": "user@example.com",
|
||
|
|
"password": "password123",
|
||
|
|
"remember_me": false
|
||
|
|
}'
|
||
|
|
```
|
||
|
|
|
||
|
|
**Response Success** :
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"success": true,
|
||
|
|
"data": {
|
||
|
|
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||
|
|
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||
|
|
"expires_in": 3600,
|
||
|
|
"token_type": "Bearer",
|
||
|
|
"user": {
|
||
|
|
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||
|
|
"email": "user@example.com",
|
||
|
|
"username": "username",
|
||
|
|
"role": "user"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Response Error (Invalid Credentials)** :
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"success": false,
|
||
|
|
"data": null,
|
||
|
|
"error": {
|
||
|
|
"code": 1000,
|
||
|
|
"message": "Invalid credentials",
|
||
|
|
"request_id": "req-123",
|
||
|
|
"timestamp": "2025-01-27T10:00:00Z"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Get User Profile
|
||
|
|
|
||
|
|
```bash
|
||
|
|
curl -X GET http://localhost:8080/api/v1/auth/me \
|
||
|
|
-H "Authorization: Bearer <token>"
|
||
|
|
```
|
||
|
|
|
||
|
|
**Response Success** :
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"success": true,
|
||
|
|
"data": {
|
||
|
|
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||
|
|
"email": "user@example.com",
|
||
|
|
"username": "username",
|
||
|
|
"role": "user",
|
||
|
|
"created_at": "2025-01-27T10:00:00Z"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Get Track
|
||
|
|
|
||
|
|
```bash
|
||
|
|
curl -X GET http://localhost:8080/api/v1/tracks/550e8400-e29b-41d4-a716-446655440000 \
|
||
|
|
-H "Authorization: Bearer <token>"
|
||
|
|
```
|
||
|
|
|
||
|
|
**Response Success** :
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"success": true,
|
||
|
|
"data": {
|
||
|
|
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||
|
|
"title": "Track Title",
|
||
|
|
"artist": "Artist Name",
|
||
|
|
"duration": 180.5,
|
||
|
|
"status": "ready",
|
||
|
|
"user_id": "550e8400-e29b-41d4-a716-446655440000",
|
||
|
|
"created_at": "2025-01-27T10:00:00Z"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Response Error (Not Found)** :
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"success": false,
|
||
|
|
"data": null,
|
||
|
|
"error": {
|
||
|
|
"code": 3000,
|
||
|
|
"message": "track not found",
|
||
|
|
"request_id": "req-123",
|
||
|
|
"timestamp": "2025-01-27T10:00:00Z"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Create Playlist
|
||
|
|
|
||
|
|
```bash
|
||
|
|
curl -X POST http://localhost:8080/api/v1/playlists \
|
||
|
|
-H "Authorization: Bearer <token>" \
|
||
|
|
-H "Content-Type: application/json" \
|
||
|
|
-d '{
|
||
|
|
"title": "My Playlist",
|
||
|
|
"description": "A great playlist",
|
||
|
|
"is_public": true
|
||
|
|
}'
|
||
|
|
```
|
||
|
|
|
||
|
|
**Response Success (201)** :
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"success": true,
|
||
|
|
"data": {
|
||
|
|
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||
|
|
"title": "My Playlist",
|
||
|
|
"description": "A great playlist",
|
||
|
|
"is_public": true,
|
||
|
|
"user_id": "550e8400-e29b-41d4-a716-446655440000",
|
||
|
|
"created_at": "2025-01-27T10:00:00Z"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Response Error (Validation)** :
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"success": false,
|
||
|
|
"data": null,
|
||
|
|
"error": {
|
||
|
|
"code": 2000,
|
||
|
|
"message": "Validation failed",
|
||
|
|
"details": [
|
||
|
|
{
|
||
|
|
"field": "title",
|
||
|
|
"message": "Title is required"
|
||
|
|
}
|
||
|
|
],
|
||
|
|
"request_id": "req-123",
|
||
|
|
"timestamp": "2025-01-27T10:00:00Z"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Health Checks
|
||
|
|
|
||
|
|
### GET `/health`
|
||
|
|
|
||
|
|
Health check simple (toujours 200 si serveur actif).
|
||
|
|
|
||
|
|
```bash
|
||
|
|
curl -X GET http://localhost:8080/api/v1/health
|
||
|
|
```
|
||
|
|
|
||
|
|
**Response** :
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"status": "ok"
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### GET `/readyz`
|
||
|
|
|
||
|
|
Readiness probe (vérifie DB, Redis, RabbitMQ).
|
||
|
|
|
||
|
|
```bash
|
||
|
|
curl -X GET http://localhost:8080/api/v1/readyz
|
||
|
|
```
|
||
|
|
|
||
|
|
**Response Success (200)** :
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"status": "ready",
|
||
|
|
"database": "ok",
|
||
|
|
"redis": "ok",
|
||
|
|
"rabbitmq": "ok"
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Response Degraded (200)** :
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"status": "degraded",
|
||
|
|
"database": "ok",
|
||
|
|
"redis": "unavailable",
|
||
|
|
"rabbitmq": "unavailable"
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Note** : `/readyz` retourne toujours 200 (même en mode dégradé) pour permettre au service de démarrer même si Redis/RabbitMQ sont indisponibles.
|
||
|
|
|
||
|
|
### GET `/status`
|
||
|
|
|
||
|
|
Status détaillé (DB, Redis, Chat Server, Stream Server).
|
||
|
|
|
||
|
|
```bash
|
||
|
|
curl -X GET http://localhost:8080/api/v1/status
|
||
|
|
```
|
||
|
|
|
||
|
|
**Response** :
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"status": "ok",
|
||
|
|
"database": {
|
||
|
|
"status": "ok",
|
||
|
|
"latency_ms": 5
|
||
|
|
},
|
||
|
|
"redis": {
|
||
|
|
"status": "ok",
|
||
|
|
"latency_ms": 2
|
||
|
|
},
|
||
|
|
"chat_server": {
|
||
|
|
"status": "ok",
|
||
|
|
"url": "http://localhost:8081"
|
||
|
|
},
|
||
|
|
"stream_server": {
|
||
|
|
"status": "ok",
|
||
|
|
"url": "http://localhost:8082"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Notes Importantes
|
||
|
|
|
||
|
|
### CORS
|
||
|
|
|
||
|
|
En production, `CORS_ALLOWED_ORIGINS` doit être configuré. Le Frontend doit être dans la liste des origines autorisées.
|
||
|
|
|
||
|
|
### Rate Limiting
|
||
|
|
|
||
|
|
- **Login** : 5 tentatives par minute
|
||
|
|
- **Global** : 100 requêtes par minute (configurable)
|
||
|
|
|
||
|
|
Les headers de rate limiting sont retournés :
|
||
|
|
```
|
||
|
|
X-RateLimit-Limit: 100
|
||
|
|
X-RateLimit-Remaining: 99
|
||
|
|
X-RateLimit-Reset: 1234567890
|
||
|
|
```
|
||
|
|
|
||
|
|
### Pagination
|
||
|
|
|
||
|
|
Les listes (tracks, playlists, etc.) supportent la pagination :
|
||
|
|
|
||
|
|
```
|
||
|
|
GET /tracks?page=1&limit=20
|
||
|
|
```
|
||
|
|
|
||
|
|
**Response** :
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"success": true,
|
||
|
|
"data": [...],
|
||
|
|
"pagination": {
|
||
|
|
"page": 1,
|
||
|
|
"limit": 20,
|
||
|
|
"total": 100,
|
||
|
|
"total_pages": 5,
|
||
|
|
"has_next": true,
|
||
|
|
"has_previous": false
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### UUID Format
|
||
|
|
|
||
|
|
Tous les IDs sont des UUID v4 :
|
||
|
|
```
|
||
|
|
550e8400-e29b-41d4-a716-446655440000
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Support
|
||
|
|
|
||
|
|
- **Documentation API** : `/swagger/index.html` (Swagger UI)
|
||
|
|
- **Issues** : [GitHub Issues](https://github.com/veza/veza-backend-api/issues)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
**Last Updated**: 2025-01-27
|