HésiaSun · API publique

Documentation de l’API v1

Créez des projets photovoltaïques, obtenez le dimensionnement et l’étude économique, suivez votre pipeline et récupérez les documents depuis votre CRM, votre site ou vos automatisations.

Base URL :
https://app.hesiasun.example/api/v1
Version de contrat :
2026-09-01
Format :
JSON, UTF-8, dates ISO 8601 (UTC)

Authentification

Chaque requête porte une clé API dans l’en-tête Authorization. Les clés commencent par bk_live_, sont générées dans Paramètres → API de l’application et affichées une seule fois : seul un hash SHA-256 est conservé côté serveur.

Authorization: Bearer bk_live_3f9a1c…

Une clé porte un ou plusieurs scopes :

  • projects:read — lire les projets, les calculs et l’usage ;
  • projects:write — créer, recalculer, modifier le pipeline ;
  • docs:read — lister les documents et obtenir des URL signées.

Clé absente, invalide, révoquée ou expirée → 401 unauthorized. Scope insuffisant → 403 forbidden. Les clés sont liées à une organisation : vous ne voyez jamais que vos propres projets.

Quotas et erreur 402

Deux compteurs mensuels s’appliquent selon votre plan : le nombre de projets créés et le nombre d’appels API. L’API est réservée au plan Pro (200 projets et 5 000 appels par mois au lancement) ; le plan Free peut consulter GET /usage mais tout autre appel renvoie 402.

HTTP/1.1 402 Payment Required
{
  "api_version": "2026-09-01",
  "error": {
    "code": "quota_exceeded",
    "message": "Quota mensuel d’appels API atteint (5000/5000).",
    "details": { "used": 5000, "limit": 5000 }
  }
}

Un appel refusé pour quota n’est pas décompté. GET /usage n’est jamais décompté : interrogez-le pour piloter vos automatisations. Les compteurs repartent à zéro le 1er de chaque mois (UTC).

Rate limit

60 requêtes par minute glissante et par clé. Au-delà : 429 rate_limited avec un en-tête Retry-After en secondes. Chaque réponse porte X-RateLimit-Remaining. Pour des volumes importants, préférez une clé par intégration et un traitement par lots côté client.

Endpoints

MéthodeCheminScopeQuotaDescription
POST/projectsprojects:writedécomptéCréer un projet et lancer le calcul
GET/projectsprojects:readdécomptéLister les projets (paginé, filtres)
GET/projects/{id}projects:readdécomptéDétail d’un projet et dernier calcul complet
PATCH/projects/{id}projects:writedécomptéMettre à jour le pipeline (statut, montant, relance, notes)
POST/projects/{id}/calculateprojects:writedécomptéRecalculer un projet
GET/projects/{id}/documentsdocs:readdécomptéDocuments générés (URL signées 1 h)
GET/usageprojects:readnon décomptéConsommation du mois et limites du plan
GET/openapi.jsonaucunnon décomptéSpécification OpenAPI 3.1 (publique)

Toutes les réponses réussies sont enveloppées : { "api_version": "…", "data": … }. Les identifiants de projet sont des UUID ; la référence lisible (SOL-2026-0042) est renvoyée dans data.reference.

POST/projectsscope projects:write

Crée le projet, consomme 1 unité du quota projets, géocode l’adresse si lat/lon sont absents (Base Adresse Nationale), interroge PVGIS puis lance le moteur. Réponse 201 avec le projet et son calcul.

Exemple : maison à Gaillac (81600), 40 m² de toiture en tuiles orientée sud, pente 30 %, 6 000 kWh/an en tarif base à 0,25 €/kWh, sans batterie.

curl -X POST https://app.hesiasun.example/api/v1/projects \
  -H "Authorization: Bearer bk_live_…" \
  -H "Content-Type: application/json" \
  -d '{
  "client": {
    "name": "Marie Dupont",
    "email": "marie.dupont@example.fr",
    "phone": "+33 6 12 34 56 78"
  },
  "location": {
    "address": "12 rue de la République",
    "postal_code": "81600",
    "city": "Gaillac"
  },
  "roof": {
    "type": "tuiles",
    "tilt_percent": 30,
    "orientation": "S",
    "surface_m2": 40
  },
  "consumption": {
    "annual_kwh": 6000,
    "profile": "RES1",
    "contract": "base",
    "price_kwh": 0.25
  },
  "battery": {
    "wanted": false
  },
  "objective": "autoconso_max",
  "financing": null,
  "pipeline": {
    "status": "prospect",
    "estimated_amount": 8900,
    "follow_up_date": "2026-09-22",
    "notes": "Rappeler après devis"
  }
}'

Réponse (extrait) :

