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+ planAuthentication
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.
https://sumgenius.ai/api/v1
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.
| HTTP | code | When |
|---|---|---|
| 400 | invalid_param | A parameter failed validation; param names it |
| 400 | invalid_json | Body is not valid JSON |
| 404 | not_found | Missing, or not owned by your account |
| 405 | Wrong method; the Allow header lists valid ones | |
| 429 | rate_limited | REST request quota or burst ceiling reached |
| 503 | service_unavailable | Temporary safety or infrastructure failure; retry as directed |
| 500 | server_error | Unexpected failure on our side |
{
"data": [ ... ],
"has_more": true,
"next_cursor": "18son403"
}
{
"error": {
"code": "invalid_param",
"message": "stage must be one of: new, contacted, qualified, customer, lost.",
"param": "stage"
}
}
Rate limits
Each account may make up to 200 authenticated REST requests per minute, with short bursts of up to 20 requests per second. All active API keys for the account share the same quota, so creating another key does not increase it.
The per-minute quota is measured over a rolling 60-second window rather than a fixed clock minute, so capacity returns gradually as individual requests age out instead of all at once on a boundary.
A separate system-wide safety ceiling protects API availability during aggregate traffic spikes. When either ceiling is reached, the API returns 429 rate_limited. Wait for Retry-After before retrying; use exponential backoff rather than an immediate retry loop.
Every authenticated REST response includes RateLimit-Limit, RateLimit-Remaining, and RateLimit-Reset. Reset is a Unix timestamp. The equivalent X-RateLimit-* headers are also sent for compatibility.
This quota controls requests accepted by /api/v1. Message platforms enforce their own separate, account-specific delivery limits; temporary platform throttles continue through the existing queue and retry behavior.
RateLimit-Limit: 200
RateLimit-Remaining: 182
RateLimit-Reset: 1785456060
Retry-After: 1
{
"error": {
"code": "rate_limited",
"message": "Too many REST API requests. Retry after the number of seconds in Retry-After."
}
}
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_fieldsobject
Your own per-contact data, as {field_key: value}. Only fields that are set and active appear; unset fields are omitted, never null. Define fields with POST /custom-fields, write values through PATCH /contacts/{id}.
// 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.
curl https://sumgenius.ai/api/v1/account \
-H "X-SumGenius-Api-Key: sgwh_your_api_key_here"
{
"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
curl "https://sumgenius.ai/api/v1/contacts?stage=qualified&limit=25" \
-H "X-SumGenius-Api-Key: sgwh_your_api_key_here"
{
"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": { "crm_deal_id": "D-2214" },
"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.
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.
-
custom_fieldsobjectoptional
Merged into the contact's fields, applied atomically with the rest of the upsert. Same semantics as PATCH. Fields must already be defined.
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"]
}'
{
"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.
-
custom_fieldsobject
Merge semantics: only the keys you send change, null as a value clears that field, other fields are untouched. Values are validated against each field's type (400 names the offending key). Every actual change fires a contact.field_changed webhook event.
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"
}'
{
"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.
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.
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.
curl -X DELETE https://sumgenius.ai/api/v1/contacts/19214 \
-H "X-SumGenius-Api-Key: sgwh_your_api_key_here"
{
"data": {
"id": 19214,
"object": "contact",
"deleted": true
}
}
Custom fields
Your own per-contact data. Define up to 15 fields (text, number, date, or boolean), then write values through PATCH /contacts/{id}. Values appear in every contact resource and in full-profile webhook payloads, and every change fires a contact.field_changed event. Don't store passwords, API keys, or payment card numbers in field values.
GET /custom-fields
Your field definitions, in creation order. Active fields only by default.
Query parameters
-
include_archivedbooleanoptional
Also return archived definitions (with archived true) so their ids stay discoverable for unarchiving.
curl https://sumgenius.ai/api/v1/custom-fields \
-H "X-SumGenius-Api-Key: sgwh_your_api_key_here"
{
"data": [
{
"id": 3,
"object": "custom_field",
"field_key": "crm_deal_id",
"label": "CRM deal ID",
"type": "text",
"archived": false,
"created_at": "2026-08-01T18:20:11+00:00"
}
],
"has_more": false,
"next_cursor": null
}
POST /custom-fields
Define a field. Returns 201 on create, 200 if an equivalent field already exists (same key and type), 409 if the key exists with a different type or archived, and 400 at the 15-active-field limit.
Body
-
field_keystringrequired
1 to 50 characters of a-z 0-9 _. Permanent after create — it is the JSON key in every payload.
-
labelstringrequired
Display name, 1 to 100 characters. Renameable later.
-
typeenumoptional
text (default, up to 500 chars) · number · date (YYYY-MM-DD) · boolean. Permanent after create.
curl -X POST https://sumgenius.ai/api/v1/custom-fields \
-H "X-SumGenius-Api-Key: sgwh_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"field_key": "crm_deal_id",
"label": "CRM deal ID",
"type": "text"
}'
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 '{
"custom_fields": { "crm_deal_id": "D-2214" }
}'
PATCH /custom-fields/{id}
Rename a field's label, or archive / unarchive it. field_key and type are immutable — sending either returns 400. There is no delete: archiving hides a field from every surface without touching any contact data, and unarchiving restores it (subject to the 15-active limit).
Body
-
labelstringoptional
-
archivedbooleanoptional
true hides the field everywhere; false restores it, values intact. Writes to an archived field's values return 400.
curl -X PATCH https://sumgenius.ai/api/v1/custom-fields/3 \
-H "X-SumGenius-Api-Key: sgwh_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{ "archived": 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.
curl "https://sumgenius.ai/api/v1/conversations?status=escalated" \
-H "X-SumGenius-Api-Key: sgwh_your_api_key_here"
{
"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.
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.
curl -X DELETE https://sumgenius.ai/api/v1/conversations/188892 \
-H "X-SumGenius-Api-Key: sgwh_your_api_key_here"
{
"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.
curl "https://sumgenius.ai/api/v1/conversations/188892/messages" \
-H "X-SumGenius-Api-Key: sgwh_your_api_key_here"
{
"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
| HTTP | code | When |
|---|---|---|
| 400 | invalid_request | Missing or invalid field |
| 403 | forbidden | Channel not permitted |
| 404 | not_found | No conversation for the routing given |
| 410 | recipient_opted_out | Recipient opted out (SMS STOP) |
| 412 | not_provisioned | Channel not connected |
| 422 | unprocessable_send | Permanent rejection: unsupported action, expired window, invalid recipient |
| 429 | rate_limited | Too many sends; retry later |
| 503 | service_unavailable | Temporary failure; may queue for retry |
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."
}'
{
"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": {
"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.
curl https://sumgenius.ai/api/v1/tags \
-H "X-SumGenius-Api-Key: sgwh_your_api_key_here"
{
"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.
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.
curl -X DELETE https://sumgenius.ai/api/v1/tags/14 \
-H "X-SumGenius-Api-Key: sgwh_your_api_key_here"
{"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.
curl "https://sumgenius.ai/api/v1/appointments?status=confirmed" \
-H "X-SumGenius-Api-Key: sgwh_your_api_key_here"
{
"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.
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
curl https://sumgenius.ai/api/v1/team \
-H "X-SumGenius-Api-Key: sgwh_your_api_key_here"
{
"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
}