Skip to main content
API recipe API REST Developer integration

How do you create a Rodz signal and route it to lemlist, entirely through the API?

Peter Cools · Updated on · 4 min read · View as Markdown

Short answer

Create the signal as a draft with POST /api/v1/signals/job-changes, read applied and warnings in the response, connect lemlist with PUT /api/v1/integrations/lemlist, get the campaign id, then route the signal with a PATCH on destinations. A last PATCH sets the status to active: from then on each lead costs 2 credits, plus 0.5 credit per email found.

Key facts

  • A signal created without status stays a draft: it produces nothing and costs nothing until it is active.
  • A job change costs 2 credits per lead, plus 0.5 credit per email found (getEmails), charged on success only.
  • The daily limit ranges from 1 to 999,999 leads (dailyLeadLimit, default 100): it is the signal’s spending cap.
  • A PATCH merges config into the stored configuration: send only the fields you change.
Cost : 2 credits per lead (+0.5 credit per email found) Setup time : 15 min

Before you start

  • A Rodz API key (Integrations → API in the dashboard), exported as RODZ_API_KEY.
  • A lemlist API key and a lemlist campaign with at least one step.
  • curl and, to read responses more easily, jq.

Steps

  1. Step 1. Pick the signal type and its allowed values

    GET /api/v1/signal-types is the entry point: each type gives its createEndpoint, configParameters, pricingModel and outputFields. For job changes, roles come from GET /api/v1/personas (the name field) and industries from GET /api/v1/signals/industries (copy them verbatim: matching is exact).

    Catalog, personas and industries

    curl -s "https://api.rodz.io/api/v1/signal-types" -H "Authorization: Bearer $RODZ_API_KEY" \
      | jq '.signalTypes[] | {signalTypeId, createEndpoint, creditsPerLead}'
    curl -s "https://api.rodz.io/api/v1/personas" -H "Authorization: Bearer $RODZ_API_KEY"
    curl -s -G "https://api.rodz.io/api/v1/signals/industries" -H "Authorization: Bearer $RODZ_API_KEY" \
      --data-urlencode "search=software"
  2. Step 2. Create the signal as a draft

    Send name, config, dailyLeadLimit and duplicateHandling. Without status, the signal is created as draft. Turn on getEmails: an email sequence needs the address.

    The 201 response returns the signal with two fields to read: applied (the settings actually in force where they differ from config) and warnings (filter values that are probably not what you meant). They are never errors: the signal is created regardless.

    POST /api/v1/signals/job-changes

    curl -s -X POST "https://api.rodz.io/api/v1/signals/job-changes" \
      -H "Authorization: Bearer $RODZ_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "name": "New sales directors, SaaS, France",
        "dailyLeadLimit": 30,
        "duplicateHandling": "refuse",
        "config": {
          "jobTitles": ["Sales Director"],
          "customTitles": ["Head of Revenue"],
          "companySizeFilters": ["51-200", "201-500"],
          "industryFilters": ["Software Development"],
          "locations": ["France"],
          "getEmails": true
        }
      }' | tee signal.json
    export SIGNAL_ID=$(jq -r .id signal.json)
  3. Step 3. Connect lemlist

    PUT /api/v1/integrations/lemlist saves your lemlist key; it is validated before it is stored, then encrypted. GET /api/v1/integrations then shows connected: true. HubSpot, Pipedrive and Google Sheets connect once through OAuth in the dashboard (the API answers 400 USE_DASHBOARD_OAUTH).

    PUT /api/v1/integrations/lemlist

    curl -s -X PUT "https://api.rodz.io/api/v1/integrations/lemlist" \
      -H "Authorization: Bearer $RODZ_API_KEY" \
      -H "Content-Type: application/json" \
      -d "{\"apiKey\": \"$LEMLIST_API_KEY\", \"label\": \"Sales team\"}"
  4. Step 4. Get the campaign id

    GET /api/v1/integrations/lemlist/campaigns returns items[] with id, name and status. The id is the campaignId of the next step. A 409 means lemlist is not connected.

    GET /api/v1/integrations/lemlist/campaigns

    curl -s "https://api.rodz.io/api/v1/integrations/lemlist/campaigns" \
      -H "Authorization: Bearer $RODZ_API_KEY" | jq '.items[] | {id, name, status}'
  5. Step 5. Route the signal to the campaign, then activate it

    A PATCH with destinations.lemlist.campaignId plugs the signal into the campaign. If several lemlist accounts are connected, add integrationId. A second PATCH sets the signal to active: from then on, every lead delivered is charged and pushed into the campaign.

    PATCH /api/v1/signals/{signal_id}

    curl -s -X PATCH "https://api.rodz.io/api/v1/signals/$SIGNAL_ID" \
      -H "Authorization: Bearer $RODZ_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{"destinations": {"lemlist": {"campaignId": "cam_aBc123XyZ"}}}'
    
    curl -s -X PATCH "https://api.rodz.io/api/v1/signals/$SIGNAL_ID" \
      -H "Authorization: Bearer $RODZ_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{"status": "active"}'

