Startup PropTech : integrer PermisAPI dans un pipeline Next.js + Postgres
Persona : dev PropTech solo ou petite equipe, pile Next.js / Python. Temps : 1-2 heures pour l'integration initiale. Budget : 0 EUR au demarrage (Free tier PermisAPI, hosting gratuit existant Vercel/Railway/Neon sur le projet PropTech).
Cas d'usage
Tu construis un produit PropTech qui :
- Affiche une carte de permis pres d'un bien immo
- Calcule un score "dynamisme du quartier" (volume permis 12 mois)
- Alerte l'utilisateur quand un nouveau permis > X m2 apparait pres de chez lui
Architecture
PermisAPI <---> Ton backend Python/Node <---> Ta DB (cache) <---> Ton frontend
|
v
Ton webhook /alerts (recoit les pushes)
Step 1. Dependances minimales
Python :
pip install httpx pydantic
Node/TypeScript :
npm install axios zod
Step 2. Client TypeScript type-safe
lib/permisapi.ts :
import axios, { AxiosInstance } from "axios";
import { z } from "zod";
const PermitSchema = z.object({
id: z.number(),
num_pa: z.string(),
dep_code: z.string().nullable(),
comm_code: z.string().nullable(),
adr_localite_ter: z.string().nullable(),
full_address: z.string().nullable(),
lat: z.number().nullable(),
lng: z.number().nullable(),
etat_pa: z.number().nullable(),
date_reelle_autorisation: z.string().nullable(),
permit_type: z.string().nullable(),
superficie_terrain: z.number().nullable(),
denom_dem: z.string().nullable(),
});
type Permit = z.infer<typeof PermitSchema>;
const PageSchema = z.object({
data: z.array(PermitSchema),
pagination: z.object({
page: z.number(),
limit: z.number(),
total: z.number(),
has_next: z.boolean(),
}),
});
export class PermisAPIClient {
private http: AxiosInstance;
constructor(apiKey: string) {
this.http = axios.create({
baseURL: "https://api.permisapi.fr",
headers: { "X-API-Key": apiKey },
timeout: 10_000,
});
}
async permitsNear(
lat: number,
lng: number,
radiusM = 1000,
limit = 50
): Promise<Permit[]> {
const resp = await this.http.get("/v1/permits/near", {
params: { lat, lng, radius_m: radiusM, limit },
});
return PageSchema.parse(resp.data).data;
}
async statsCommune(code: string) {
const resp = await this.http.get(`/v1/stats/commune/${code}`);
return resp.data;
}
}
Step 3. Cache cote serveur (important)
PermisAPI Free = 500 req/mois. Sans cache, si ton user parcourt 10 biens, tu fais 10 req. 50 users = quota explose.
Strategie : cache Redis / Postgres TTL 1h :
// lib/cachedPermisapi.ts
import { PermisAPIClient } from "./permisapi";
const api = new PermisAPIClient(process.env.PERMISAPI_KEY!);
export async function permitsNearCached(
lat: number, lng: number, radiusM = 1000
): Promise<Permit[]> {
// 3 decimales = ~100m. Groupe les requetes dans une tile grossiere.
const key = `permits:${lat.toFixed(3)}:${lng.toFixed(3)}:${radiusM}`;
const cached = await redis.get(key);
if (cached) return JSON.parse(cached);
const fresh = await api.permitsNear(lat, lng, radiusM);
await redis.setex(key, 3600, JSON.stringify(fresh));
return fresh;
}
Step 4. Route Next.js pour ton front
app/api/permits-near/route.ts :
import { NextRequest, NextResponse } from "next/server";
import { permitsNearCached } from "@/lib/cachedPermisapi";
export async function GET(req: NextRequest) {
const sp = req.nextUrl.searchParams;
const lat = parseFloat(sp.get("lat")!);
const lng = parseFloat(sp.get("lng")!);
if (!lat || !lng) {
return NextResponse.json({ error: "lat/lng required" }, { status: 400 });
}
const permits = await permitsNearCached(lat, lng);
return NextResponse.json({ permits });
}
Important : n'expose jamais ta cle PermisAPI au browser. Toutes les requetes passent par ton backend.
Step 5. Composant carte
Voir le tuto separe docs/tutorials/05_carte_interactive.md (si ecrit)
ou utilise directement MapLibre GL + le composant deja developpe sur
notre landing (landing/components/map/).
Step 6. Score dynamisme de quartier
export async function dynamismScore(commCode: string) {
const stats = await api.statsCommune(commCode);
const {
total_permits,
last_12_months_count,
trend_yoy_pct,
} = stats;
// Formule simple, a ajuster
const volume = Math.min(last_12_months_count / 100, 1); // 0-1
const trend = Math.max(Math.min((trend_yoy_pct || 0) / 50, 1), -1);
return {
score: Math.round((volume * 0.6 + (trend + 1) / 2 * 0.4) * 100),
volume, trend,
raw: stats,
};
}
Step 7. Webhook recevoir les alertes
Endpoint de ton backend pour recevoir les POST PermisAPI :
// app/api/permisapi-webhook/route.ts
import crypto from "crypto";
export async function POST(req: NextRequest) {
const body = await req.text();
const sig = req.headers.get("x-permisapi-signature") || "";
// Verification HMAC SHA256
const expected = crypto
.createHmac("sha256", process.env.PERMISAPI_WEBHOOK_SECRET!)
.update(body).digest("hex");
if (sig !== expected) {
return NextResponse.json({ error: "invalid signature" }, { status: 401 });
}
const event = JSON.parse(body);
// event = { event: "permit_matched", alert_id, permit: {...} }
await savePermitToMyDB(event.permit);
await notifyUser(event.permit);
return NextResponse.json({ ok: true });
}
Step 8. Deploy
Ton projet PropTech probablement sur Vercel + Neon. Ajoute :
PERMISAPI_KEY=pk_live_...
PERMISAPI_WEBHOOK_SECRET=...
REDIS_URL=... (Upstash free 10k commandes/jour)
Deploy : git push. Rien de plus.
Budget total
- PermisAPI Free : 0 EUR (Explorer 49 EUR necessaire si > 500 req/mois)
- Vercel : 0 EUR (Hobby tier)
- Neon : 0 EUR (Free 512 MB)
- Upstash Redis : 0 EUR (10k cmd/jour)
- Total : 0-49 EUR/mois selon volume
Upgrade vers Pro quand ?
A partir de 100 users actifs / jour :
-
500 req PermisAPI/mois probable
- Enrichment SIRENE utile pour afficher "demandeur = societe X"
- Historique > 6 mois utile pour analyses trend
Pro 199 EUR = ~5 EUR/user actif/mois, marge saine pour un SaaS PropTech freemium.
Next steps
- Integrer DVF (valeurs foncieres) pour estimer prix marche
- Integrer IRIS INSEE pour socio-demo du quartier
- Ajouter des alertes webhook pour notification temps reel (plan Pro)