Appelez GET /api/v1/signals/{signal_id}/leads avec le paramètre since, paginez avec limit et offset tant que hasMore vaut true, puis écrivez chaque lead dans votre CRM en le dédupliquant sur contact.lead_id. Lire les leads est gratuit : un lead est facturé une seule fois, quand il est produit. Un script planifié toutes les heures suffit, sans serveur exposé à internet.
Chiffres clés
Lire les leads est gratuit : un lead est facturé une seule fois, au moment où le signal le produit.
Une page contient de 1 à 100 leads (limit, 25 par défaut), du plus récent au plus ancien.
La limite de débit est de 100 requêtes par seconde par clé API ; au-delà, l'API répond 429 avec Retry-After: 1.
Chaque lead a exactement la forme du webhook du signal : objets signal, contact et company, champs en snake_case.
Coût : Gratuit à la lecture (le lead est facturé à sa production) Mise en place : 20 min
Avant de commencer
Une clé API Rodz, créée dans Intégrations → API du tableau de bord (format USR_...).
Un signal actif, par exemple un signal Nominations, et son identifiant.
Python 3.9+ avec la bibliothèque requests (pip install requests), ou simplement curl.
Un endroit où écrire les leads : CRM, base de données, tableur.
Étapes
Étape 1.
Trouvez l'identifiant de votre signal
GET /api/v1/signals liste les signaux du compte avec leur id, leur status, leur signalTypeId et leurs compteurs leadsToday et leadsTotal. Filtrez avec status=active ou signalTypeId=job_changes.
GET /api/v1/signals/{signal_id}/leads renvoie les leads du plus récent au plus ancien. since (horodatage ISO-8601) ne garde que les leads produits à partir de cet instant. La réponse donne total, limit, offset et hasMore.
Les leads écartés par vos filtres ne sont pas renvoyés : vous ne lisez que ce qui a été livré.
Tant que hasMore vaut true, rappelez la même URL avec offset augmenté de limit. Sur une réponse 429, attendez le nombre de secondes indiqué par l'en-tête Retry-After, puis renvoyez la même requête.
Le script ci-dessous fait les deux, et garde l'heure de la dernière exécution dans un petit fichier pour ne relire que les nouveaux leads.
poll_rodz_leads.py
import jsonimport osimport pathlibimport timefrom datetime import datetime, timedelta, timezoneimport requestsAPI = "https://api.rodz.io/api/v1"SIGNAL_ID = os.environ["RODZ_SIGNAL_ID"]STATE = pathlib.Path("rodz_last_run.json")session = requests.Session()session.headers["Authorization"] = f"Bearer {os.environ['RODZ_API_KEY']}"def get(path: str, params: dict) -> dict: """GET with a retry on 429 (Retry-After is given in seconds).""" while True: resp = session.get(f"{API}{path}", params=params, timeout=30) if resp.status_code == 429: time.sleep(float(resp.headers.get("Retry-After", "1"))) continue resp.raise_for_status() return resp.json()def leads_since(since: str): """Yield every lead produced at or after `since`, page by page.""" offset = 0 while True: page = get( f"/signals/{SIGNAL_ID}/leads", {"since": since, "limit": 100, "offset": offset}, ) yield from page["leads"] if not page["hasMore"]: return offset += page["limit"]def upsert_into_crm(lead: dict) -> None: """Replace with your CRM call. Key on contact.lead_id so a re-read is harmless.""" contact = lead.get("contact") or {} # "" or null on company-only leads company = lead.get("company") or {} key = contact.get("lead_id") or f"{lead['signal']['id']}:{company.get('website', '')}" print(key, contact.get("first_name"), contact.get("last_name"), company.get("name"))def main() -> None: # First run: the last 24 hours. Later runs: from the start of the previous run. default = (datetime.now(timezone.utc) - timedelta(days=1)).isoformat() since = json.loads(STATE.read_text())["since"] if STATE.exists() else default started = datetime.now(timezone.utc).isoformat() # taken BEFORE reading count = 0 for lead in leads_since(since): upsert_into_crm(lead) count += 1 STATE.write_text(json.dumps({"since": started})) print(f"{count} lead(s) since {since}")if __name__ == "__main__": main()
Étape 4.
Planifiez le script
Lancez-le toutes les heures (cron, tâche planifiée, job CI) avec RODZ_API_KEY et RODZ_SIGNAL_ID en variables d'environnement. L'heure de départ est enregistrée avant la lecture : un lead produit pendant l'exécution sera relu la fois suivante, et la clé lead_id rend cette relecture sans effet dans votre CRM.
Les deux approches se combinent : un webhook pour le temps réel, et le script de lecture une fois par jour comme filet de sécurité.
Lire plusieurs signaux
Bouclez sur les identifiants renvoyés par GET /api/v1/signals?status=active et gardez une date since par signal. Pour ne voir que les signaux créés par une intégration donnée (et pas ceux de la production quand vous testez), ajoutez createdByThisKey=true.
Dépannage
404 NOT_FOUND : l’identifiant du signal est faux, ou le signal appartient à un autre compte que celui de la clé.
401 UNAUTHORIZED : la clé est absente ou révoquée ; l’en-tête doit être Authorization: Bearer USR_....
Aucun lead : vérifiez que le signal est actif et que since n’est pas dans le futur. L’entonnoir du signal (GET /api/v1/signals/{signal_id}/funnel) montre quel filtre écarte les candidats.
Questions fréquentes
Webhook ou lecture par l'API : que choisir ?
Le webhook livre chaque lead au moment où il est produit, mais demande un serveur joignable depuis internet. La lecture par l'API se contente d'un script planifié et sert aussi à rattraper un CRM après une panne. Les deux renvoient exactement le même format.
La lecture des leads consomme-t-elle des crédits ?
Non. Un lead est facturé une seule fois, quand le signal le produit. Le relire par l'API, autant de fois que nécessaire, est gratuit.
Comment éviter les doublons dans mon CRM ?
Utilisez contact.lead_id comme clé d'insertion ou de mise à jour. Comme since inclut les leads produits à partir de l'instant donné, deux exécutions peuvent se recouvrir : la clé rend ce recouvrement sans effet.
Que se passe-t-il si je dépasse la limite de débit ?
L'API répond 429 Too Many Requests avec un en-tête Retry-After. Attendez ce nombre de secondes et renvoyez la requête. À 100 requêtes par seconde par clé, un script de lecture horaire n'atteint pas cette limite.
Pourquoi `contact` est-il vide sur certains leads ?
Certains signaux livrent des entreprises sans personne (une levée de fonds sans ciblage de persona, par exemple). contact est alors vide : prévoyez une clé de secours basée sur l'entreprise, comme dans le script.