EZWhatsApp Docs

Current API reference

Build against the current EZWhatsApp Platform API.

This is the public implementation reference for customers, agencies, and developers using EZWhatsApp. It covers the line-token Channel API, logged-in Workspace API, webhook setup, inbound contact identity, managed customer tags, inbox operations, analytics, campaigns, and the boundaries that must not be guessed.

Quickstart

1. Log in

Get a workspace ID token.

Workspace routes act as a logged-in customer user. Use this token for admin, inbox, campaign, contact, Kanban, and line-management operations.

curl -X POST 'https://app.ezw.solutions/api/auth/login' \
  -H 'Content-Type: application/json' \
  -d '{
    "email": "owner@example.com",
    "password": "password"
  }'
2. Create a line token

Create or rotate a line-scoped API token.

Channel API routes use a line API token beginning with ezw_line_. It is scoped to exactly one WhatsApp line.

curl -X POST 'https://app.ezw.solutions/api/lines/{lineId}/api-token' \
  -H 'Authorization: Bearer {idToken}'
3. Check health

Confirm the line is sendable.

wakeup=false makes the check read-only. Do not flatten the result into only connected or disconnected.

curl 'https://app.ezw.solutions/api/v1/health?wakeup=false&channel_type=web' \
  -H 'Authorization: Bearer {lineApiToken}'
4. Send idempotently

Queue one durable outbound attempt.

Send routes return 202 because delivery is asynchronous. Use a deterministic idempotency key for retry-safe sends.

curl -X POST 'https://app.ezw.solutions/api/v1/messages/text' \
  -H 'Authorization: Bearer {lineApiToken}' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: client-order-123' \
  -d '{
    "to": "+972501234567",
    "body": "Your order is ready"
  }'

Base URLs

Production

Workspace API: https://app.ezw.solutions
Channel API v1: https://app.ezw.solutions/api/v1

Local operator runtime

Workspace API: http://127.0.0.1:3010
Channel API v1: http://127.0.0.1:3010/api/v1

Authentication

API family Token Use for Do not use for
Channel API v1 Authorization: Bearer ezw_line_... Machine-to-machine health, direct or group sends, webhooks, exact inbound-message contact identity, and same-line contact display-name updates. Customer tag access is a separate, explicitly enabled organization-directory permission. Workspace admin/operator routes, team management, campaigns, contact imports, Kanban, or the separate evaluation evidence route. Do not assume customer tag access from the token alone.
Workspace API Authorization: Bearer {idToken} Logged-in owner, admin, or operator work: lines, QR, tokens, webhooks, inbox, assignments, notes, image sends, contacts, customer tags, analytics, campaigns, Kanban, billing, and signed media reads, subject to route permissions. Line-token Channel API v1 routes or the separate evaluation evidence route.
Bot evaluation evidence Authorization: Bearer ezw_eval_... Approved evaluator only: bounded, audited evidence reads for one token-bound organization and line, limited by the token's operation allowlist. Messaging, webhooks, customer tags, workspace routes, or self-service access. There is no public token-provisioning route.

Current support matrix

