Pluia

API Pluia public-api/1

Mêmes règles que l'écran Analyse et le rapport PDF de Pluia, mêmes droits et mêmes quotas que le compte titulaire de la clé. Chaque règle porte son statut (Vérifié, Texte officiel, Selon le projet, Sous conditions, Non déterminé, À vérifier, Pas de règle propre) et sa preuve (page, extrait, lien vers le PDF officiel à la page). Seules les règles is_verified = true ont été relues à la page citée du règlement officiel ; elles s'appliquent avec leur condition.

Démarrer

  1. Créez une clé dans Compte → Clés d’API (affichée une seule fois).
  2. Envoyez-la dans l’en-tête Authorization: Bearer pluia_…, jamais dans l’adresse (une clé vue dans une adresse est révoquée).
  3. Les opérations qui consomment un dossier, un export PDF ou une question décomptent le même quota que l’application.
curl -H "Authorization: Bearer $PLUIA_API_KEY" https://pluia.fr/api/v1/me
curl -H "Authorization: Bearer $PLUIA_API_KEY" "https://pluia.fr/api/v1/parcels/search?address=1+place+de+la+mairie+91430+Igny"

Statuts des règles

Seules les valeurs « Vérifié » (is_verified = true) ont été relues à la page citée du PDF officiel ; elles s’appliquent avec leur condition. Toutes les autres se lisent dans le règlement, à la page indiquée par proof.href. Une source qui ne répond pas n’est jamais présentée comme « aucune règle ».

assertionLibelléSens
verifiedVérifiéValeur relue à la page citée du PDF officiel de la zone, à lire avec sa condition.
official_textTexte officielPassage officiel cité, valeur à confirmer en le lisant.
by_projectSelon le projetValeur fixée par le règlement pour chaque cas du projet.
conditionalSous conditionsValeur applicable sous conditions à lire.
undeterminedNon déterminéRègle non trouvée ou non tranchée dans les extraits : à lire dans le règlement.
to_verifyÀ vérifierValeur introuvable à la page citée, ou preuve non relue dans le PDF officiel.
zone_silentPas de règle propreLe chapitre de la zone ne fixe pas de règle propre sur ce point (l’article qui l’encadre ne le vise pas) : à confirmer dans le règlement, jamais une valeur.

Opérations

GET/

Point d'entrée de l'API

Sans clé.

GET/openapi.json

Contrat OpenAPI 3.1

Sans clé.

GET/docs

Documentation lisible

Sans clé.

GET/me

Ma clé, mon offre et mes quotas

Quotas lus comme l'écran Abonnement (mêmes chiffres).

Portée : read.

Réponse 200 :

{
    "key": {
        "public_id": "a1b2c3d4",
        "name": "Cursor",
        "environment": "live",
        "scopes": [
            "read",
            "studio"
        ],
        "expires_at": "2027-10-07T00:00:00Z",
        "rate_limit_per_minute": 120,
        "daily_consumption_cap": 20
    },
    "account": {
        "plan": "pro",
        "plan_label": "Pro",
        "email_verified": true
    },
    "quotas": {
        "dossiers": {
            "used": 3,
            "limit": 20,
            "remaining": 17,
            "unlimited": false
        }
    },
    "period": {
        "scope": "billing_period",
        "start": "2026-10-01T00:00:00Z",
        "end": "2026-10-31T23:59:59Z"
    }
}

GET/parcels/{idu}

Une parcelle avec sa géométrie

Portée : read.

ParamètreOùDescription
idu *pathIDU cadastral, ex. 91312000AE0290

GET/parcels/{idu}/zoning

Zonage PLU d'une parcelle

Zone au point de référence (moyenne des sommets, point intérieur si elle tombe hors de la parcelle), zones qui traversent la parcelle avec leur part de surface, document du Géoportail, état de l'index et version du règlement indexé.

Portée : read.

ParamètreOùDescription
idu *pathIDU cadastral, ex. 91312000AE0290

GET/parcels/{idu}/constraints

Servitudes, prescriptions et patrimoine

Une liste vaut « unavailable » quand sa source n'a pas répondu, « partial » quand une couche manque : jamais « aucune » par défaut.

Portée : read.

ParamètreOùDescription
idu *pathIDU cadastral, ex. 91312000AE0290

GET/communes/{insee}/regulation

État du règlement d'une commune

Lecture seule : ne déclenche jamais d'indexation (la création d'un dossier le fait, comme dans l'application).

Portée : read.

ParamètreOùDescription
insee *pathCode INSEE

GET/documents/{document_id}

État d'un document indexé

Portée : read.

ParamètreOùDescription
document_id *pathDocument indexé (plu_documents)

GET/documents/{document_id}/pages/{page}

Texte d'une page du règlement officiel

Documents cités par un de vos dossiers (ou téléversés par vous), 300 pages par jour. Texte de la couche texte du PDF officiel, non retouché ; null si le PDF est scanné ou pas encore extrait (ouvrir pdf_url).

Portée : read.

ParamètreOùDescription
document_id *pathDocument indexé (plu_documents)
page *pathPage (1 = première)

GET/dossiers

