curl --fail-with-body --max-time 30 "https://api.freshtalent.ai/v1/targets/550e8400-e29b-41d4-a716-446655440000/members?status=proposed&limit=50&offset=0" \
-H "Authorization: Bearer $FRESHTALENT_API_KEY"
const params = new URLSearchParams({ status: "proposed", limit: "50", offset: "0" });
const response = await fetch(
`https://api.freshtalent.ai/v1/targets/550e8400-e29b-41d4-a716-446655440000/members?${params}`,
{ signal: AbortSignal.timeout(30_000), headers: { Authorization: `Bearer ${process.env.FRESHTALENT_API_KEY}` } },
);
const body = await response.json();
if (!response.ok) throw new Error(`${response.status}: ${JSON.stringify(body)}`);
const next = body.meta.count > 50 ? new URL(response.url) : null;
if (next) next.searchParams.set("offset", "50");
console.log(body.data, next?.toString());
import json
import os
import urllib.parse
import urllib.request
query = urllib.parse.urlencode({"status": "proposed", "limit": 50, "offset": 0})
request = urllib.request.Request(
"https://api.freshtalent.ai/v1/targets/550e8400-e29b-41d4-a716-446655440000/members?" + query,
headers={"Authorization": f"Bearer {os.environ['FRESHTALENT_API_KEY']}"},
)
with urllib.request.urlopen(request, timeout=30) as response:
body = json.load(response)
print(body["data"], body["meta"]["count"])
{
"data": [
{
"id": "8c5f6a6b-5cb7-4e3f-a1fd-9aabf2d2c6e0",
"watch_id": "550e8400-e29b-41d4-a716-446655440000",
"linkedin_url": "https://www.linkedin.com/in/derek-morrow",
"status": "proposed",
"full_name": "Derek Morrow",
"headline": "Forward Deployed Engineer",
"current_company": "Palantir",
"is_current": false,
"public_identifier": "derek-morrow",
"profile_slug": "derek-morrow",
"linkedin_id": "123456789",
"location": "New York, United States",
"profile_picture_url": null,
"enriched_at": null,
"first_seen_at": "2026-09-07T10:00:00.000Z"
}
],
"meta": { "count": 1 }
}
List target members
List a Target’s members with status, people, stint, location, role, and enrichment filters.
GET
/
targets
/
{id}
/
members
curl --fail-with-body --max-time 30 "https://api.freshtalent.ai/v1/targets/550e8400-e29b-41d4-a716-446655440000/members?status=proposed&limit=50&offset=0" \
-H "Authorization: Bearer $FRESHTALENT_API_KEY"
const params = new URLSearchParams({ status: "proposed", limit: "50", offset: "0" });
const response = await fetch(
`https://api.freshtalent.ai/v1/targets/550e8400-e29b-41d4-a716-446655440000/members?${params}`,
{ signal: AbortSignal.timeout(30_000), headers: { Authorization: `Bearer ${process.env.FRESHTALENT_API_KEY}` } },
);
const body = await response.json();
if (!response.ok) throw new Error(`${response.status}: ${JSON.stringify(body)}`);
const next = body.meta.count > 50 ? new URL(response.url) : null;
if (next) next.searchParams.set("offset", "50");
console.log(body.data, next?.toString());
import json
import os
import urllib.parse
import urllib.request
query = urllib.parse.urlencode({"status": "proposed", "limit": 50, "offset": 0})
request = urllib.request.Request(
"https://api.freshtalent.ai/v1/targets/550e8400-e29b-41d4-a716-446655440000/members?" + query,
headers={"Authorization": f"Bearer {os.environ['FRESHTALENT_API_KEY']}"},
)
with urllib.request.urlopen(request, timeout=30) as response:
body = json.load(response)
print(body["data"], body["meta"]["count"])
{
"data": [
{
"id": "8c5f6a6b-5cb7-4e3f-a1fd-9aabf2d2c6e0",
"watch_id": "550e8400-e29b-41d4-a716-446655440000",
"linkedin_url": "https://www.linkedin.com/in/derek-morrow",
"status": "proposed",
"full_name": "Derek Morrow",
"headline": "Forward Deployed Engineer",
"current_company": "Palantir",
"is_current": false,
"public_identifier": "derek-morrow",
"profile_slug": "derek-morrow",
"linkedin_id": "123456789",
"location": "New York, United States",
"profile_picture_url": null,
"enriched_at": null,
"first_seen_at": "2026-09-07T10:00:00.000Z"
}
],
"meta": { "count": 1 }
}
Returns member rows for one Target. It is the read operation for the dashboard’s Everyone, Monitored, Pending Approval, and Removed views.
All query parameters are optional. The route does not framework-validate query strings; use the documented values and URL-encode them.
Request
string
required
Target UUID.
string
all (default), proposed, watching, or excluded. Dashboard labels are Pending Approval, Monitored, and Removed.string
Case-insensitive substring search across
full_name, headline, and current_company. keywords is accepted as an alias when q is absent.string
all (default), current, or alum, based on is_current. company_current=true is an alias for stint=current; company_left_min_months_ago greater than zero is an alias for stint=alum.string
Semicolon-separated places. The shared parser keeps at most 20 distinct segments and truncates each segment to 120 characters. Matching is case-insensitive; commas remain part of a place such as
Paris, France.string
Same 20-segment/120-character parser. Excluded locations take priority over included locations. With no
location, this excludes matching places everywhere.string
Case-insensitive substring match against
current_company.string
Comma-separated role tokens, maximum five parsed roles. A family token is
family[:minimum_months][@company]; @company sets company scope. A title token is t.exact.<title-or-titles-separated-by-semicolons>.<minimum_months> or t.contains.... The parser clamps encoded minimum months to 0–600, but this Target-member route applies the parsed family/title patterns to member headlines; it does not apply the parsed minimum-month or scope values. This filter is independent of the stored discovery query.boolean
Pass the exact string
true to exclude headlines matching recruiter, talent-acquisition, sourcer, staffing, or talent-partner terms. Other values behave as false.string
all (default), stub (not enriched), or enriched (enriched_at is non-null).string
Timestamp parsed by PostgreSQL as
timestamptz; rows are included when first_seen_at is at or after it. Use an ISO-8601 timestamp.integer
Default
50; clamped to a minimum of 1 and maximum of 500.integer
Default
0; negative values become 0. This is offset pagination, not cursor pagination.Response and pagination
200 OK returns { "data": Member[], "meta": { "count": integer } }. meta.count is the total number of rows matching all filters, not the page length. Rows are ordered with proposed first, then watching, then excluded, and by full_name with nulls last.
There is no has_more or server-generated next link. The next page is offset + limit while that value is below meta.count; stop when it is equal to or greater than meta.count.
Each member has this schema:
| Field | Type | Nullability and meaning |
|---|---|---|
id | string | Non-null member UUID. |
watch_id | string | Non-null Target UUID. |
linkedin_url | string | Non-null normalized LinkedIn profile URL. |
status | "watching" | "excluded" | "proposed" | Non-null membership state. |
full_name, headline, current_company, public_identifier, linkedin_id, location, profile_picture_url, profile_slug | string | Nullable profile/enrichment fields. |
is_current | boolean | Nullable when current/alumni is unknown. |
enriched_at | string | Nullable timestamp. |
first_seen_at | string | Non-null timestamp. |
{
"data": [
{
"id": "8c5f6a6b-5cb7-4e3f-a1fd-9aabf2d2c6e0",
"watch_id": "550e8400-e29b-41d4-a716-446655440000",
"linkedin_url": "https://www.linkedin.com/in/derek-morrow",
"status": "proposed",
"full_name": "Derek Morrow",
"headline": "Forward Deployed Engineer",
"current_company": "Palantir",
"is_current": false,
"public_identifier": "derek-morrow",
"profile_slug": "derek-morrow",
"linkedin_id": "123456789",
"location": "New York, United States",
"profile_picture_url": null,
"enriched_at": null,
"first_seen_at": "2026-09-07T10:00:00.000Z"
}
],
"meta": { "count": 1 }
}
Examples
curl --fail-with-body --max-time 30 "https://api.freshtalent.ai/v1/targets/550e8400-e29b-41d4-a716-446655440000/members?status=proposed&limit=50&offset=0" \
-H "Authorization: Bearer $FRESHTALENT_API_KEY"
const params = new URLSearchParams({ status: "proposed", limit: "50", offset: "0" });
const response = await fetch(
`https://api.freshtalent.ai/v1/targets/550e8400-e29b-41d4-a716-446655440000/members?${params}`,
{ signal: AbortSignal.timeout(30_000), headers: { Authorization: `Bearer ${process.env.FRESHTALENT_API_KEY}` } },
);
const body = await response.json();
if (!response.ok) throw new Error(`${response.status}: ${JSON.stringify(body)}`);
const next = body.meta.count > 50 ? new URL(response.url) : null;
if (next) next.searchParams.set("offset", "50");
console.log(body.data, next?.toString());
import json
import os
import urllib.parse
import urllib.request
query = urllib.parse.urlencode({"status": "proposed", "limit": 50, "offset": 0})
request = urllib.request.Request(
"https://api.freshtalent.ai/v1/targets/550e8400-e29b-41d4-a716-446655440000/members?" + query,
headers={"Authorization": f"Bearer {os.environ['FRESHTALENT_API_KEY']}"},
)
with urllib.request.urlopen(request, timeout=30) as response:
body = json.load(response)
print(body["data"], body["meta"]["count"])
Errors and recovery
401 unauthorized,402 payment_required, and platform-key400 invalid_request: fix authentication, access, or organization header.404 not_foundwithwatch not found: verify the Target ID and organization.429/5xx: retry the read with bounded backoff and honorRetry-After.- An invalid timestamp or malformed numeric query can become a database/framework error rather than a typed validation response. Send ISO timestamps and decimal non-negative integers.
Lifecycle
A row withstatus: "watching" is the membership state used by Monitoring. Listing does not enrich or change members. A proposed row can be moved with Accept target members; status changes to watching queue enrichment, but do not guarantee immediate monitoring freshness.
Next
- Get target for aggregate counts and job progress.
- Accept target members to mutate selected rows or a filtered set.