Capability Endpoint Auth Status
Check line health GET /api/v1/health Line API token Supported.
Send direct or group text POST /api/v1/messages/text Line API token Supported. Direct/conversation text may include CTA URL; group text may not.
Send direct or group URL media POST /api/v1/messages/media Line API token Supported for external URL image, video, audio, and document. No conversationId target on this route.
Configure line webhooks GET/POST /api/v1/webhooks Line API token Supported for the current line.
Update contact display name by phone PATCH /api/v1/contacts/by-phone Line API token Best-effort exact normalized-phone match on the current line. A miss is a successful no-op.
Read inbound-message contact identity POST /api/v1/messages/contact-identity Line API token Exact retained individual inbound message on the token's line; returns a verified phone or explicit missing/conflict.
Read bounded bot evaluation evidence POST /api/v1/evaluation/evidence Dedicated evaluation evidence token Restricted approved integration. Allowed operations are context_window, context_windows, daily_coverage, and direct_text_reply_pairs.
Read definitions or change customer tags by phone GET /api/v1/customer-tags, GET/PUT/DELETE /api/v1/contacts/by-phone/tags Line API token with customer tags access enabled Organization-shared directory metadata. Exact phone lookup; no implicit customer creation.
Create or rotate line token POST /api/lines/{lineId}/api-token Workspace ID token Supported for allowed workspace users.
Request QR / reconnect line POST /api/lines/{lineId}/qr Workspace ID token Supported when the line state allows QR request.
Workspace send POST /api/lines/{lineId}/send Workspace ID token Supported for text, one image upload, or forwarding a stored message.
Read inbox data GET /api/conversations, GET /api/conversations/{conversationId}/messages Workspace ID token Supported with organization and line visibility checks.
Team seen state and bot ownership POST /api/conversations/{conversationId}/messages/seen, POST /api/conversations/bot-control, GET/POST /api/conversations/{conversationId}/bot-control Workspace ID token Seen state is internal team metadata. Bot ownership is available only through the configured Printeam integration; unknown remains unknown.
Contacts GET/POST /api/contacts Workspace ID token Supported for owners/admins.
Manage customer tag definitions and assignments GET/POST /api/customer-tags, PATCH /api/customer-tags/{tagId}, GET /api/contacts/{contactId}/tags, PUT/DELETE /api/contacts/{contactId}/tags/{tagId} Workspace ID token Organization owners/admins manage definitions and assignments. Every active line token can manage tags within its own organization.
Campaigns GET/POST /api/campaigns, POST /api/campaigns/{campaignId}/launch, POST /api/campaigns/{campaignId}/cancel Workspace ID token Supported for owners/admins in the scoped customer-admin campaign surface.
Customer-admin analytics GET /api/admin/analytics, POST /api/admin/analytics/refresh Workspace ID token Owners/admins read the last complete snapshot and may queue a durable refresh.
Kanban fields and conversation status GET/POST /api/contact-statuses, PATCH /api/conversations/{conversationId}/status Workspace ID token Supported with optimistic version checks.
Live update hints GET /api/live/stream Workspace ID token Supported as SSE hints only. Refetch DB APIs for truth.

Target model

Target Fields Allowed on Rules
Direct phone recipient to on Channel API, recipientPhoneNumber or phoneNumber on Workspace API Channel text, Channel URL media, Workspace send. Use an explicit phone number. Do not derive a phone number from provider-only ids.
WhatsApp group groupId, group_id, groupJid, or group_jid Channel text and Channel URL media only. Must be an exact WhatsApp group JID ending in @g.us. Not a Workspace send target.
Existing Platform conversation conversationId or conversation_id Channel text and Workspace send. Use this for replies to an existing Platform conversation. Channel URL media does not accept conversationId.

Check channel health

Use this before sending and before diagnosing automation failures. wakeup=false makes the check read-only; wakeup=true may queue a wakeup when the line needs one.

curl -X GET 'https://app.ezw.solutions/api/v1/health?wakeup=false&channel_type=web' \
  -H 'Authorization: Bearer {lineApiToken}'
{
  "channelId": "{lineId}",
  "channelType": "web",
  "wakeupRequested": false,
  "health": {
    "status": {
      "code": 4,
      "text": "AUTH"
    }
  },
  "line": {
    "displayName": "Billing",
    "phoneNumber": "+972...",
    "lifecycleState": "connected",
    "effectiveSendState": "sendable",
    "sendable": true
  }
}

Send text with Channel API v1

Use this for machine-to-machine text sends from the line resolved by the line API token. Use exactly one of to, groupId, or conversationId.

Direct phone recipient

curl -X POST 'https://app.ezw.solutions/api/v1/messages/text' \
  -H 'Authorization: Bearer {lineApiToken}' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: send-once-123' \
  -d '{
    "to": "+972501234567",
    "body": "Test message",
    "ctaUrlButton": {
      "displayText": "Open offer",
      "url": "https://example.com/offers/123"
    }
  }'

Group target

curl -X POST 'https://app.ezw.solutions/api/v1/messages/text' \
  -H 'Authorization: Bearer {lineApiToken}' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: group-send-once-123' \
  -d '{
    "groupId": "120363012345678901@g.us",
    "body": "Group update"
  }'

Existing conversation

curl -X POST 'https://app.ezw.solutions/api/v1/messages/text' \
  -H 'Authorization: Bearer {lineApiToken}' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: conversation-reply-123' \
  -d '{
    "conversationId": "{conversationId}",
    "body": "Thanks, I am checking this now."
  }'
Field Required Notes
to Conditional Direct recipient phone number. Use exactly one target field.
groupId, group_id, groupJid, or group_jid Conditional Exact WhatsApp group JID ending @g.us. CTA URL buttons are not supported for group sends.
conversationId or conversation_id Conditional Existing Platform conversation id. Use this for webhook replies when available.
body Yes Text body. Empty strings are rejected.
ctaUrlButton or cta_url_button No Direct/conversation text only. Requires displayText or text, plus an HTTP/HTTPS URL.
Idempotency-Key header or idempotencyKey body field Recommended Header takes precedence. Use a deterministic key for retry-safe sends.

