Skip to content

API Contract Agent

Définit le contrat d'API : endpoints, authentification, format des erreurs, pagination et rate limiting.

Quand l'utiliser

  • Nouveau endpoint REST
  • Modification de la signature d'un endpoint existant
  • Ajout d'un nouveau type de ressource
  • Intégration avec le frontend Next.js

Quand ne pas l'utiliser

  • Modification interne sans impact sur l'API
  • Changement de logique métier à signature identique
  • Refactoring de code backend uniquement

Entrées requises

EntréeSource
Feature SpecActions utilisateur, données requises
Domain ModelEntités, attributs exposés
Auth existanteSanctum, Passport, ou sessions
Conventions APIVersioning, nommage existant

Sortie attendue

markdown
## API Contract: [Nom du module]

### Endpoints

| Méthode | URI | Description | Auth |
|---------|-----|-------------|------|
| GET | /api/v1/invoices | Liste des factures | Sanctum |
| GET | /api/v1/invoices/{id} | Détail facture | Sanctum |
| POST | /api/v1/invoices | Créer facture | Sanctum |
| PUT | /api/v1/invoices/{id} | Modifier facture | Sanctum |
| DELETE | /api/v1/invoices/{id} | Supprimer facture | Sanctum |

### Request/Response

#### GET /api/v1/invoices

**Query params :**
| Param | Type | Défaut | Description |
|-------|------|--------|-------------|
| page | int | 1 | Numéro de page |
| per_page | int | 15 | Items par page (max 100) |
| status | string | - | Filtre par statut |

**Response 200 :**
```json
{
  "data": [
    {
      "id": 1,
      "number": "INV-2024-001",
      "total_amount": "150.00",
      "status": "paid",
      "created_at": "2024-01-15T10:30:00Z"
    }
  ],
  "meta": {
    "current_page": 1,
    "last_page": 5,
    "per_page": 15,
    "total": 72
  }
}

Erreurs

CodeSituationBody
401Non authentifié{"message": "Unauthenticated."}
403Non autorisé{"message": "This action is unauthorized."}
404Ressource inexistante{"message": "Invoice not found."}
422Validation échouée{"message": "...", "errors": {...}}

Validation (FormRequest)

ChampRègles
numberrequired, string, max:20, unique:invoices
total_amountrequired, numeric, min:0
statusrequired, in:draft,sent,paid

Rate Limiting

  • Authentifié : 60 requêtes/minute
  • Endpoint sensible : adapter selon besoin

Compatibilité ascendante

RègleApplication
Suppression de champInterdit sans deprecation préalable
Renommage de champExposer les deux pendant 1 version
Nouveau champ requisFournir une valeur par défaut
Changement de typeBreaking → versioning API

Documentation Scribe

CritèreVérifié
FormRequest utiliséOui / Non
Règles de validation complètesOui / Non
Doc générable (php artisan scribe:generate)Oui / Non
Exemples de response documentésOui / Non

Implémentation Laravel (standard)

ComposantFichierStatus
ControllerInvoiceController.phpOrchestration only
FormRequestStoreInvoiceRequest.phpValidation + authorize
PolicyInvoicePolicy.phpview/create/update/delete
JsonResourceInvoiceResource.phpContrat de réponse
ServiceInvoiceService.phpLogique métier

Voir Template CLAUDE.md section 7 pour le standard complet.

Exposition des permissions utilisateur

L'API DOIT exposer les permissions de l'utilisateur connecté pour que le frontend puisse adapter l'UI.

Endpoint recommandé : GET /api/me

json
{
  "data": {
    "id": 1,
    "name": "John Doe",
    "email": "john@example.com"
  },
  "can": {
    "invoices.view": true,
    "invoices.create": true,
    "invoices.update": true,
    "invoices.delete": false,
    "users.manage": false
  }
}

Implémentation Laravel :

php
// UserResource.php
public function toArray($request): array
{
    return [
        'data' => [
            'id' => $this->id,
            'name' => $this->name,
            'email' => $this->email,
        ],
        'can' => [
            'invoices.view' => $request->user()->can('viewAny', Invoice::class),
            'invoices.create' => $request->user()->can('create', Invoice::class),
            'invoices.update' => $request->user()->can('update', $this->resource),
            'invoices.delete' => $request->user()->can('delete', $this->resource),
            'users.manage' => $request->user()->can('viewAny', User::class),
        ],
    ];
}
RègleExplication
Permissions = PoliciesLes can reflètent exactement les Policies Laravel
Nommage cohérentresource.action (ex: invoices.create)
Pas de rôles brutsNe pas exposer role: "admin" mais les abilities réelles
Frontend consommeLe frontend utilise can.invoices.create pour afficher/masquer

## Andon (STOP)

::: danger Conditions bloquantes
- Endpoint sans authentification sur données privées
- Pas de validation sur les inputs utilisateur
- Réponse non paginée sur collection potentiellement grande
- Pas de gestion d'erreur 404/403 explicite
- Exposition de données sensibles (password, tokens)
- Implémentation prévue sans FormRequest (validation inline)
- Implémentation prévue sans Policy (authZ implicite)
- Implémentation prévue avec `return $model` brut (sans JsonResource)
- `/api/me` n'expose pas les permissions utilisateur (ou équivalent)
- Exposition des rôles bruts (`role: "admin"`) au lieu des abilities
:::

## Checklist Done

```markdown
- [ ] Tous les endpoints listés avec méthode et URI
- [ ] Auth spécifiée pour chaque endpoint
- [ ] Format request/response documenté
- [ ] Pagination sur les listes (meta inclus)
- [ ] Codes d'erreur et messages définis
- [ ] Validation FormRequest rédigée (pas de validation inline)
- [ ] Policy définie pour chaque ressource protégée
- [ ] JsonResource définie pour chaque réponse
- [ ] Rate limiting défini
- [ ] Pas de donnée sensible exposée
- [ ] Compat ascendante vérifiée (pas de breaking change)
- [ ] Documentation Scribe générable
- [ ] `/api/me` expose les permissions (`can`) basées sur les Policies
- [ ] Pas de rôle brut exposé (abilities uniquement)

Exemple minimal

markdown
## API Contract: Export PDF

### Endpoints

| Méthode | URI | Description | Auth |
|---------|-----|-------------|------|
| GET | /api/v1/invoices/{id}/pdf | Télécharger PDF | Sanctum |

### Request/Response

#### GET /api/v1/invoices/{id}/pdf

**Response 200 :**
- Content-Type: `application/pdf`
- Content-Disposition: `attachment; filename="INV-2024-001.pdf"`

**Response 403 :**
```json
{"message": "Vous ne pouvez pas accéder à cette facture."}

Response 404 :

json
{"message": "Facture introuvable."}

Validation

  • {id} : exists:invoices,id
  • Policy : user_id == auth()->id()

## Référence

Voir [Documentation API (Scribe)](/guides/scribe-documentation) pour générer la doc OpenAPI.