{
  "api_version": "2026-09-01",
  "data": {
    "id": "8f1c0f2e-…",
    "reference": "SOL-2026-0042",
    "status": "prospect",
    "client_name": "Marie Dupont",
    "city": "Gaillac",
    "lat": 43.9015, "lon": 1.8975,
    "input": { … },
    "calculation": {
      "id": "c2a7…",
      "engine_version": "1.4.0+calc.1.0.0",
      "created_at": "2026-09-08T09:12:44.120Z",
      "result": {
        "sizing": { "selected_kwc": 3, "modules": { "count": 6, "power_wc": 500 }, "scenarios": [ … ] },
        "production": { "source": "pvgis", "annual_kwh": 3915, "monthly_kwh": [ … ] },
        "variants": {
          "without_battery": {
            "energy": { "autoconso_rate": 0.62, "autarky_rate": 0.4 },
            "economics": { "capex": 7200, "savings_year1": 606, "payback_years": 10.8, "irr": 0.071 }
          },
          "with_battery": null
        },
        "warnings": []
      }
    }
  }
}

Si le calcul échoue (toiture trop petite, par exemple), le projet est tout de même créé : la réponse reste 201 aveccalculation: null et calculation_error: { code, message }. Corrigez les entrées dans l’application ou relancezPOST /projects/{id}/calculate. Émet le webhook project.created.

GET/projectsscope projects:read

Liste paginée, triée par création décroissante. Paramètres : status (prospect, sent, won, lost), q (recherche sur le nom du client), page (défaut 1), per_page (défaut 25, max 100). Chaque élément porte un résumé du dernier calcul, pas le résultat complet.

curl "https://app.hesiasun.example/api/v1/projects?status=sent&q=dupont&per_page=50" \
  -H "Authorization: Bearer bk_live_…"
{
  "api_version": "2026-09-01",
  "data": {
    "items": [
      {
        "id": "8f1c0f2e-…", "reference": "SOL-2026-0042", "status": "sent", "client_name": "Marie Dupont",
        "calculation": {
          "id": "c2a7…", "engine_version": "1.4.0+calc.1.0.0", "created_at": "2026-09-08T09:12:44.120Z",
          "summary": { "selected_kwc": 3, "annual_production_kwh": 3915, "autoconso_rate": 0.62, "savings_year1": 606, "capex": 7200, "payback_years": 10.8 }
        }
      }
    ],
    "page": 1, "per_page": 50, "total": 1
  }
}
GET/projects/{id}scope projects:read

Projet complet avec son dernier calcul (même forme que la création). 404 not_found si l’identifiant n’appartient pas à votre organisation.

curl https://app.hesiasun.example/api/v1/projects/8f1c0f2e-… -H "Authorization: Bearer bk_live_…"
PATCH/projects/{id}scope projects:write

Met à jour le pipeline uniquement : status, estimated_amount, follow_up_date (AAAA-MM-JJ), notes. Au moins un champ. Les entrées techniques (toiture, consommation…) se modifient dans l’application, puis se recalculent.

curl -X PATCH https://app.hesiasun.example/api/v1/projects/8f1c0f2e-… \
  -H "Authorization: Bearer bk_live_…" \
  -H "Content-Type: application/json" \
  -d '{ "status": "won", "estimated_amount": 8900 }'

Un changement de statut émet project.status_changed (avec previous_status) ; le passage à won émet en plus project.won.

POST/projects/{id}/calculatescope projects:write

Relance le calcul à partir des entrées enregistrées et crée une nouvelle ligne de calcul (l’historique est conservé). Corps optionnel :{ "reuse_production": true } réutilise la production PVGIS du calcul précédent si les paramètres (position, pente, orientation, pertes) n’ont pas changé — utile après une mise à jour des hypothèses tarifaires.

curl -X POST https://app.hesiasun.example/api/v1/projects/8f1c0f2e-…/calculate \
  -H "Authorization: Bearer bk_live_…" \
  -H "Content-Type: application/json" \
  -d '{ "reuse_production": true }'

Réponse : { "data": { "project": … } } avec le nouveau calcul. Toiture trop petite → 422 calculation_error.

GET/projects/{id}/documentsscope docs:read

Pièces générées pour le projet (synthèse client, CERFA DP, notice, plans, dossier complet). Chaque élément porte une url signée valable 1 heure : ne la stockez pas, redemandez-la au besoin. Tant qu’aucun document n’a été généré, items est vide.

curl https://app.hesiasun.example/api/v1/projects/8f1c0f2e-…/documents -H "Authorization: Bearer bk_live_…"
{
  "api_version": "2026-09-01",
  "data": {
    "items": [
      { "id": "d41f…", "kind": "synthese", "mime_type": "application/pdf", "file_size_bytes": 412331, "watermarked": false,
        "created_at": "2026-09-08T09:13:02.000Z", "url": "https://…/storage/v1/object/sign/documents/…?token=…" }
    ]
  }
}
GET/usagescope projects:read

Consommation du mois courant et limites du plan. Non décompté du quota.

curl https://app.hesiasun.example/api/v1/usage -H "Authorization: Bearer bk_live_…"
{
  "api_version": "2026-09-01",
  "data": {
    "period": "2026-09",
    "plan": "pro",
    "projects": { "used": 12, "limit": 200 },
    "api_calls": { "used": 340, "limit": 5000, "enabled": true },
    "storage": { "used_bytes": 18350080, "limit_mb": 2048 }
  }
}

Format des erreurs