Send URL media with Channel API v1

Use this when the media is already available at a public HTTP/HTTPS URL. This route accepts direct phone or group targets only.

Direct URL media

curl -X POST 'https://app.ezw.solutions/api/v1/messages/media' \
  -H 'Authorization: Bearer {lineApiToken}' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: media-once-123' \
  -d '{
    "to": "+972501234567",
    "type": "document",
    "media": {
      "url": "https://cdn.example.com/invoice.pdf",
      "mime_type": "application/pdf",
      "filename": "invoice.pdf"
    },
    "caption": "Invoice"
  }'

Group URL media

curl -X POST 'https://app.ezw.solutions/api/v1/messages/media' \
  -H 'Authorization: Bearer {lineApiToken}' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: group-media-once-123' \
  -d '{
    "groupId": "120363012345678901@g.us",
    "type": "image",
    "mediaUrl": "https://cdn.example.com/photo.jpg",
    "contentType": "image/jpeg",
    "caption": "Photo update"
  }'
Field Required Notes
to Conditional Direct recipient phone number. Use exactly one of to or groupId.
groupId, group_id, groupJid, or group_jid Conditional Exact WhatsApp group JID ending @g.us.
type or mediaKind Yes One of image, video, audio, or document.
media.url, mediaUrl, or url Yes External HTTP/HTTPS media URL. Localhost, loopback, and private IP targets are rejected.
media.mime_type, mimeType, or contentType Yes MIME type such as image/jpeg or application/pdf.
media.filename or fileName Required for documents Preserved as the document filename sent through the provider.
caption or body No Caption for image, video, or document media. Audio captions are ignored by the provider.

Workspace send

Workspace sends act as a logged-in user and enforce organization scope, line visibility, line sendability, and membership permissions. Idempotency-Key is required.

Reply to an existing conversation

curl -X POST 'https://app.ezw.solutions/api/lines/{lineId}/send' \
  -H 'Authorization: Bearer {idToken}' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: workspace-send-001' \
  -d '{
    "conversationId": "{conversationId}",
    "bodyText": "Thanks, I am checking this now."
  }'

Send to a direct phone number

curl -X POST 'https://app.ezw.solutions/api/lines/{lineId}/send' \
  -H 'Authorization: Bearer {idToken}' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: workspace-direct-001' \
  -d '{
    "recipientPhoneNumber": "+972501234567",
    "bodyText": "Thanks, I am checking this now."
  }'

Send one uploaded image

curl -X POST 'https://app.ezw.solutions/api/lines/{lineId}/send' \
  -H 'Authorization: Bearer {idToken}' \
  -H 'Idempotency-Key: image-send-001' \
  -F 'conversationId={conversationId}' \
  -F 'bodyText=Optional caption text' \
  -F 'media=@/absolute/path/photo.jpg;type=image/jpeg'

Forward a stored message

curl -X POST 'https://app.ezw.solutions/api/lines/{lineId}/send' \
  -H 'Authorization: Bearer {idToken}' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: forward-001' \
  -d '{
    "recipientPhoneNumber": "+972501234567",
    "forwardedFromMessageId": "{messageId}"
  }'
  • Use exactly one target: conversationId or recipientPhoneNumber. phoneNumber is accepted as an alias.
  • Send content must be one of bodyText, one image in media, or forwardedFromMessageId.
  • Forwarded sends preserve the selected message content and cannot include replacement text, media, or CTA content.
  • Workspace multipart upload accepts one image file up to 25 MB. Generic documents, PDFs, video, audio, and arbitrary files are not supported on this route.
  • There is no Workspace API group send route.

Webhooks

Webhooks let customer or agency systems receive durable event deliveries for the configured line. Configure by line token or workspace token.

Line token

Configure current line webhook

curl -X POST 'https://app.ezw.solutions/api/v1/webhooks' \
  -H 'Authorization: Bearer {lineApiToken}' \
  -H 'Content-Type: application/json' \
  -d '{
    "targetUrl": "https://agency.example.com/ezw/webhook",
    "events": ["messages.post", "statuses.post", "chats.post"],
    "payloadFormat": "whapi",
    "enabled": true,
    "persistent": true
  }'
Workspace token

Configure webhook for a managed line

