ChatGenius Developer REST API Markdown

REST API v1

Read and manage your ChatGenius data: contacts, conversations, messages, tags, appointments, and your team. One base URL, one API key, one envelope.

Webhook & API Add-on · $29/month · Requires Creator+ plan

Authentication

Every request is authenticated with your API key from the portal's Webhook & Send API page. The same key opens the REST API, the Send API, and webhooks. Send it as any one of three headers; X-SumGenius-Api-Key is preferred.

The API is server-to-server. CORS is intentionally disabled, so call it from your backend and keep the key out of browsers.

  • 401 unauthorized

    No API key sent.

  • 401 invalid_api_key

    Key not recognized.

  • 403 addon_inactive

    Key is valid but the add-on is not active.

Base URL
https://sumgenius.ai/api/v1
Authenticated request
curl https://sumgenius.ai/api/v1/account \
  -H "X-SumGenius-Api-Key: sgwh_your_api_key_here"

Envelope, errors & pagination

The HTTP status carries success or failure. A single resource arrives as {"data": {...}}; a list adds has_more and next_cursor. Errors carry a stable code to branch on; message is human-facing and may change.

Lists are newest-first and cursor-paginated: pass limit (1 to 100, default 25) and feed next_cursor back as starting_after until has_more is false. Tags and team return their whole set in one page.

HTTPcodeWhen
400invalid_paramA parameter failed validation; param names it
400invalid_jsonBody is not valid JSON
404not_foundMissing, or not owned by your account
405 Wrong method; the Allow header lists valid ones
500server_errorUnexpected failure on our side
List envelope
{
  "data": [ ... ],
  "has_more": true,
  "next_cursor": "18son403"
}
Error envelope
{
  "error": {
    "code": "invalid_param",
    "message": "stage must be one of: new, contacted, qualified, customer, lost.",
    "param": "stage"
  }
}

Data conventions

  • timestampsUTC ISO-8601

    Everywhere, e.g. 2026-07-25T04:35:23+00:00. The updated_since filters accept the same format.

  • channelenum

    facebook · instagram · sms · telegram · whatsapp

  • stageenum

    new · contacted · qualified · customer · lost

  • statusenum

    active · resolved · pending · archived · escalated (conversations)

  • custom_fieldsreserved

    Always {} for now and not writable. Do not build against it until custom fields ship.

Identity
// A contact is keyed by its natural identity:
(channel, platform_user_id)

// e.g. an Instagram user:
("instagram", "17845629031224067")

Account

GET /account

Your account identity, plan, and configured webhook endpoints. A good first call to verify a key works.

Request
curl https://sumgenius.ai/api/v1/account \
  -H "X-SumGenius-Api-Key: sgwh_your_api_key_here"
Response 200
{
  "data": {
    "account": {
      "id": 1042,
      "business_name": "Riverside Dental",
      "plan": "Professional",
      "addon": "webhook_api"
    },
    "webhooks": {
      "endpoints": [
        {
          "id": 7,
          "name": "Production endpoint",
          "url": "https://api.example.com/webhooks",
          "enabled": true,
          "event_filters": ["message.received"],
          "envelope_style": "standard"
        }
      ]
    }
  }
}

Contacts

A contact is a person, keyed by (channel, platform_user_id). Contacts are created automatically when someone messages you, or explicitly via the API.

GET /contacts

List contacts, newest first. Filters combine with AND. An invalid filter value returns 400 invalid_param.

Query parameters

  • channelenumoptional
  • stageenumoptional
  • tagstringoptional

    Exact tag name; contacts carrying that tag.

  • emailstringoptional

    Exact match.

  • phonestringoptional

    Exact match.

  • namestringoptional

    Substring match on the display name.

  • updated_sincetimestampoptional

    Contacts updated at or after this time.

  • limit · starting_afterpaginationoptional
Request
curl "https://sumgenius.ai/api/v1/contacts?stage=qualified&limit=25" \
  -H "X-SumGenius-Api-Key: sgwh_your_api_key_here"
Response 200 · the contact resource
{
  "data": [
    {
      "id": 19214,
      "channel": "instagram",
      "platform_user_id": "17845629031224067",
      "name": "Riley Chen",
      "email": "riley@example.com",
      "phone": null,
      "ig_username": "rileychen",
      "profile_pic_url": null,
      "stage": "qualified",
      "tags": ["VIP"],
      "lead_score": 30,
      "lead_temperature": "warm",
      "notes": null,
      "custom_fields": {},
      "created_at": "2026-07-25T04:35:23+00:00",
      "updated_at": "2026-07-26T08:05:04+00:00"
    }
  ],
  "has_more": false,
  "next_cursor": null
}