Toutes les erreurs ont la même forme. details est optionnel : liste des champs invalides pour 422, compteurs pour 402.

{
  "api_version": "2026-09-01",
  "error": {
    "code": "validation_error",
    "message": "Entrées invalides",
    "details": [
      { "path": "location", "message": "Fournir lat/lon, ou un code postal, ou une adresse" },
      { "path": "consumption", "message": "Consommation annuelle ou mensuelle requise" }
    ]
  }
}
HTTPcodeQuand
401unauthorizedClé absente, invalide, révoquée ou expirée
402quota_exceededQuota mensuel atteint, ou API non incluse dans le plan
403forbiddenScope insuffisant
404not_foundProjet inconnu dans votre organisation
409conflictÉtat incompatible (réservé)
422validation_errorCorps ou paramètres invalides
422calculation_errorCalcul impossible (ex. roof_too_small dans details.code)
429rate_limited60 requêtes / minute / clé dépassées
500internal_errorErreur interne : réessayez, puis contactez le support

Webhooks sortants

Configurez une URL et choisissez vos événements dans Paramètres → Webhooks. Le secret HMAC est affiché une seule fois. Nous envoyons unPOST JSON ; toute réponse 2xx vaut accusé de réception. Sinon, 5 tentatives avec backoff (1 min, 5 min, 30 min, 2 h, 12 h).

  • project.created — projet créé (API ou application) ;
  • project.status_changed — statut du pipeline modifié, avec previous_status ;
  • project.won — projet passé à won (envoyé en plus de status_changed).

En-têtes :

  • x-solaire-event — nom de l’événement ;
  • x-solaire-timestamp — horodatage Unix (secondes) de l’envoi ;
  • x-solaire-signaturesha256=<hex>, HMAC-SHA256 de `${timestamp}.${body}` avec votre secret, oùbody est le corps brut exactement tel que reçu.
{
  "event": "project.won",
  "occurred_at": "2026-09-08T14:02:11.482Z",
  "data": {
    "id": "8f1c0f2e-…",
    "reference": "SOL-2026-0042",
    "client_name": "Marie Dupont",
    "status": "won",
    "previous_status": "sent",
    "estimated_amount": 8900,
    "kwc": 3,
    "pdf_urls": []
  }
}

Vérification en Node (Express, corps brut) — rejetez aussi les horodatages de plus de 5 minutes pour éviter le rejeu :

import { createHmac, timingSafeEqual } from 'node:crypto';
import express from 'express';

const app = express();
const SECRET = process.env.SOLAIRE_WEBHOOK_SECRET;

app.post('/webhooks/solaire', express.raw({ type: 'application/json' }), (req, res) => {
  const ts = req.header('x-solaire-timestamp') ?? '';
  const sig = (req.header('x-solaire-signature') ?? '').replace(/^sha256=/, '');
  const body = req.body.toString('utf8');

  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return res.status(400).send('timestamp');

  const expected = createHmac('sha256', SECRET).update(`${ts}.${body}`).digest('hex');
  const ok = sig.length === expected.length && timingSafeEqual(Buffer.from(sig, 'hex'), Buffer.from(expected, 'hex'));
  if (!ok) return res.status(401).send('signature');

  const event = JSON.parse(body);
  // event.event === 'project.won' → créer l'affaire dans votre CRM, etc.
  res.sendStatus(204);
});

Brancher via Make, Zapier ou Pipedrive

Make / Zapier

Sans code : créez un module HTTP → Make a request (Make) ou Webhooks by Zapier → Custom Request avec l’URL https://app.hesiasun.example/api/v1/projects, la méthode POST, l’en-tête Authorization: Bearer bk_live_… et un corps JSON construit depuis votre formulaire (Typeform, Tally, site web). Mappez ensuite data.reference, data.calculation.result.sizing.selected_kwc etdata.calculation.result.variants.without_battery.economics.capex vers votre CRM ou votre feuille de calcul. Pour le sens inverse, exposez unCustom Webhook Make/Zapier comme URL de webhook HésiaSun et filtrez sur l’en-tête x-solaire-event.

App privée Pipedrive

Dans Pipedrive, créez une app privée (Developer Hub) ou une simple automatisation Webhook déclenchée à la création d’un lead : elle appellePOST /projects avec l’adresse et la consommation saisies dans les champs personnalisés, puis écrit la référence et la puissance retenue dans l’affaire. Côté HésiaSun, pointez le webhook sur votre app : à project.won, déplacez l’affaire dans l’étape « Gagné » et renseignezestimated_amount comme valeur. La connexion native Pipedrive/HubSpot (mapping statut → étape) arrive dans l’application ; l’API et les webhooks ci-dessus restent le socle.

OpenAPI

La spécification machine (OpenAPI 3.1) est publique et sans authentification : /api/v1/openapi.json. Importez-la dans Postman, Insomnia, Bruno ou un générateur de client (openapi-typescript, openapi-generator) ; elle est mise en cache 1 heure.

La version de contrat (2026-09-01) ne change que sur rupture de forme. Les ajouts de champs sont rétro-compatibles : ignorez les propriétés inconnues.