{"openapi": "3.1.0", "info": {"title": "CoinDuDoc", "version": "1.0.0", "description": "Annonces m\u00e9dicales et param\u00e9dicales. Toutes les routes exigent un jeton Bearer, sauf POST /api/auth/login. Le sch\u00e9ma et Swagger sont publics.\n\nErreurs : 401, 403 et 404 renvoient {\"detail\": \"message\"}. Un 422 de validation renvoie {\"detail\": {\"champ\": [\"message\"]}}."}, "paths": {"/api/auth/login": {"post": {"operationId": "app_api_routers_auth_login", "summary": "Obtenir un jeton", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/LoginOut"}}}}}, "description": "\u00c9met un jeton pour un compte actif dont l'email est v\u00e9rifi\u00e9. Le mot de passe n'est pas conserv\u00e9. 10 tentatives par 15 minutes et par adresse IP, puis 429. Le jeton en clair n'est renvoy\u00e9 qu'ici.", "tags": ["Compte"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/LoginIn"}}}, "required": true}}}, "/api/auth/logout": {"post": {"operationId": "app_api_routers_auth_logout", "summary": "R\u00e9voquer le jeton", "parameters": [], "responses": {"200": {"description": "OK"}}, "description": "R\u00e9voque le jeton Bearer de cet appel. R\u00e9ponse 204.", "tags": ["Compte"], "security": [{"BearerAuth": []}]}}, "/api/me": {"get": {"operationId": "app_api_routers_auth_me", "summary": "Compte connect\u00e9", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MeOut"}}}}}, "description": "Email, r\u00f4le administrateur et nombre d'annonces actives du compte.", "tags": ["Compte"], "security": [{"BearerAuth": []}]}}, "/api/me/listings": {"get": {"operationId": "app_api_routers_auth_my_listings", "summary": "Annonces actives du compte", "parameters": [{"in": "query", "name": "limit", "schema": {"default": 20, "description": "Nombre de r\u00e9sultats, entier de 1 \u00e0 100.", "title": "Limit", "type": "integer"}, "required": false, "description": "Nombre de r\u00e9sultats, entier de 1 \u00e0 100."}, {"in": "query", "name": "offset", "schema": {"default": 0, "description": "Nombre de r\u00e9sultats saut\u00e9s, entier \u00e0 partir de 0.", "title": "Offset", "type": "integer"}, "required": false, "description": "Nombre de r\u00e9sultats saut\u00e9s, entier \u00e0 partir de 0."}, {"in": "query", "name": "category", "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Slug de cat\u00e9gorie, pas le nom. Voir GET /api/categories. Exemples : offre-demploi-remplacement, locaux, materiel, autres.", "title": "Category"}, "required": false, "description": "Slug de cat\u00e9gorie, pas le nom. Voir GET /api/categories. Exemples : offre-demploi-remplacement, locaux, materiel, autres."}, {"in": "query", "name": "profession", "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Slug de profession, pas le nom. Voir GET /api/professions. Exemple : osteopathe. Optionnel.", "title": "Profession"}, "required": false, "description": "Slug de profession, pas le nom. Voir GET /api/professions. Exemple : osteopathe. Optionnel."}, {"in": "query", "name": "department", "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Slug de d\u00e9partement. Voir GET /api/departments. Obligatoire pour la France, ignor\u00e9 pour la Belgique et la Suisse. Exemple : haute-garonne.", "title": "Department"}, "required": false, "description": "Slug de d\u00e9partement. Voir GET /api/departments. Obligatoire pour la France, ignor\u00e9 pour la Belgique et la Suisse. Exemple : haute-garonne."}, {"in": "query", "name": "country", "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Slug de pays. Voir GET /api/countries. Exemples : france, belgique, suisse.", "title": "Country"}, "required": false, "description": "Slug de pays. Voir GET /api/countries. Exemples : france, belgique, suisse."}, {"in": "query", "name": "city", "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Ville, recherche partielle.", "title": "City"}, "required": false, "description": "Ville, recherche partielle."}, {"in": "query", "name": "q", "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Recherche dans le titre et la ville. Un admin cherche aussi dans l'email de contact.", "title": "Q"}, "required": false, "description": "Recherche dans le titre et la ville. Un admin cherche aussi dans l'email de contact."}, {"in": "query", "name": "created_after", "schema": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "description": "Date-heure ISO 8601 inclusive. Exemple : 2026-09-01T00:00:00+02:00.", "title": "Created After"}, "required": false, "description": "Date-heure ISO 8601 inclusive. Exemple : 2026-09-01T00:00:00+02:00."}, {"in": "query", "name": "created_before", "schema": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "description": "Date-heure ISO 8601 inclusive. Exemple : 2026-09-26T23:59:59+02:00.", "title": "Created Before"}, "required": false, "description": "Date-heure ISO 8601 inclusive. Exemple : 2026-09-26T23:59:59+02:00."}, {"in": "query", "name": "since", "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Annonces post\u00e9es il y a moins de cette dur\u00e9e. Format : 30m, 12h ou 7d.", "examples": ["7d"], "title": "Since"}, "required": false, "description": "Annonces post\u00e9es il y a moins de cette dur\u00e9e. Format : 30m, 12h ou 7d.", "examples": ["7d"]}, {"in": "query", "name": "status", "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Visibilit\u00e9 : active, archived ou all. R\u00e9serv\u00e9 \u00e0 un admin. Sans valeur, un admin re\u00e7oit toutes les annonces.", "examples": ["all"], "title": "Status"}, "required": false, "description": "Visibilit\u00e9 : active, archived ou all. R\u00e9serv\u00e9 \u00e0 un admin. Sans valeur, un admin re\u00e7oit toutes les annonces.", "examples": ["all"]}, {"in": "query", "name": "is_external", "schema": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "description": "Annonce externe. R\u00e9serv\u00e9 \u00e0 un admin.", "title": "Is External"}, "required": false, "description": "Annonce externe. R\u00e9serv\u00e9 \u00e0 un admin."}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ListingPage"}}}}}, "description": "Annonces actives dont le compte connect\u00e9 est propri\u00e9taire. Les filtres status et is_external r\u00e9pondent 403 : cette route n'est pas la recherche globale. Les autres filtres sont ceux de GET /api/listings.", "tags": ["Compte"], "security": [{"BearerAuth": []}]}}, "/api/listings": {"get": {"operationId": "app_api_routers_listings_list_listings", "summary": "Lister les annonces", "parameters": [{"in": "query", "name": "limit", "schema": {"default": 20, "description": "Nombre de r\u00e9sultats, entier de 1 \u00e0 100.", "title": "Limit", "type": "integer"}, "required": false, "description": "Nombre de r\u00e9sultats, entier de 1 \u00e0 100."}, {"in": "query", "name": "offset", "schema": {"default": 0, "description": "Nombre de r\u00e9sultats saut\u00e9s, entier \u00e0 partir de 0.", "title": "Offset", "type": "integer"}, "required": false, "description": "Nombre de r\u00e9sultats saut\u00e9s, entier \u00e0 partir de 0."}, {"in": "query", "name": "category", "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Slug de cat\u00e9gorie, pas le nom. Voir GET /api/categories. Exemples : offre-demploi-remplacement, locaux, materiel, autres.", "title": "Category"}, "required": false, "description": "Slug de cat\u00e9gorie, pas le nom. Voir GET /api/categories. Exemples : offre-demploi-remplacement, locaux, materiel, autres."}, {"in": "query", "name": "profession", "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Slug de profession, pas le nom. Voir GET /api/professions. Exemple : osteopathe. Optionnel.", "title": "Profession"}, "required": false, "description": "Slug de profession, pas le nom. Voir GET /api/professions. Exemple : osteopathe. Optionnel."}, {"in": "query", "name": "department", "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Slug de d\u00e9partement. Voir GET /api/departments. Obligatoire pour la France, ignor\u00e9 pour la Belgique et la Suisse. Exemple : haute-garonne.", "title": "Department"}, "required": false, "description": "Slug de d\u00e9partement. Voir GET /api/departments. Obligatoire pour la France, ignor\u00e9 pour la Belgique et la Suisse. Exemple : haute-garonne."}, {"in": "query", "name": "country", "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Slug de pays. Voir GET /api/countries. Exemples : france, belgique, suisse.", "title": "Country"}, "required": false, "description": "Slug de pays. Voir GET /api/countries. Exemples : france, belgique, suisse."}, {"in": "query", "name": "city", "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Ville, recherche partielle.", "title": "City"}, "required": false, "description": "Ville, recherche partielle."}, {"in": "query", "name": "q", "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Recherche dans le titre et la ville. Un admin cherche aussi dans l'email de contact.", "title": "Q"}, "required": false, "description": "Recherche dans le titre et la ville. Un admin cherche aussi dans l'email de contact."}, {"in": "query", "name": "created_after", "schema": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "description": "Date-heure ISO 8601 inclusive. Exemple : 2026-09-01T00:00:00+02:00.", "title": "Created After"}, "required": false, "description": "Date-heure ISO 8601 inclusive. Exemple : 2026-09-01T00:00:00+02:00."}, {"in": "query", "name": "created_before", "schema": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "description": "Date-heure ISO 8601 inclusive. Exemple : 2026-09-26T23:59:59+02:00.", "title": "Created Before"}, "required": false, "description": "Date-heure ISO 8601 inclusive. Exemple : 2026-09-26T23:59:59+02:00."}, {"in": "query", "name": "since", "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Annonces post\u00e9es il y a moins de cette dur\u00e9e. Format : 30m, 12h ou 7d.", "examples": ["7d"], "title": "Since"}, "required": false, "description": "Annonces post\u00e9es il y a moins de cette dur\u00e9e. Format : 30m, 12h ou 7d.", "examples": ["7d"]}, {"in": "query", "name": "status", "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Visibilit\u00e9 : active, archived ou all. R\u00e9serv\u00e9 \u00e0 un admin. Sans valeur, un admin re\u00e7oit toutes les annonces.", "examples": ["all"], "title": "Status"}, "required": false, "description": "Visibilit\u00e9 : active, archived ou all. R\u00e9serv\u00e9 \u00e0 un admin. Sans valeur, un admin re\u00e7oit toutes les annonces.", "examples": ["all"]}, {"in": "query", "name": "is_external", "schema": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "description": "Annonce externe. R\u00e9serv\u00e9 \u00e0 un admin.", "title": "Is External"}, "required": false, "description": "Annonce externe. R\u00e9serv\u00e9 \u00e0 un admin."}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ListingPage"}}}}}, "description": "Un compte normal ne re\u00e7oit que les annonces actives. Un admin les re\u00e7oit toutes lorsque status est absent. status et is_external sont r\u00e9serv\u00e9s \u00e0 un admin. q cherche dans le titre et la ville, et aussi dans l'email pour un admin. Les champs de contact suivent ListingOut.", "tags": ["Annonces"], "security": [{"BearerAuth": []}]}, "post": {"operationId": "app_api_routers_listings_post_listing", "summary": "Cr\u00e9er une annonce", "parameters": [], "responses": {"201": {"description": "Created", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ListingOut"}}}}}, "description": "Cr\u00e9e une annonce active appartenant au compte du jeton. L'email et le t\u00e9l\u00e9phone de contact peuvent \u00eatre ceux d'un autre employ\u00e9. Les slugs viennent de GET /api/categories, /api/professions, /api/countries et /api/departments. L'exemple Offre d'emploi laisse de c\u00f4t\u00e9 le prix, la surface et le type de transaction : ils servent aux locaux. L'exemple Locaux les renseigne. Un 422 renvoie {\"detail\": {\"champ\": [\"message\"]}}.", "tags": ["Annonces"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ListingIn"}, "examples": {"emploi": {"summary": "Offre d'emploi", "value": {"category": "offre-demploi-remplacement", "title": "Recherche rempla\u00e7ant(e) pour cabinet d'ost\u00e9opathie", "description": "Cabinet d'ost\u00e9opathie \u00e0 Toulouse, remplacement de deux semaines.", "country": "france", "department": "haute-garonne", "city": "Toulouse", "profession": "osteopathe", "job_type": "Remplacement", "start_date": "2026-10-12", "end_date": "2026-10-23", "salary": "50\u20ac/h", "email": "votre@email.com", "phone": "06 12 34 56 78"}}, "locaux": {"summary": "Locaux", "value": {"category": "locaux", "title": "Cabinet d'ost\u00e9opathie \u00e0 louer", "description": "Cabinet de 50 m\u00b2 \u00e0 louer \u00e0 Toulouse.", "country": "france", "department": "haute-garonne", "city": "Toulouse", "profession": "osteopathe", "transaction_type": "Location", "surface": 50, "price": "150000", "email": "votre@email.com", "phone": "06 12 34 56 78"}}}}}, "required": true}, "security": [{"BearerAuth": []}]}}, "/api/listings/{slug}": {"get": {"operationId": "app_api_routers_listings_get_listing", "summary": "D\u00e9tail d'une annonce", "parameters": [{"in": "path", "name": "slug", "schema": {"title": "Slug", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ListingOut"}}}}}, "description": "Un compte normal re\u00e7oit une annonce active, ou sa propre annonce archiv\u00e9e. L'annonce archiv\u00e9e d'un autre compte r\u00e9pond 404.", "tags": ["Annonces"], "security": [{"BearerAuth": []}]}, "patch": {"operationId": "app_api_routers_listings_patch_listing", "summary": "Modifier une annonce", "parameters": [{"in": "path", "name": "slug", "schema": {"title": "Slug", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ListingOut"}}}}}, "description": "Met \u00e0 jour les champs envoy\u00e9s. Un champ absent ne change pas. user_email, is_active et is_external sont r\u00e9serv\u00e9s \u00e0 un admin. user_email null d\u00e9tache le compte. Passer is_active \u00e0 false enregistre l'appelant dans deactivated_by. R\u00e9activer ne modifie ni deactivated_at ni deactivated_by.", "tags": ["Annonces"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ListingUpdateIn"}}}, "required": true}, "security": [{"BearerAuth": []}]}, "delete": {"operationId": "app_api_routers_listings_delete_listing", "summary": "Supprimer une annonce", "parameters": [{"in": "path", "name": "slug", "schema": {"title": "Slug", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK"}}, "description": "R\u00e9serv\u00e9 \u00e0 un admin. Supprime l'annonce et ses images. R\u00e9ponse 204.", "tags": ["Annonces"], "security": [{"BearerAuth": []}]}}, "/api/listings/{slug}/archive": {"post": {"operationId": "app_api_routers_listings_archive_listing", "summary": "Archiver une annonce", "parameters": [{"in": "path", "name": "slug", "schema": {"title": "Slug", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ListingOut"}}}}}, "description": "Passe is_active \u00e0 false et enregistre l'appelant dans deactivated_by. Une annonce d\u00e9j\u00e0 archiv\u00e9e reste inchang\u00e9e et r\u00e9pond 200.", "tags": ["Annonces"], "security": [{"BearerAuth": []}]}}, "/api/listings/{slug}/restore": {"post": {"operationId": "app_api_routers_listings_restore_listing", "summary": "R\u00e9activer une annonce", "parameters": [{"in": "path", "name": "slug", "schema": {"title": "Slug", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ListingOut"}}}}}, "description": "R\u00e9serv\u00e9 \u00e0 un admin. Passe is_active \u00e0 true. Ne modifie ni deactivated_at ni deactivated_by.", "tags": ["Annonces"], "security": [{"BearerAuth": []}]}}, "/api/listings/{slug}/images": {"post": {"operationId": "app_api_routers_listings_add_image", "summary": "Ajouter une image", "parameters": [{"in": "path", "name": "slug", "schema": {"title": "Slug", "type": "string"}, "required": true}], "responses": {"201": {"description": "Created", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ImageOut"}}}}}, "description": "R\u00e9serv\u00e9 au propri\u00e9taire ou \u00e0 un admin. Le serveur redimensionne \u00e0 2 000 px maximum et convertit en JPEG. R\u00e9ponse 201.", "tags": ["Annonces"], "requestBody": {"content": {"multipart/form-data": {"schema": {"properties": {"image": {"format": "binary", "title": "Image", "type": "string"}}, "required": ["image"], "title": "FileParams", "type": "object"}}}, "required": true}, "security": [{"BearerAuth": []}]}}, "/api/listings/{slug}/images/{image_id}": {"patch": {"operationId": "app_api_routers_listings_patch_image", "summary": "Changer l'ordre d'une image", "parameters": [{"in": "path", "name": "slug", "schema": {"title": "Slug", "type": "string"}, "required": true}, {"in": "path", "name": "image_id", "schema": {"title": "Image Id", "type": "integer"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ImageOut"}}}}}, "description": "0 est la premi\u00e8re image.", "tags": ["Annonces"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ImagePatch"}}}, "required": true}, "security": [{"BearerAuth": []}]}, "delete": {"operationId": "app_api_routers_listings_delete_image", "summary": "Supprimer une image", "parameters": [{"in": "path", "name": "slug", "schema": {"title": "Slug", "type": "string"}, "required": true}, {"in": "path", "name": "image_id", "schema": {"title": "Image Id", "type": "integer"}, "required": true}], "responses": {"200": {"description": "OK"}}, "description": "Supprime cette image de l'annonce. R\u00e9ponse 204.", "tags": ["Annonces"], "security": [{"BearerAuth": []}]}}, "/api/contact": {"post": {"operationId": "app_api_routers_messages_post_contact", "summary": "Envoyer un message de contact", "parameters": [], "responses": {"201": {"description": "Created", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ContactOut"}}}}}, "description": "Enregistre le message et pr\u00e9vient l'\u00e9quipe. L'email est obligatoire, le t\u00e9l\u00e9phone est optionnel.", "tags": ["Contact"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ContactIn"}}}, "required": true}, "security": [{"BearerAuth": []}]}}, "/api/contact-messages": {"get": {"operationId": "app_api_routers_messages_list_contact_messages", "summary": "Lister les messages de contact", "parameters": [{"in": "query", "name": "limit", "schema": {"default": 20, "description": "Nombre de r\u00e9sultats, entier de 1 \u00e0 100.", "title": "Limit", "type": "integer"}, "required": false, "description": "Nombre de r\u00e9sultats, entier de 1 \u00e0 100."}, {"in": "query", "name": "offset", "schema": {"default": 0, "description": "Nombre de r\u00e9sultats saut\u00e9s, entier \u00e0 partir de 0.", "title": "Offset", "type": "integer"}, "required": false, "description": "Nombre de r\u00e9sultats saut\u00e9s, entier \u00e0 partir de 0."}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ContactPage"}}}}}, "description": "R\u00e9serv\u00e9 \u00e0 un admin. Du plus r\u00e9cent au plus ancien.", "tags": ["Contact"], "security": [{"BearerAuth": []}]}}, "/api/contact-messages/{message_id}": {"get": {"operationId": "app_api_routers_messages_get_contact_message", "summary": "D\u00e9tail d'un message", "parameters": [{"in": "path", "name": "message_id", "schema": {"title": "Message Id", "type": "integer"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ContactOut"}}}}}, "description": "R\u00e9serv\u00e9 \u00e0 un admin.", "tags": ["Contact"], "security": [{"BearerAuth": []}]}, "delete": {"operationId": "app_api_routers_messages_delete_contact_message", "summary": "Supprimer un message", "parameters": [{"in": "path", "name": "message_id", "schema": {"title": "Message Id", "type": "integer"}, "required": true}], "responses": {"200": {"description": "OK"}}, "description": "R\u00e9serv\u00e9 \u00e0 un admin. R\u00e9ponse 204.", "tags": ["Contact"], "security": [{"BearerAuth": []}]}}, "/api/tracking": {"get": {"operationId": "app_api_routers_messages_list_tracking", "summary": "Lister les r\u00e9v\u00e9lations de contact", "parameters": [{"in": "query", "name": "limit", "schema": {"default": 20, "description": "Nombre de r\u00e9sultats, entier de 1 \u00e0 100.", "title": "Limit", "type": "integer"}, "required": false, "description": "Nombre de r\u00e9sultats, entier de 1 \u00e0 100."}, {"in": "query", "name": "offset", "schema": {"default": 0, "description": "Nombre de r\u00e9sultats saut\u00e9s, entier \u00e0 partir de 0.", "title": "Offset", "type": "integer"}, "required": false, "description": "Nombre de r\u00e9sultats saut\u00e9s, entier \u00e0 partir de 0."}, {"in": "query", "name": "listing", "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Slug de l'annonce. R\u00e9pond 404 si ce slug est inconnu.", "title": "Listing"}, "required": false, "description": "Slug de l'annonce. R\u00e9pond 404 si ce slug est inconnu."}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/TrackingPage"}}}}}, "description": "R\u00e9serv\u00e9 \u00e0 un admin. Chaque entr\u00e9e est une r\u00e9v\u00e9lation des coordonn\u00e9es sur le site. listing filtre par slug d'annonce.", "tags": ["Contact"], "security": [{"BearerAuth": []}]}}, "/api/users": {"get": {"operationId": "app_api_routers_users_list_users", "summary": "Lister les comptes", "parameters": [{"in": "query", "name": "limit", "schema": {"default": 20, "description": "Nombre de r\u00e9sultats, entier de 1 \u00e0 100.", "title": "Limit", "type": "integer"}, "required": false, "description": "Nombre de r\u00e9sultats, entier de 1 \u00e0 100."}, {"in": "query", "name": "offset", "schema": {"default": 0, "description": "Nombre de r\u00e9sultats saut\u00e9s, entier \u00e0 partir de 0.", "title": "Offset", "type": "integer"}, "required": false, "description": "Nombre de r\u00e9sultats saut\u00e9s, entier \u00e0 partir de 0."}, {"in": "query", "name": "q", "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Recherche partielle dans l'email.", "title": "Q"}, "required": false, "description": "Recherche partielle dans l'email."}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/UserPage"}}}}}, "description": "R\u00e9serv\u00e9 \u00e0 un admin. q cherche dans l'email.", "tags": ["Utilisateurs"], "security": [{"BearerAuth": []}]}, "post": {"operationId": "app_api_routers_users_post_user", "summary": "Cr\u00e9er un compte", "parameters": [], "responses": {"201": {"description": "Created", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/UserOut"}}}}}, "description": "R\u00e9serv\u00e9 \u00e0 un admin. Cr\u00e9e un compte avec cet email et ce mot de passe.", "tags": ["Utilisateurs"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/UserIn"}}}, "required": true}, "security": [{"BearerAuth": []}]}}, "/api/users/{user_id}": {"get": {"operationId": "app_api_routers_users_get_user", "summary": "D\u00e9tail d'un compte", "parameters": [{"in": "path", "name": "user_id", "schema": {"title": "User Id", "type": "integer"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/UserDetail"}}}}}, "description": "R\u00e9serv\u00e9 \u00e0 un admin. Inclut les annonces du compte.", "tags": ["Utilisateurs"], "security": [{"BearerAuth": []}]}, "patch": {"operationId": "app_api_routers_users_patch_user", "summary": "Modifier un compte", "parameters": [{"in": "path", "name": "user_id", "schema": {"title": "User Id", "type": "integer"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/UserOut"}}}}}, "description": "R\u00e9serv\u00e9 \u00e0 un admin. is_active et is_staff. Un champ absent ne change pas. null est refus\u00e9.", "tags": ["Utilisateurs"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/UserPatch"}}}, "required": true}, "security": [{"BearerAuth": []}]}}, "/api/users/{user_id}/send-password-reset": {"post": {"operationId": "app_api_routers_users_password_reset", "summary": "Envoyer la r\u00e9initialisation du mot de passe", "parameters": [{"in": "path", "name": "user_id", "schema": {"title": "User Id", "type": "integer"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/DetailOut"}}}}}, "description": "R\u00e9serv\u00e9 \u00e0 un admin. Envoie l'email de r\u00e9initialisation. Ne renvoie pas le mot de passe.", "tags": ["Utilisateurs"], "security": [{"BearerAuth": []}]}}, "/api/categories": {"get": {"operationId": "app_api_routers_reference_list_categories", "summary": "Lister les cat\u00e9gories", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/CategoryOut"}, "title": "Response", "type": "array"}}}}}, "description": "Slug et nom de chaque cat\u00e9gorie. Les slugs connus sont offre-demploi-remplacement, locaux, materiel et autres.", "tags": ["R\u00e9f\u00e9rentiel"], "security": [{"BearerAuth": []}]}}, "/api/professions": {"get": {"operationId": "app_api_routers_reference_list_professions", "summary": "Lister les professions", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/ProfessionOut"}, "title": "Response", "type": "array"}}}}}, "description": "Slug et nom de chaque profession.", "tags": ["R\u00e9f\u00e9rentiel"], "security": [{"BearerAuth": []}]}}, "/api/departments": {"get": {"operationId": "app_api_routers_reference_list_departments", "summary": "Lister les d\u00e9partements", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/DepartmentOut"}, "title": "Response", "type": "array"}}}}}, "description": "Slug, nom et num\u00e9ro. Obligatoire pour une annonce en France. Ignor\u00e9 pour la Belgique et la Suisse.", "tags": ["R\u00e9f\u00e9rentiel"], "security": [{"BearerAuth": []}]}}, "/api/countries": {"get": {"operationId": "app_api_routers_reference_list_countries", "summary": "Lister les pays", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/CountryOut"}, "title": "Response", "type": "array"}}}}}, "description": "Slug, nom et code ISO. Exemples de slug : france, belgique, suisse.", "tags": ["R\u00e9f\u00e9rentiel"], "security": [{"BearerAuth": []}]}}}, "components": {"schemas": {"LoginOut": {"properties": {"token": {"description": "Jeton Bearer \u00e0 envoyer dans Authorization.", "title": "Token", "type": "string"}, "email": {"title": "Email", "type": "string"}, "is_staff": {"description": "true si le compte est administrateur.", "title": "Is Staff", "type": "boolean"}}, "required": ["token", "email", "is_staff"], "title": "LoginOut", "type": "object"}, "LoginIn": {"properties": {"email": {"description": "Adresse email du compte.", "title": "Email", "type": "string"}, "password": {"description": "Mot de passe. Il n'est pas conserv\u00e9.", "title": "Password", "type": "string"}}, "required": ["email", "password"], "title": "LoginIn", "type": "object"}, "MeOut": {"properties": {"email": {"title": "Email", "type": "string"}, "is_staff": {"description": "true si le compte est administrateur.", "title": "Is Staff", "type": "boolean"}, "listings_count": {"description": "Nombre d'annonces actives du compte.", "title": "Listings Count", "type": "integer"}}, "required": ["email", "is_staff", "listings_count"], "title": "MeOut", "type": "object"}, "ListingFilters": {"properties": {"limit": {"default": 20, "description": "Nombre de r\u00e9sultats, entier de 1 \u00e0 100.", "title": "Limit", "type": "integer"}, "offset": {"default": 0, "description": "Nombre de r\u00e9sultats saut\u00e9s, entier \u00e0 partir de 0.", "title": "Offset", "type": "integer"}, "category": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Slug de cat\u00e9gorie, pas le nom. Voir GET /api/categories. Exemples : offre-demploi-remplacement, locaux, materiel, autres.", "title": "Category"}, "profession": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Slug de profession, pas le nom. Voir GET /api/professions. Exemple : osteopathe. Optionnel.", "title": "Profession"}, "department": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Slug de d\u00e9partement. Voir GET /api/departments. Obligatoire pour la France, ignor\u00e9 pour la Belgique et la Suisse. Exemple : haute-garonne.", "title": "Department"}, "country": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Slug de pays. Voir GET /api/countries. Exemples : france, belgique, suisse.", "title": "Country"}, "city": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Ville, recherche partielle.", "title": "City"}, "q": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Recherche dans le titre et la ville. Un admin cherche aussi dans l'email de contact.", "title": "Q"}, "created_after": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "description": "Date-heure ISO 8601 inclusive. Exemple : 2026-09-01T00:00:00+02:00.", "title": "Created After"}, "created_before": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "description": "Date-heure ISO 8601 inclusive. Exemple : 2026-09-26T23:59:59+02:00.", "title": "Created Before"}, "since": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Annonces post\u00e9es il y a moins de cette dur\u00e9e. Format : 30m, 12h ou 7d.", "examples": ["7d"], "title": "Since"}, "status": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Visibilit\u00e9 : active, archived ou all. R\u00e9serv\u00e9 \u00e0 un admin. Sans valeur, un admin re\u00e7oit toutes les annonces.", "examples": ["all"], "title": "Status"}, "is_external": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "description": "Annonce externe. R\u00e9serv\u00e9 \u00e0 un admin.", "title": "Is External"}}, "title": "ListingFilters", "type": "object"}, "CategoryOut": {"properties": {"slug": {"description": "Identifiant utilis\u00e9 dans les filtres et \u00e0 la cr\u00e9ation.", "title": "Slug", "type": "string"}, "name": {"description": "Nom affich\u00e9.", "title": "Name", "type": "string"}}, "required": ["slug", "name"], "title": "CategoryOut", "type": "object"}, "CountryOut": {"properties": {"slug": {"description": "Identifiant utilis\u00e9 dans les filtres et \u00e0 la cr\u00e9ation.", "title": "Slug", "type": "string"}, "name": {"description": "Nom affich\u00e9.", "title": "Name", "type": "string"}, "code": {"description": "Code ISO 3166-1 alpha-2. Exemple : FR.", "title": "Code", "type": "string"}}, "required": ["slug", "name", "code"], "title": "CountryOut", "type": "object"}, "DepartmentOut": {"properties": {"slug": {"description": "Identifiant utilis\u00e9 dans les filtres et \u00e0 la cr\u00e9ation.", "title": "Slug", "type": "string"}, "name": {"description": "Nom affich\u00e9.", "title": "Name", "type": "string"}, "number": {"description": "Num\u00e9ro du d\u00e9partement. Exemples : 31, 2A.", "title": "Number", "type": "string"}}, "required": ["slug", "name", "number"], "title": "DepartmentOut", "type": "object"}, "ImageOut": {"properties": {"id": {"title": "Id", "type": "integer"}, "url": {"description": "URL absolue de l'image.", "title": "Url", "type": "string"}, "order": {"description": "Ordre d'affichage. 0 est la premi\u00e8re image.", "title": "Order", "type": "integer"}}, "required": ["id", "url", "order"], "title": "ImageOut", "type": "object"}, "ListingOut": {"properties": {"id": {"title": "Id", "type": "integer"}, "slug": {"description": "Identifiant de l'annonce, comme dans /annonces/{slug}/.", "title": "Slug", "type": "string"}, "title": {"title": "Title", "type": "string"}, "description": {"title": "Description", "type": "string"}, "category": {"$ref": "#/components/schemas/CategoryOut"}, "profession": {"anyOf": [{"$ref": "#/components/schemas/ProfessionOut"}, {"type": "null"}]}, "country": {"$ref": "#/components/schemas/CountryOut"}, "department": {"anyOf": [{"$ref": "#/components/schemas/DepartmentOut"}, {"type": "null"}]}, "city": {"title": "City", "type": "string"}, "price": {"anyOf": [{"pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", "type": "string"}, {"type": "null"}], "description": "Prix. Pour un local, exemple : 150000. Pour le mat\u00e9riel et les autres annonces, exemple : 1500. Ignor\u00e9 pour une offre d'emploi.", "examples": ["150000", "1500"], "title": "Price"}, "job_type": {"description": "Type de contrat, 50 caract\u00e8res maximum. Obligatoire pour offre-demploi-remplacement, ignor\u00e9 sinon. Exemple : Remplacement.", "title": "Job Type", "type": "string"}, "start_date": {"anyOf": [{"format": "date", "type": "string"}, {"type": "null"}], "description": "Date au format AAAA-MM-JJ. D\u00e9but et fin vont ensemble, ou aucune des deux. Ignor\u00e9es hors offre d'emploi.", "title": "Start Date"}, "end_date": {"anyOf": [{"format": "date", "type": "string"}, {"type": "null"}], "description": "Date au format AAAA-MM-JJ. D\u00e9but et fin vont ensemble, ou aucune des deux. Ignor\u00e9es hors offre d'emploi.", "title": "End Date"}, "salary": {"description": "R\u00e9mun\u00e9ration, 100 caract\u00e8res maximum. Exemple : 50\u20ac/h. Ignor\u00e9e hors offre d'emploi.", "title": "Salary", "type": "string"}, "transaction_type": {"description": "Type de transaction d'une annonce locaux, 50 caract\u00e8res maximum. Obligatoire pour locaux, ignor\u00e9 pour les autres cat\u00e9gories. Exemples : Location, Vente, Cession de bail.", "examples": ["Location", "Vente", "Cession de bail"], "title": "Transaction Type", "type": "string"}, "surface": {"anyOf": [{"type": "integer"}, {"type": "null"}], "description": "Surface en m\u00b2 d'une annonce locaux, entier \u00e0 partir de 0. Ignor\u00e9e pour les autres cat\u00e9gories. Exemple : 50.", "examples": [50], "title": "Surface"}, "is_active": {"description": "false lorsque l'annonce est archiv\u00e9e. Visible par tous les comptes.", "title": "Is Active", "type": "boolean"}, "images": {"items": {"$ref": "#/components/schemas/ImageOut"}, "title": "Images", "type": "array"}, "created_at": {"format": "date-time", "title": "Created At", "type": "string"}, "updated_at": {"format": "date-time", "title": "Updated At", "type": "string"}, "email": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Email de contact. null si l'appelant n'est pas admin.", "title": "Email"}, "phone": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "T\u00e9l\u00e9phone de contact. null si l'appelant n'est pas admin.", "title": "Phone"}, "management_token": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Jeton du lien de gestion d'une annonce sans compte. null si l'appelant n'est pas admin. Null aussi lorsqu'un compte est propri\u00e9taire.", "title": "Management Token"}, "is_external": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "description": "Annonce externe. null si l'appelant n'est pas admin.", "title": "Is External"}, "user": {"anyOf": [{"$ref": "#/components/schemas/UserBrief"}, {"type": "null"}], "description": "Compte propri\u00e9taire. null si l'appelant n'est pas admin. Null aussi lorsque l'annonce n'a pas de compte."}, "deactivated_at": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "description": "Date de la derni\u00e8re d\u00e9sactivation. null si l'appelant n'est pas admin. Null aussi si l'annonce n'a jamais \u00e9t\u00e9 d\u00e9sactiv\u00e9e.", "title": "Deactivated At"}, "deactivated_by": {"anyOf": [{"$ref": "#/components/schemas/UserBrief"}, {"type": "null"}], "description": "Compte qui a d\u00e9sactiv\u00e9 l'annonce. null si l'appelant n'est pas admin. Null aussi pour un archivage automatique, un lien invit\u00e9, ou si l'annonce n'a jamais \u00e9t\u00e9 d\u00e9sactiv\u00e9e."}}, "required": ["id", "slug", "title", "description", "category", "profession", "country", "department", "city", "price", "job_type", "start_date", "end_date", "salary", "transaction_type", "surface", "is_active", "images", "created_at", "updated_at"], "title": "ListingOut", "type": "object"}, "ListingPage": {"properties": {"count": {"description": "Nombre total de r\u00e9sultats, avant pagination.", "title": "Count", "type": "integer"}, "limit": {"title": "Limit", "type": "integer"}, "offset": {"title": "Offset", "type": "integer"}, "results": {"items": {"$ref": "#/components/schemas/ListingOut"}, "title": "Results", "type": "array"}}, "required": ["count", "limit", "offset", "results"], "title": "ListingPage", "type": "object"}, "ProfessionOut": {"properties": {"slug": {"description": "Identifiant utilis\u00e9 dans les filtres et \u00e0 la cr\u00e9ation.", "title": "Slug", "type": "string"}, "name": {"description": "Nom affich\u00e9.", "title": "Name", "type": "string"}}, "required": ["slug", "name"], "title": "ProfessionOut", "type": "object"}, "UserBrief": {"properties": {"id": {"title": "Id", "type": "integer"}, "email": {"title": "Email", "type": "string"}}, "required": ["id", "email"], "title": "UserBrief", "type": "object"}, "ListingIn": {"examples": [{"category": "offre-demploi-remplacement", "city": "Toulouse", "country": "france", "department": "haute-garonne", "description": "Cabinet d'ost\u00e9opathie \u00e0 Toulouse, remplacement de deux semaines.", "email": "votre@email.com", "end_date": "2026-10-23", "job_type": "Remplacement", "phone": "06 12 34 56 78", "profession": "osteopathe", "salary": "50\u20ac/h", "start_date": "2026-10-12", "title": "Recherche rempla\u00e7ant(e) pour cabinet d'ost\u00e9opathie"}, {"category": "locaux", "city": "Toulouse", "country": "france", "department": "haute-garonne", "description": "Cabinet de 50 m\u00b2 \u00e0 louer \u00e0 Toulouse.", "email": "votre@email.com", "phone": "06 12 34 56 78", "price": "150000", "profession": "osteopathe", "surface": 50, "title": "Cabinet d'ost\u00e9opathie \u00e0 louer", "transaction_type": "Location"}], "properties": {"category": {"description": "Slug de cat\u00e9gorie, pas le nom. Voir GET /api/categories. Exemples : offre-demploi-remplacement, locaux, materiel, autres.", "examples": ["offre-demploi-remplacement"], "title": "Category", "type": "string"}, "title": {"description": "Titre, 200 caract\u00e8res maximum.", "maxLength": 200, "title": "Title", "type": "string"}, "description": {"description": "Texte de l'annonce.", "title": "Description", "type": "string"}, "country": {"description": "Slug de pays. Voir GET /api/countries. Exemples : france, belgique, suisse.", "examples": ["france"], "title": "Country", "type": "string"}, "department": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Slug de d\u00e9partement. Voir GET /api/departments. Obligatoire pour la France, ignor\u00e9 pour la Belgique et la Suisse. Exemple : haute-garonne.", "examples": ["haute-garonne"], "title": "Department"}, "city": {"description": "Ville, 100 caract\u00e8res maximum.", "maxLength": 100, "title": "City", "type": "string"}, "profession": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Slug de profession, pas le nom. Voir GET /api/professions. Exemple : osteopathe. Optionnel.", "examples": ["osteopathe"], "title": "Profession"}, "job_type": {"default": "", "description": "Type de contrat, 50 caract\u00e8res maximum. Obligatoire pour offre-demploi-remplacement, ignor\u00e9 sinon. Exemple : Remplacement.", "examples": ["Remplacement"], "maxLength": 50, "title": "Job Type", "type": "string"}, "start_date": {"anyOf": [{"format": "date", "type": "string"}, {"type": "null"}], "description": "Date au format AAAA-MM-JJ. D\u00e9but et fin vont ensemble, ou aucune des deux. Ignor\u00e9es hors offre d'emploi.", "title": "Start Date"}, "end_date": {"anyOf": [{"format": "date", "type": "string"}, {"type": "null"}], "description": "Date au format AAAA-MM-JJ. D\u00e9but et fin vont ensemble, ou aucune des deux. Ignor\u00e9es hors offre d'emploi.", "title": "End Date"}, "salary": {"default": "", "description": "R\u00e9mun\u00e9ration, 100 caract\u00e8res maximum. Exemple : 50\u20ac/h. Ignor\u00e9e hors offre d'emploi.", "examples": ["50\u20ac/h"], "maxLength": 100, "title": "Salary", "type": "string"}, "transaction_type": {"default": "", "description": "Type de transaction d'une annonce locaux, 50 caract\u00e8res maximum. Obligatoire pour locaux, ignor\u00e9 pour les autres cat\u00e9gories. Exemples : Location, Vente, Cession de bail.", "examples": ["Location", "Vente", "Cession de bail"], "maxLength": 50, "title": "Transaction Type", "type": "string"}, "surface": {"anyOf": [{"minimum": 0, "type": "integer"}, {"type": "null"}], "description": "Surface en m\u00b2 d'une annonce locaux, entier \u00e0 partir de 0. Ignor\u00e9e pour les autres cat\u00e9gories. Exemple : 50.", "examples": [50], "title": "Surface"}, "price": {"anyOf": [{"type": "number"}, {"pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", "type": "string"}, {"type": "null"}], "description": "Prix. Pour un local, exemple : 150000. Pour le mat\u00e9riel et les autres annonces, exemple : 1500. Ignor\u00e9 pour une offre d'emploi.", "examples": ["150000", "1500"], "title": "Price"}, "email": {"description": "Email de contact. Peut diff\u00e9rer du compte connect\u00e9. Obligatoire \u00e0 la cr\u00e9ation.", "title": "Email", "type": "string"}, "phone": {"default": "", "description": "T\u00e9l\u00e9phone de contact, 20 caract\u00e8res maximum. Optionnel.", "maxLength": 20, "title": "Phone", "type": "string"}}, "required": ["category", "title", "description", "country", "city", "email"], "title": "ListingIn", "type": "object"}, "ListingUpdateIn": {"properties": {"category": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Slug de cat\u00e9gorie, pas le nom. Voir GET /api/categories. Exemples : offre-demploi-remplacement, locaux, materiel, autres.", "examples": ["offre-demploi-remplacement"], "title": "Category"}, "title": {"anyOf": [{"maxLength": 200, "type": "string"}, {"type": "null"}], "description": "Titre, 200 caract\u00e8res maximum.", "title": "Title"}, "description": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Texte de l'annonce.", "title": "Description"}, "country": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Slug de pays. Voir GET /api/countries. Exemples : france, belgique, suisse.", "examples": ["france"], "title": "Country"}, "department": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Slug de d\u00e9partement. Voir GET /api/departments. Obligatoire pour la France, ignor\u00e9 pour la Belgique et la Suisse. Exemple : haute-garonne.", "examples": ["haute-garonne"], "title": "Department"}, "city": {"anyOf": [{"maxLength": 100, "type": "string"}, {"type": "null"}], "description": "Ville, 100 caract\u00e8res maximum.", "title": "City"}, "profession": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Slug de profession, pas le nom. Voir GET /api/professions. Exemple : osteopathe. Optionnel.", "examples": ["osteopathe"], "title": "Profession"}, "job_type": {"anyOf": [{"maxLength": 50, "type": "string"}, {"type": "null"}], "description": "Type de contrat, 50 caract\u00e8res maximum. Obligatoire pour offre-demploi-remplacement, ignor\u00e9 sinon. Exemple : Remplacement.", "examples": ["Remplacement"], "title": "Job Type"}, "start_date": {"anyOf": [{"format": "date", "type": "string"}, {"type": "null"}], "description": "Date au format AAAA-MM-JJ. D\u00e9but et fin vont ensemble, ou aucune des deux. Ignor\u00e9es hors offre d'emploi.", "title": "Start Date"}, "end_date": {"anyOf": [{"format": "date", "type": "string"}, {"type": "null"}], "description": "Date au format AAAA-MM-JJ. D\u00e9but et fin vont ensemble, ou aucune des deux. Ignor\u00e9es hors offre d'emploi.", "title": "End Date"}, "salary": {"anyOf": [{"maxLength": 100, "type": "string"}, {"type": "null"}], "description": "R\u00e9mun\u00e9ration, 100 caract\u00e8res maximum. Exemple : 50\u20ac/h. Ignor\u00e9e hors offre d'emploi.", "examples": ["50\u20ac/h"], "title": "Salary"}, "transaction_type": {"anyOf": [{"maxLength": 50, "type": "string"}, {"type": "null"}], "description": "Type de transaction d'une annonce locaux, 50 caract\u00e8res maximum. Obligatoire pour locaux, ignor\u00e9 pour les autres cat\u00e9gories. Exemples : Location, Vente, Cession de bail.", "examples": ["Location", "Vente", "Cession de bail"], "title": "Transaction Type"}, "surface": {"anyOf": [{"minimum": 0, "type": "integer"}, {"type": "null"}], "description": "Surface en m\u00b2 d'une annonce locaux, entier \u00e0 partir de 0. Ignor\u00e9e pour les autres cat\u00e9gories. Exemple : 50.", "examples": [50], "title": "Surface"}, "price": {"anyOf": [{"type": "number"}, {"pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", "type": "string"}, {"type": "null"}], "description": "Prix. Pour un local, exemple : 150000. Pour le mat\u00e9riel et les autres annonces, exemple : 1500. Ignor\u00e9 pour une offre d'emploi.", "examples": ["150000", "1500"], "title": "Price"}, "email": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Email de contact. Peut diff\u00e9rer du compte connect\u00e9.", "title": "Email"}, "phone": {"anyOf": [{"maxLength": 20, "type": "string"}, {"type": "null"}], "description": "T\u00e9l\u00e9phone de contact, 20 caract\u00e8res maximum. Optionnel.", "title": "Phone"}, "user_email": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Email du compte propri\u00e9taire. R\u00e9serv\u00e9 \u00e0 un admin. null d\u00e9tache le compte.", "title": "User Email"}, "is_active": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "description": "R\u00e9serv\u00e9 \u00e0 un admin. false archive l'annonce et enregistre l'appelant. true r\u00e9active sans modifier la date ni l'auteur de la d\u00e9sactivation.", "title": "Is Active"}, "is_external": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "description": "R\u00e9serv\u00e9 \u00e0 un admin. true : archivage automatique au bout de 10 jours. false : 60 jours.", "title": "Is External"}}, "title": "ListingUpdateIn", "type": "object"}, "ImagePatch": {"properties": {"order": {"description": "Ordre d'affichage. 0 est la premi\u00e8re image.", "minimum": 0, "title": "Order", "type": "integer"}}, "required": ["order"], "title": "ImagePatch", "type": "object"}, "ContactOut": {"properties": {"id": {"title": "Id", "type": "integer"}, "email": {"title": "Email", "type": "string"}, "phone": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Phone"}, "message": {"title": "Message", "type": "string"}, "created_at": {"format": "date-time", "title": "Created At", "type": "string"}}, "required": ["id", "email", "phone", "message", "created_at"], "title": "ContactOut", "type": "object"}, "ContactIn": {"properties": {"email": {"description": "Email de l'exp\u00e9diteur.", "title": "Email", "type": "string"}, "phone": {"anyOf": [{"maxLength": 20, "type": "string"}, {"type": "null"}], "description": "T\u00e9l\u00e9phone, 20 caract\u00e8res maximum. Optionnel.", "title": "Phone"}, "message": {"description": "Texte du message.", "title": "Message", "type": "string"}}, "required": ["email", "message"], "title": "ContactIn", "type": "object"}, "PageQuery": {"properties": {"limit": {"default": 20, "description": "Nombre de r\u00e9sultats, entier de 1 \u00e0 100.", "title": "Limit", "type": "integer"}, "offset": {"default": 0, "description": "Nombre de r\u00e9sultats saut\u00e9s, entier \u00e0 partir de 0.", "title": "Offset", "type": "integer"}}, "title": "PageQuery", "type": "object"}, "ContactPage": {"properties": {"count": {"description": "Nombre total de messages, avant pagination.", "title": "Count", "type": "integer"}, "limit": {"title": "Limit", "type": "integer"}, "offset": {"title": "Offset", "type": "integer"}, "results": {"items": {"$ref": "#/components/schemas/ContactOut"}, "title": "Results", "type": "array"}}, "required": ["count", "limit", "offset", "results"], "title": "ContactPage", "type": "object"}, "TrackingQuery": {"properties": {"limit": {"default": 20, "description": "Nombre de r\u00e9sultats, entier de 1 \u00e0 100.", "title": "Limit", "type": "integer"}, "offset": {"default": 0, "description": "Nombre de r\u00e9sultats saut\u00e9s, entier \u00e0 partir de 0.", "title": "Offset", "type": "integer"}, "listing": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Slug de l'annonce. R\u00e9pond 404 si ce slug est inconnu.", "title": "Listing"}}, "title": "TrackingQuery", "type": "object"}, "TrackingOut": {"properties": {"id": {"title": "Id", "type": "integer"}, "listing_slug": {"description": "Slug de l'annonce dont les coordonn\u00e9es ont \u00e9t\u00e9 r\u00e9v\u00e9l\u00e9es.", "title": "Listing Slug", "type": "string"}, "listing_title": {"title": "Listing Title", "type": "string"}, "created_at": {"description": "Date de la r\u00e9v\u00e9lation.", "format": "date-time", "title": "Created At", "type": "string"}}, "required": ["id", "listing_slug", "listing_title", "created_at"], "title": "TrackingOut", "type": "object"}, "TrackingPage": {"properties": {"count": {"description": "Nombre total de r\u00e9v\u00e9lations, avant pagination.", "title": "Count", "type": "integer"}, "limit": {"title": "Limit", "type": "integer"}, "offset": {"title": "Offset", "type": "integer"}, "results": {"items": {"$ref": "#/components/schemas/TrackingOut"}, "title": "Results", "type": "array"}}, "required": ["count", "limit", "offset", "results"], "title": "TrackingPage", "type": "object"}, "UserQuery": {"properties": {"limit": {"default": 20, "description": "Nombre de r\u00e9sultats, entier de 1 \u00e0 100.", "title": "Limit", "type": "integer"}, "offset": {"default": 0, "description": "Nombre de r\u00e9sultats saut\u00e9s, entier \u00e0 partir de 0.", "title": "Offset", "type": "integer"}, "q": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Recherche partielle dans l'email.", "title": "Q"}}, "title": "UserQuery", "type": "object"}, "UserOut": {"properties": {"id": {"title": "Id", "type": "integer"}, "email": {"title": "Email", "type": "string"}, "is_active": {"description": "false lorsque le compte ne peut plus se connecter.", "title": "Is Active", "type": "boolean"}, "is_staff": {"description": "true si le compte est administrateur.", "title": "Is Staff", "type": "boolean"}, "date_joined": {"format": "date-time", "title": "Date Joined", "type": "string"}}, "required": ["id", "email", "is_active", "is_staff", "date_joined"], "title": "UserOut", "type": "object"}, "UserPage": {"properties": {"count": {"description": "Nombre total de comptes, avant pagination.", "title": "Count", "type": "integer"}, "limit": {"title": "Limit", "type": "integer"}, "offset": {"title": "Offset", "type": "integer"}, "results": {"items": {"$ref": "#/components/schemas/UserOut"}, "title": "Results", "type": "array"}}, "required": ["count", "limit", "offset", "results"], "title": "UserPage", "type": "object"}, "UserIn": {"properties": {"email": {"description": "Email du nouveau compte.", "title": "Email", "type": "string"}, "password": {"description": "Mot de passe du nouveau compte. Il n'est pas renvoy\u00e9.", "title": "Password", "type": "string"}}, "required": ["email", "password"], "title": "UserIn", "type": "object"}, "UserDetail": {"properties": {"id": {"title": "Id", "type": "integer"}, "email": {"title": "Email", "type": "string"}, "is_active": {"description": "false lorsque le compte ne peut plus se connecter.", "title": "Is Active", "type": "boolean"}, "is_staff": {"description": "true si le compte est administrateur.", "title": "Is Staff", "type": "boolean"}, "date_joined": {"format": "date-time", "title": "Date Joined", "type": "string"}, "listings": {"description": "Annonces de ce compte, actives et archiv\u00e9es.", "items": {"$ref": "#/components/schemas/ListingOut"}, "title": "Listings", "type": "array"}}, "required": ["id", "email", "is_active", "is_staff", "date_joined", "listings"], "title": "UserDetail", "type": "object"}, "UserPatch": {"properties": {"is_active": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "description": "Autorise ou bloque la connexion. Un champ absent ne change pas.", "title": "Is Active"}, "is_staff": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "description": "Donne ou retire le r\u00f4le administrateur. Un champ absent ne change pas.", "title": "Is Staff"}}, "title": "UserPatch", "type": "object"}, "DetailOut": {"properties": {"detail": {"description": "Message de confirmation.", "title": "Detail", "type": "string"}}, "required": ["detail"], "title": "DetailOut", "type": "object"}}, "securitySchemes": {"BearerAuth": {"type": "http", "scheme": "bearer"}}}, "servers": []}