Lister mes dossiers

Portée : read.

ParamètreOùDescription
iduqueryDossier d'une parcelle
limitqueryTaille de page
offsetqueryDécalage

POST/dossiers

Créer le dossier PLU d'une parcelle (1 dossier)

Même analyse que l'application (même moteur, même quota, réservation idempotente par parcelle et période). Commune non indexée : 202 indexing, rien décompté. Commune sans PLU (RNU, carte communale, POS) : dossier enregistré, non décompté.

Portée : analyze. Quota : dossiers (même décompte que l’application).

ParamètreOùDescription
Idempotency-KeyheaderMême clé + même corps = même opération, jamais décomptée deux fois ; autre corps = 409.
PreferheaderAttente maximale (RFC 7240, 1 à 240 s, défaut 60) ; au-delà, 202 processing puis relance à l'identique.

Corps :

{
    "idu": "91312000AE0290"
}

GET/dossiers/{id}

Un dossier

Portée : read.

ParamètreOùDescription
id *pathDossier (parcel_analyses)

GET/dossiers/{id}/rules

Règles clés prouvées

Mêmes valeurs, statuts, pages et liens que l'écran Analyse et le rapport PDF. Chaque règle : assertion (statut), is_verified, proof (page, extrait, lien PDF#page=N).

Portée : read.

ParamètreOùDescription
id *pathDossier (parcel_analyses)
includequerytopics (12 thèmes) et/ou attention (points d'attention)

Réponse 200 :

{
    "dossier_id": "0d8f3c1e-0000-4000-8000-000000000001",
    "zone": "UH",
    "document": {
        "name": "91312_PLU_20231005",
        "approval": "05/10/2023",
        "version_status": "current"
    },
    "versioned": true,
    "key_rules": [
        {
            "id": "emprise_sol",
            "label": "Emprise au sol",
            "state": "value",
            "assertion": "verified",
            "assertion_label": "Vérifié",
            "is_verified": true,
            "value": "40 %",
            "measure": {
                "value": 40,
                "unit": "%"
            },
            "condition": "",
            "to_read": "",
            "reference": {
                "article": "Article UH 9",
                "pages": "p. 87",
                "label": "Article UH 9 · p. 87",
                "href": "https://data.geopf.fr/annexes/gpu/documents/DU_91312/…/91312_reglement_20231005.pdf#page=87"
            },
            "proof": {
                "status": "verified",
                "label": "Vérifié · p. 87",
                "page": 87,
                "excerpt": "L'emprise au sol des constructions ne peut excéder 40 %…",
                "href": "https://data.geopf.fr/…#page=87",
                "figures": [
                    "40 %"
                ],
                "official": true
            }
        }
    ],
    "notice": "Seules les valeurs « Vérifié » (is_verified = true) ont été relues à la page citée du règlement officiel ; elles s’appliquent avec leur condition. Les autres se lisent dans le règlement à la page indiquée.",
    "sources": []
}

GET/dossiers/{id}/regulation-version

Version du règlement servi

Portée : read.

ParamètreOùDescription
id *pathDossier (parcel_analyses)

GET/dossiers/{id}/studio-rules

Seuils sourcés pour le Studio et la CAO (studio-rules/1)

Portée : studio.

ParamètreOùDescription
id *pathDossier (parcel_analyses)
If-None-MatchheaderETag d'une réponse précédente

POST/dossiers/{id}/checks

Vérifier un projet (verdict règle par règle)

Verdict d’un projet (extension, véranda, piscine, garage, abri…) sur les règles du dossier, avec le moteur du Studio : mêmes seuils, mêmes textes que le Studio, l’écran et le PDF. Les mesures sont DÉCLARÉES par l’appelant (aucun plan n’est contrôlé) et le disent dans `warnings`. Une mesure absente reste « Non déterminé » ; sans `existing_footprint_m2`, l’emprise n’est jamais déclarée conforme. Le code de sortie du CLI suit `exit_code` (0 oui, 1 non, 2 sous conditions ou non déterminé). Aucun quota, aucun appel LLM. Même garde d’offre que le Studio ; 503 tant que le moteur n’est pas installé sur le serveur.

Portée : studio.

ParamètreOùDescription
id *pathDossier (parcel_analyses)

Corps :

{
    "project_type": "extension",
    "measures": {
        "height_m": 3.5,
        "project_footprint_m2": 30,
        "existing_footprint_m2": 120,
        "street_distance_m": 6,
        "side_distance_m": 4,
        "rear_distance_m": 10,
        "open_ground_m2": 250
    },
    "answers": {
        "facade_openings": false
    }
}

Réponse 200 :

{
    "contract": "pluia-check/1",
    "engine": "rule-eval/1",
    "rules_contract": "studio-rules/1",
    "dossier_id": "0d8f3c1e-5a2b-4c3d-8e9f-0a1b2c3d4e5f",
    "verdict": {
        "code": "conditional",
        "label": "Sous conditions",
        "subtitle": null,
        "detail": "Le verdict dépend de vos précisions sur le projet.",
        "conclusive": false,
        "reasons": [],
        "corrective_actions": []
    },
    "exit_code": 2,
    "checks": [
        {
            "id": "hauteur",
            "family": "hauteur",
            "metric": "height_max",
            "label": "Hauteur maximale",
            "status": "pass",
            "status_label": "Conforme",
            "requirement": "Maximum 10 m",
            "measured": "3,5 m",
            "note": null,
            "confidence": "high",
            "question_ids": [],
            "corrective_action": null,
            "indicative": false,
            "source": {
                "article": "Article UC 10",
                "article_verified": true,
                "page": 88,
                "excerpt": "La hauteur maximale des constructions ne peut excéder 10 mètres.",
                "href": "https://www.geoportail-urbanisme.gouv.fr/api/document/0123/files/reglement.pdf#page=88",
                "document_id": "6b1e0a52-3f4d-4c5b-9a6e-7f8091a2b3c4",
                "assertion": "official_text"
            }
        }
    ],
    "questions": [
        {
            "id": "facade_openings",
            "label": "La façade du projet face à la limite aura-t-elle des fenêtres ou des baies ?",
            "kind": "boolean",
            "decisive": true,
            "options": [
                {
                    "value": true,
                    "label": "Oui"
                },
                {
                    "value": false,
                    "label": "Non"
                }
            ],
            "answer": false
        }
    ],
    "blockers": [
        "Retrait des limites séparatives : répondre à la question sur les baies"
    ],
    "source_status": "ok",
    "warnings": [
        "Mesures déclarées par l’appelant, non contrôlées sur un plan."
    ]
}

POST/dossiers/{id}/report

Rapport PDF du dossier (1 export)

Le rapport de l'application (même modèle que l'écran). Rejeu de la même Idempotency-Key : jamais décompté deux fois. Rendu PDF indisponible : HTML imprimable (Pluia-Report-Format: html).