curl -X POST 'https://app.ezw.solutions/api/lines/{lineId}/webhooks' \
  -H 'Authorization: Bearer {idToken}' \
  -H 'Content-Type: application/json' \
  -d '{
    "targetUrl": "https://agency.example.com/ezw/webhook",
    "events": ["messages.post", "statuses.post"],
    "enabled": true
  }'

Deliveries include X-EZW-Webhook-Event, X-EZW-Line-Id, X-EZW-Webhook-Id, and X-EZW-Delivery-Id headers. Return any 2xx status to accept a delivery. Store delivery ids to make receivers idempotent. A messages.post payload produced by identity-conflict recovery has _ezw_recovery_kind: "identity_conflict" at its root; record it without treating it as a new customer turn. Ordinary live inbound and delivery retries do not receive that marker.

Read the webhook guide

Read inbound-message contact identity

Use the exact provider message id from an individual inbound message. The line token determines organization and line scope. The request body accepts only providerMessageId; it does not accept a caller-selected line or organization.

curl -X POST 'https://app.ezw.solutions/api/v1/messages/contact-identity' \
  -H 'Authorization: Bearer {lineApiToken}' \
  -H 'Content-Type: application/json' \
  -d '{"providerMessageId":"{exactProviderMessageId}"}'
{
  "ok": true,
  "channelId": "{lineId}",
  "conversationId": "{conversationId}",
  "messageId": "{messageId}",
  "providerMessageId": "{exactProviderMessageId}",
  "direction": "inbound",
  "retentionState": "retained",
  "contactIdentity": {
    "state": "resolved",
    "phoneNumber": "+972555000001",
    "source": "provider_phone_jid"
  }
}

providerMessageId must be an unmodified 1–512 character identifier without whitespace or an id: prefix. contactIdentity.state may be resolved, missing, or conflict. For missing or conflict, phoneNumber and source are null. A resolved phone comes only from a provider phone JID or a retained one-to-one provider-observed alias; never derive one from LID digits, display names, imported contacts, or webhook claims. Compare the returned canonical ids with the message being linked. An absent, outbound, or non-individual message returns 404; a tombstoned message returns 410. This read route returns no message body and changes no customer record.

Restricted bot evaluation evidence

POST /api/v1/evaluation/evidence is a separate read-only integration for an approved evaluator. It requires a dedicated ezw_eval_... token bound to one organization and line, with an operation allowlist, expiry, revocation, rate limit, and access audit. Line and workspace tokens are rejected. There is no public provisioning route or UI; an authorized operator must provision and transfer the credential before use. The request does not select another organization or line.

The supported operations are context_window, context_windows, daily_coverage, and direct_text_reply_pairs. This route does not grant contact identity or message-send access. Approved evaluators should use the exact request contract supplied during credential provisioning.

Read inbox data

Use workspace tokens for inbox reads. Routes are organization-scoped and enforce line visibility. SSE events are hints only; clients must refetch durable DB-backed APIs.

curl 'https://app.ezw.solutions/api/conversations?lineId={lineId}&limit=25' \
  -H 'Authorization: Bearer {idToken}'

curl 'https://app.ezw.solutions/api/conversations/{conversationId}/messages?limit=50' \
  -H 'Authorization: Bearer {idToken}'

curl -N 'https://app.ezw.solutions/api/live/stream' \
  -H 'Authorization: Bearer {idToken}' \
  -H 'Accept: text/event-stream'

Advance internal team seen state

After inbound messages are actually visible to an operator, submit 1–100 unique message UUIDs from the same visible conversation. The latest supplied message advances the team seen frontier through older inbound history. The first human viewer is retained; repeated calls are idempotent. This does not send a WhatsApp read receipt to the customer.

curl -X POST 'https://app.ezw.solutions/api/conversations/{conversationId}/messages/seen' \
  -H 'Authorization: Bearer {idToken}' \
  -H 'Content-Type: application/json' \
  -d '{"messageIds":["{inboundMessageId}"]}'

Read bot ownership separately from inbox assignment

For visible individual conversations, a bounded list request checks up to 50 unique conversation UUIDs. The selected-conversation read requires scope=selected. Ownership may be bot, human, or unknown when the configured Printeam integration cannot prove a state. Inbox assignment is a separate Platform record and must not be inferred from bot ownership.

curl -X POST 'https://app.ezw.solutions/api/conversations/bot-control' \
  -H 'Authorization: Bearer {idToken}' \
  -H 'Content-Type: application/json' \
  -d '{"scope":"list","conversationIds":["{conversationId}"]}'