GET /contacts/{id}

One contact by id, in the same shape as the list. A non-numeric, unknown, or unowned id returns 404.

Request
curl https://sumgenius.ai/api/v1/contacts/19214 \
  -H "X-SumGenius-Api-Key: sgwh_your_api_key_here"

POST /contacts

Create or update a contact by its natural identity. A new identity returns 201; an existing one is updated and returns 200. Updates are partial: omitted fields are untouched and existing tags are kept (tags adds, it does not replace).

Body

  • channelenumrequired
  • platform_user_idstringrequired

    The person's id on that channel, 1 to 255 characters. For SMS this is the phone number.

  • namestringoptional

    Up to 255 characters.

  • emailstringoptional

    Valid email address.

  • phonestringoptional

    Digits and + ( ) . - # x, 3 to 50 characters.

  • stageenumoptional
  • notesstringoptional

    Up to 5,000 characters.

  • tagsstring[]optional

    Tag names; missing tags are created automatically.

Request
curl -X POST https://sumgenius.ai/api/v1/contacts \
  -H "X-SumGenius-Api-Key: sgwh_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "channel": "sms",
    "platform_user_id": "+17025550100",
    "name": "Riley Chen",
    "stage": "new",
    "tags": ["Imported"]
  }'
Response 201 · 200 on update
{
  "data": {
    "id": 19301,
    "channel": "sms",
    "platform_user_id": "+17025550100",
    "name": "Riley Chen",
    "stage": "new",
    "tags": ["Imported"],
    ...
  }
}

PATCH /contacts/{id}

Partial update by id. Only fields present in the body change. Send "" to clear a field. Idempotent by nature, so no idempotency key is needed. An empty body returns 400 listing the updatable fields.

Body · all optional

  • stageenum
  • namestring

    Up to 255 characters, or "" to clear.

  • emailstring

    Valid email, or "" to clear.

  • phonestring

    3 to 50 characters, or "" to clear.

  • notesstring

    Up to 5,000 characters, or "" to clear.

Request
curl -X PATCH https://sumgenius.ai/api/v1/contacts/19214 \
  -H "X-SumGenius-Api-Key: sgwh_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "stage": "customer",
    "notes": "Paid annual plan on Jul 29"
  }'
Response 200
{
  "data": {
    "id": 19214,
    "stage": "customer",
    "notes": "Paid annual plan on Jul 29",
    ...
  }
}

POST /contacts/{id}/tags

Attach a tag by name. A tag that does not exist yet is created, then attached. Re-attaching is a no-op 200. Returns the updated contact.

Body

  • namestringrequired

    1 to 50 characters. Created if missing, with the default color.

Request
curl -X POST https://sumgenius.ai/api/v1/contacts/19214/tags \
  -H "X-SumGenius-Api-Key: sgwh_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"name": "VIP"}'

DELETE /contacts/{id}/tags/{tag}

Detach a tag from a contact. {tag} is a tag id or a URL-encoded name; a name that is all digits or contains / must go by id. Detaching a tag the contact does not have is an idempotent 200; a tag that does not exist at all is 404. Only the attachment is removed; the tag itself survives.

Request
curl -X DELETE https://sumgenius.ai/api/v1/contacts/19214/tags/VIP \
  -H "X-SumGenius-Api-Key: sgwh_your_api_key_here"

DELETE /contacts/{id}

Permanently erases a person: the contact and every conversation, message, and appointment they have. Built for right-to-erasure requests.

⚠️

This cannot be undone. To remove one thread and keep the person, use delete conversation instead.

Tag definitions and send-history audit records survive. Repeating the call is a safe 404.

Request
curl -X DELETE https://sumgenius.ai/api/v1/contacts/19214 \
  -H "X-SumGenius-Api-Key: sgwh_your_api_key_here"
Response 200
{
  "data": {
    "id": 19214,
    "object": "contact",
    "deleted": true
  }
}

Conversations

One thread per person per channel. Deleted conversations are never returned.

GET /conversations

List conversations, newest first. contact_id links each thread to its person.

Query parameters

  • channelenumoptional
  • statusenumoptional
  • assigned_tointegeroptional

    A team member id from team.

  • updated_sincetimestampoptional
  • limit · starting_afterpaginationoptional
ℹ️

assigned_to id space: positive is a team member (assigned_to_name resolves); negative is an owner-level assignment (assigned_to_name is null); null is unassigned.

Request
curl "https://sumgenius.ai/api/v1/conversations?status=escalated" \
  -H "X-SumGenius-Api-Key: sgwh_your_api_key_here"