Portée : report. Quota : pdf_exports (même décompte que l’application).

ParamètreOùDescription
id *pathDossier (parcel_analyses)
Idempotency-KeyheaderMême clé + même corps = même opération, jamais décomptée deux fois ; autre corps = 409.
PreferheaderAttente maximale (RFC 7240, 1 à 240 s, défaut 60) ; au-delà, 202 processing puis relance à l'identique.

Corps :

[]

POST/dossiers/{id}/questions

Poser une question sur le règlement (1 question)

Agent PLU de l'application (même moteur de réponse, mêmes citations). Chaque question compte ; un refus motivé est une réponse valide.

Portée : ask. Quota : agent_questions (même décompte que l’application).

ParamètreOùDescription
id *pathDossier (parcel_analyses)
Idempotency-KeyheaderMême clé + même corps = même opération, jamais décomptée deux fois ; autre corps = 409.

Corps :

{
    "question": "Puis-je construire en limite séparative ?"
}

Agents IA (MCP) et CLI

Le même noyau est exposé au protocole MCP : POST https://pluia.fr/mcp (Streamable HTTP, JSON-RPC, Authorization: Bearer). Les outils (trouver_parcelle, zonage_parcelle, regles_parcelle, verifier_projet, citer_reglement…) exécutent les opérations de cette page avec la même clé, les mêmes portées et les mêmes quotas ; un client ne voit que les outils que les portées de sa clé ouvrent, et ceux qui consomment demandent l’accord de l’utilisateur.

claude mcp add --transport http pluia https://pluia.fr/mcp --header "Authorization: Bearer $PLUIA_API_KEY"

La CLI pluia-plu (login, regles, verifier, surveiller…) et son pont stdio pluia-plu mcp sont dans le dépôt, packages/pluia-plu.

Erreurs

Format application/problem+json (RFC 9457) : type, title, status, code, detail, request_id, éventuellement legacy_code et details.

codeHTTPTitre
invalid_api_key401Clé d’API invalide
api_key_in_url400Clé d’API dans l’adresse
bad_request400Requête invalide
scope_missing403Portée manquante
email_verification_required403E-mail à vérifier
plan_required403Offre requise
current_password_invalid403Mot de passe incorrect
quota_exceeded402Quota épuisé
free_parcel_locked402Découverte liée à une autre parcelle
not_found404Ressource introuvable
parcel_not_found404Parcelle introuvable
dossier_required404Dossier à créer
method_not_allowed405Méthode non autorisée
not_acceptable406Format de réponse non disponible
idempotency_conflict409Clé d’idempotence déjà utilisée
operation_in_progress409Opération en cours
operation_already_completed409Opération déjà traitée
manual_review_required409Vérification nécessaire
key_limit_reached409Nombre maximal de clés atteint
payload_too_large413Corps trop volumineux
unsupported_media_type415Format de corps non pris en charge
validation_failed422Paramètres invalides
rate_limited429Trop de requêtes
too_many_concurrent429Trop d’opérations simultanées
consumption_cap_reached429Plafond quotidien de la clé atteint
global_cap_reached429Capacité quotidienne du service atteinte
indexing_budget_exhausted429Plafond d’indexations atteint
internal_error500Erreur interne
upstream_unavailable502Source officielle indisponible
service_unavailable503Service indisponible
timeout504Délai dépassé