curl 'https://app.ezw.solutions/api/conversations/{conversationId}/bot-control?scope=selected' \
  -H 'Authorization: Bearer {idToken}'

On the configured Printeam line, an active member with conversations.claim may call POST /api/conversations/{conversationId}/bot-control to request return to bot. Treat it as complete only when the response explicitly confirms bot ownership. The request does not clear the Platform assignment.

Contacts

Contacts are customer organization data. A line API token can set the final operator-facing display name through an exact phone match on its own line. Owners and admins can separately list or import directory contacts.

External update by phone

curl -X PATCH 'https://app.ezw.solutions/api/v1/contacts/by-phone' \
  -H 'Authorization: Bearer {lineApiToken}' \
  -H 'Content-Type: application/json' \
  -d '{
    "phoneNumber": "+972501234567",
    "displayName": "Dana Levi · Order 12345"
  }'

Use an international number beginning with +. Platform searches only the line resolved from the token, updates every exact phone match, and never creates a contact. HTTP 200 with matched: false is an expected completed no-op.

List contacts

curl 'https://app.ezw.solutions/api/contacts?query=Acme&limit=100' \
  -H 'Authorization: Bearer {idToken}'

Import contacts

curl -X POST 'https://app.ezw.solutions/api/contacts' \
  -H 'Authorization: Bearer {idToken}' \
  -H 'Content-Type: application/json' \
  -d '{
    "sourceFilename": "customers.csv",
    "rows": [
      {
        "phoneNumber": "+972501234567",
        "displayName": "Dana Levi",
        "company": "Acme",
        "email": "dana@example.com",
        "tags": ["vip", "orders"],
        "externalId": "crm-123"
      }
    ]
  }'

External display-name updates are idempotent at the stored-value level: an exact match already holding the final name is not rewritten or audited again. For workspace imports, accepted row aliases include phone for phoneNumber, name for displayName, external_id for externalId, and semicolon/comma-separated string tags. Imports must contain 1 to 5000 rows.

New canonical individual messages can create one organization directory customer when the counterpart has a validated international phone identity. Opaque LIDs, group messages, quarantined identities, and rejected sends do not create one. Message-created customers do not overwrite existing customer details or tag assignments. A directory row is metadata for enrichment; it is not proof of WhatsApp identity, marketing consent, or campaign eligibility.

Managed customer tags

Managed definitions have stable UUIDs and belong to the organization directory. They are separate from free-text tags on contact imports. Renaming preserves assignments; archiving keeps existing assignments visible and blocks new attachments until restoration. Operators can see managed tags in the inbox, while owners/admins manage definitions and assignments.

Create and manage definitions

curl -X POST 'https://app.ezw.solutions/api/customer-tags' \
  -H 'Authorization: Bearer {idToken}' \
  -H 'Content-Type: application/json' \
  -d '{"name":"VIP"}'

curl 'https://app.ezw.solutions/api/customer-tags' \
  -H 'Authorization: Bearer {idToken}'

curl -X PATCH 'https://app.ezw.solutions/api/customer-tags/{tagId}' \
  -H 'Authorization: Bearer {idToken}' \
  -H 'Content-Type: application/json' \
  -d '{"archived":true}'

Attach one tag to a directory customer

curl -X PUT 'https://app.ezw.solutions/api/contacts/{contactId}/tags/{tagId}' \
  -H 'Authorization: Bearer {idToken}'

curl 'https://app.ezw.solutions/api/contacts/{contactId}/tags' \
  -H 'Authorization: Bearer {idToken}'

Use DELETE /api/contacts/{contactId}/tags/{tagId} to remove that one assignment. Attach/remove responses include changed; an already satisfied operation returns false.

Use any active line token for customer tags

Every active line token can access shared organization directory tags, including customers associated with other lines in its organization. Token rotation keeps this capability; revocation or line archival ends it. Message and provider permissions do not change.

curl 'https://app.ezw.solutions/api/v1/customer-tags' \
  -H 'Authorization: Bearer {lineApiToken}'

Use the returned tag id, not its display name. The line-token routes GET/PUT/DELETE /api/v1/contacts/by-phone/tags use an exact international directory phone number beginning with +. A missing directory customer returns 404 and is never created by tagging. See copy-ready examples for encoded phone queries and write bodies.

Campaigns

Campaigns are a scoped customer-admin workflow for owners and admins. They are separate from inbox replies and direct-send loops. Do not use this surface for scraped, purchased, unrelated, or cold outbound recipient lists.

Create draft

