# API platform — process & intégration

Ce proxy expose plusieurs **micro-services** derrière une façade unique. Cette page
décrit le process de bout en bout : découverte, authentification, flux d'une
requête, cache, rate limiting, et comment ajouter un service.

- **Portail HTML** : [`/platform`](/platform) — catalogue lisible + exemples `curl`.
- **Manifeste JSON** : `GET /api` — même catalogue, exploitable par une machine.

## 1. Micro-services exposés

| Service | Route | Auth | Rôle |
| --- | --- | --- | --- |
| **search** | `GET /api/search` | clé | Proxy SerpApi brut (passthrough) + cache BDD |
| **shopping** | `GET /api/shopping` | clé | Pipeline multi-engine + dédoublonnage 3 niveaux + prix ([spec](serpapi-algo-dedup-ebay.md)) |
| **health** | `GET /health` | public | Statut DB, clé SerpApi, stats cache, rate limit |
| **catalog** | `GET /api` | public | Manifeste de découverte (cette plateforme) |

> Le manifeste `/api` est la **source de vérité** : le portail HTML et ce document
> en dérivent. Ajouter un service = éditer `api_services()` dans `index.php`.

## 2. Flux d'une requête

```
Client ──(clé proxy)──▶ index.php (routeur)
                           │
                           ├─ require_client_key()      ← auth clé client
                           ├─ enforce_rate_limit()      ← quota par clé/IP (429 + Retry-After)
                           ├─ cache BDD (serp_cache)    ← hit → réponse immédiate
                           │     └─ miss ▼
                           ├─ micro-service (Shopping / SerpApiClient)
                           │     └─ SerpApi (+ SERPAPI_KEY côté serveur)
                           └─ store cache (TTL) + réponse JSON
```

La clé **SerpApi** est injectée côté serveur (`SerpApiClient`) : elle ne sort
jamais côté client. Les clients n'utilisent que leur **clé proxy**.

## 3. Authentification

Si `PROXY_CLIENT_KEYS` est défini (`.env`), chaque service authentifié exige une
clé client, au choix :

- query : `?key=VOTRE_CLE`
- en-tête : `X-Api-Key: VOTRE_CLE`

Plusieurs clés possibles (séparées par des virgules) pour distinguer les clients.
Clé absente/invalide → `401`. Vide → ouvert (dev uniquement).

## 4. Rate limiting

Fenêtre fixe par clé client (ou IP). En-têtes sur chaque réponse :
`X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`. Dépassement →
`429` + `Retry-After`. Réglage : `RATE_LIMIT`, `RATE_WINDOW`.

## 5. Cache

Cache BDD `serp_cache`, clé = `sha256` des paramètres normalisés (versionnée par
micro-service, ex. `mode:shopping`). TTL configurable (`CACHE_TTL`). `?nocache=1`
force un appel amont. Admin : [`/admin`](/admin) (KPIs, purge).

## 6. Consommer la plateforme

```bash
# Découverte
curl https://price.test.gpa07.com/api

# Service brut
curl "https://price.test.gpa07.com/api/search?q=iphone+15&engine=google&key=VOTRE_CLE"

# Service shopping (prix dédoublonné multi-engine)
curl "https://price.test.gpa07.com/api/shopping?q=620905715R+pompe+ABS+Renault&key=VOTRE_CLE"
```

Réponse `shopping` (extrait) :

```json
{
  "query": "620905715R pompe ABS Renault",
  "engines": ["google_shopping_light", "ebay"],
  "counts": { "raw": 95, "after_dedup": 94, "after_group": 9, "kept": 9 },
  "price_stats": { "min": 13.88, "max": 105.41, "avg": 52.27, "count": 9 },
  "shopping_results": [ { "source": "ovoko.fr", "price": 13.88, "currency": "EUR", "...": "..." } ]
}
```

## 7. Ajouter un micro-service

1. Écrire la logique dans `src/` (ex. une classe service réutilisable).
2. Ajouter un `handle_xxx()` + une route dans le `match` de `index.php`.
3. Le déclarer dans `api_services()` → il apparaît automatiquement dans le
   manifeste `/api` **et** le portail `/platform`.
4. Réutiliser `require_client_key()`, `enforce_rate_limit()`, `cache_service()`
   pour rester cohérent (auth, quota, cache).
5. Ajouter des tests hors-ligne (cf. `tests/`).

## 8. Codes de réponse

| Code | Sens |
| --- | --- |
| 200 | OK (`cache_status: hit\|miss`) |
| 400 | Paramètre manquant (`q`) |
| 401 | Clé client manquante/invalide |
| 429 | Quota dépassé (`Retry-After`) |
| 502 | Échec amont SerpApi |
| 500 | Erreur interne |
