Peopleseek is used by employers to run hiring. This page describes the candidate data the product holds on an employer's behalf, and what actually happens when a candidate asks for access or erasure.
01How to read this page
Everything below about stored data, erasure and deliberate retention is generated from the product's own data register — the same register the access-request and erasure pipelines run against. If the product changes, this page changes with it. Sections that require a legal answer are marked and left unanswered.
02What erasure does
An erasure request is executed against every place candidate data is held. Each place has one of the following outcomes:
Required columns are overwritten with a placeholder, so audit history survives while carrying no identity. (3 of 38 places)
The row is deleted outright — the row is the person's data. (33 of 38 places)
Identifying columns are cleared and a skeleton row is kept, so records that point at it do not break. (2 of 38 places)
03Where candidate data is held (38 places)
Each entry names the store, how one person's records are found in it, and what erasure does there. The descriptions are the product's own.
booking_manage_token
The row is deleted outright — the row is the person's data.
Found by: interview_id ∈ (interviews of the person: lead_id ∈ leadIds | an attendee ∈ emails, case-insensitive)
The candidate's manage link of a booking (migration 0191): a sha256 hash, the interview it manages and four instants. The row carries NO identity of its own and is registered for the screen_access_token reasons: (1) it is the CREDENTIAL to the person's booking (move it, cancel it, see its meeting link), and a live link outliving an erasure is a live door to a deleted meeting; (2) executeErasure and exportPersonData enumerate this registry and nothing else, so an unregistered child makes the report understate what was destroyed. Found through the person's interviews: by lead_id, and by their address among the attendees, which also reaches the links of interviews that were never matched to a candidate (the interview entry below finds rows by lead_id only, a documented gap). Listed BEFORE interview: its delete_row cascades to these rows and would leave this count at 0. Until migration 0191 is applied the table is absent and this entry erases and exports nothing: it checks for the table first, so the erasure transaction never aborts on it. A SAR export gets a hash and timestamps, noise rather than disclosure.
buying_signal
The row is deleted outright — the row is the person's data.
Found by: contact_id
Normalized signal data about the person. Erased before signal_event so cascade deletes cannot hide rows from the report.
call_run
Required columns are overwritten with a placeholder, so audit history survives while carrying no identity.
Cleared: to_number, from_number, recording_url, recording_sid, consent_audio_url, follow_up_note, error_message.
Found by: crm_activity_id ∈ the person's activities | to/from number = person.phone or a contact_point phone (digits)
A call with the person, the AI voice agent's or a recruiter's own (Phase 2 call-core). The two numbers are overwritten (notNull), the recording link, the consent audio, the follow-up note and Twilio's error text are nulled; the row stays for the call count, its duration and cost, and the member who placed it. NOT delete_row: the per-member daily call cap counts these rows. The recording itself lives at Twilio: the app does not erase it here; a recording of the platform Twilio account is deleted at Twilio by the erasure path when the platform names are set, and an AI agent's recording on a workspace's own Twilio account stays there (the workspace removes it in its console).
call_transcript_segment
The row is deleted outright — the row is the person's data.
Found by: call_run_id ∈ the person's calls (crm_activity link | to/from number = their phone)
What was said on a call with the person (written by the voice bridge, PII-redacted but their words all the same). Registered by judgement: no column name or person key on this table can be flagged by the drift gate. delete_row: the segments are the content; the call_run row they hang off stays, anonymized (entry below). Before the anonymization, so a call matched by its number still reaches its transcript.
candidate
Identifying columns are cleared and a skeleton row is kept, so records that point at it do not break.
Cleared: raw_data, normalized_data; the record is also marked deleted.
Found by: id ∈ leadIds (resolved via contact_id / normalized_data->>'email')
Skeleton row kept for FK integrity (dispatch/approval history hangs off it); the imported person data (raw/normalized jsonb) is nulled and the row soft-deleted.
candidate_pipeline_member
The row is deleted outright — the row is the person's data.
Found by: lead_id
Pipeline membership of the erased candidate (table candidate_pipeline_member).
candidate_score
The row is deleted outright — the row is the person's data.
Found by: lead_id
Score rationale can quote person facts; meaningless post-erasure.
channel_message
The row is deleted outright — the row is the person's data.
Found by: contact_id ∈ contactIds | lead_id ∈ leadIds | (no contact_id and no lead_id AND from_identity or to_identity = one of the person's own phone numbers, LinkedIn links or addresses)
Texts, LinkedIn messages and connection requests, and WhatsApp messages, in either direction (migration 0194): the message text and the person's phone number or LinkedIn link in clear. The row IS the person's data and has no FK children: deleted outright. Found by its links to the person (contact_id → person, lead_id → candidate) and, for a row linked to nobody (a text or a reply from a number no candidate was matched to), by identity: from_identity or to_identity equal to one of the person's own phone numbers, LinkedIn links or addresses, read from their person rows (and the person row of each of their candidates) and from their candidates' contact points; numbers are compared digit by digit, links without scheme, www, query or trailing slash. A row linked to another person is never reached by identity. Enumerated BEFORE contact_point and person, whose numbers and links that lookup reads. Until migration 0194 is applied the table is absent and this entry erases and exports nothing: it checks for the table first, so the erasure transaction never aborts on it. Gap, documented: a number stored without its country code on one side and with it on the other is not recognised as the same.
clay_enrichment_request
The row is deleted outright — the row is the person's data.
Found by: lead_id
Clay webhook responses — the enriched person row.
contact_point
The row is deleted outright — the row is the person's data.
Found by: lead_id ∈ leadIds | (kind='email' AND value_normalized ∈ emails)
Wave-3 reachability rows (migration 0166): a person's e-mail / phone / LinkedIn in clear text in value + value_normalized, one row per contact point. The row IS the person's data and has no FK children — deleted outright. THIS ENTRY IS THE ONLY ERASER: the drift gate cannot demand it (its heuristic matches COLUMN NAMES, and these columns are called kind and value), and the lead_id → candidate(id) ON DELETE cascade never fires because candidate's own strategy is null_fields + softDelete — the candidate row survives erasure by design. (The COLUMN is still lead_id: wave 4 renamed tables, never columns, so the FK followed its table without being touched — proven by the cold replay.) Without this entry, erasure would leave the address in clear text AND the SAR export would return an incomplete file: both iterate this registry and nothing else. Gap, twin of sourcing_candidate's: a phone/linkedin row hung on ANOTHER person's candidate has no resolvable key (DSR resolution yields addresses, not numbers or URLs).
crm_activity
The row is deleted outright — the row is the person's data.
Found by: contact_id | lead_id
Free-text notes/calls/meetings about the person. Inbound FKs (meeting.crm_activity_id, call_run.crm_activity_id) are SET NULL — audit rows survive.
crm_email
The row is deleted outright — the row is the person's data.
Found by: contact_id | lead_id | from_email | to_emails/cc_emails contains
Synced mail rows about/with the person (from/to/cc, subject, snippet).
crm_external_link
The row is deleted outright — the row is the person's data.
Found by: entity_id ∈ contactIds ∪ leadIds (polymorphic, no FK)
Maps a provider record id onto one of our rows. When object_type is contact or candidate, external_id IS a provider-side identifier for that person — it must not outlive an erasure, and it belongs in the SAR file. Registered by JUDGMENT: neither half of the drift gate can ask for it. The column heuristic reads names (entity_id / external_id say nothing), and the structural criterion looks for a declared FK to candidate/person or a column named lead_id/contact_id — this table's references are bare uuids, polymorphic by design. delete_row and not null_fields: entity_id and external_id ARE the row; a skeleton would keep the provider-side identifier alive, which is the whole point of deleting it. Ordered BEFORE candidate/person so the counts are real — candidate is null_fields + softDelete and never cascades.
crm_sync_conflict
The row is deleted outright — the row is the person's data.
Found by: entity_id ∈ contactIds ∪ leadIds (polymorphic)
our_value/their_value hold raw field values — for email/phone conflicts, the person's identifiers verbatim.
crm_task
The row is deleted outright — the row is the person's data.
Found by: contact_id | lead_id
Tasks attached to the erased subject (titles/bodies routinely name the person). Standalone teammate tasks (no subject link) are untouched.
enrichment_record
The row is deleted outright — the row is the person's data.
Found by: lead_id
Provider enrichment payloads: emails, phones, employment history.
gtm_artifact
The row is deleted outright — the row is the person's data.
Found by: lead_id ∈ leadIds
Wave-5 shareable artifact (kinds candidate_dossier / shortlist_pack / mission_report, migration 0168 adds the publish token). Registered, not excluded, for two reasons. (1) The row IS a document ABOUT the person: its jsonb body is a dossier, and executeErasure/exportPersonData enumerate this registry and nothing else, so an unregistered artifact would leave that dossier in place after a valid erasure and absent from the SAR export. (2) It carries a live share token: deleting the row removes it from the partial unique index, so the shared link dies with the document — an erasure that left a working link would be an erasure in name only. delete_row and not null_fields: the only FK child is gtm_artifact_access, which CASCADEs, and that log is itself person data (hashed reader fingerprints) with no independent audit value once the document is gone. Ordered BEFORE candidate so the counts are real; candidate's own strategy is null_fields + softDelete, so it never cascades and cannot hide these rows. GAP, documented: a shortlist_pack covering several candidates carries no lead_id and is unreachable by this predicate — the jsonb-embedding class the drift gate's own header calls out of scope (WS-142 redaction owns it).
inbox_draft
The row is deleted outright — the row is the person's data.
Found by: lead_id | to/cc/bcc_addresses contains
Drafts addressed to the person (recipient lists + reply bodies).
interview
The row is deleted outright — the row is the person's data.
Found by: lead_id
Meeting capture for the person's lead; deleting cascades meeting_transcript_segment. Meetings matched only via the attendees array (multi-person rows) are a documented gap — see the PR.
interview_feedback_rating
The row is deleted outright — the row is the person's data.
Found by: feedback_id → interview_panel_feedback → interview.lead_id ∈ leadIds
Wave-5 per-criterion interview ratings (migration 0168): rating + note + criterion_snapshot, i.e. a named judgement of a candidate against a hiring criterion. The row IS the person's data and is deleted outright. LISTED BEFORE ITS PARENT ON PURPOSE: erasure walks this registry IN ARRAY ORDER, and interview is delete_row — if the parent ran first the CASCADE would take these rows and this entry would report 0 while rows had in fact disappeared, i.e. a statutory report that understates. THE DRIFT GATE CANNOT DEMAND THIS ENTRY: its heuristic matches COLUMN NAMES, and these columns are called rating, note and criterion_snapshot. Nor does any cascade rescue it during an erasure: candidate's own strategy is null_fields + softDelete, so the candidate row survives and its children are never cascaded away. Without this entry, erasure would leave the judgement in clear text AND the SAR export would return an incomplete file: erasure-service.ts and export-service.ts iterate this registry and nothing else. criterion_snapshot is what keeps the row readable after the requirement is gone — all the more reason for the registry to be the thing that erases it.
interview_panel_feedback
The row is deleted outright — the row is the person's data.
Found by: interview_id → interview.lead_id ∈ leadIds
Wave-5 interview feedback (migration 0168): overall verdict + free-text summary about a named candidate, written by a named human (interviewer_user_id NOT NULL). The row IS the person's data — delete_row, not anonymize: nothing downstream hangs off it that has to survive (its only child, interview_feedback_rating, is erased by the entry above). LISTED BEFORE interview for the same array-order reason as its own child. The interviewer's identity is NOT the data subject here: interviewer_user_id points at `user`, a platform member, which REVIEWED_EXCLUSIONS covers under member-offboarding. Deleting these rows is also what unblocks that flow — the FK is ON DELETE RESTRICT, so a member who has ever written feedback cannot be deleted until it is gone.
interview_panel_member
The row is deleted outright — the row is the person's data.
Found by: contact_id
Stance/notes about a named, FK-linked person.
message_draft
Required columns are overwritten with a placeholder, so audit history survives while carrying no identity.
Cleared: subject, body.
Found by: lead_id
NOT delete_row: approval and execution_dispatch CASCADE on draft delete — erasing content must not destroy approval/dispatch audit. Body/subject overwritten instead.
outreach_enrollment
The row is deleted outright — the row is the person's data.
Found by: lead_id | contact_email (an address, or person:<person id>)
contact_email/contact_name are denormalized snapshots; a person with no email is keyed person:<person id> in contact_email (lib/gtm/journey-key). Deleting cascades outreach_send (FK-only rows).
person
Identifying columns are cleared and a skeleton row is kept, so records that point at it do not break.
Cleared: first_name, last_name, email, title, linkedin_url, phone, metadata; the record is also marked deleted.
Found by: id ∈ contactIds (resolved via id / lower(email))
The canonical person row: every identity column nulled, row soft-deleted. Kept (not delete_row) so dangling references elsewhere stay dangling instead of cascading — reconciliation law #1's end state.
pitch_personalization
The row is deleted outright — the row is the person's data.
Found by: lead_id | recipient_email
Personalized pitch content addressed to the person. contact_id here references the global media directory, NOT the person table — matched by email/candidate instead.
pitch_send
Required columns are overwritten with a placeholder, so audit history survives while carrying no identity.
Cleared: recipient_email.
Found by: recipient_email | lead_id
Send-history rows stay (proof of what was sent, open/reply stats) — the notNull recipient address is overwritten; the tombstone owns re-contact prevention.
qualification_decision
The row is deleted outright — the row is the person's data.
Found by: lead_id
raw_reply is the person's verbatim inbound message.
recruiting_project_contact
The row is deleted outright — the row is the person's data.
Found by: contact_id ∈ contactIds AND project_id ∈ (recruiting_project du workspace)
Projets de recrutement (migration 0173) — l'appartenance à un espace de travail. La ligne ne porte pas de nom ni d'adresse, mais elle est bien une donnée sur la personne : elle dit QU'ELLE A ÉTÉ RASSEMBLÉE dans un espace de recrutement nommé, avec la date et par qui. C'est un fait à sortir dans le dossier d'accès et à effacer avec le reste — d'où delete_row, la ligne n'ayant rien en dessous. Même raison que la shortlist de ne pas compter sur la cascade : `person` est en null_fields, sa ligne survit et ne cascade jamais. La garde de dérive ne pouvait pas exiger cette entrée toute seule — son heuristique apparie des NOMS DE COLONNES et celles-ci s'appellent project_id et contact_id. Pas de workspace_id : scope par le projet.
recruiting_project_shortlist
The row is deleted outright — the row is the person's data.
Found by: contact_id ∈ contactIds AND project_id ∈ (recruiting_project du workspace)
Projets de recrutement (migration 0173) — la shortlist. `note` est le texte libre d'un recruteur SUR cette personne : la ligne EST la donnée de la personne, donc delete_row. LA CASCADE NE SAUVE PAS CETTE TABLE : `contact_id` est bien ON DELETE cascade vers `person`, mais la stratégie de `person` est `null_fields` — la ligne `person` SURVIT à l'effacement, donc sa cascade ne se déclenche JAMAIS. Sans cette entrée, l'effacement laisserait l'appréciation écrite en clair ET le dossier d'accès serait incomplet : erasure-service et export-service n'énumèrent que ce registre. Enregistrée AVANT recruiting_project_contact, qui n'est pas son parent (les deux pendent du projet) mais que le rapport doit lire dans cet ordre : la shortlist est le sous-ensemble, l'effectif le sur-ensemble. Pas de workspace_id ici — le scope locataire passe par le projet, voir recruitingProjectIdsOfWorkspace.
relationship_fact
The row is deleted outright — the row is the person's data.
Found by: contact_id
Verbatim facts the person told us. The table's own 'never deleted by the application' rule yields to statutory erasure — this pipeline is the single lawful deleter.
research_finding
The row is deleted outright — the row is the person's data.
Found by: lead_id
Free-text research about the person behind the lead.
screen
The row is deleted outright — the row is the person's data.
Found by: candidate_id ∈ leadIds
Wave-5 candidate screen (migration 0168): the questionnaire issued to one candidate against one pinned, approved brief. delete_row and not null_fields — unlike message_draft, nothing FK-hangs off a screen except its own three children (all registered above and all CASCADE), so there is no audit row to keep alive. Erased AFTER its children so their counts are real. Gap, documented: a screen issued on an application whose candidate_id is NULL (the column is nullable) has no person key and is unreachable — the twin of sourcing_candidate's gap.
screen_access_token
The row is deleted outright — the row is the person's data.
Found by: screen_id ∈ (screens of candidate_id ∈ leadIds)
Wave-5 screen access tokens (migration 0168). The row carries NO identity of its own — a sha256 hash and four timestamps — and is registered anyway, for two reasons a reviewer can veto. (1) It is the CREDENTIAL to the person's answers: a live token outliving an erasure is a live door to a deleted room. (2) executeErasure and exportPersonData enumerate this registry and nothing else, so an unregistered child makes the report silently understate what was destroyed. Listed BEFORE screen for the same cascade-visibility reason as screen_answer. It contributes a hash and timestamps to a SAR export, which is noise rather than disclosure — the honest trade against an incomplete report.
screen_answer
The row is deleted outright — the row is the person's data.
Found by: screen_id ∈ (screens of candidate_id ∈ leadIds)
Wave-5 candidate screen answers (migration 0168): question_text is the snapshot of what we asked and answer_text is the candidate's own free text — person data in clear. Registered BEFORE screen so the parent's ON DELETE cascade cannot hide these rows from the statutory report (the buying_signal/signal_event rationale). The drift gate cannot demand this entry: its heuristic matches COLUMN NAMES and these columns are called question_text and answer_text. No FK cascade saves it either — screen.candidate_id is ON DELETE cascade, but candidate's own strategy is null_fields + softDelete, so the candidate row survives erasure by design and never cascades. The row IS the person's data and has no FK children: deleted outright.
screen_video_session
The row is deleted outright — the row is the person's data.
Found by: screen_id ∈ (screens of candidate_id ∈ leadIds)
Video screen calls (migration 0197, lib/screen/video): the candidate's screen taken as a call with the AI interviewer. `transcript` holds every word said in the call by the candidate and the interviewer — person data in clear — beside the consent time, the language and how the call ended; no recording and no camera analysis are kept. Found through the person's screens like screen_answer, and listed BEFORE screen for the same cascade-visibility reason. The row IS the person's data and has no FK children: deleted outright. The provider keeps its own copy of the transcript under its terms (a processor named on the candidate privacy page); erasing here does not reach it. Until migration 0197 is applied the table is absent and this entry erases and exports nothing: it checks for the table first, so the erasure transaction never aborts on it.
signal_event
The row is deleted outright — the row is the person's data.
Found by: contact_id
raw_payload holds unfiltered inbound webhook/form bodies. Rows matched only by payload content (no contact link) are a documented gap — see the PR.
sourcing_candidate
The row is deleted outright — the row is the person's data.
Found by: email | matched_contact_id | matched_lead_id | imported_lead_id
WS-158 sourcing staging rows: the row IS the sourced person's data (name/email/title/linkedin + raw provider payload) with no FK children — deleted outright. Rows whose only identity is a linkedin URL and were never matched/imported are a documented gap (no email/contact/lead key to resolve by); the (run_id, dedupe_key) arbiter never resurrects an erased identity as 'already known' because dedupe re-reads live CRM rows only.
04What erasure deliberately does not touch (28 places)
Some records are kept on purpose, each for a reason a reviewer can challenge. The most important one: when a person asks never to be contacted again, the record of that request is kept — deleting it would make the request unenforceable and the contact could resume.
account
better-auth OAuth credential rows for platform members; erased with the member account, not via CRM DSR.
action_ledger
Append-only audit store. Reconciliation law #1: carries personRef tokens, never inline person data; tokens dangle harmlessly after erasure. Legacy inline data = the single audited redaction-write exception (follow-up).
channel_suppression
Suppression law (WS-136 channel-identity overlay: linkedin/phone do-not-contact identities): same retention rationale as email_suppression — a suppressed identity must stay unsendable on its channel; erasure never deletes suppression rows.
code_push
repo_full_name is a GitHub owner/repo slug on push audit rows, not a person's name — heuristic false positive, reviewed.
code_repo_link
repo_full_name is a GitHub owner/repo slug (workspace deploy config), not a person's name — heuristic false positive, reviewed.
connected_account
provider_account_id is the MEMBER's own mailbox address (workspace credential, member scope).
contacts
Global cross-tenant media-contact directory (crawler-owned reference data, no workspaceId). Workspace-scoped erasure cannot lawfully reach it — that part stands on its own. What does NOT stand: this entry used to add 'it has its own opted_out_at control', and that control does not exist. `contacts.opted_out_at` has six readers and ZERO writers: two repository filters that always pass (`IS NULL`), two match guards that can never fire, two DTO projections. No path in the product can set it. So this table currently has NO compensating control, and the follow-up below is not a nicety — it is the whole control. Follow-up: a directory-level DSR process, which must include the writer.
email_identity
The member's own signature identity (name/title/address) — member scope.
email_suppression
Suppression law: suppression identity is retained under legal obligation (a suppressed address must stay unsendable). Erasure ADDS a suppression_tombstone; it never deletes suppression rows.
employer
Company record (table ex-`gtm_account`, renommée par 0167) — name/domain/linkedin_url identify the company, not a person. Deleted via the CRM company DELETE route, not person erasure.
erasure_request
The DSR record itself — carries only a salted hash and dangling subject ids by design.
outcome_meter
Wave-6 north-star counter (migration 0169): one row per (workspace, job_requisition, ISO week) carrying an integer count of qualified interviews, a shadow/billed mode and a deterministic billing key. No name, no address, no link to a person — `identifier` is constrained in-database to ^v[0-9]+:[A-Za-z0-9:_-]+$ (outcome_meter_identifier_shape), which makes an email, a name or a URL unrepresentable. The interviews it counts live in `interview`, which IS registered. Listed here (though the column heuristic does not flag it) so a reviewer can veto the claim that a billing counter carries no identity.
phone_caller_id
The MEMBER's own phone number, verified as their caller ID on the platform Twilio account, and whether it can text (migration 0190) — member scope, like email_identity: no candidate's number is ever stored here. Deleted with the member (user FK cascade) or the workspace, or by the member in Settings › Phone.
recruiter_application
Recruiter Network applications: recruiters who apply on the website to work on client projects, with their name, email, agency, specialties, where they work, their experience and an optional link. These are the platform's own records, kept apart from candidate data: no workspace owns them, nothing links them to a candidate or a contact, and no search reads them, so a workspace's erasure request cannot reach them. An applicant who asks to see or delete their application writes to the platform operators, who answer from their own list of applications.
requisition_cost
Wave-3 per-mission money buckets (migration 0166): data / model / communication totals for one job_requisition over one UTC day. Sums and counters only — no name, no address, no link to a person. The contact points they total live in contact_point, which IS registered. Listed here (though the column heuristic does not flag it) so a reviewer can veto the claim that a cost table carries no identity.
search_history
Search-history log (migration 0172): the workspace's saved searches and recent searches (workspace-wide: the 'Continue working' rail shows every member's searches) — the recruiter's `criteria` ({q, location, …}), a display `name` for the saved search, and a `kind`. It is workspace-scoped (workspace_id) and torn down WITH the workspace, not by per-person DSR: the table carries no person-identifying column (`name` labels the SEARCH, not a person) and no candidate/lead/person key by which one data subject's rows could be selected, so an individual erasure has nothing to match against it. Its `criteria` jsonb can quote a searched-for name in free text — the jsonb-embedding class this gate defers to WS-142 redaction (see screen_answer's note) — but that free text is the searcher's own phrasing, not a stored profile. Listed here though NEITHER the column-name NOR the structural heuristic flags it (its only fk is user_id → the platform member, not a person root), so a reviewer can veto the claim that a saved-search log carries no data-subject identity — the same posture as sourcing_run. The table is created by migration 0172 and read/written best-effort so an undeployed migration degrades to empty state; this exclusion needs only the SQL name and so carries no load-bearing import of that schema.
session
Platform member sessions; expire and are purged by better-auth, not person erasure.
sourcing_run
reveal_emails is a boolean run OPTION (spend email-reveal credits or not), and criteria/provider_plan hold firmographic search filters — no person identity. The person rows live in sourcing_candidate, which IS registered.
sso_provider
IdP configuration owned by the workspace; the domain is an org identifier.
suppressed_contact
Suppression law (import-time list): same retention rationale as email_suppression.
suppressed_domain
Company domains only — no person identity.
suppression_tombstone
THE suppression-law artifact: a one-way salted hash, deliberately retained forever so 'forget me' can never cause 'contact me again'.
twilio_connection
The workspace's own provisioned phone numbers.
user
Platform member identity (auth), not a CRM data subject — member offboarding/account deletion is a separate flow (better-auth owns it).
user_email_signature
The member's own signature block — member scope.
verification
better-auth verification tokens (short-lived); member-account scope.
voice_agent_config
The workspace's OWN Twilio numbers and scripts, not a data subject's.
workspace_member
Membership rows for platform members; member-offboarding scope.
05Professional profiles we search but do not own
Besides the records an employer keeps in its own workspace, Peopleseek searches professional profiles that it reads but does not own or edit: profile data licensed from data providers, and data from public professional sources. A profile carries the kind of information found on a professional profile page — name, a headline or short professional summary, current and past roles and employers, location (country, region, city), skills, spoken languages, a link to a public profile and, for some people, business contact details such as a work email address or phone number.
Peopleseek does not create, change or sell these profiles. It reads them to find people who may fit a role an employer is hiring for.
Access and correction
To ask what Peopleseek holds and shows about you, or to have it corrected, email hello@peopleseek.ai.
Deletion
Peopleseek cannot delete a profile it does not own. It can stop showing you: say which employer the request concerns, and you are put on that employer's do-not-contact list, described next. A request to delete or correct the source record itself has to be made with the source it came from; ask, and you are told where that request has to go.
Not being contacted
Every outreach email sent through Peopleseek carries an unsubscribe link. Using it puts you on that employer's do-not-contact list, which every search and every message run from that employer's workspace checks: the employer no longer sees you in results and cannot write to you from Peopleseek. You can also ask for this by email. The record of such a request is kept on purpose, so that it can be enforced — see "What erasure deliberately does not touch" above.
06Known limits
These are recorded in the product itself rather than discovered by a reader.
- A shortlist document covers several candidates at once and carries no single candidate key. An erasure request from one of those candidates does not reach that document through the candidate lookup. The registry records this as a known gap rather than hiding it.
- The automated guard that forces a new person-bearing table to be registered matches COLUMN NAMES. It cannot recognise free-text columns such as the question asked in a screen and the candidate's own answer, so those tables are registered by human judgment. A future table with similarly neutral column names could be missed if nobody exercises that judgment.
- When a candidate takes a screen as a video call, the call runs at a video provider, which keeps its own copy of the transcript under its terms. An erasure in Peopleseek deletes Peopleseek's copy of the call; it does not reach the provider's copy.
07Not yet answered
The sections below need a legal answer. They are deliberately left as questions: an invented answer here would be worse than no answer at all.
Legal basis
Legal review required
On what basis is candidate data processed, and does that basis differ between an employer's own applicant records and candidates the product surfaces? Counsel must answer before this section states anything.
Retention
Legal review required
How long is candidate data kept, per category, and what triggers deletion? The product records what erasure does; it does not yet record how long anything is kept.
Access and erasure for a person with no account
Legal review required
How does a candidate who has never signed in prove identity and exercise access or erasure, and within what deadline? The mechanism that fulfils such a request already exists; the process around it is not written.
Controller and processor roles
Legal review required
Which party is responsible for candidate data in each flow — the employer using the workspace, or the platform? The answer changes who a candidate must contact.
Jurisdictions
Legal review required
Which regimes apply to a candidate depending on where they and the employer are, and which disclosures does each require? Counsel must enumerate them.
Decisions taken with the product's help
Legal review required
What must be disclosed about decisions a human takes with the product's assistance, and about the evidence the product assembles for them? Counsel must draft this section.
08Contact
To ask what is held about you, or to ask for it to be erased, email hello@peopleseek.ai. Say which employer or role the request concerns, so the request can be routed to the right workspace.