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 examplebody 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 return 404 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.
Was this helpful?