API recipes: contact lifecycle
The contact-lifecycle cookbook. The canonical pages — Opt-outs API, Segments API, Frequency Caps API, Analytics API, Contacts API — document each endpoint; these recipes chain them into runnable loops: create, refresh, enrich, consent, read, decide. Every request sample ships as a six-language group — cURL, Node.js (TypeScript), Python, Go, Ruby, PHP, in that order, each dependency-free so it runs without an SDK installed — followed by the complete response envelope. All five loops stay on tenant-owned controls — the contact record, opt-outs, segments, and caps are your rules, set and enforced by your key. Base URL ishttps://api.orbit.devotel.io/api/v1 throughout; authenticate with X-API-Key and a test key (dv_test_sk_…) for the sandbox.
5. The contact record: create → upsert → enrich → consent → read back → dedupe
The base lifecycle chain, run against the contact record itself: pin your source-of-truth id on create, refresh idempotently by that pin, write attributes and consent as recorded state, read back to verify, and scan for duplicates the pin could not prevent. This is the recipe form of Import, dedupe, and upsert contacts; the counters and endpoints agree with that page. Create withexternal_id — the pin that keeps one live row per customer:
201
external_id resolves to this row and refreshes it — it never mints a second. Phone numbers normalize to E.164 server-side, and a contact needs at least one of phone or email at creation.
Upsert refresh — the refresh path is the same write at scale: POST /contacts/bulk keys every row on external_id, so an existing pin updates the row instead of inserting a new one. Up to 10 000 rows per call; the response carries the same inserted / updated / skipped counters as the bulk import loop:
200
inserted + updated + skipped per chunk and reconcile against your source row count; skipped rows download per job from GET /contacts/import-jobs/{id}/skipped.csv. A rerun of an identical batch moves rows into updated or skipped — never into a second live contact.
Custom-field write — attribute and custom-field writes ride the contact update; import the scalar facts you personalize with, never derived values (segments, scores), which the CDP recomputes from events:
200
200
200
200
GET /contacts/duplicates for the full cluster list; identity resolution walkthrough covers reading them, and POST /contacts/merge combines a cluster into one survivor. Merge after import, never to hide a missing pin.
1. Opt-outs: bulk suppress → export → re-opt-in one channel
A suppression file arrives from support and you need the round trip: opt the contacts out in one batch, export the suppression list as compliance evidence, then restore the one contact who re-consents by phone or email.contact identifies the address-book row by phone (E.164) or email — pick the id off GET /contacts first if your file lists raw addresses.
Bulk opt-out — up to 500 rows per call, per-row outcomes isolated:
succeeded / skipped / failed count independently, so one unknown phone number never rolls the rest back:
207
ok: false, status: "failed", and an actionable error string — clean the named rows and resend only them; skipped rows (already opted out) never double-stamp the consent trail.
Export the suppression list — compliance evidence, up to 10 000 contacts:
200
channel to export every opt-out across channels. Past 10 000, page GET /contacts/optouts on its cursor instead — same rows, no cap.
Re-opt-in the one contact who re-consents — delete the channel’s opt-out:
200 returns the updated contact row. The contact is eligible again on that channel subject to consent records — the delete clears the suppression, and any affirmative-consent requirement (a WhatsApp 24-hour window, RCS marketing consent) still has to be satisfied by your own capture. The round trip closes: every enlisted row, plus one restored.
Labelled error envelope — a channel outside the canonical nine (case-insensitive, whitespace-trimmed) fails the whole request before any row is processed:
422
200 with already_opted_out: true, not an error.
Canonical page: Opt-outs API. Envelope reference: API error handling by example.
2. Segments: autopilot → evaluate → materialize-lookalike → members
The AI-assisted audience loop: describe the audience, persist the suggestion as a real segment, force a re-evaluation when data moves, then grow it — preview a lookalike expansion and materialize it as a static segment you can page members out of. Autopilot from a description — suggest rules + live preview in one round-trip, nothing persisted:200
POST /contacts/segments (the main cookbook’s task 15); membership materializes on create.
Labelled error envelope — the model emits a rule the segmentation engine cannot validate, and the error names the field:
422
503 means no AI provider is configured on the deployment — fall back to the manual builder.
Evaluate on demand — after data (not rules) changed, rebuild membership now instead of waiting for the periodic refresh:
200 returns the evaluation result — the matched count, duration, and the membership diff — in the standard envelope.
Materialize a lookalike audience — promote a preview into a new static segment (no rules, auto_refresh off — the k-NN snapshot is a point-in-time cohort the refresh scheduler never wipes):
201 returns materialized: true plus the new segment — its id is a first-class segment you activate like any other. A preview with no candidates returns 200 with materialized: false and a reason, persisting nothing.
Read members — page the materialized snapshot with the same cursor contract as any list:
meta.pagination.cursor while has_more is true. The members list reads the last-materialized snapshot — call evaluate (above) first if you need the very latest audience.
Canonical pages: Segments API (AI surfaces), Contacts API (saved-segment CRUD + membership).
3. Frequency caps: read the rules behind a send decision
Caps are the tenant’s rolling-window limits on how often one contact can be messaged. Two reads serve the “what applies to this contact?” question: the per-contact rules read, then the live per-recipient usage you branch a send decision on. Rule writes ridePATCH /frequency-caps/:id; reads ride GET /contacts/:id/frequency-caps (rules) and GET /contacts/:id/cap-status (live usage) — the decision loop below uses the rule IDs from the first read to target back-off per category.
Read the rules that apply to the contact — flat plus grouped by channel, globals separated:
200
max_count / current_count / sends_remaining and (once slots are exhausted) the next_slot_at ISO timestamp, plus the channel’s consent state — one read answers both “how many left” and “may we send at all”.
Update a rule — widen the marketing SMS cap; rule writes are owner/admin/developer with frequency-caps:write:
429
4. Usage: fetch a day of metering for guardrails
The metering guardrail read: the home-overview usage chart’s same daily timeline, pulled for a one-day window so your own guardrails (budget dashboards, spend alerts, anomaly heuristics) sit on the numbers the platform charts. Read-only; an explicitstart_date / end_date pair takes precedence over the days trailing window.
200
totals into a budget check before you queue a batch, or row-by-row into a daily anomaly heuristic. For spend-shaped guardrails instead of volume, the billing surface’s spend series and burn rate (the main cookbook’s task 22) price the same window.
Labelled error envelope — an explicit pair with a missing end:
400
start_date (or end_date) is a 400 — the pair refines together, and the details issues list names the fix instead of silently falling back to the trailing days window.
Canonical page: Analytics API → per-day usage timeline. Finer-grained metering (rate a batch, credit notes): Usage metering.
See also
- REST API recipes — the main task-by-task cookbook these four loops extend
- API recipes: operations endpoints — the operational surfaces (approvals, cascade preview, search DSL, SMPP window, brand trust, risk gate)
- Import, dedupe, and upsert contacts — the source page for the create-to-dedupe chain in section 5
- Using worked samples — translate any curl block here into your HTTP client
- API error handling by example — retriable-vs-terminal for every failure shape above