Skip to main content
Contacts are the people you message. LazyApp stores them per workspace and mode; broadcasts and marketing sends require a contact row plus granted marketing consent. There is no profileId, accountId, or platform on contact calls — the API key scopes everything.

Quick start

Create and update

POST /v1/contacts upserts on the first identity field you provide: At least one of phone_number, bsuid, or external_id is required. Optional fields: username, email, preferred_language, timezone, attributes (free-form JSON for CRM fields). attributes is merged, not replaced, on both the upsert and PATCH /v1/contacts/{contact}: the keys you send are set, a key you set to null is removed, and every other key stays. So your CRM sync can write company while a lead-scoring job writes lead_score without either erasing the other. Send "attributes": null to clear them all. PATCH /v1/contacts/{contact} accepts the same body fields. Look up {contact} by uuid, phone number, external_id, or bsuid.

Service window on read

GET /v1/contacts/{contact} includes service_window — whether the 24-hour window is open, when it expires, and the last inbound timestamp. Use this before choosing template vs free-form send.
Cursor-paginated (next_cursor). q matches phone, external_id, or email. Contacts can also opt out by message. STOP, UNSUBSCRIBE or REMOVE opts out of marketing, STOP ALL out of everything, and START or UNSTOP opts back in to marketing (translations in Hindi, Spanish, Portuguese, Arabic and French work too). The whole message has to be the keyword. A plain “yes” never opts anyone back in: in a conversation it answers whatever you just asked. Granting marketing consent lifts a matching suppression. See Broadcasts for how exclusions work at send time.

Merge duplicates

When the same person arrives under two identities:
Messages and attributes consolidate onto the target contact.

Bulk import

Upsert up to 1000 contacts in one request:
Response: { "created", "updated", "failed": [{ "index", "message" }] }.

Tags

Structured membership (keep free-form CRM fields in attributes):
DELETE /v1/contacts/{uuid}/tags detaches by tag uuid or name. GET /v1/tags lists tags.

Segments

Every filter you give must match: For example, everyone you tagged over the API for an intake announcement:
A tag name or uuid that does not exist in the workspace is refused with 422, so a typo cannot silently produce an empty audience. Reads return each tag’s names, uuids and internal ids. Use the segment uuid as contact_segment_uuid on broadcasts.

Business profile

Prefer native routes on a connection uuid (also available via the Meta-compatible proxy):

Block users

users takes phone numbers or BSUIDs. DELETE with the same body unblocks, and GET lists the block list. Limits, per-person failures and the identity change check are in Identity, privacy and data.

What we skip (Zernio-style)