Contact ( contact )
A contact is a professional contact attached to a company . It carries its display name, contact details, and job title and may be assigned one or more roles that qualify it in relation to the company.
Role
The contact separates the professional contact from the legal entity. A company may have several contacts — legal representative, payment authorizer, billing contact — each with their own information and verification level.
KYC, AML, PEP, sanctions, or liveness checks are recorded in
compliance_check resources linked to the contact, never copied onto it. A contact can be searched by email and resolved by role in orchestration processes.
Identifier and structure
Each contact has a stable identifier prefixed with ctc_. It references its parent company through company_id.
{
"object": "contact",
"id": "ctc_d12e3f4a5b6c7d8e",
"company_id": "cmp_3a8f1d9c2b4e7f6a",
"full_name": "Marie Dupont",
"first_name": "Marie",
"last_name": "Dupont",
"email": "marie.dupont@dupont-industries.fr",
"phone": "+33612345678",
"job_title": "Finance Director",
"preferred_locale": "en-US",
"suspended": false,
"suspended_at": null,
"suspension_reason": null,
"source_reference": "RH-00421",
"metadata": {},
"created_at": "2026-01-12T10:00:00.000Z",
"updated_at": "2026-03-05T14:20:00.000Z"
}Fields
| Field | Type | Required | Description |
|---|---|---|---|
id | string | yes | Contact identifier (prefix `ctc_`). |
object | string | yes | Always "contact". |
merchant_id | string | yes | Merchant to which the contact belongs. |
company_id | string | yes | Parent company identifier (`cmp_…`). |
full_name | string | yes | Canonical full name, preserved exactly as supplied. |
first_name | string | no | Structured first name, when known without inference. |
last_name | string | no | Structured last name, when known without inference. |
email | string | no | Email address. Used as the lookup key for email search. |
phone | string | no | Phone number in international E.164 format, for example `+33123456789`. |
job_title | string | no | Fonction professionnelle libre. |
preferred_locale | string | no | Preferred BCP 47 locale, for example `fr-FR`. |
suspended | boolean | yes | Whether the contact is suspended. A suspended contact keeps its history but is no longer selected for future operations. |
suspended_at | datetime | no | Date of the current suspension. |
suspension_reason | string | no | Free-form reason for the current suspension. |
source_reference | string | no | Reference in your system (CRM, HR…). |
metadata | object | no | Flat map of `string | number | boolean` scalars, following the API metadata convention. |
created_at | datetime | yes | Creation date. |
updated_at | datetime | yes | Last update date. |
Identity and compliance
Regulatory outcomes are not intrinsic properties of the contact. Each verification produces a compliance_check with subject_type=contact, the contact ID in subject_id, a nature in
check_type and a normalized result in result.
Processes submit check drafts through submit_draft. Events compliance_check.passed, compliance_check.failed,
compliance_check.review_required and compliance_check.expired
carry compliance transitions.
Suspension
A person may need to stop acting for a company without disappearing from the database: sanctions alert, fraud suspicion, compromised access, departure from staff. Deleting the contact would lose the history of past acts — orders, invoices, onboarding cases, compliance checks. Suspension meets this need: it disables the contact without erasing anything.
POST /v1/contacts/ctc_d12e3f4a5b6c7d8e/suspend
Content-Type: application/json
{
"reason": "sanctions_match"
}A suspended contact:
- is no longer returned by
GET /v1/companies/:id/contacts/resolve, except withinclude_suspended=true— resolution nodes therefore route tonot_found; - cannot receive a new
contact_role: creation returns409; - remains readable and editable so its data can be corrected and suspension lifted.
POST /v1/contacts/:id/reactivate lifts the suspension and clears
suspended_at and suspension_reason.
Suspension concerns the person and can be lifted. Revocation of a role is a dated, definitive fact describing the end of a mandate. Leaving the company is represented by revoking roles; a merchant-imposed block is represented by suspension. Suspending a company does not suspend its contacts, and vice versa.
Events contact.suspended and
contact.reactivated can trigger a process, for example to collect a new signatory when an ongoing case loses its current one.
Draft mode
During onboarding, contact data is collected progressively from the end user before a company necessarily exists. The platform supports this through a draft:
a contact in draft mode, which carries the same business payload without an identifier and is not yet attached to a final company.
In processes, the draft flows with type
platform.contact (draft). The node
submit_contact and is converted into a persisted contact as soon as a company is available:
[Collect contact (draft)] → [Create company] → [Submit contact]
↓
contact (persisted, ctc_…)KYC nodes — identity verification, liveness, SIRENE — also accept drafts as inputs, so verification can start before final submission.
Relationships
| Linked object | Link | Access |
|---|---|---|
company | Parent (1..1) | company_id field · Company page |
contact_role | 0..n roles |
|
Resolving a contact by role and certification level is documented in the
In processes
section below and on the
page company
(endpoint GET /v1/companies/:id/contacts/resolve).
In processes
| Node | Role | Key inputs / outputs |
|---|---|---|
create_contact | Creates a contact directly attached to a company | Accepts: company · Produces: contact |
submit_contact | Converts a collected draft into a contact and attaches it to a company | Accepts: company, contact (draft) · Produces: contact |
find_contact_by_email | Searches for a contact by email and routes according to the result | Accepts: email, company (optional) · Route found / not_found · found produces: contact |
resolve_contact | Resolves the best contact through active roles | Accepts: company, role_types, certification_levels, selection_strategy · Route found / not_found |
sirene.verify_sirene_company_director | Checks whether the contact is a declared officer in SIRENE | Accepts: contact, company · Route verified / not_verified |
get_contact_compliance_checks | Retrieves the contact's compliance checks from newest to oldest | Accepts: contact · Produces: compliance_checks, count |
Role-based resolution
The node resolve_contact identifies the best contact for a company among active roles — not revoked and within the
valid_from / valid_untilwindow. The selection strategy determines which one is retained when several candidates match:
| Strategy | Behavior |
|---|---|
first | First role found (creation order). |
latest | Most recently created role. |
most_certified | Role with the highest certification level (certified > verified > declarative). |
best_role_type_fit | Role whose type best matches the priority order supplied in role_types. |
On success, the route found produces selected_contact,
selected_contact_role, selected_role_type and
other_candidates.
Email search
GET /v1/contacts/search ?merchant_id=mrc_1a2b &email=marie.dupont@dupont-industries.fr
{
"object": "contact_search_result",
"contact": {
"id": "ctc_d12e",
"email": "marie.dupont@dupont-industries.fr"
}
}When two contacts share the same email, search returns an error
409 duplicate_contact_match. Adding the parameter
company_id restricts search to a given company and avoids this conflict.
Events
Contact changes and compliance decisions use distinct event families. Decisions are emitted by compliance_check.*.
| Event | Trigger |
|---|---|
contact.created | The contact was just created. |
contact.updated | The contact was just updated. |
contact.suspended | The contact was suspended. |
contact.reactivated | The contact suspension was lifted. |
Every event includes the complete contact object in its payload. Webhook configuration is described in
Receive webhooks.