scorimmo 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- scorimmo-0.2.0/PKG-INFO +598 -0
- scorimmo-0.2.0/README.md +582 -0
- scorimmo-0.2.0/pyproject.toml +30 -0
- scorimmo-0.2.0/scorimmo/__init__.py +46 -0
- scorimmo-0.2.0/scorimmo/client.py +843 -0
- scorimmo-0.2.0/scorimmo/webhook.py +295 -0
- scorimmo-0.2.0/scorimmo.egg-info/PKG-INFO +598 -0
- scorimmo-0.2.0/scorimmo.egg-info/SOURCES.txt +12 -0
- scorimmo-0.2.0/scorimmo.egg-info/dependency_links.txt +1 -0
- scorimmo-0.2.0/scorimmo.egg-info/requires.txt +9 -0
- scorimmo-0.2.0/scorimmo.egg-info/top_level.txt +1 -0
- scorimmo-0.2.0/setup.cfg +4 -0
- scorimmo-0.2.0/tests/test_client.py +419 -0
- scorimmo-0.2.0/tests/test_webhook.py +182 -0
scorimmo-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,598 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: scorimmo
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Official Scorimmo SDK for Python — API v2 client & webhook handler
|
|
5
|
+
License: MIT
|
|
6
|
+
Keywords: scorimmo,real-estate,crm,leads,webhook,sdk,immobilier
|
|
7
|
+
Requires-Python: >=3.9
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: httpx>=0.27.0
|
|
10
|
+
Provides-Extra: flask
|
|
11
|
+
Requires-Dist: flask>=3.0.0; extra == "flask"
|
|
12
|
+
Provides-Extra: dev
|
|
13
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
14
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
|
|
15
|
+
Requires-Dist: respx>=0.21.0; extra == "dev"
|
|
16
|
+
|
|
17
|
+
# scorimmo
|
|
18
|
+
|
|
19
|
+
SDK officiel Python pour la plateforme [Scorimmo](https://pro.scorimmo.com) — client API **v2** & récepteur webhook.
|
|
20
|
+
|
|
21
|
+
- **Client API** — récupérez, filtrez et mettez à jour vos leads avec gestion automatique du JWT (access + refresh token, rotation).
|
|
22
|
+
- **Réception de webhooks** — recevez les événements Scorimmo en temps réel, avec vérification optionnelle de la signature HMAC-SHA256.
|
|
23
|
+
|
|
24
|
+
> **Documentation de référence :**
|
|
25
|
+
> [API REST v2](https://pro.scorimmo.com/api/v2/doc) · [Webhooks](https://pro.scorimmo.com/webhook/doc)
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Sommaire
|
|
30
|
+
|
|
31
|
+
- [Installation](#installation)
|
|
32
|
+
- [Identifiants API](#identifiants-api)
|
|
33
|
+
- [Client API](#client-api)
|
|
34
|
+
- [Webhooks](#webhooks)
|
|
35
|
+
- [Intégration Flask](#intégration-flask)
|
|
36
|
+
- [Référence — Ressources](#référence--ressources)
|
|
37
|
+
- [Référence — Gestion des tokens](#référence--gestion-des-tokens)
|
|
38
|
+
- [Référence — Événements webhook](#référence--événements-webhook)
|
|
39
|
+
- [Gestion des erreurs](#gestion-des-erreurs)
|
|
40
|
+
- [Support](#support)
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Installation
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install scorimmo
|
|
48
|
+
|
|
49
|
+
# Avec support Flask (vue webhook clé-en-main)
|
|
50
|
+
pip install scorimmo[flask]
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**Prérequis :** Python ≥ 3.9
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Identifiants API
|
|
58
|
+
|
|
59
|
+
Les identifiants (`email` / `password`) sont ceux fournis par Scorimmo. L'identifiant est l'adresse email du compte API v2.
|
|
60
|
+
|
|
61
|
+
Après le premier appel authentifié, un **refresh token** est disponible via `get_refresh_token()`. Il permet de réinitialiser le client sans repasser par les identifiants (voir [Gestion des tokens](#référence--gestion-des-tokens)).
|
|
62
|
+
|
|
63
|
+
Pour le webhook, le secret HMAC (`SCORIMMO_WEBHOOK_SIGNATURE_SECRET`) est une valeur que vous choisissez librement — communiquez-la ensuite à Scorimmo lors de la configuration (voir [Configurer le webhook chez Scorimmo](#configurer-le-webhook-chez-scorimmo)).
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Client API
|
|
68
|
+
|
|
69
|
+
### Initialisation
|
|
70
|
+
|
|
71
|
+
**Avec identifiants email/password :**
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
from scorimmo import ScorimmoClient
|
|
75
|
+
|
|
76
|
+
client = ScorimmoClient(
|
|
77
|
+
email="votre-email",
|
|
78
|
+
password="votre-mot-de-passe",
|
|
79
|
+
# base_url="https://pro.scorimmo.com" # par défaut
|
|
80
|
+
)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Avec un refresh token persisté (sans exposer les identifiants) :**
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
client = ScorimmoClient(refresh_token=persisted_token)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Le token JWT est géré automatiquement : refresh silencieux à l'expiration puis fallback sur email/password si nécessaire.
|
|
90
|
+
|
|
91
|
+
### Récupérer les leads récents
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
from datetime import datetime, timedelta, timezone
|
|
95
|
+
|
|
96
|
+
# Tous les leads des dernières 24 heures — pagination + dédup automatiques
|
|
97
|
+
since = datetime.now(timezone.utc) - timedelta(hours=24)
|
|
98
|
+
leads = client.leads.since(since, include=["customer", "seller"])
|
|
99
|
+
|
|
100
|
+
# Depuis une date précise
|
|
101
|
+
leads = client.leads.since("2026-06-01 00:00:00")
|
|
102
|
+
|
|
103
|
+
# Leads modifiés récemment (plutôt que créés)
|
|
104
|
+
leads = client.leads.since(since, field="updated_at")
|
|
105
|
+
|
|
106
|
+
# Restreindre à un point de vente + callback de progression
|
|
107
|
+
leads = client.leads.since(
|
|
108
|
+
since,
|
|
109
|
+
store_id=776,
|
|
110
|
+
include=["customer", "seller"],
|
|
111
|
+
on_progress=lambda page, count, total, meta: print(f"Page {page}: {total} leads"),
|
|
112
|
+
)
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Récupérer un lead par ID
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
lead = client.leads.get(42)
|
|
119
|
+
lead = client.leads.get(42, include=["customer", "seller", "appointments", "comments"])
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Rechercher des leads
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
result = client.leads.list(
|
|
126
|
+
interest="Transaction",
|
|
127
|
+
store_id=1,
|
|
128
|
+
**{"created_at[gte]": "2026-01-01T00:00:00+00:00"},
|
|
129
|
+
sort="created_at:desc",
|
|
130
|
+
limit=20,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
# result["data"] contient les leads, result["meta"] contient la pagination
|
|
134
|
+
for lead in result["data"]:
|
|
135
|
+
print(lead["id"], lead.get("customer_id"))
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Filtres complets disponibles :
|
|
139
|
+
|
|
140
|
+
| Paramètre | Type | Description |
|
|
141
|
+
|---|---|---|
|
|
142
|
+
| `page` | `int` | Numéro de page (défaut : 1) |
|
|
143
|
+
| `limit` | `int` | Résultats par page (défaut : 10, max : 100) |
|
|
144
|
+
| `sort` | `str` | `"champ:asc"` ou `"champ:desc"` — champs : `id`, `created_at`, `updated_at`, `status` |
|
|
145
|
+
| `include` | `str` | Relations : `"customer,seller,appointments,reminders,requests,comments"` |
|
|
146
|
+
| `store_id` | `int` | Restreindre à un point de vente |
|
|
147
|
+
| `seller_id` | `int` | Restreindre à un conseiller |
|
|
148
|
+
| `status` | `str` | Statut du lead |
|
|
149
|
+
| `substatus` | `str` | Sous-statut |
|
|
150
|
+
| `interest` | `str` | `TRANSACTION`, `LOCATION`, `GESTION`… |
|
|
151
|
+
| `origin` | `str` | Origine du lead |
|
|
152
|
+
| `contact_type` | `str` | `physical`, `phone` ou `digital` |
|
|
153
|
+
| `purpose` | `str` | Achat, Location, Bailleur, Vente, Recherche, Locataire, Non renseigné |
|
|
154
|
+
| `customer_first_name` | `str` | Prénom du contact |
|
|
155
|
+
| `customer_last_name` | `str` | Nom du contact |
|
|
156
|
+
| `customer.email` | `str` | Email (à passer via `**{"customer.email": …}`) |
|
|
157
|
+
| `customer.phone` | `str` | Téléphone (OR sur phone/other_phone) |
|
|
158
|
+
| `external_lead_id` | `str` | Référence CRM |
|
|
159
|
+
| `requests_reference` | `str` | Référence du bien |
|
|
160
|
+
| `ids` | `str` | IDs multiples séparés par virgule |
|
|
161
|
+
| `created_at[gte\|lte\|eq]` | `str` | Filtres de date ISO 8601 |
|
|
162
|
+
| `updated_at[gte\|lte\|eq]` | `str` | Filtres de date ISO 8601 |
|
|
163
|
+
|
|
164
|
+
### Mise à jour partielle
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
client.leads.update(42, {"external_lead_id": "CRM-456", "seller_id": 3533})
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
> **Création de leads :** volontairement non exposée par le SDK. Les leads doivent être créés via l'interface Scorimmo, un formulaire (`client.form.submit`), un webcallback ou un import.
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## Webhooks
|
|
175
|
+
|
|
176
|
+
### Initialisation
|
|
177
|
+
|
|
178
|
+
L'authentification des webhooks est **optionnelle** — la sécurisation de l'endpoint est à la charge de l'intégrateur. Scorimmo propose plusieurs mécanismes complémentaires :
|
|
179
|
+
|
|
180
|
+
1. **Signature HMAC-SHA256** (fortement recommandé) — Scorimmo signe le corps brut avec un secret partagé et envoie la signature dans le header `X-Signature-256` sous la forme `sha256=<hex>`. Le SDK la vérifie en temps constant via `hmac.compare_digest()`.
|
|
181
|
+
2. **HTTP Basic auth via URL** — enregistrez le webhook comme `https://user:pass@host/path`, l'auth est déléguée au serveur/framework, transparente pour le SDK.
|
|
182
|
+
3. **Restriction réseau** (IP whitelist, VPN, mTLS…) — hors périmètre du SDK.
|
|
183
|
+
|
|
184
|
+
**Avec signature HMAC (recommandé) :**
|
|
185
|
+
|
|
186
|
+
```python
|
|
187
|
+
import os
|
|
188
|
+
from scorimmo import ScorimmoWebhook
|
|
189
|
+
|
|
190
|
+
webhook = ScorimmoWebhook(
|
|
191
|
+
signature_secret=os.environ["SCORIMMO_WEBHOOK_SIGNATURE_SECRET"],
|
|
192
|
+
# signature_header="X-Signature-256", # valeur par défaut
|
|
193
|
+
)
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
**Sans vérification** (auth déportée sur le serveur) :
|
|
197
|
+
|
|
198
|
+
```python
|
|
199
|
+
webhook = ScorimmoWebhook()
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
> **Important :** si vous activez la signature, le corps brut doit être passé sous forme de `bytes` (ou `str`) **avant tout parsing JSON** — sinon la signature ne pourra pas être vérifiée. En Flask : `request.get_data()` ; en FastAPI : `await request.body()`.
|
|
203
|
+
|
|
204
|
+
### Traitement d'une requête entrante (générique)
|
|
205
|
+
|
|
206
|
+
```python
|
|
207
|
+
from scorimmo import WebhookAuthError, WebhookValidationError
|
|
208
|
+
|
|
209
|
+
# headers : dict des en-têtes HTTP (str ou list, insensible à la casse)
|
|
210
|
+
# raw_body : corps brut de la requête (bytes recommandé)
|
|
211
|
+
try:
|
|
212
|
+
webhook.handle(headers, raw_body, {
|
|
213
|
+
"new_lead": on_new_lead,
|
|
214
|
+
"update_lead": on_update_lead,
|
|
215
|
+
"new_comment": on_new_comment,
|
|
216
|
+
"new_rdv": on_new_rdv,
|
|
217
|
+
"new_reminder": on_new_reminder,
|
|
218
|
+
"closure_lead": on_closure_lead,
|
|
219
|
+
# Événement futur inconnu (arrivé avec X-Scorimmo-Event: webhook.<name>) :
|
|
220
|
+
"unknown": lambda event: print(f"Événement inconnu : {event}"),
|
|
221
|
+
})
|
|
222
|
+
# → HTTP 200
|
|
223
|
+
except WebhookAuthError:
|
|
224
|
+
# Signature manquante ou invalide → HTTP 401
|
|
225
|
+
pass
|
|
226
|
+
except WebhookValidationError:
|
|
227
|
+
# Payload JSON invalide ou champ "event" manquant → HTTP 400
|
|
228
|
+
pass
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Headers webhook v2
|
|
232
|
+
|
|
233
|
+
Chaque requête webhook envoyée par Scorimmo inclut ces headers :
|
|
234
|
+
|
|
235
|
+
| Header | Exemple | Description |
|
|
236
|
+
|---|---|---|
|
|
237
|
+
| `X-Signature-256` | `sha256=8f4c…` | Signature HMAC-SHA256 du corps brut (présent si vous avez configuré un secret ; nom personnalisable) |
|
|
238
|
+
| `X-Scorimmo-Event` | `lead.created` | Nom sémantique de l'événement |
|
|
239
|
+
| `X-Scorimmo-Version` | `2026-04-20` | Version d'API (date, format `YYYY-MM-DD` — pas un numéro sémantique) |
|
|
240
|
+
| `User-Agent` | `Scorimmo/1.42.0` | Version applicative Scorimmo (distincte de `X-Scorimmo-Version`) |
|
|
241
|
+
|
|
242
|
+
```python
|
|
243
|
+
event_name = webhook.get_semantic_event(headers) # ex: 'lead.created'
|
|
244
|
+
api_version = webhook.get_api_version(headers) # ex: '2026-04-20'
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
Correspondance entre le champ `event` du payload et `X-Scorimmo-Event` :
|
|
248
|
+
|
|
249
|
+
| `event` (payload) | `X-Scorimmo-Event` |
|
|
250
|
+
|---|---|
|
|
251
|
+
| `new_lead` | `lead.created` |
|
|
252
|
+
| `update_lead` | `lead.updated` |
|
|
253
|
+
| `closure_lead` | `lead.closed` |
|
|
254
|
+
| `new_comment` | `lead.comment_added` |
|
|
255
|
+
| `new_rdv` | `lead.appointment_created` |
|
|
256
|
+
| `new_reminder` | `lead.reminder_created` |
|
|
257
|
+
| _(événement futur inconnu)_ | `webhook.<name>` |
|
|
258
|
+
|
|
259
|
+
> Enregistrez un handler `'unknown'` pour capturer les événements futurs non encore modélisés — Scorimmo peut ajouter de nouveaux événements sans breaking change.
|
|
260
|
+
|
|
261
|
+
### Idempotence & retries
|
|
262
|
+
|
|
263
|
+
Scorimmo effectue jusqu'à **6 tentatives de livraison** (initial + 5 retries) avec backoff exponentiel (5 s → 60 s max). Le corps et la signature sont identiques à chaque retry.
|
|
264
|
+
|
|
265
|
+
Aucun `Idempotency-Key` n'est envoyé — votre receveur doit être idempotent, typiquement en dédupliquant sur `(event, lead_id, created_at)` ou `(event, id, updated_at)` selon le type d'événement.
|
|
266
|
+
|
|
267
|
+
### Configurer le webhook chez Scorimmo
|
|
268
|
+
|
|
269
|
+
Une fois votre endpoint déployé, transmettez les informations suivantes à votre **account manager Scorimmo** (voir [Support](#support)) :
|
|
270
|
+
|
|
271
|
+
```
|
|
272
|
+
URL du webhook : https://votre-app.com/webhook/scorimmo
|
|
273
|
+
|
|
274
|
+
Authentification (au choix, fortement recommandé) :
|
|
275
|
+
Option A - Signature HMAC-SHA256 (recommandé)
|
|
276
|
+
Le back-office Scorimmo n'a pas de champ « secret » dédié : la signature
|
|
277
|
+
se configure via un header custom, en convention in-place.
|
|
278
|
+
|
|
279
|
+
Dans l'écran de configuration du webhook, ajoutez un header custom :
|
|
280
|
+
Nom : X-Signature-256
|
|
281
|
+
(nom libre côté back, MAIS il doit correspondre à
|
|
282
|
+
`signature_header` côté SDK — valeur par défaut du SDK :
|
|
283
|
+
X-Signature-256. Si vous choisissez un autre nom, pensez à
|
|
284
|
+
le passer via `signature_header=` à `ScorimmoWebhook`.)
|
|
285
|
+
Valeur : sha256=<votre-secret>
|
|
286
|
+
(le préfixe `sha256=` est obligatoire — c'est ce qui déclenche
|
|
287
|
+
la convention. Le back détecte le préfixe, extrait la partie
|
|
288
|
+
après `sha256=` comme secret HMAC, puis remplace la valeur
|
|
289
|
+
envoyée par :
|
|
290
|
+
sha256=<hex(hmac_sha256(rawBody, <votre-secret>))>
|
|
291
|
+
avant l'appel HTTP.)
|
|
292
|
+
|
|
293
|
+
Secret côté SDK :
|
|
294
|
+
La chaîne que vous avez collée après `sha256=` ci-dessus doit être
|
|
295
|
+
strictement identique à `SCORIMMO_WEBHOOK_SIGNATURE_SECRET`
|
|
296
|
+
(paramètre `signature_secret` de `ScorimmoWebhook`).
|
|
297
|
+
|
|
298
|
+
Option B - HTTP Basic auth via URL
|
|
299
|
+
URL : https://user:pass@votre-app.com/webhook/scorimmo
|
|
300
|
+
|
|
301
|
+
Événements à activer :
|
|
302
|
+
☑ Nouveau lead (new_lead)
|
|
303
|
+
☑ Mise à jour lead (update_lead)
|
|
304
|
+
☑ Nouveau commentaire (new_comment)
|
|
305
|
+
☑ Rendez-vous (new_rdv)
|
|
306
|
+
☑ Rappel (new_reminder)
|
|
307
|
+
☑ Clôture lead (closure_lead)
|
|
308
|
+
|
|
309
|
+
Point(s) de vente concerné(s) : [indiquez vos points de vente]
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
> **Important :** Scorimmo considère la livraison réussie uniquement si votre endpoint retourne HTTP 200.
|
|
313
|
+
|
|
314
|
+
---
|
|
315
|
+
|
|
316
|
+
## Intégration Flask
|
|
317
|
+
|
|
318
|
+
```bash
|
|
319
|
+
pip install scorimmo[flask]
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
```python
|
|
323
|
+
import os
|
|
324
|
+
from flask import Flask
|
|
325
|
+
from scorimmo import ScorimmoWebhook
|
|
326
|
+
|
|
327
|
+
app = Flask(__name__)
|
|
328
|
+
|
|
329
|
+
webhook = ScorimmoWebhook(
|
|
330
|
+
signature_secret=os.environ.get("SCORIMMO_WEBHOOK_SIGNATURE_SECRET"),
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
app.add_url_rule(
|
|
334
|
+
"/webhook/scorimmo",
|
|
335
|
+
view_func=webhook.flask_view({
|
|
336
|
+
"new_lead": lambda event: on_new_lead(event),
|
|
337
|
+
"update_lead": lambda event: on_update_lead(event),
|
|
338
|
+
"new_comment": lambda event: on_new_comment(event),
|
|
339
|
+
"new_rdv": lambda event: on_new_rdv(event),
|
|
340
|
+
"new_reminder": lambda event: on_new_reminder(event),
|
|
341
|
+
"closure_lead": lambda event: on_closure_lead(event),
|
|
342
|
+
"unknown": lambda event: log(event),
|
|
343
|
+
}),
|
|
344
|
+
methods=["POST"],
|
|
345
|
+
)
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
La vue générée par `flask_view()` :
|
|
349
|
+
- lit le corps brut via `request.get_data()` avant tout `json_decode()`,
|
|
350
|
+
- retourne `401` si la signature HMAC est absente ou invalide,
|
|
351
|
+
- retourne `400` si le payload est mal formé,
|
|
352
|
+
- retourne `{"ok": true}` avec HTTP 200 en cas de succès.
|
|
353
|
+
|
|
354
|
+
---
|
|
355
|
+
|
|
356
|
+
## Référence — Ressources
|
|
357
|
+
|
|
358
|
+
Toutes les ressources ci-dessous exposent `list(**query)` (et `get(id)` quand l'endpoint le permet).
|
|
359
|
+
|
|
360
|
+
### Leads — `client.leads`
|
|
361
|
+
|
|
362
|
+
Voir [Client API](#client-api) ci-dessus. Méthodes : `get`, `list`, `update`, `since`.
|
|
363
|
+
|
|
364
|
+
### Rendez-vous — `client.appointments`
|
|
365
|
+
|
|
366
|
+
| Paramètre | Type | Description |
|
|
367
|
+
|---|---|---|
|
|
368
|
+
| `lead_id` | `int` | Filtrer par lead |
|
|
369
|
+
| `ids` | `str` | IDs séparés par virgule |
|
|
370
|
+
| `created_at[gte\|lte\|eq]` | `str` | Filtres de date (ISO 8601) |
|
|
371
|
+
| `updated_at[gte\|lte]` | `str` | Idem |
|
|
372
|
+
| `start_time[gte\|lte\|eq]` | `str` | Idem |
|
|
373
|
+
| `sort` | `str` | `id`, `created_at`, `updated_at`, `start_time` (avec `:asc`/`:desc`) |
|
|
374
|
+
|
|
375
|
+
### Commentaires — `client.comments`
|
|
376
|
+
|
|
377
|
+
| Paramètre | Type | Description |
|
|
378
|
+
|---|---|---|
|
|
379
|
+
| `lead_id` | `int` | Filtrer par lead |
|
|
380
|
+
| `ids` | `str` | IDs séparés par virgule |
|
|
381
|
+
| `created_at[gte\|lte\|eq]` | `str` | Filtres de date |
|
|
382
|
+
| `sort` | `str` | `id`, `created_at` |
|
|
383
|
+
|
|
384
|
+
### Rappels — `client.reminders`
|
|
385
|
+
|
|
386
|
+
| Paramètre | Type | Description |
|
|
387
|
+
|---|---|---|
|
|
388
|
+
| `lead_id` | `int` | Filtrer par lead |
|
|
389
|
+
| `ids` | `str` | IDs séparés par virgule |
|
|
390
|
+
| `created_at[gte\|lte\|eq]` | `str` | Filtres de date |
|
|
391
|
+
| `updated_at[gte\|lte]` | `str` | Idem |
|
|
392
|
+
| `start_time[gte\|lte\|eq]` | `str` | Idem (`reminder_date` côté serveur) |
|
|
393
|
+
| `sort` | `str` | `id`, `created_at`, `updated_at`, `start_time` |
|
|
394
|
+
|
|
395
|
+
### Demandes — `client.requests`
|
|
396
|
+
|
|
397
|
+
| Paramètre | Type | Description |
|
|
398
|
+
|---|---|---|
|
|
399
|
+
| `lead_id` | `int` | Filtrer par lead |
|
|
400
|
+
| `reference` | `str` | Référence du bien |
|
|
401
|
+
| `ids` | `str` | IDs séparés par virgule |
|
|
402
|
+
| `created_at[gte\|lte\|eq]` | `str` | Filtres de date |
|
|
403
|
+
| `updated_at[gte\|lte]` | `str` | Idem |
|
|
404
|
+
| `sort` | `str` | `id`, `created_at`, `updated_at` |
|
|
405
|
+
|
|
406
|
+
### Contacts — `client.customers`
|
|
407
|
+
|
|
408
|
+
| Paramètre | Type | Description |
|
|
409
|
+
|---|---|---|
|
|
410
|
+
| `search` | `str` | Recherche full-text |
|
|
411
|
+
| `email` | `str` | Recherche par email |
|
|
412
|
+
| `phone` | `str` | Recherche par téléphone (OR sur phone/other_phone) |
|
|
413
|
+
| `sort` | `str` | `id` |
|
|
414
|
+
|
|
415
|
+
### Origines — `client.origins`
|
|
416
|
+
|
|
417
|
+
| Paramètre | Type | Description |
|
|
418
|
+
|---|---|---|
|
|
419
|
+
| `store_id` | `int` | Filtrer par point de vente |
|
|
420
|
+
| `has_tracking` | `bool` | `True` = origines avec au moins un traceur actif |
|
|
421
|
+
| `tracking_channel` | `str` | `phone` ou `email` (validé, sinon `ValueError`) |
|
|
422
|
+
| `include` | `str` | `tracking` pour inclure les numéros/emails traceurs |
|
|
423
|
+
|
|
424
|
+
### Utilisateurs — `client.users`
|
|
425
|
+
|
|
426
|
+
| Paramètre | Type | Description |
|
|
427
|
+
|---|---|---|
|
|
428
|
+
| `store_id` | `int` | Filtrer par point de vente |
|
|
429
|
+
| `interest` | `str` | Filtrer par intérêt |
|
|
430
|
+
| `role` | `str` | `admin`, `manager`, `agent` ou `virtual` (validé, sinon `ValueError`) |
|
|
431
|
+
| `sort` | `str` | `id`, `last_name`, `created_at` |
|
|
432
|
+
|
|
433
|
+
### Statuts — `client.status`
|
|
434
|
+
|
|
435
|
+
| Paramètre | Type | Description |
|
|
436
|
+
|---|---|---|
|
|
437
|
+
| `ids` | `str` | Liste d'ids séparés par virgule |
|
|
438
|
+
| `interest` | `str \| list` | Liste CSV d'intérêts (`"TRANSACTION,LOCATION"`) ou liste Python |
|
|
439
|
+
| `store_id` | `str \| list` | Idem pour les points de vente |
|
|
440
|
+
|
|
441
|
+
Réponse : `[{"label": "...", "sub_status": [...] | null}, …]`.
|
|
442
|
+
|
|
443
|
+
### Points de vente / Champs additionnels / Champs de demande
|
|
444
|
+
|
|
445
|
+
- `client.stores` — GET `/api/v2/stores`, GET `/api/v2/stores/{id}`
|
|
446
|
+
- `client.additional_fields` — GET `/api/v2/additional_fields` (`store_id`, `interest`)
|
|
447
|
+
- `client.request_fields` — GET `/api/v2/requests/fields` (`store_id`, `interest`)
|
|
448
|
+
|
|
449
|
+
### Formulaires publics — `client.form`
|
|
450
|
+
|
|
451
|
+
Soumission d'un formulaire de contact qui crée un lead et envoie un email au destinataire. **Scope requis : `ROLE_API_FORM_WRITE`** (à demander séparément de `lead:write`).
|
|
452
|
+
|
|
453
|
+
```python
|
|
454
|
+
response = client.form.submit({
|
|
455
|
+
"store_id": 776,
|
|
456
|
+
"libelle_id": 12,
|
|
457
|
+
"to_email": "contact@agence.fr", # ou list de destinataires
|
|
458
|
+
"origin": "Site web",
|
|
459
|
+
"message": "Je souhaite visiter le bien X.",
|
|
460
|
+
"subject": "Demande de visite", # optionnel
|
|
461
|
+
"customer": {
|
|
462
|
+
"civility": "M.",
|
|
463
|
+
"first_name": "Jean",
|
|
464
|
+
"last_name": "Dupont",
|
|
465
|
+
"email": "jean@example.com",
|
|
466
|
+
"phone": "0612345678",
|
|
467
|
+
},
|
|
468
|
+
"requests": [{"...": "..."}], # optionnel, labels du référentiel
|
|
469
|
+
"additional_fields": [{"...": "..."}], # optionnel, labels du référentiel
|
|
470
|
+
"external_lead_id": "CRM-12345", # optionnel
|
|
471
|
+
})
|
|
472
|
+
# response == {"status": 200, "message": "email created", "id": 42, "store_id": 776, ...}
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
### Appels sortants — `client.web_callbacks`
|
|
476
|
+
|
|
477
|
+
Déclenche un appel depuis le PBX Scorimmo vers un numéro. **N'utilise pas** l'authentification Bearer : passez la clé personnelle `WebCallback` fournie par Scorimmo pour votre point de vente.
|
|
478
|
+
|
|
479
|
+
```python
|
|
480
|
+
client.web_callbacks.launch("votre-cle-wcb", "+33612345678")
|
|
481
|
+
# {"results": ["..."], "information": 200}
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
---
|
|
485
|
+
|
|
486
|
+
## Référence — Gestion des tokens
|
|
487
|
+
|
|
488
|
+
Le client gère automatiquement l'access token. À chaque expiration, il tente d'abord un refresh silencieux, puis bascule sur email/password si nécessaire.
|
|
489
|
+
|
|
490
|
+
```python
|
|
491
|
+
# 1. Premier démarrage — authentification par identifiants
|
|
492
|
+
client = ScorimmoClient(email="...", password="...")
|
|
493
|
+
|
|
494
|
+
# 2. Forcer l'auth initiale et récupérer le refresh token
|
|
495
|
+
client.get_token()
|
|
496
|
+
refresh_token = client.get_refresh_token()
|
|
497
|
+
# → persister refresh_token (cache Redis, base de données, coffre-fort…)
|
|
498
|
+
|
|
499
|
+
# 3. Démarrages suivants — sans identifiants
|
|
500
|
+
refresh_token = ... # charger depuis le stockage
|
|
501
|
+
client = ScorimmoClient(refresh_token=refresh_token)
|
|
502
|
+
|
|
503
|
+
# 4. Après chaque session, le refresh token a tourné — le re-persister
|
|
504
|
+
client.get_token()
|
|
505
|
+
new_refresh_token = client.get_refresh_token()
|
|
506
|
+
# → mettre à jour le stockage
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
> **Rotation automatique :** chaque refresh token ne peut être utilisé qu'une seule fois. Le nouveau refresh token est disponible via `get_refresh_token()` après chaque renouvellement.
|
|
510
|
+
|
|
511
|
+
Méthodes disponibles :
|
|
512
|
+
|
|
513
|
+
- `client.get_token() -> str`
|
|
514
|
+
- `client.get_refresh_token() -> str | None`
|
|
515
|
+
- `client.refresh_access_token(refresh_token) -> dict`
|
|
516
|
+
- `client.revoke_token(refresh_token=None) -> dict` — `None` révoque tous les tokens du compte
|
|
517
|
+
- `client.validate_token() -> dict` — retourne `version`, `status`, `authenticated`, `scopes`, `stores_id`, `interests`
|
|
518
|
+
|
|
519
|
+
---
|
|
520
|
+
|
|
521
|
+
## Référence — Événements webhook
|
|
522
|
+
|
|
523
|
+
### `new_lead` — Nouveau lead reçu
|
|
524
|
+
|
|
525
|
+
Payload : objet lead complet — `id`, `store_id`, `interest`, `status`, `origin`, `contact_type`, `seller_present_on_creation`, `customer` (`first_name`, `last_name`, `email`, `phone`, `other_phone`, `pro`, `legal_name`, `former`…), `seller` (`id`, `first_name`, `last_name`, `email`, `is_virtual?`), `requests` (liste de biens avec clés en français : `"Type de bien"`, `"Prix"`, `"Surface"`, `"Ville"`, `"Code postal"`, `"Référence"`/`"Programme"`), `additional_fields`, `comments`, `external_lead_id?`, `external_customer_id?`.
|
|
526
|
+
|
|
527
|
+
### `update_lead` — Lead modifié
|
|
528
|
+
|
|
529
|
+
Payload **sparse** : `id`, `updated_at`, et uniquement les champs modifiés (même forme que ci-dessus mais partiel).
|
|
530
|
+
|
|
531
|
+
### `new_comment`
|
|
532
|
+
|
|
533
|
+
Payload : `lead_id`, `comment`, `created_at`, `external_lead_id?`.
|
|
534
|
+
|
|
535
|
+
### `new_rdv`
|
|
536
|
+
|
|
537
|
+
Payload : `lead_id`, `start_time`, `location`, `detail` (nullable — `Estimation`, `Découverte`, `Visite`, `Suivi`, `Proposition`, `Signature`), `comment`, `created_at`, `external_lead_id?`.
|
|
538
|
+
|
|
539
|
+
### `new_reminder`
|
|
540
|
+
|
|
541
|
+
Payload : `lead_id`, `start_time`, `detail` (`offer` ou `recontact`), `comment`, `created_at`, `external_lead_id?`.
|
|
542
|
+
|
|
543
|
+
### `closure_lead` — Lead clôturé
|
|
544
|
+
|
|
545
|
+
| Champ | Type | Description |
|
|
546
|
+
|---|---|---|
|
|
547
|
+
| `lead_id` | `int` | Identifiant du lead clôturé |
|
|
548
|
+
| `status` | `str` | Libellé du statut de clôture : `Succès` (vente/location conclue), `Fermé` (abandonné), `Fermé par l'opérateur` |
|
|
549
|
+
| `close_reason` | `str \| None` | Motif de clôture (présent quand `status` = `Fermé` ou `Succès`) |
|
|
550
|
+
| `external_lead_id` | `str \| None` | Référence CRM du lead, si renseignée |
|
|
551
|
+
|
|
552
|
+
```python
|
|
553
|
+
def on_closure_lead(event: dict) -> None:
|
|
554
|
+
if event["status"] == "Succès":
|
|
555
|
+
# Vente ou location conclue
|
|
556
|
+
...
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
> Pour la structure complète de chaque payload, consultez la [documentation webhooks](https://pro.scorimmo.com/webhook/doc).
|
|
560
|
+
|
|
561
|
+
---
|
|
562
|
+
|
|
563
|
+
## Gestion des erreurs
|
|
564
|
+
|
|
565
|
+
```python
|
|
566
|
+
from scorimmo import (
|
|
567
|
+
ScorimmoApiError, ScorimmoAuthError,
|
|
568
|
+
WebhookAuthError, WebhookValidationError,
|
|
569
|
+
)
|
|
570
|
+
|
|
571
|
+
# Erreurs API
|
|
572
|
+
try:
|
|
573
|
+
lead = client.leads.get(999)
|
|
574
|
+
except ScorimmoAuthError:
|
|
575
|
+
# Identifiants incorrects, refresh token révoqué, ou 401 sur endpoint non authentifié
|
|
576
|
+
print("Erreur d'authentification")
|
|
577
|
+
except ScorimmoApiError as e:
|
|
578
|
+
print(f"Erreur API {e.status_code} ({e.api_code}) : {e}")
|
|
579
|
+
# Codes courants : 400 (VALIDATION_ERROR), 403 (FORBIDDEN), 404 (NOT_FOUND)
|
|
580
|
+
|
|
581
|
+
# Erreurs webhook
|
|
582
|
+
try:
|
|
583
|
+
event = webhook.parse(headers, raw_body)
|
|
584
|
+
except WebhookAuthError:
|
|
585
|
+
# Signature manquante ou invalide → HTTP 401
|
|
586
|
+
pass
|
|
587
|
+
except WebhookValidationError:
|
|
588
|
+
# JSON invalide ou champ "event" manquant → HTTP 400
|
|
589
|
+
pass
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
---
|
|
593
|
+
|
|
594
|
+
## Support
|
|
595
|
+
|
|
596
|
+
- Votre account manager Scorimmo
|
|
597
|
+
- [Formulaire de contact](https://pro.scorimmo.com/contact)
|
|
598
|
+
- [pro.scorimmo.com](https://pro.scorimmo.com)
|