curl --fail-with-body --max-time 30 --get "https://api.freshtalent.ai/v1/events" \
--data-urlencode "since=24h" \
--data-urlencode "type=left_company" \
--data-urlencode "limit=100" \
-H "Authorization: Bearer $FRESHTALENT_API_KEY"
const params = new URLSearchParams({ since: "24h", type: "left_company", limit: "100" });
const response = await fetch(`https://api.freshtalent.ai/v1/events?${params}`, {
signal: AbortSignal.timeout(30_000), headers: { Authorization: `Bearer ${process.env.FRESHTALENT_API_KEY}` },
});
if (!response.ok) throw new Error(`FreshTalent HTTP ${response.status}`);
const page = await response.json();
for (const event of page.data) console.log(event.id, event.person.linkedin_url);
import json
import os
from urllib.parse import urlencode
from urllib.request import Request, urlopen
query = urlencode({"since": "24h", "type": "left_company", "limit": 100})
request = Request(
"https://api.freshtalent.ai/v1/events?" + query,
headers={"Authorization": "Bearer " + os.environ["FRESHTALENT_API_KEY"]},
)
with urlopen(request, timeout=30) as response:
page = json.load(response)
for event in page["data"]:
print(event["id"], event["person"]["linkedin_url"])
List events
Cursor-paginated profile diffs visible on the authenticated organization’s Lists and Monitored Target members.
GET
/
events
curl --fail-with-body --max-time 30 --get "https://api.freshtalent.ai/v1/events" \
--data-urlencode "since=24h" \
--data-urlencode "type=left_company" \
--data-urlencode "limit=100" \
-H "Authorization: Bearer $FRESHTALENT_API_KEY"
const params = new URLSearchParams({ since: "24h", type: "left_company", limit: "100" });
const response = await fetch(`https://api.freshtalent.ai/v1/events?${params}`, {
signal: AbortSignal.timeout(30_000), headers: { Authorization: `Bearer ${process.env.FRESHTALENT_API_KEY}` },
});
if (!response.ok) throw new Error(`FreshTalent HTTP ${response.status}`);
const page = await response.json();
for (const event of page.data) console.log(event.id, event.person.linkedin_url);
import json
import os
from urllib.parse import urlencode
from urllib.request import Request, urlopen
query = urlencode({"since": "24h", "type": "left_company", "limit": 100})
request = Request(
"https://api.freshtalent.ai/v1/events?" + query,
headers={"Authorization": "Bearer " + os.environ["FRESHTALENT_API_KEY"]},
)
with urlopen(request, timeout=30) as response:
page = json.load(response)
for event in page["data"]:
print(event["id"], event["person"]["linkedin_url"])
Returns a newest-first, cursor-paginated stream of profile diffs for the authenticated organization. Without
Only the documented query parameters are supported. The current framework strips unknown query fields rather than returning a validation error; do not rely on this behavior to detect typos.
Checkpoint the pass start time only after all pages and downstream writes succeed. Use an idempotent destination or transactional outbox to handle a crash between the external write and checkpoint. There is no snapshot isolation across pages; changed membership or late-arriving diffs can affect a later pass.
Use a fixed lower bound for a complete pass, follow
The Gateway’s default rate limit is 300 requests per minute per client IP, not per API key. Framework errors can use a different envelope; branch on HTTP status first.
list, visibility is evaluated when the request runs and includes current watching members on the organization’s Targets, shared default-orbit watches, and current members of the organization’s Lists. With list, only members of that organization-owned List are considered. This is the membership-scoped Monitoring stream, not the graph-wide email or Slack notification fan-out.
Each response row is one stored profile diff. The API does not publish a retention SLA or delivery guarantee. The same person can appear more than once, and membership changes can change which events are visible on later requests.
Query parameters
string
Optional lower bound. Use an ISO-8601 date/time or a relative window written as digits plus one unit:
m (minutes), h (hours), d (days), or w (weeks), for example 24h or 2w. Events satisfy seen_at >= since. There is no default when omitted. For pagination, compute an absolute timestamp at the start of a pass and reuse it for every page. Repeating since=24h recalculates the lower bound on each request.string
Optional exact field filter:
open_to_work, between_roles, exploring, left_company, joined_company, removed_position, headline, about, or role_description.string
Optional List selector, at least one character: a List UUID or a case-insensitive List name. A missing List returns
404.string
Optional opaque value copied from the previous page’s
meta.next. It pages toward older events in the same pass. It is not a future-event polling cursor and must not replace a new since value on the next scheduled poll.integer
default:"25"
Number of rows, from
1 through 100 inclusive.since, type, list, and cursor must be non-empty when supplied.
Response
200 returns data plus meta. before, after, and every field in person except linkedin_url are nullable. meta.next is null when there is no next page.
200
{
"data": [
{
"id": "3b241101-e2bb-4255-8caf-4136c566a962",
"field": "left_company",
"before": "Palantir",
"after": "Anduril",
"seen_at": "2026-08-23T10:00:00.000Z",
"severity": "hot",
"source": "snapshot_diff",
"label": "Left Palantir → Anduril",
"person": {
"slug": "derek-morrow",
"name": "Derek Morrow",
"headline": null,
"linkedin_url": "https://www.linkedin.com/in/derek-morrow",
"current_company": "Anduril"
}
}
],
"meta": {
"count": 1,
"limit": 25,
"has_more": false,
"next": null,
"since": "2026-08-22T10:00:00.000Z"
}
}
severity is hot or quiet. source is ingest, snapshot_diff, or demo. meta.since is the normalized ISO-8601 lower bound, or null when no since was supplied.
Ordering, cursors, and polling
Events are ordered byseen_at DESC, id DESC. The cursor uses that pair to select strictly older rows; equal timestamps are ordered by UUID. Keep since, type, and list fixed throughout a pass. meta.count is the number of rows on this page, not a total. A 200 with data: [] is valid.
| Field | Type | Meaning |
|---|---|---|
id | string (UUID) | Stored profile-diff ID; deduplication key |
field | string enum | One of the supported type values |
before, after | string or null | Recorded values; not nested JSON objects |
seen_at | string | ISO timestamp of the recorded diff |
severity, source | string enum | Values listed above |
label | string | Human-readable change description |
person | object | Display metadata and required linkedin_url; other values may be null |
meta.count, meta.limit | integer | Returned row count and page size |
meta.has_more | boolean | Whether an older page is available |
meta.next | string or null | Opaque older-page cursor |
meta.since | string or null | Normalized lower bound |
meta.next until has_more is false, and persist event.id as the event deduplication key. Start the next scheduled pass with a new lower bound based on the last successful checkpoint and a small overlap, such as five minutes. The overlap protects against boundary and clock differences; UUID deduplication makes the replay safe. Never carry the final page cursor into the next pass.
This read is safe to retry on a transient network failure, 429, or 5xx with bounded exponential backoff and jitter. Honor Retry-After. Recheck the request and fix the cause for 400, 401, 402, or 404 instead of retrying unchanged.
Errors
| Status | Meaning | Recovery |
|---|---|---|
400 | Invalid since, enum, bounds, or cursor | Correct the request; do not retry unchanged. |
401 | Missing, malformed, or revoked API key | Supply a valid key. |
402 | Organization trial ended or subscription is required | Resolve organization access. |
404 | The requested List is not found in this organization | Check the UUID/name and organization scope. |
429 | Shared Gateway limit exceeded | Honor Retry-After and back off. |
5xx | Gateway or dependency failure | Retry the bounded read with backoff; if completeness matters, rerun the pass with overlap. |
curl --fail-with-body --max-time 30 --get "https://api.freshtalent.ai/v1/events" \
--data-urlencode "since=24h" \
--data-urlencode "type=left_company" \
--data-urlencode "limit=100" \
-H "Authorization: Bearer $FRESHTALENT_API_KEY"
const params = new URLSearchParams({ since: "24h", type: "left_company", limit: "100" });
const response = await fetch(`https://api.freshtalent.ai/v1/events?${params}`, {
signal: AbortSignal.timeout(30_000), headers: { Authorization: `Bearer ${process.env.FRESHTALENT_API_KEY}` },
});
if (!response.ok) throw new Error(`FreshTalent HTTP ${response.status}`);
const page = await response.json();
for (const event of page.data) console.log(event.id, event.person.linkedin_url);
import json
import os
from urllib.parse import urlencode
from urllib.request import Request, urlopen
query = urlencode({"since": "24h", "type": "left_company", "limit": 100})
request = Request(
"https://api.freshtalent.ai/v1/events?" + query,
headers={"Authorization": "Bearer " + os.environ["FRESHTALENT_API_KEY"]},
)
with urlopen(request, timeout=30) as response:
page = json.load(response)
for event in page["data"]:
print(event["id"], event["person"]["linkedin_url"])
Related
- Get account for organization access and
watch_slots. - Get notification preferences and Update notification preferences for graph-wide delivery settings.
- Build your first integration for a tested polling script.
- ATS/CRM architecture for a polling integration.
- Errors and Rate limits for shared retry rules.