---
title: "Add people to a watchlist"
method: POST
path: "/api/v1/watchlists/{signalId}/profiles"
tags: ["Watchlists"]
---

# Add people to a watchlist

`POST /api/v1/watchlists/{signalId}/profiles`

## Request

- Base URL: `https://api.rodz.io` (the document declares no server; this is the origin it was published from)
- URL: `https://api.rodz.io/api/v1/watchlists/{signalId}/profiles`
- Auth: HTTP bearer

Add people to a Job Change Watchlist without recreating the signal — grow the list as you win new champions, or swap it wholesale when your CRM segment changes.

**Input:** `signalId` (path), `profiles` (1 to 10,000 entries), and `mode`: `append` (default — adds these and leaves everyone else in place) or `replace` (makes this the whole list; anyone not in it stops being watched, with their history kept).

Each entry in `profiles` is either a LinkedIn profile URL on its own, or an object `{url, currentTitle, currentCompany, currentCompanyUrl}` carrying the role you already have on file. Both forms can be mixed in one call. Country subdomains and tracking query strings are accepted and normalized.

**Send the role you have.** Every check is billed, the first one included. With a role on file, the first check compares against it and reports any change straight away. With a bare URL, the first check can only report a move made in the last 6 months (it compares the current job with the one before it); anyone who moved earlier stays invisible, recorded as they stand today, and a later move is reported at the next check, one `cadenceHours` later. Either field helps on its own — `currentCompany` catches an employer change, `currentTitle` catches a promotion.

One watchlist holds at most 40,000 people. That is a limit on the list, not on the request: send a larger list as successive `append` calls of up to 10,000, and the call that would take the list past 40,000 is rejected with 400.

**Output:** `added` (now watched, were not before), `alreadyWatched` (already on the list, unchanged), `rejected` (not a usable personal profile URL), `stoppedWatching` (dropped by a `replace`; always 0 on `append`), and `totalWatched` (the size of the list after this call).

A bad URL never fails the request — it is counted in `rejected` so one typo in a 500-person list does not reject the other 499. Check `rejected` against what you sent. Re-adding someone previously removed resumes their history rather than reporting their current role as a fresh change.

## Path parameters

- `signalId` string, required: Id of the watchlist signal, as returned by the create endpoint.

## Request body

- object
  - `profiles` union[], required: People to watch. Each entry is either a LinkedIn profile URL on its own, or an object carrying the role you already have on file — send the object form when you can, because it is what lets the first check report a change instead of only recording a starting point. The two forms can be mixed in one call. Country subdomains and tracking query strings are accepted and normalized; anything that is not a personal profile URL is counted in `rejected` rather than failing the request.
    - union
      - string
      - WatchedProfileSeed: A person to watch, with the role you already have on file for them. `{url, currentTitle, currentCompany, currentCompanyUrl}` — only `url` is required. Send the role whenever your CRM has it: it is the comparison point for the FIRST check, so a move made at any time since you recorded that role is reported straight away. With a bare URL the first check can only report a move made in the last 6 months; anyone who moved earlier is recorded as they stand today and reported only on their next move.
        - `url` string, required: LinkedIn profile URL of the person to watch.
        - `currentTitle` string, nullable: Their job title as you have it on file — the comparison point.
        - `currentCompany` string, nullable: Their employer as you have it on file — the comparison point.
        - `currentCompanyUrl` string, nullable: Their employer's LinkedIn company URL, if you have it.
  - `mode` 'append' | 'replace': `append` (default) adds these people to the list and leaves everyone else in place. `replace` makes this the whole list: anyone not in it stops being watched. Their history is kept either way.

## Response `200`

Successful Response

- object: What a list change actually did. Every outcome is reported separately because they mean different things to the caller: `rejected` is input to fix, `alreadyWatched` is a no-op, and `stoppedWatching` is people who dropped off a `replace`.
  - `added` integer, required: Entries now being watched that were not before.
  - `alreadyWatched` integer: Entries already on the list, left unchanged.
  - `rejected` integer: Entries that could not be used — not a valid identifier for this list.
  - `stoppedWatching` integer: Entries dropped because `mode` was `replace`. Always 0 on `append`.
  - `totalWatched` integer, required: Entries being watched after this call.

## Other responses

- `400`: The signal exists but watches the other kind of subject
- `404`: No watchlist signal with this id on your account
- `422`: Validation Error
- `429`: Rate limit exceeded. Retry after the number of seconds in the `Retry-After` response header.

---

[API](https://skmtc.dev/rodz/apis/rodz-api.md) · [All operations](https://skmtc.dev/rodz/apis/rodz-api/llms.txt) · [OpenAPI document](https://skmtc.dev/rodz/apis/rodz-api/revisions/3003505ff896?raw)