Response 200 · the conversation resource
{
  "data": [
    {
      "id": 188892,
      "channel": "instagram",
      "platform_user_id": "17845629031224067",
      "customer_name": "Riley",
      "status": "escalated",
      "assigned_to": 90011,
      "assigned_to_name": "Sam Ortiz",
      "contact_id": 19214,
      "lead_email": "riley@example.com",
      "lead_phone": null,
      "last_message_at": "2026-07-26T08:05:04+00:00",
      "created_at": "2026-07-25T04:35:23+00:00",
      "updated_at": "2026-07-26T13:00:54+00:00"
    }
  ],
  "has_more": false,
  "next_cursor": null
}

POST /conversations/{id}/assign

Assign or unassign a conversation, exactly like the inbox does it: a pending or escalated thread becomes active, history is recorded, and a system line is added to the transcript. Returns the updated conversation.

Body

  • assigned_tointeger | nullrequired

    A team member id from team, or null to unassign. An unknown id is 400.

Request
curl -X POST https://sumgenius.ai/api/v1/conversations/188892/assign \
  -H "X-SumGenius-Api-Key: sgwh_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"assigned_to": 90011}'

DELETE /conversations/{id}

Permanently deletes one conversation: the full transcript, in-chat appointments, tag labels, and assignment records.

⚠️

This cannot be undone. The contact survives; to erase the person entirely use delete contact.

The response counts what was removed. Analytics rows are kept but stripped of message text and unlinked; send-history audit survives. A repeat delete is a safe 404.

Request
curl -X DELETE https://sumgenius.ai/api/v1/conversations/188892 \
  -H "X-SumGenius-Api-Key: sgwh_your_api_key_here"
Response 200
{
  "data": {
    "id": 188892,
    "object": "conversation",
    "deleted": true,
    "removed": {
      "messages": 15,
      "appointments": 1,
      "conversation_tags": 2,
      "contexts": 1,
      "metrics_detached": 5
    }
  }
}

Messages

GET /conversations/{id}/messages

The transcript of one conversation, newest first (default limit 50). Copilot drafts are excluded; this is the conversation as the customer saw it.

  • directionderived

    user is inbound; bot, agent, followup are outbound; the rest is system.

  • typeenum

    text · image · video · audio · file · quick_reply · button · postback · comment_trigger

  • body_purgedboolean

    When a body was purged for compliance, text is null and this is true; the message is still listed.

Request
curl "https://sumgenius.ai/api/v1/conversations/188892/messages" \
  -H "X-SumGenius-Api-Key: sgwh_your_api_key_here"
Response 200 · the message resource
{
  "data": [
    {
      "id": 56698,
      "message_id": "usr_6a65bfb099523",
      "direction": "inbound",
      "sender_type": "user",
      "sender_name": null,
      "type": "text",
      "text": "Do you have anything open Friday?",
      "attachments": [],
      "is_template": false,
      "body_purged": false,
      "created_at": "2026-07-26T08:05:04+00:00"
    }
  ],
  "has_more": true,
  "next_cursor": "56610"
}

POST /messages

Send on any channel. Same engine as the Send API with full parity; recommended for new integrations. Route by conversation_id, by platform + platform_user_id, or by to_phone for SMS and WhatsApp.

Body

  • idempotency_keystringrequired

    Your unique key per send. A replay returns the original result without resending. Also accepted as an Idempotency-Key header.

  • actionenumoptional

    send_message (default) · react · unreact · typing_on. Support varies by channel.

  • conversation_idinteger

    Route by an existing thread.

  • platform + platform_user_idstring

    Route by the person's channel id.

  • to_phonestring

    Route by phone (SMS, WhatsApp).

  • message_textstringrequired

    Not required for typing_on or WhatsApp template sends (template_name, template_language, template_components).

Cold outbound

SMS, WhatsApp, and Telegram may send to a recipient with no existing conversation; the thread is created automatically. Facebook and Instagram require the customer to message first.

⚠️

A failed cold send still creates the conversation and contact before the delivery attempt. Repeats reuse the same records; remove them with delete contact if unwanted.

Send errors

HTTPcodeWhen
400invalid_requestMissing or invalid field
403forbiddenChannel not permitted
404not_foundNo conversation for the routing given
410recipient_opted_outRecipient opted out (SMS STOP)
412not_provisionedChannel not connected
422unprocessable_sendPermanent rejection: unsupported action, expired window, invalid recipient
429rate_limitedToo many sends; retry later
503service_unavailableTemporary failure; may queue for retry
Request
curl -X POST https://sumgenius.ai/api/v1/messages \
  -H "X-SumGenius-Api-Key: sgwh_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "idempotency_key": "order-4821-confirm",
    "conversation_id": 188892,
    "message_text": "Your order shipped today."
  }'
