The contact resource
A contact is one record in your directory — phone, email, name, tags, consent, plus your own data layered on top. Custom-field values ride on the contact as thecustom_fields object: set them on a contact and they come back inline on
every contact response — create, read, list — with no separate lookup.
Custom-field keys and their types are defined under
/api/v1/custom-fields. Write a value with
PUT /api/v1/custom-fields/values
and read it back on the contact’s custom_fields object. Every response on
this page follows the same envelope — data plus a meta block carrying
request_id, timestamp, and, on list responses, pagination — so the
examples below hold for every endpoint here.
Errors follow Devotel Orbit’s { error, meta } envelope, shown once under
Error envelope.
Worked sequence: create, set custom fields, read back
The run-through every integration does first: create the contact, attach your own data, then pull the contact back and see the values inline.1. Create the contact
POST /api/v1/contacts/phone or email is required. Send phone numbers in E.164
(+14155551234); display formats are normalized to E.164 before insert.
Request
custom_fields object until you set values.
2. Set custom-field values
PUT /api/v1/custom-fields/valueskey must match a custom-field definition you already
created, and the value must match that definition’s type — values of the wrong
type are rejected.
Request
3. Read back — values inline
GET /api/v1/contacts/{id}Worked sequence: list with filters, then paginate
GET /api/v1/contacts/tags=vip,
country_code=US — search narrows by name, phone, or email. The response is
cursor-paginated: while meta.pagination.has_more is true, pass
meta.pagination.cursor back as the cursor query parameter to fetch the
next page. Here the list is filtered to one tag.
Request
has_more is true, so request the next page:
has_more comes back false.
Error envelope
Validation failures return422 with a VALIDATION_ERROR code. The classic
case is a phone value that fails the format and length checks — below the
E.164 floor or not recognizable:
422
+) and retry. A missing-or-unknown
custom-field key on PUT /custom-fields/values surfaces the same envelope
with a four-hundred status, so handle both before you code a special case.
GDPR erasure — from request to confirmed status
Erasure is a tenant-owned control: you file it, Orbit executes it and keeps the audit chain. The generated reference section forDELETE /api/v1/contacts/{id}/gdpr/delete documents the synchronous
cascade but stops at 204 No Content — nothing there tells you how to
confirm the deletion you just ran, or how to read the pending/completed
status on the cancellable flow. This section closes that gap.
The immediate delete answers 204; the status lives one call away
DELETE /api/v1/contacts/{id}/gdpr/deleteid returns
404. The probe pair: mint the re-auth challenge, delete, then confirm.
Step 1 — mint the re-auth challenge
200
X-Reauth-Challenge, the delete returns
401 and erases nothing:
401 Error
The cancellable erasure request returns a full status envelope
POST /api/v1/contacts/{id}/gdpr/erasure-request202 with the full
request — so you always know whether the deletion is still pending,
executing, completed, or cancellable:
status moves pending → executing → executed once the scheduled
worker hard-deletes the contact past cooling_off_ends_at (a failed run
leaves failure_reason and retries up to its attempt cap; a cancelled
request records cancelled_at and never executes). To cancel while it is
still pending, pass the request id to
POST /:id/gdpr/erasure-request/:requestId/cancel.
Poll the status on the per-contact list endpoint — the object above under
data.items:
200
Duplicate detection and merge review
Two endpoints handle duplicate detection: a cheap aggregate count for banner surfaces and pre-merge checks, and the full group list you fetch when you open the merge workflow.GET /api/v1/contacts/duplicates/countextras — the number of contacts that would be merged away — plus
hasMore indicating whether duplicates exist beyond the slice the count scanned.
Use it for banner headlines and pre-merge checks.
string
exact (default) or fuzzy. exact is the right choice for banner counts;
fuzzy mirrors the detection pass the merge endpoint uses.integer
Per-strategy group cap, default
100, range 1–200. Raise it when
hasMore returns true and you need to scan deeper before reporting
“0 duplicates.”GET /duplicates to fetch the full group list (with per-group contact
records) when you actually render the merge review UI — fetching it just for a
banner paints the contacts page with data you don’t read.