Example payload

`201 Created` response of `POST /api/v1/signals/job-changes` (fictitious data). An empty `applied` means everything runs as stored.

{
  "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "name": "New sales directors, SaaS, France",
  "status": "draft",
  "signalTypeId": "job_changes",
  "webhookUrl": null,
  "config": {
    "jobTitles": ["Sales Director"],
    "customTitles": ["Head of Revenue"],
    "companySizeFilters": ["51-200", "201-500"],
    "industryFilters": ["Software Development"],
    "locations": ["France"],
    "getEmails": true
  },
  "dailyLeadLimit": 30,
  "leadsToday": 0,
  "leadsTotal": 0,
  "duplicateHandling": "refuse",
  "createdAt": "2026-10-12T08:00:00Z",
  "updatedAt": "2026-10-12T08:00:00Z",
  "lastLeadAt": null,
  "applied": {},
  "warnings": []
}

The calls, in order

#CallPurpose
1GET /api/v1/signal-typesPick the type, read configParameters and pricingModel
2GET /api/v1/personas, GET /api/v1/signals/industriesAllowed filter values
3POST /api/v1/signals/job-changesCreate the signal as a draft
4PUT /api/v1/integrations/lemlistConnect lemlist (once per account)
5GET /api/v1/integrations/lemlist/campaignsFind the campaignId
6PATCH /api/v1/signals/{signal_id}Route to the campaign
7PATCH /api/v1/signals/{signal_id}Set to active

Steps 4 and 5 are not repeated for each signal: once lemlist is connected, every new signal only needs steps 3, 6 and 7.

The variables lemlist receives

Each lead enters the campaign with its variables: firstName, lastName, email, companyName, title, linkedinUrl, companyWebsite, signalName, matchedPersona. Use them in your messages, for example “Congratulations on joining {{companyName}}”.

Troubleshooting

  • 422 on create: a config field does not exist for this type. The message names it; the exact list is in configParameters of GET /api/v1/signal-types.
  • No lead in lemlist: the campaign needs at least one step, and a lead without an email cannot enter an email sequence.
  • Quiet signal: GET /api/v1/signals/{signal_id}/funnel shows how many candidates each filter removed.

Frequently asked questions

Why create the signal as a draft?

A draft produces no lead and costs nothing. You can read applied and warnings, plug in the destination, then activate. Activating a signal with no destination and no webhook also works: its leads are then read with GET /api/v1/signals/{signal_id}/leads.

What do `applied` and `warnings` mean?

applied lists the settings applied differently from how they are stored (for example maxJobAgeDays raised to its 180 minimum). warnings flags filter values that are probably wrong, such as an unknown industry or a category sent along with its own industries. The signal is created either way.

How do I change one filter without resending everything?

PATCH /api/v1/signals/{signal_id} merges config into the stored configuration: send only the fields you change. To clear a list, send it empty, for example "customTitles": [].

Can the same signal feed a CRM and lemlist?

Yes. destinations takes several destinations at once, for example hubspot (enabled: true) and lemlist (campaignId). HubSpot and Pipedrive must first be connected through OAuth in the dashboard.

How do I test without waiting for a real lead?

If the signal has a webhookUrl, POST /api/v1/signals/{signal_id}/test-webhook sends a sample lead flagged signal.is_test: true. For lemlist, check the first real lead in the campaign after activation.

Sources

Try it on your own market

Create an account: 100 free credits to test, no credit card.