{"info":{"name":"Odealz API 1.0.0","description":"API petites annonces Odealz.\n\n## Catégories\n\n- GET /categories retourne l’arbre des catégories actives, ordonné pour les sélecteurs d’annonce.\n- Une annonce doit choisir une **sous-catégorie** (feuille), pas une catégorie racine.\n- Les catégories réservées au commercial sont exclues pour les annonces non commerciales et lorsque le mode commercial est désactivé.\n- GET /listings?categorySlug=... filtre une catégorie et tous ses descendants.\n- L’administration peut créer, modifier et désactiver une catégorie via /admin/categories ; la désactivation inclut ses sous-catégories et conserve les annonces existantes.\n- Les annonces peuvent préciser l’état du produit : NEW, LIKE_NEW, GOOD, FAIR ou FOR_PARTS. Le champ est facultatif, notamment pour les annonces de services.\n\n## Médias, confiance et cycle de vie\n\n- Les annonceurs peuvent ajouter jusqu’à 10 images JPEG, PNG ou WebP (8 Mo par image), les réordonner et les supprimer. Les fichiers ne deviennent accessibles publiquement que pour une annonce publiée et non expirée.\n- Les photos sont réencodées et les métadonnées sont retirées avant stockage.\n- Les adresses utilisent les provinces et territoires ainsi que les codes postaux canadiens. Rue, unité, code postal et coordonnées restent privés. La recherche par rayon est limitée à 100 km.\n- Les photos sont stockées dans un bucket S3 privé en production, avec versionnage activé ; le stockage local reste destiné au développement.\n- Les annonces expirent après 30 jours et peuvent être renouvelées par leur propriétaire. Le catalogue ignore les annonces dont l’échéance est passée.\n- Les utilisateurs connectés peuvent signaler une annonce ou un compte, et bloquer/débloquer un compte. L’administration peut traiter les signalements, retirer une annonce ou suspendre un compte.\n- Les limites anti-abus sont stockées de manière atomique dans PostgreSQL et partagées entre les instances API.\n- Après une remise terminée, seul l’intéressé peut attribuer une note de 1 à 5 à l’annonceur. Une seule note est acceptée par remise ; la réputation publique expose uniquement une moyenne, un total et une distribution, sans commentaire ni identité du déposant.\n- Un compte peut télécharger ses données au format JSON et demander sa suppression en confirmant son mot de passe. La suppression efface également les annonces et conversations liées par les règles de cascade PostgreSQL.\n\n## Accès\n\n- **Routes métier** : préfixe `/api/v1`, authentification **JWT Bearer** lorsque indiqué.\n- **Documentation** : `/` (interface visuelle), `/openapi.json` (schéma OpenAPI) et `/postman.json` (collection Postman v2.1), accessibles sans authentification. Les routes `/api/v1` privées restent protégées par JWT Bearer. `GET /health` est public.\n- **Healthcheck** : `GET /health` (hors version, sans authentification). Le navigateur reçoit un tableau HTML ; `?format=json` ou `Accept: application/json` renvoie le rapport JSON. Chaque ligne indique si le service est configuré dans l’environnement et s’il répond.\n\n## Mode commercial (`COMMERCIAL_FEATURES_ENABLED`)\n\nLorsque la variable vaut `false` :\n\n- Les annonces **COMMERCIAL** sont masquées du catalogue public et des favoris.\n- Création / modification d’annonces commerciales refusée.\n- Historique de prix visible uniquement par le **propriétaire** de l’annonce.\n- Alertes favori `notifyPriceChange` refusées.\n- **Nouvelles** conversations sur annonces commerciales refusées ; les conversations déjà ouvertes restent utilisables (messages et remises).\n\n## Statuts d’annonce\n\n- Création : passage direct à **PUBLISHED** (pas de brouillon via l’API actuelle).\n- **RESERVED** : lors de la **confirmation** d’une remise (`confirm`), si l’annonce était **PUBLISHED** (transition atomique).\n- **COMPLETED** : lors du marquage **complete** d’une remise confirmée.\n- **ARCHIVED** : via `POST /listings/{id}/archive` (propriétaire) ; idempotent si déjà archivée.\n\n## Remises (pickup)\n\nStatuts : **PROPOSED** → **CONFIRMED** | **CANCELLED** (refus) ; **CONFIRMED** → **COMPLETED** | **CANCELLED** (annulation par le proposeur) ; **PROPOSED** → **CANCELLED** (annulation par le proposeur).\n\n| Action | Qui | Effet annonce |\n| ------ | --- | ------------- |\n| `confirm` | Destinataire (non proposeur) | **PUBLISHED** → **RESERVED** si succès |\n| `reject` | Destinataire | Remise **CANCELLED** |\n| `cancel` | Proposeur (proposition ou remise confirmée) | **CANCELLED** ; si était confirmée, annonce **RESERVED** → **PUBLISHED** |\n| `complete` | Participant | Remise **COMPLETED**, annonce **COMPLETED** |\n\nLes champs de lieu de remise ne sont renvoyés qu’aux **participants** de la conversation.\n\n## Chat (conversations)\n\n- **Liste** : `GET /me/conversations` — dernier message et `unreadCount`.\n- **Fil** : `GET /conversations/{id}/messages` sans `page` — fenêtre des derniers messages ; `before` / `after` pour historique et polling.\n- **Lu** : `POST /conversations/{id}/read` — curseur de lecture par participant.\n- Temps réel : polling `?after=` ; WebSocket/SSE en évolution.\n\n## Notifications\n\nPersistées par l’API (`PENDING` à la création) ; envoi e-mail/SMS par le **worker** (`SENT` / `FAILED`). Les préférences utilisateur et le canal déterminent les lignes créées. **Aucune garantie « exactement une fois »** côté fournisseur (retry possible après acceptation).\n\n`GET /me/notifications` : historique du destinataire connecté uniquement ; payload public sans secrets ni contenu privé des messages.\n\n## Recherches enregistrées\n\n- `POST /me/saved-searches` enregistre une alerte par catégorie (sous-catégories incluses), ville/pays, type et plage de prix/devise.\n- La publication d’une annonce déclenche un fanout pg-boss par lots. Les notifications suivent les préférences e-mail/SMS activées du compte.\n- Un compte peut enregistrer jusqu’à 20 recherches, les suspendre ou les supprimer. Les recherches commerciales respectent `COMMERCIAL_FEATURES_ENABLED`.\n\n## Récupération de compte\n\n- `POST /auth/password-reset/request` répond de façon identique que l’adresse existe ou non.\n- `POST /auth/password-reset/confirm` vérifie un code à usage unique, change le mot de passe et révoque les jetons de rafraîchissement.\n- Le code expire après 15 minutes. L’envoi nécessite `ACCOUNT_CODE_ENCRYPTION_KEY`, identique dans le backend et le worker.\n- Les coordonnées ne sont pas obligatoirement vérifiées pour utiliser l’API. Les vérifications peuvent être demandées depuis une session authentifiée.","schema":"https://schema.getpostman.com/json/collection/v2.1.0/collection.json"},"variable":[{"key":"rootUrl","value":"http://localhost:3000"},{"key":"baseUrl","value":"http://localhost:3000/api/v1"},{"key":"accessToken","value":""}],"auth":{"type":"bearer","bearer":[{"key":"token","value":"{{accessToken}}","type":"string"}]},"item":[{"name":"Auth","item":[{"name":"POST /auth/register — Inscription","request":{"method":"POST","header":[{"key":"Accept","value":"application/json"},{"key":"Content-Type","value":"application/json"}],"url":"{{baseUrl}}/auth/register","description":"","body":{"mode":"raw","raw":"{}","options":{"raw":{"language":"json"}}}},"response":[]},{"name":"POST /auth/login — Connexion","request":{"method":"POST","header":[{"key":"Accept","value":"application/json"},{"key":"Content-Type","value":"application/json"}],"url":"{{baseUrl}}/auth/login","description":"","body":{"mode":"raw","raw":"{}","options":{"raw":{"language":"json"}}}},"response":[]},{"name":"POST /auth/password-reset/request — Demander un code de réinitialisation","request":{"method":"POST","header":[{"key":"Accept","value":"application/json"},{"key":"Content-Type","value":"application/json"}],"url":"{{baseUrl}}/auth/password-reset/request","description":"Réponse identique que le compte existe ou non. Limité à 3 demandes par minute et par client.","body":{"mode":"raw","raw":"{}","options":{"raw":{"language":"json"}}}},"response":[]},{"name":"POST /auth/password-reset/confirm — Confirmer le code et choisir un nouveau mot de passe","request":{"method":"POST","header":[{"key":"Accept","value":"application/json"},{"key":"Content-Type","value":"application/json"}],"url":"{{baseUrl}}/auth/password-reset/confirm","description":"Code à usage unique, valable 15 minutes. Les jetons de rafraîchissement sont révoqués après confirmation.","body":{"mode":"raw","raw":"{}","options":{"raw":{"language":"json"}}}},"response":[]},{"name":"POST /auth/refresh — Rafraîchir les jetons","request":{"method":"POST","header":[{"key":"Accept","value":"application/json"},{"key":"Content-Type","value":"application/json"}],"url":"{{baseUrl}}/auth/refresh","description":"","body":{"mode":"raw","raw":"{}","options":{"raw":{"language":"json"}}}},"response":[]},{"name":"POST /auth/logout — Déconnexion (révocation du refresh token)","request":{"method":"POST","header":[{"key":"Accept","value":"application/json"},{"key":"Content-Type","value":"application/json"}],"url":"{{baseUrl}}/auth/logout","description":"","body":{"mode":"raw","raw":"{}","options":{"raw":{"language":"json"}}}},"response":[]}]},{"name":"Profil","item":[{"name":"POST /me/email-verification/request — Envoyer un code de vérification e-mail","request":{"method":"POST","header":[{"key":"Accept","value":"application/json"},{"key":"Content-Type","value":"application/json"},{"key":"Authorization","value":"Bearer {{accessToken}}"}],"url":"{{baseUrl}}/me/email-verification/request","description":"JWT requis. Envoi transactionnel, sans dépendre des préférences de notifications. Le code expire après 15 minutes."},"response":[]},{"name":"POST /me/email-verification/confirm — Confirmer le code e-mail","request":{"method":"POST","header":[{"key":"Accept","value":"application/json"},{"key":"Content-Type","value":"application/json"},{"key":"Authorization","value":"Bearer {{accessToken}}"}],"url":"{{baseUrl}}/me/email-verification/confirm","description":"","body":{"mode":"raw","raw":"{}","options":{"raw":{"language":"json"}}}},"response":[]},{"name":"POST /me/phone-verification/request — Envoyer un code de vérification SMS","request":{"method":"POST","header":[{"key":"Accept","value":"application/json"},{"key":"Content-Type","value":"application/json"},{"key":"Authorization","value":"Bearer {{accessToken}}"}],"url":"{{baseUrl}}/me/phone-verification/request","description":"JWT requis. Le numéro doit être renseigné. Envoi transactionnel via Twilio, sans dépendre des préférences SMS."},"response":[]},{"name":"POST /me/phone-verification/confirm — Confirmer le code SMS","request":{"method":"POST","header":[{"key":"Accept","value":"application/json"},{"key":"Content-Type","value":"application/json"},{"key":"Authorization","value":"Bearer {{accessToken}}"}],"url":"{{baseUrl}}/me/phone-verification/confirm","description":"","body":{"mode":"raw","raw":"{}","options":{"raw":{"language":"json"}}}},"response":[]},{"name":"GET /me — Profil courant","request":{"method":"GET","header":[{"key":"Accept","value":"application/json"},{"key":"Authorization","value":"Bearer {{accessToken}}"}],"url":"{{baseUrl}}/me","description":""},"response":[]},{"name":"PATCH /me — Mettre à jour le profil","request":{"method":"PATCH","header":[{"key":"Accept","value":"application/json"},{"key":"Content-Type","value":"application/json"},{"key":"Authorization","value":"Bearer {{accessToken}}"}],"url":"{{baseUrl}}/me","description":"","body":{"mode":"raw","raw":"{}","options":{"raw":{"language":"json"}}}},"response":[]},{"name":"DELETE /me — Supprimer définitivement son compte","request":{"method":"DELETE","header":[{"key":"Accept","value":"application/json"},{"key":"Authorization","value":"Bearer {{accessToken}}"}],"url":"{{baseUrl}}/me","description":"Confirmation du mot de passe requise. Les données liées sont supprimées selon les relations de cascade.","body":{"mode":"raw","raw":"{}","options":{"raw":{"language":"json"}}}},"response":[]},{"name":"GET /me/export — Exporter ses données personnelles (JSON)","request":{"method":"GET","header":[{"key":"Accept","value":"application/json"},{"key":"Authorization","value":"Bearer {{accessToken}}"}],"url":"{{baseUrl}}/me/export","description":""},"response":[]},{"name":"POST /me/become-advertiser — Passer au rôle annonceur","request":{"method":"POST","header":[{"key":"Accept","value":"application/json"},{"key":"Content-Type","value":"application/json"},{"key":"Authorization","value":"Bearer {{accessToken}}"}],"url":"{{baseUrl}}/me/become-advertiser","description":""},"response":[]},{"name":"GET /me/notification-preferences — Préférences de notification","request":{"method":"GET","header":[{"key":"Accept","value":"application/json"},{"key":"Authorization","value":"Bearer {{accessToken}}"}],"url":"{{baseUrl}}/me/notification-preferences","description":""},"response":[]},{"name":"PATCH /me/notification-preferences — Modifier les préférences de notification","request":{"method":"PATCH","header":[{"key":"Accept","value":"application/json"},{"key":"Content-Type","value":"application/json"},{"key":"Authorization","value":"Bearer {{accessToken}}"}],"url":"{{baseUrl}}/me/notification-preferences","description":"","body":{"mode":"raw","raw":"{}","options":{"raw":{"language":"json"}}}},"response":[]}]},{"name":"Annonces","item":[{"name":"POST /listings/{id}/renew — Renouveler une annonce","request":{"method":"POST","header":[{"key":"Accept","value":"application/json"},{"key":"Content-Type","value":"application/json"},{"key":"Authorization","value":"Bearer {{accessToken}}"}],"url":"{{baseUrl}}/listings/{id}/renew","description":"Propriétaire annonceur uniquement. Prolonge la publication de 30 jours. Refus si réservée, terminée, archivée ou retirée."},"response":[]},{"name":"POST /listings/{id}/images — Téléverser une image","request":{"method":"POST","header":[{"key":"Accept","value":"application/json"},{"key":"Content-Type","value":"application/json"},{"key":"Authorization","value":"Bearer {{accessToken}}"}],"url":"{{baseUrl}}/listings/{id}/images","description":"Annonceur propriétaire uniquement. JPEG, PNG ou WebP ; 8 Mo maximum et 10 images par annonce. Le fichier est réencodé et les métadonnées sont retirées avant stockage.","body":{"mode":"raw","raw":"{}","options":{"raw":{"language":"json"}}}},"response":[]},{"name":"DELETE /listings/{id}/images/{imageId} — Supprimer une image","request":{"method":"DELETE","header":[{"key":"Accept","value":"application/json"},{"key":"Authorization","value":"Bearer {{accessToken}}"}],"url":"{{baseUrl}}/listings/{id}/images/{imageId}","description":""},"response":[]},{"name":"PATCH /listings/{id}/images/order — Réordonner les images","request":{"method":"PATCH","header":[{"key":"Accept","value":"application/json"},{"key":"Content-Type","value":"application/json"},{"key":"Authorization","value":"Bearer {{accessToken}}"}],"url":"{{baseUrl}}/listings/{id}/images/order","description":"","body":{"mode":"raw","raw":"{}","options":{"raw":{"language":"json"}}}},"response":[]},{"name":"GET /media/{imageId} — Récupérer une image publiquement visible","request":{"method":"GET","header":[{"key":"Accept","value":"application/json"}],"url":"{{rootUrl}}/media/{imageId}","description":"Accessible seulement pour une annonce publiée, non expirée et visible selon le mode commercial."},"response":[]},{"name":"GET /listings — Catalogue public paginé","request":{"method":"GET","header":[{"key":"Accept","value":"application/json"}],"url":"{{baseUrl}}/listings","description":"Uniquement les annonces **PUBLISHED**. Si `status` est fourni et différent de `PUBLISHED`, réponse **400**. Sans mode commercial, seules les annonces **NON_COMMERCIAL** sont listées."},"response":[]},{"name":"POST /listings — Créer une annonce (annonceur)","request":{"method":"POST","header":[{"key":"Accept","value":"application/json"},{"key":"Content-Type","value":"application/json"},{"key":"Authorization","value":"Bearer {{accessToken}}"}],"url":"{{baseUrl}}/listings","description":"","body":{"mode":"raw","raw":"{}","options":{"raw":{"language":"json"}}}},"response":[]},{"name":"GET /listings/{id}/prices — Historique des prix (annonce commerciale)","request":{"method":"GET","header":[{"key":"Accept","value":"application/json"},{"key":"Authorization","value":"Bearer {{accessToken}}"}],"url":"{{baseUrl}}/listings/{id}/prices","description":"JWT **optionnel**. Propriétaire : accès même si mode commercial désactivé. Autres utilisateurs : mode commercial requis et annonce **PUBLISHED**."},"response":[]},{"name":"POST /listings/{id}/price — Modifier le prix (propriétaire, annonce commerciale)","request":{"method":"POST","header":[{"key":"Accept","value":"application/json"},{"key":"Content-Type","value":"application/json"},{"key":"Authorization","value":"Bearer {{accessToken}}"}],"url":"{{baseUrl}}/listings/{id}/price","description":"","body":{"mode":"raw","raw":"{}","options":{"raw":{"language":"json"}}}},"response":[]},{"name":"GET /listings/{id} — Détail public d’une annonce publiée","request":{"method":"GET","header":[{"key":"Accept","value":"application/json"}],"url":"{{baseUrl}}/listings/{id}","description":""},"response":[]},{"name":"PATCH /listings/{id} — Modifier une annonce (propriétaire annonceur)","request":{"method":"PATCH","header":[{"key":"Accept","value":"application/json"},{"key":"Content-Type","value":"application/json"},{"key":"Authorization","value":"Bearer {{accessToken}}"}],"url":"{{baseUrl}}/listings/{id}","description":"","body":{"mode":"raw","raw":"{}","options":{"raw":{"language":"json"}}}},"response":[]},{"name":"POST /listings/{id}/archive — Archiver une annonce","request":{"method":"POST","header":[{"key":"Accept","value":"application/json"},{"key":"Content-Type","value":"application/json"},{"key":"Authorization","value":"Bearer {{accessToken}}"}],"url":"{{baseUrl}}/listings/{id}/archive","description":""},"response":[]},{"name":"GET /me/listings — Mes annonces (tous statuts, y compris commerciales masquées du public)","request":{"method":"GET","header":[{"key":"Accept","value":"application/json"},{"key":"Authorization","value":"Bearer {{accessToken}}"}],"url":"{{baseUrl}}/me/listings","description":""},"response":[]}]},{"name":"Favoris","item":[{"name":"GET /me/favorites — Mes favoris","request":{"method":"GET","header":[{"key":"Accept","value":"application/json"},{"key":"Authorization","value":"Bearer {{accessToken}}"}],"url":"{{baseUrl}}/me/favorites","description":""},"response":[]},{"name":"PUT /me/favorites/{listingId} — Ajouter ou mettre à jour un favori (idempotent)","request":{"method":"PUT","header":[{"key":"Accept","value":"application/json"},{"key":"Content-Type","value":"application/json"},{"key":"Authorization","value":"Bearer {{accessToken}}"}],"url":"{{baseUrl}}/me/favorites/{listingId}","description":"","body":{"mode":"raw","raw":"{}","options":{"raw":{"language":"json"}}}},"response":[]},{"name":"PATCH /me/favorites/{listingId} — Modifier l’alerte de changement de prix","request":{"method":"PATCH","header":[{"key":"Accept","value":"application/json"},{"key":"Content-Type","value":"application/json"},{"key":"Authorization","value":"Bearer {{accessToken}}"}],"url":"{{baseUrl}}/me/favorites/{listingId}","description":"","body":{"mode":"raw","raw":"{}","options":{"raw":{"language":"json"}}}},"response":[]},{"name":"DELETE /me/favorites/{listingId} — Retirer un favori","request":{"method":"DELETE","header":[{"key":"Accept","value":"application/json"},{"key":"Authorization","value":"Bearer {{accessToken}}"}],"url":"{{baseUrl}}/me/favorites/{listingId}","description":""},"response":[]}]},{"name":"Conversations","item":[{"name":"POST /listings/{id}/conversations — Démarrer ou retrouver une conversation","request":{"method":"POST","header":[{"key":"Accept","value":"application/json"},{"key":"Content-Type","value":"application/json"},{"key":"Authorization","value":"Bearer {{accessToken}}"}],"url":"{{baseUrl}}/listings/{id}/conversations","description":"Idempotent : retourne la conversation existante pour ce couple (annonce, utilisateur intéressé). Refus si propriétaire de l’annonce, annonce non **PUBLISHED**, ou nouvelle conversation commerciale avec mode commercial désactivé."},"response":[]},{"name":"GET /me/conversations — Mes conversations (propriétaire ou intéressé)","request":{"method":"GET","header":[{"key":"Accept","value":"application/json"},{"key":"Authorization","value":"Bearer {{accessToken}}"}],"url":"{{baseUrl}}/me/conversations","description":""},"response":[]},{"name":"GET /conversations/{id}/messages — Messages d’une conversation (chat)","request":{"method":"GET","header":[{"key":"Accept","value":"application/json"},{"key":"Authorization","value":"Bearer {{accessToken}}"}],"url":"{{baseUrl}}/conversations/{id}/messages","description":"Participant uniquement. Sans `page` : fenêtre chat (derniers messages, curseurs `before` / `after`). Avec `page` : pagination classique (`PaginatedMessages`)."},"response":[]},{"name":"POST /conversations/{id}/messages — Envoyer un message","request":{"method":"POST","header":[{"key":"Accept","value":"application/json"},{"key":"Content-Type","value":"application/json"},{"key":"Authorization","value":"Bearer {{accessToken}}"}],"url":"{{baseUrl}}/conversations/{id}/messages","description":"Participant uniquement. Refus si annonce **ARCHIVED** ou **COMPLETED**. Déclenche une notification au autre participant (asynchrone).","body":{"mode":"raw","raw":"{}","options":{"raw":{"language":"json"}}}},"response":[]},{"name":"POST /conversations/{id}/read — Marquer la conversation comme lue","request":{"method":"POST","header":[{"key":"Accept","value":"application/json"},{"key":"Content-Type","value":"application/json"},{"key":"Authorization","value":"Bearer {{accessToken}}"}],"url":"{{baseUrl}}/conversations/{id}/read","description":"Met à jour le curseur de lecture du participant connecté. Corps optionnel (`messageId`).","body":{"mode":"raw","raw":"{}","options":{"raw":{"language":"json"}}}},"response":[]},{"name":"POST /conversations/{id}/pickups — Proposer une remise","request":{"method":"POST","header":[{"key":"Accept","value":"application/json"},{"key":"Content-Type","value":"application/json"},{"key":"Authorization","value":"Bearer {{accessToken}}"}],"url":"{{baseUrl}}/conversations/{id}/pickups","description":"Participant uniquement. Lieu et horaire visibles des participants. Une seule proposition **PROPOSED** ou **CONFIRMED** à la fois.","body":{"mode":"raw","raw":"{}","options":{"raw":{"language":"json"}}}},"response":[]},{"name":"PATCH /pickups/{id} — Transition de remise","request":{"method":"PATCH","header":[{"key":"Accept","value":"application/json"},{"key":"Content-Type","value":"application/json"},{"key":"Authorization","value":"Bearer {{accessToken}}"}],"url":"{{baseUrl}}/pickups/{id}","description":"Voir la section « Remises » de la description générale. Participant ou destinataire selon l’action.","body":{"mode":"raw","raw":"{}","options":{"raw":{"language":"json"}}}},"response":[]}]},{"name":"Notifications","item":[{"name":"GET /me/notifications — Mes notifications (historique)","request":{"method":"GET","header":[{"key":"Accept","value":"application/json"},{"key":"Authorization","value":"Bearer {{accessToken}}"}],"url":"{{baseUrl}}/me/notifications","description":"Propriétaire des lignes uniquement. Statuts **PENDING** (en cours d’envoi), **SENT**, **FAILED**. Pas de contenu privé des messages dans le payload."},"response":[]}]},{"name":"Système","item":[{"name":"GET /health — Santé du service","request":{"method":"GET","header":[{"key":"Accept","value":"application/json"}],"url":"{{rootUrl}}/health","description":"Hors préfixe `/api/v1`. Public, sans JWT. `ok` si tous les contrôles passent, `degraded` si un service facultatif est mal configuré ou injoignable, `down` si un service obligatoire ne répond pas. Le navigateur reçoit du HTML ; `?format=json` force le JSON."},"response":[]}]},{"name":"Catégories","item":[{"name":"GET /categories — Lister l’arbre des catégories actives","request":{"method":"GET","header":[{"key":"Accept","value":"application/json"}],"url":"{{baseUrl}}/categories","description":"Retourne les catégories racines et leurs sous-catégories. Les catégories réservées aux annonces commerciales sont masquées si le mode commercial est désactivé ou si listingType=NON_COMMERCIAL."},"response":[]},{"name":"POST /admin/categories — Créer une catégorie (administration)","request":{"method":"POST","header":[{"key":"Accept","value":"application/json"},{"key":"Content-Type","value":"application/json"},{"key":"Authorization","value":"Bearer {{accessToken}}"}],"url":"{{baseUrl}}/admin/categories","description":"Réservé au rôle ADMIN. Le slug est stable et unique ; la catégorie est active à sa création.","body":{"mode":"raw","raw":"{}","options":{"raw":{"language":"json"}}}},"response":[]},{"name":"PATCH /admin/categories/{slug} — Modifier une catégorie (administration)","request":{"method":"PATCH","header":[{"key":"Accept","value":"application/json"},{"key":"Content-Type","value":"application/json"},{"key":"Authorization","value":"Bearer {{accessToken}}"}],"url":"{{baseUrl}}/admin/categories/{slug}","description":"","body":{"mode":"raw","raw":"{}","options":{"raw":{"language":"json"}}}},"response":[]},{"name":"DELETE /admin/categories/{slug} — Désactiver une catégorie et ses descendantes","request":{"method":"DELETE","header":[{"key":"Accept","value":"application/json"},{"key":"Authorization","value":"Bearer {{accessToken}}"}],"url":"{{baseUrl}}/admin/categories/{slug}","description":"Désactivation logique ; les annonces historiques conservent leur relation."},"response":[]}]},{"name":"Confiance et sécurité","item":[{"name":"GET /admin/moderation/images/{imageId} — Consulter une photo signalée (ADMIN)","request":{"method":"GET","header":[{"key":"Accept","value":"application/json"},{"key":"Authorization","value":"Bearer {{accessToken}}"}],"url":"{{baseUrl}}/admin/moderation/images/{imageId}","description":""},"response":[]},{"name":"POST /listings/{id}/reports — Signaler une annonce","request":{"method":"POST","header":[{"key":"Accept","value":"application/json"},{"key":"Content-Type","value":"application/json"},{"key":"Authorization","value":"Bearer {{accessToken}}"}],"url":"{{baseUrl}}/listings/{id}/reports","description":"","body":{"mode":"raw","raw":"{}","options":{"raw":{"language":"json"}}}},"response":[]},{"name":"POST /listings/{id}/images/{imageId}/reports — Signaler une photo d’annonce","request":{"method":"POST","header":[{"key":"Accept","value":"application/json"},{"key":"Content-Type","value":"application/json"},{"key":"Authorization","value":"Bearer {{accessToken}}"}],"url":"{{baseUrl}}/listings/{id}/images/{imageId}/reports","description":"","body":{"mode":"raw","raw":"{}","options":{"raw":{"language":"json"}}}},"response":[]},{"name":"POST /users/{userId}/reports — Signaler un compte","request":{"method":"POST","header":[{"key":"Accept","value":"application/json"},{"key":"Content-Type","value":"application/json"},{"key":"Authorization","value":"Bearer {{accessToken}}"}],"url":"{{baseUrl}}/users/{userId}/reports","description":"","body":{"mode":"raw","raw":"{}","options":{"raw":{"language":"json"}}}},"response":[]},{"name":"GET /users/{userId}/reputation — Consulter la réputation publique d’un annonceur","request":{"method":"GET","header":[{"key":"Accept","value":"application/json"}],"url":"{{baseUrl}}/users/{userId}/reputation","description":"Expose uniquement les agrégats des notes reçues après des remises terminées. Les comptes suspendus ou non annonceurs ne sont pas trouvés."},"response":[]},{"name":"POST /me/blocked-users/{userId} — Bloquer un compte","request":{"method":"POST","header":[{"key":"Accept","value":"application/json"},{"key":"Content-Type","value":"application/json"},{"key":"Authorization","value":"Bearer {{accessToken}}"}],"url":"{{baseUrl}}/me/blocked-users/{userId}","description":""},"response":[]},{"name":"DELETE /me/blocked-users/{userId} — Débloquer un compte","request":{"method":"DELETE","header":[{"key":"Accept","value":"application/json"},{"key":"Authorization","value":"Bearer {{accessToken}}"}],"url":"{{baseUrl}}/me/blocked-users/{userId}","description":""},"response":[]},{"name":"GET /me/blocked-users — Lister les comptes bloqués","request":{"method":"GET","header":[{"key":"Accept","value":"application/json"},{"key":"Authorization","value":"Bearer {{accessToken}}"}],"url":"{{baseUrl}}/me/blocked-users","description":""},"response":[]},{"name":"GET /admin/reports — Lister les signalements ouverts (ADMIN)","request":{"method":"GET","header":[{"key":"Accept","value":"application/json"},{"key":"Authorization","value":"Bearer {{accessToken}}"}],"url":"{{baseUrl}}/admin/reports","description":""},"response":[]},{"name":"GET /admin/moderation — File paginée des signalements à examiner (ADMIN)","request":{"method":"GET","header":[{"key":"Accept","value":"application/json"},{"key":"Authorization","value":"Bearer {{accessToken}}"}],"url":"{{baseUrl}}/admin/moderation","description":"Comprend les signalements d’annonces, de photos et de comptes."},"response":[]},{"name":"GET /admin/audit-logs — Consulter le journal des actions d’administration (ADMIN)","request":{"method":"GET","header":[{"key":"Accept","value":"application/json"},{"key":"Authorization","value":"Bearer {{accessToken}}"}],"url":"{{baseUrl}}/admin/audit-logs","description":""},"response":[]},{"name":"PATCH /admin/reports/{id} — Traiter un signalement (ADMIN)","request":{"method":"PATCH","header":[{"key":"Accept","value":"application/json"},{"key":"Content-Type","value":"application/json"},{"key":"Authorization","value":"Bearer {{accessToken}}"}],"url":"{{baseUrl}}/admin/reports/{id}","description":"","body":{"mode":"raw","raw":"{}","options":{"raw":{"language":"json"}}}},"response":[]},{"name":"POST /pickups/{id}/review — Évaluer l’annonceur après une remise terminée","request":{"method":"POST","header":[{"key":"Accept","value":"application/json"},{"key":"Content-Type","value":"application/json"},{"key":"Authorization","value":"Bearer {{accessToken}}"}],"url":"{{baseUrl}}/pickups/{id}/review","description":"JWT requis. Seul l’intéressé de la conversation peut noter l’annonceur, une fois par remise complétée. Aucun commentaire libre n’est accepté.","body":{"mode":"raw","raw":"{}","options":{"raw":{"language":"json"}}}},"response":[]}]},{"name":"Recherches enregistrées","item":[{"name":"GET /me/saved-searches — Lister mes recherches enregistrées","request":{"method":"GET","header":[{"key":"Accept","value":"application/json"},{"key":"Authorization","value":"Bearer {{accessToken}}"}],"url":"{{baseUrl}}/me/saved-searches","description":"JWT requis. Pagination par page et limite (1 à 100). Maximum 20 recherches par compte."},"response":[]},{"name":"POST /me/saved-searches — Enregistrer une recherche et ses alertes","request":{"method":"POST","header":[{"key":"Accept","value":"application/json"},{"key":"Content-Type","value":"application/json"},{"key":"Authorization","value":"Bearer {{accessToken}}"}],"url":"{{baseUrl}}/me/saved-searches","description":"Les recherches par prix requièrent une devise et concernent les annonces commerciales. Une catégorie parente inclut ses sous-catégories actives. Max. 20 recherches par compte.","body":{"mode":"raw","raw":"{}","options":{"raw":{"language":"json"}}}},"response":[]},{"name":"PATCH /me/saved-searches/{id} — Modifier ou suspendre une recherche enregistrée","request":{"method":"PATCH","header":[{"key":"Accept","value":"application/json"},{"key":"Content-Type","value":"application/json"},{"key":"Authorization","value":"Bearer {{accessToken}}"}],"url":"{{baseUrl}}/me/saved-searches/{id}","description":"","body":{"mode":"raw","raw":"{}","options":{"raw":{"language":"json"}}}},"response":[]},{"name":"DELETE /me/saved-searches/{id} — Supprimer une recherche enregistrée","request":{"method":"DELETE","header":[{"key":"Accept","value":"application/json"},{"key":"Authorization","value":"Bearer {{accessToken}}"}],"url":"{{baseUrl}}/me/saved-searches/{id}","description":""},"response":[]}]}]}