curl -X POST 'https://app.ezw.solutions/api/campaigns' \
  -H 'Authorization: Bearer {idToken}' \
  -F 'name=May offers' \
  -F 'lineId={lineId}' \
  -F 'bodyText=Hello, {name}. Your offer is ready.' \
  -F 'recipientsCsv=phone,name,company
+972501234567,Dana Levi,Acme' \
  -F 'sourceFilename=may-offers.csv' \
  -F 'minDelaySeconds=30' \
  -F 'maxDelaySeconds=120' \
  -F 'hourlyCap=60'

Launch, inspect, cancel

curl 'https://app.ezw.solutions/api/campaigns?limit=100' \
  -H 'Authorization: Bearer {idToken}'

curl -X POST 'https://app.ezw.solutions/api/campaigns/{campaignId}/launch' \
  -H 'Authorization: Bearer {idToken}'

curl -X POST 'https://app.ezw.solutions/api/campaigns/{campaignId}/cancel' \
  -H 'Authorization: Bearer {idToken}'
  • Required draft fields: name, lineId, bodyText, and recipientsCsv.
  • Optional fields: sourceFilename, minDelaySeconds, maxDelaySeconds, hourlyCap, and image file field image.
  • Campaign images must be PNG, JPEG, WebP, or GIF and 25 MB or smaller.
  • maxDelaySeconds must be greater than or equal to minDelaySeconds. hourlyCap must be positive.
  • A campaign can launch only from draft status and only when valid recipients exist. Terminal campaigns cannot be cancelled.

Customer-admin analytics snapshots

Owners/admins can read the last complete snapshot for 7, 30, or 90 days, optionally scoped to one active organization line. A queued or failed refresh does not erase an earlier successful snapshot. A manual refresh queues durable worker work or coalesces with an existing request; it does not calculate results in the web request.

curl 'https://app.ezw.solutions/api/admin/analytics?windowDays=30&lineId={lineId}' \
  -H 'Authorization: Bearer {idToken}'

curl -X POST 'https://app.ezw.solutions/api/admin/analytics/refresh' \
  -H 'Authorization: Bearer {idToken}'

Read responses may contain analytics: null while a first snapshot is queued. Inspect refresh.state, snapshot.generatedAt, and snapshot.dataThrough before using the figures.

Kanban and conversation status

Kanban fields and conversation status changes are durable workspace state. Use expectedVersion when moving conversations so stale clients do not overwrite newer state.

List or create fields

curl 'https://app.ezw.solutions/api/contact-statuses' \
  -H 'Authorization: Bearer {idToken}'

curl -X POST 'https://app.ezw.solutions/api/contact-statuses' \
  -H 'Authorization: Bearer {idToken}' \
  -H 'Content-Type: application/json' \
  -d '{ "name": "Waiting for payment" }'

Move a conversation

curl -X PATCH 'https://app.ezw.solutions/api/conversations/{conversationId}/status' \
  -H 'Authorization: Bearer {idToken}' \
  -H 'Content-Type: application/json' \
  -d '{
    "statusId": "{statusId}",
    "expectedVersion": 7
  }'

Use "statusId": null to remove a conversation from Kanban. A stale expectedVersion returns a conflict with current state.

Common status codes

Status Meaning
200Read or update succeeded.
201Token, webhook, contact import, customer tag, campaign draft, or Kanban field was created.
202Send was durably queued or an idempotent attempt already existed.
400Invalid query, payload, target, media, campaign, webhook, or idempotency key.
401Missing or invalid bearer token.
403Authenticated actor is not allowed to perform the operation.
404Tenant-scoped resource was not visible or does not exist.
409Optimistic conflict, campaign state conflict, or line not currently sendable.
410The exact inbound message requested for contact identity was tombstoned.
501Required PostgreSQL source of truth or media bucket is not configured.

Non-negotiable implementation boundaries

  • The API is for support inbox workflows, client notifications, and scoped customer-admin campaign workflows, not cold outbound tooling.
  • Every customer-owned record is organization-scoped.
  • Line API tokens are scoped to exactly one WhatsApp line for messaging and provider-contact reads. Customer tag access is a separate, explicitly enabled organization-directory capability.
  • Outbound sends are idempotent and asynchronous.
  • Provider echoes must not be treated as authoritative assignment or ownership state.
  • Redis and live events are never the source of truth. Refetch DB-backed APIs.
  • Session credentials and provider runtime details stay behind EZWhatsApp provider contracts.
  • Campaigns require explicit recipient-source, consent, opt-out, policy, and compliance review by the customer.