CRM Resources
Manage your ZingaConnect CRM data programmatically. These endpoints expose
contacts, lists, campaigns, templates, teams, agents and more through a
uniform list / read / create / update / delete pattern under
/papi/v1/crm/. Learn it once and it works the same for every resource.
Reads require data:read; writes require data:write:*. (These are the standard
data scopes — not the messaging:* scopes used by the Messaging API.)
The pattern
For any {resource} in the table below:
| Method | Path | Purpose |
|---|---|---|
GET |
/papi/v1/crm/{resource} |
List (supports limit, offset, q, filters) |
GET |
/papi/v1/crm/{resource}/{id} |
Read one |
POST |
/papi/v1/crm/{resource} |
Create |
PATCH |
/papi/v1/crm/{resource}/{id} |
Update (only the fields you send) |
DELETE |
/papi/v1/crm/{resource}/{id} |
Delete |
List responses use the standard { data, total, limit, offset } envelope, and
every field is returned in both camelCase and snake_case (see
Conventions). Request bodies accept either form.
curl "https://api-v1.zingasuite.com/papi/v1/crm/contacts?limit=20&q=sam" \
-H "Authorization: Bearer zk_live_xxx"
Available resources
| Resource key | What it is |
|---|---|
contacts |
The shared contact spine — identity + per-channel marketing consent |
lists |
Static or dynamic (filter-based) contact lists |
campaigns |
Campaign definitions (drip / calendar / broadcast) |
message-templates |
Email & SMS templates with {{merge_field}} tokens |
whatsapp-templates |
WhatsApp templates — read + create/submit via Meta (see below) |
teams |
Sales/agent teams |
agents |
Team members with CRM access |
accounts |
Companies/organizations |
contact-profiles |
CRM profile (lifecycle stage, owner) for a contact |
tags |
Contact tags |
email-headers |
Reusable email masthead/header blocks |
custom-fields |
Custom field definitions |
pipelines, pipeline-stages |
Deal pipelines and their stages |
lost-reasons, lead-sources, assignment-rules |
Sales configuration |
Leads and deals are intentionally not exposed here. In the console they are
narrowed to each user's team, and a flat key-authed list would bypass that
scoping — so they remain portal-only for now.
Contacts
contacts is the same identity spine shared with ZingaShop. Notable fields:
firstName, lastName, displayName, email, phone_e164, whatsapp_e164,
companyName, city, country, the per-channel consent flags
(email_marketing_consent, sms_marketing_consent, whatsapp_marketing_consent),
locale, timezone, and metadata_json.
curl -X POST https://api-v1.zingasuite.com/papi/v1/crm/contacts \
-H "Authorization: Bearer zk_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"firstName": "Sam", "lastName": "Lee",
"email": "[email protected]", "phone_e164": "+14155551234",
"email_marketing_consent": true
}'
Lists
kind is static (members added explicitly) or dynamic (membership resolved
from a saved filter_json).
curl -X POST https://api-v1.zingasuite.com/papi/v1/crm/lists \
-H "Authorization: Bearer zk_live_xxx" -H "Content-Type: application/json" \
-d '{ "name": "VIP customers", "kind": "static", "description": "Top buyers" }'
Campaigns
A campaign is an audience (a list) plus an ordered sequence of steps. Build it in
three parts.
1. Create the campaign shell. Fields: name, description, campaign_type
(drip / calendar / broadcast), list_id, enrollment_mode
(static / continuous), default_timezone. status is not writable here
— a new campaign starts as a draft and is activated via the action below.
curl -X POST https://api-v1.zingasuite.com/papi/v1/crm/campaigns \
-H "Authorization: Bearer zk_live_xxx" -H "Content-Type: application/json" \
-d '{ "name": "Summer drip", "campaign_type": "drip", "list_id": "<list-id>" }'
2. Wire the drip steps — each step fires a template on a channel at a delay
after enrolment. channel is email / sms / whatsapp; pass
messageTemplateId (email/SMS) or waTemplateId (WhatsApp), plus delayAmount
and delayUnit (minutes / hours / days / weeks / months / years).
curl -X POST https://api-v1.zingasuite.com/papi/v1/crm/campaigns/<campaign-id>/steps \
-H "Authorization: Bearer zk_live_xxx" -H "Content-Type: application/json" \
-d '{ "channel": "email", "messageTemplateId": "<template-id>", "delayAmount": 0, "delayUnit": "days" }'
| Method | Path | Purpose |
|---|---|---|
GET |
/crm/campaigns/{id}/steps |
List the steps |
POST |
/crm/campaigns/{id}/steps |
Add a step |
PATCH |
/crm/campaigns/{id}/steps/{stepId} |
Update a step (send the full step body) |
DELETE |
/crm/campaigns/{id}/steps/{stepId} |
Remove a step |
3. Activate. Activation validates the audience + active steps and enrols
contacts, so it runs through a dedicated action rather than a raw status write:
curl -X POST https://api-v1.zingasuite.com/papi/v1/crm/campaigns/<campaign-id>/activate \
-H "Authorization: Bearer zk_live_xxx"
Pause an active campaign (or resume a paused one) with
POST /papi/v1/crm/campaigns/{id}/pause.
Email & SMS templates
Resource key message-templates. One table serves both channels via channel
(email | sms): email uses subject + body_html, SMS uses body_text.
Content may contain {{merge_field}} tokens.
curl -X POST https://api-v1.zingasuite.com/papi/v1/crm/message-templates \
-H "Authorization: Bearer zk_live_xxx" -H "Content-Type: application/json" \
-d '{
"channel": "email", "name": "Welcome", "category": "marketing",
"subject": "Welcome, {{first_name}}!",
"body_html": "<p>Hi {{first_name}}, thanks for joining.</p>"
}'
Email headers
email-headers are reusable HTML header blocks (masthead / logo / banner) that
an email template prepends. Fields: name, body_html, is_active.
curl -X POST https://api-v1.zingasuite.com/papi/v1/crm/email-headers \
-H "Authorization: Bearer zk_live_xxx" -H "Content-Type: application/json" \
-d '{
"name": "Company masthead",
"body_html": "<div style=\"text-align:center\"><img src=\"https://cdn.example.com/logo.png\" alt=\"Acme\"></div>",
"is_active": true
}'
To apply it, set headerId (the header's id) when you create or update an
email message-templates record — its HTML is rendered above the template body.
WhatsApp templates
WhatsApp templates live on Meta's WhatsApp Business platform, so they aren't a
plain table you edit. There are two paths.
Read (generic CRUD) — list your templates and each one's Meta approval
status (LOCAL, PENDING, APPROVED, REJECTED, PAUSED, DISABLED). Only
APPROVED templates are sendable.
curl "https://api-v1.zingasuite.com/papi/v1/crm/whatsapp-templates?limit=50" \
-H "Authorization: Bearer zk_live_xxx"
Create + submit to Meta — a dedicated endpoint creates the template and, by
default (submitNow: true), submits it to Meta for review. category is
MARKETING / UTILITY / AUTHENTICATION; components is Meta's component
array (HEADER / BODY / FOOTER / BUTTONS); the name is normalized (lowercased,
spaces to underscores).
Sample values. Meta requires a sample for every {{n}} variable at
submission. Provide them as a simple mapping in example — body for the BODY
variables (in order) and header for a text HEADER's variables. The mapping is
stored on the template (example_json), and at submit time it's turned into
Meta's example.body_text / example.header_text shape for you. Any variable
you don't supply defaults to "Sample", so a submission never fails just for a
missing sample.
curl -X POST https://api-v1.zingasuite.com/papi/v1/crm/whatsapp-templates \
-H "Authorization: Bearer zk_live_xxx" -H "Content-Type: application/json" \
-d '{
"channelConfigId": "<channel-id>", "name": "order_update",
"language": "en_US", "category": "UTILITY",
"components": [{ "type": "BODY", "text": "Your order {{1}} has shipped." }],
"example": { "body": ["A1234"] },
"submitNow": true
}'
So {{1}} is submitted to Meta with the sample A1234. (Media headers still
need a sample-media handle — upload that in the console.)
Edit a draft. While a template is still LOCAL (not yet submitted) you can
change it with PATCH /crm/whatsapp-templates/{id} — same fields as create
(components, example, name, language, category; optional submitNow).
Once it's been submitted to Meta it's immutable, and PATCH returns
400 not_editable. (DELETE isn't exposed on the API — remove templates in the
console.)
Submit an existing local draft later with
POST /papi/v1/crm/whatsapp-templates/{id}/submit (you may pass an example
mapping in that call to set the samples first). The template becomes sendable
once Meta approves it — poll status (or sync it in the console).
Teams & agents
teams groups agents; agents links a platform user to CRM access via
access_tier (own / team / all).
curl -X POST https://api-v1.zingasuite.com/papi/v1/crm/agents \
-H "Authorization: Bearer zk_live_xxx" -H "Content-Type: application/json" \
-d '{ "user_id": "<user-id>", "access_tier": "team", "title": "Account Executive", "team_id": "<team-id>" }'
Notes
- Every write is scoped to your entity and validated against the key owner's
permissions; unknown resource keys return404 unknown_resource. - Uniqueness rules (e.g. a template's name per channel) surface as
409 duplicate_or_invalid_reference. - For error codes and rate limits, see Errors and
Rate limits.