Aller au contenu principal
Recette API API REST Intégration développeur

Comment récupérer les leads d'un signal Rodz par l'API, sans webhook ?

Peter Cools · Mis à jour le · 4 min de lecture · Version Markdown

En bref

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

  1. É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.

    Lister les signaux actifs

    curl -G "https://api.rodz.io/api/v1/signals" \
      -H "Authorization: Bearer $RODZ_API_KEY" \
      --data-urlencode "status=active"
  2. Étape 2. Lisez les leads produits depuis une date

    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é.

    Une page de leads

    curl -G "https://api.rodz.io/api/v1/signals/$SIGNAL_ID/leads" \
      -H "Authorization: Bearer $RODZ_API_KEY" \
      --data-urlencode "since=2026-10-11T00:00:00+00:00" \
      --data-urlencode "limit=100" \
      --data-urlencode "offset=0"
  3. Étape 3. Paginez et respectez la limite de débit

    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 json
    import os
    import pathlib
    import time
    from datetime import datetime, timedelta, timezone
    
    import requests
    
    API = "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()
  4. É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.

    crontab : toutes les heures, à la minute 5

    5 * * * * cd /opt/rodz && RODZ_API_KEY=USR_xxx RODZ_SIGNAL_ID=a1b2c3d4-... python3 poll_rodz_leads.py >> poll.log 2>&1

Exemple de payload

Une page renvoyée par `GET /api/v1/signals/{signal_id}/leads` (un lead Nominations, données fictives).

{
  "signalId": "d4f5a6b7-c8d9-4e0f-a1b2-c3d4e5f6a7b8",
  "total": 42,
  "limit": 100,
  "offset": 0,
  "hasMore": false,
  "leads": [
    {
      "signal": {
        "id": "d4f5a6b7-c8d9-4e0f-a1b2-c3d4e5f6a7b8",
        "name": "Nouveaux dirigeants commerciaux SaaS",
        "type": "job_changes",
        "last_signal": "Job Change",
        "last_signal_date": "2026-10-11",
        "previous_title": "Head of Sales",
        "previous_company": "Northwind Analytics",
        "new_title": "Directeur commercial",
        "new_company": "Tech Corp",
        "new_job_started_on": "09/2026",
        "new_job_start_month": "2026-09",
        "change_type": "company"
      },
      "contact": {
        "lead_id": "b2c3d4e5-f6a7-8901-bcde-f23456789012",
        "created_at": "2026-10-11T09:12:00+00:00",
        "first_name": "Julien",
        "last_name": "Martin",
        "email": "julien.martin@techcorp.example",
        "phone": "",
        "title": "Directeur commercial",
        "location": "Paris, Île-de-France, France",
        "linkedin_profile_url": "https://www.linkedin.com/in/julien-martin-example",
        "matched_persona": "Sales Director"
      },
      "company": {
        "name": "Tech Corp",
        "website": "https://techcorp.example",
        "linkedin_url": "https://www.linkedin.com/company/techcorp-example",
        "industry": "Software Development",
        "employee_range": "201-500",
        "hq_country_code": "FR"
      }
    }
  ]
}

Quand préférer la lecture par l’API

BesoinWebhookLecture par l’API
Recevoir le lead à la secondeOuiNon (au rythme de votre planification)
Serveur joignable depuis internetObligatoireInutile
Rattraper des leads après une pannePOST /signals/{id}/replay-webhookOui, avec since
CoûtInclus dans le prix du leadGratuit

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.

Sources

Essayez sur votre marché

Créez un compte : 100 crédits offerts pour tester, sans carte bancaire.