Metadata-Version: 2.5
Name: permisapi-client
Version: 0.8.1
Summary: Client Python officiel pour PermisAPI : permis de construire de France en API REST.
Project-URL: Homepage, https://permisapi.fr
Project-URL: Documentation, https://permisapi.fr/docs
Project-URL: Changelog, https://permisapi.fr/changelog
Author-email: Evan Caroux <evan@permisapi.fr>
License: MIT
Keywords: api,construire,france,geocoding,permis,proptech,real-estate,sitadel
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Natural Language :: French
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.9
Requires-Dist: httpx>=0.24.0
Provides-Extra: dev
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=7.4; extra == 'dev'
Requires-Dist: respx>=0.20; extra == 'dev'
Description-Content-Type: text/markdown

# permisapi-client

Client Python officiel de [PermisAPI](https://permisapi.fr) : 1,3 million de permis de
construire en France (base Sitadel, permis autorisés depuis 2013, 101 départements) dans
une API REST.

## Installation

```bash
pip install permisapi-client
```

## Démarrage rapide

```python
from permisapi_client import PermisAPI

client = PermisAPI(api_key="pk_live_...")  # clé obtenue sur https://permisapi.fr/#pricing

# Liste paginée : {"data": [...], "pagination": {page, limit, total, has_next, next_url}}
page = client.permits.list(dep_code="75", limit=10)
for permit in page["data"]:
    print(permit["num_pa"], permit["full_address"])
print(page["pagination"]["total"], "permis au total")

# Recherche géographique (PostGIS) : liste de permis, chacun avec distance_m
nearby = client.permits.near(lat=48.8599, lng=2.3423, radius_m=300)

# Détail d'un permis
permit = client.permits.get("07510125V0025")

# Statistiques d'une commune (Paris entier est rangé sous le code 75056 dans Sitadel)
stats = client.stats.commune("75056")
print(stats["total_permits"], stats["granted"], stats["trend_yoy_pct"])

# Toutes les pages, jusqu'à pagination.has_next == False
for p in client.permits.iter_all(dep_code="75", permit_type="PC_LOGEMENT"):
    print(p["num_pa"])
```

Coût : une recherche (`list`, `iter_all`, `near`, `search`) décompte 1 requête par permis
renvoyé. `iter_all` sur un département entier peut donc épuiser le quota mensuel de la
formule Gratuit (500 requêtes) ou Explorer (5 000).

Formules : **Gratuit** (1 département, 6 mois d'historique), **Explorer** (5 départements,
6 mois), **Régional** (5 départements, historique complet et données avancées), **Pro**
(les mêmes données avancées sur la France entière), **Business** et **Enterprise**. Les
exemples de cette page portent sur Paris (75).

## Données avancées (formules Régional, Pro, Business et Enterprise)

Les formules Gratuit et Explorer reçoivent une erreur 402 sur ces routes.

```python
num_pa = "07510125V0025"

# Ventes immobilières DVF voisines (ventes de 2021 à 2025)
dvf = client.permits.dvf(num_pa, limit=5)
for m in dvf["matches"]:
    print(m["date_mutation"], m["valeur_fonciere"], "EUR à", m["distance_m"], "m")
print("Prix médian au m² :", dvf["dvf_median_price_per_m2"])

# Score d'opportunité marchand de biens v0.3 (note 0-100 et palier)
score = client.permits.score_mdb(num_pa)
print(f"Score : {score['score']}/100 ({score['tier']})")
# Score déjà pré-calculé : components vaut None ; le détail des 11 signaux
# est donné par la route /v1/permits/{num_pa}/score/explain.

# Zonage PLU (constructible, à urbaniser, agricole, naturel)
plu = client.permits.plu(num_pa)
if plu["has_plu"]:
    z = plu["zonage"]
    print(f"Zone {z['code']}, constructible : {z['constructible']}")

# Risques Géorisques (inondation, séisme, sites Seveso proches...)
risks = client.permits.risks(num_pa)
print(f"Risque : {risks['risk_score']}/100 ({risks['risk_tier']})")
for risk in risks["risks"]:
    print(f"- {risk['label']} (PPR : {risk['has_ppr']})")

# Votre avis sur un score
client.permits.score_feedback(
    num_pa=num_pa,
    score_received=72,
    tier_received="high",
    useful=True,
    accuracy="correct",
    comment="Score cohérent avec notre analyse du quartier.",
)
```

## Export CSV (Business et Enterprise)

```python
# Plafond : 100 000 lignes en Business, 1 000 000 en Enterprise
csv_bytes = client.permits.export(
    dep_code="75",
    permit_type="PC_LOGEMENT",
    date_from="2024-01-01",
    max_rows=5000,
)
with open("paris.csv", "wb") as f:
    f.write(csv_bytes)
```

## Serveur MCP (Claude, ChatGPT, Cursor)

Le serveur MCP officiel (paquet séparé `permisapi-mcp`) permet à Claude, ChatGPT, Claude
Desktop, Cursor et Windsurf d'interroger PermisAPI en langage naturel :

```bash
pip install permisapi-mcp
```

Configurez votre clé dans le client MCP, puis demandez directement :
> *« Liste les permis de logement autorisés à Paris ce mois-ci »*

Mise en place complète : [permisapi.fr/mcp](https://permisapi.fr/mcp).

## Async

```python
import asyncio
from permisapi_client import AsyncPermisAPI

async def main():
    async with AsyncPermisAPI(api_key="pk_live_...") as client:
        page = await client.permits.list(dep_code="75")
        print(page["pagination"]["total"])
        async for p in client.permits.iter_all(dep_code="75", permit_type="PC_LOGEMENT"):
            print(p["num_pa"])

asyncio.run(main())
```

## Alertes par webhook

```python
alert = client.alerts.create(
    name="Paris 18e rénovations",
    center_lat=48.88,
    center_lng=2.35,
    radius_km=3,
    permit_types=["PC_LOGEMENT"],
    webhook_url="https://votre-application.fr/permisapi-hook",
)
# Le webhook_secret n'est renvoyé qu'ICI : conservez-le pour vérifier les signatures HMAC.
print(alert["webhook_secret"])
```

## Gestion des erreurs

```python
from permisapi_client import (
    PermisAPI,
    PermisAPIRateLimited,
    PermisAPIQuotaExceeded,
    PermisAPIUnauthorized,
)

try:
    client.permits.list(dep_code="75")
except PermisAPIUnauthorized:
    print("Clé API invalide")
except PermisAPIQuotaExceeded:
    print("Quota mensuel atteint : il se renouvelle le 1er du mois (UTC)")
except PermisAPIRateLimited as e:
    print(f"Trop de requêtes par minute, nouvel essai possible dans {e.retry_after} s")
```

## Licence

MIT. Source des permis : SDES, base Sitadel, data.gouv.fr (Licence Ouverte Etalab).

## Liens

- Documentation : https://permisapi.fr/docs
- Référence complète de l'API : https://api.permisapi.fr/docs
- Nouveautés : https://permisapi.fr/changelog