Response 200
{
  "data": {
    "status": "sent",
    "request_id": "msgreq_c8d83cf861ec1f48",
    "conversation_id": 188892,
    "platform": "instagram",
    "action": "send_message",
    "sent_at": "2026-07-30T06:14:18+00:00",
    "message": "Message sent successfully."
  }
}
Error 422
{
  "error": {
    "code": "unprocessable_send",
    "message": "action 'react' is not supported for platform 'telegram'.",
    "status": "dead_letter"
  }
}

Tags

Tag definitions live account-wide; attaching them to contacts is a contact operation.

GET /tags

All tag definitions, ordered by name, in one page. name is what the contacts ?tag= filter matches.

Request
curl https://sumgenius.ai/api/v1/tags \
  -H "X-SumGenius-Api-Key: sgwh_your_api_key_here"
Response 200
{
  "data": [
    {
      "id": 14,
      "name": "Booking",
      "color": "#DD6B20",
      "created_at": "2026-03-06T19:55:17+00:00"
    }
  ],
  "has_more": false,
  "next_cursor": null
}

POST /tags

Create a tag definition. Idempotent on name: new returns 201, existing returns 200 with its color unchanged.

Body

  • namestringrequired

    1 to 50 characters, unique per account.

  • colorstringoptional

    6-digit hex like #FF0000; anything else is 400.

Request
curl -X POST https://sumgenius.ai/api/v1/tags \
  -H "X-SumGenius-Api-Key: sgwh_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"name": "VIP", "color": "#FF0000"}'

DELETE /tags/{id}

Deletes a tag definition account-wide and removes it from every contact and conversation carrying it. By numeric id only.

Request
curl -X DELETE https://sumgenius.ai/api/v1/tags/14 \
  -H "X-SumGenius-Api-Key: sgwh_your_api_key_here"
Response 200
{"data": {"id": 14, "deleted": true}}

Appointments

Bookings made through the AI booking agent. Read-only.

GET /appointments

List bookings, newest first.

Query parameters

  • statusstringoptional

    Exact match, e.g. confirmed or cancelled. Free text: an unknown value returns an empty list, not 400.

  • conversation_idintegeroptional
  • limit · starting_afterpaginationoptional
ℹ️

datetime is UTC, booked in timezone. Convert back with the timezone field to display the time the customer chose. meeting_link and google_event_id are null without Google Calendar sync.

Request
curl "https://sumgenius.ai/api/v1/appointments?status=confirmed" \
  -H "X-SumGenius-Api-Key: sgwh_your_api_key_here"
Response 200 · the appointment resource
{
  "data": [
    {
      "id": 91772,
      "confirmation_code": "SG-FHZS9V",
      "conversation_id": 187864,
      "contact_id": 16240,
      "channel": "instagram",
      "datetime": "2026-07-24T23:00:00+00:00",
      "timezone": "America/Los_Angeles",
      "duration_minutes": 60,
      "service_type": "Consultation",
      "cost": "0.00",
      "status": "confirmed",
      "meeting_link": "https://meet.google.com/abc-defg-hij",
      "google_event_id": "oa9daqujesg8dktkb777ds5gs0",
      "reminder_sent": false,
      "location": {"city": null, "state": null},
      "customer": {
        "name": "Riley",
        "email": "riley@example.com",
        "phone": "+17025550100",
        "address": null
      },
      "notes": "Prefers mornings",
      "created_at": "2026-07-24T07:33:22+00:00"
    }
  ],
  "has_more": false,
  "next_cursor": null
}

GET /appointments/{id}

One appointment by id, same shape as the list. Unknown or unowned id returns 404.

Request
curl https://sumgenius.ai/api/v1/appointments/91772 \
  -H "X-SumGenius-Api-Key: sgwh_your_api_key_here"

Team

GET /team

Your team members, ordered by name, in one page. A member's id is what assigned_to references and what assign accepts. Credentials and permission data are never exposed.

  • roleenum

    agent · manager

  • statusenum

    online · away · busy · offline

Request
curl https://sumgenius.ai/api/v1/team \
  -H "X-SumGenius-Api-Key: sgwh_your_api_key_here"
Response 200
{
  "data": [
    {
      "id": 90011,
      "name": "Sam Ortiz",
      "email": "sam@example.com",
      "role": "manager",
      "status": "online",
      "avatar_url": null,
      "active_conversations": 1,
      "total_resolved": 0,
      "created_at": "2026-07-22T06:16:06+00:00"
    }
  ],
  "has_more": false,
  "next_cursor": null
}