contacts

Create or safely match a contact

Create a contact, or match and update an existing one. Matching uses the normalized email, never name similarity. An ambiguous identity returns candidates and writes nothing.

post/v1/contacts:upsert

Request

  • Base URL: https://api.indraft.io
  • URL: https://api.indraft.io/v1/contacts:upsert
  • Auth: HTTP bearer

Request body

idempotencyKeystring

Your own name for this write, so retrying it cannot do it twice. Send the same key again and you get the SAME result back rather than a second record, which is what makes a re-run safe after a timeout you never saw the answer to. Derive it from what the write is about, not from the clock: nightly-sync-halden retries correctly and a timestamp does not.

expectedVersioninteger

The version you read before deciding to write. If the record has moved since, the write is refused instead of overwriting whatever changed. Send it whenever you read, reasoned, and then wrote: without it the last writer wins and neither of you finds out.

sourceIdstring

The recorded source this claim came from, if one exists. It attaches evidence to the write, so a person reading the field later can see where it came from rather than only who sent it.

assertionKind'observed' | 'inferred' | 'imported' | 'user_supplied'

HOW YOU KNOW. This is the field the product is built around, so say what is true rather than what is strongest: observed you saw it stated directly, imported it came from a file or another system, inferred you worked it out, user_supplied a person told you in so many words. They rank, weakest first: inferred < imported < observed < user_supplied < confirmed. A weaker claim does not overwrite a stronger one; it is reported in held[] and the stored value stands, so an unattended re-run converges instead of eroding what somebody checked. confirmed is not accepted here: it means a person checked the value, and only a signed-in person can set it.

attributionLabelstring

A short name for the work this write belongs to, shown beside the actor so one credential can distinguish separate jobs. It is recorded as CALLER-SUPPLIED and grants nothing: it changes no permission, no idempotency scope and no quota.

dryRunboolean

Report what this write WOULD do and commit nothing. The response carries the same change list, held fields and refusals as the real call, so it is the way to check a risky write, or a whole import, before it lands. Nothing is metered.

overrideStandingboolean

Apply this write even when the value standing in the record was asserted more strongly than this one. Absent means no, which is the safe answer for a re-run. WITHOUT IT, a write can be accepted and change nothing: precedence is confirmed > user_supplied > observed > imported > inferred, and a weaker claim leaves the standing value in place and reports the field in held[] rather than failing. That is the whole of why a write can return 200 and not take. WITH it, this write wins regardless of what it is overruling, INCLUDING a value a person confirmed, so an agent that sets it by default has switched the provenance mechanism off. Set it per write, when the caller knows its claim supersedes what is there.

idstring
displayNamestring required
emailstring
phonestring
titlestring
companyIdstring
companyRole'employee' | 'founder' | 'executive' | 'advisor' | 'other'
ownerActorIdstring
customFieldsobject

Values for fields this workspace defined, keyed by the field's key. Read the schema first: a key that is not declared is REFUSED, with the available keys in the error, rather than stored where nobody will find it. Send null to clear one.

tagsstring[]
externalProviderstring

Which system issued externalId, for example salesforce or stripe. Required with it: two systems each numbering their records from one is the normal case, so an id without its issuer is not an identity and is refused.

externalIdstring

This record's id in that other system. Matched on BEFORE domain or email, so an integration can sync without remembering our ids.

createDistinctboolean

Create a new record even though the evidence matches an existing one. For the case where you know two look alike and really are different. Absent means match, which is the safe answer for a re-run.

Response

Success

status'created' | 'matched' | 'ambiguous' required

Whether this created a record, matched an existing one on real identity evidence, or found several and committed nothing. ambiguous is not an error: it means the evidence you sent fits more than one record, and candidates lists them so a caller can choose rather than have one picked for it.

matchReasonstring

Why this matched the record it matched, in words, when it matched one.

requestIdstring required

Changes

No recorded changes to this operation across all 1 revision of this API.