---
title: "REST API v1"
source: https://sumgenius.ai/docs/rest-api
generated: 2026-07-30
---

# 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

Account

[*GET*/account](#get-account)

Contacts

[*GET*/contacts](#list-contacts)

[*GET*/contacts/{id}](#get-contact)

[*POST*/contacts](#create-contact)

[*PATCH*/contacts/{id}](#update-contact)

[*POST*/contacts/{id}/tags](#add-contact-tag)

[*DEL*/contacts/{id}/tags/{tag}](#remove-contact-tag)

[*DEL*/contacts/{id}](#delete-contact)

Conversations

[*GET*/conversations](#list-conversations)

[*POST*/conversations/{id}/assign](#assign-conversation)

[*DEL*/conversations/{id}](#delete-conversation)

Messages

[*GET*/conversations/{id}/messages](#list-messages)

[*POST*/messages](#send-message)

Tags

[*GET*/tags](#list-tags)

[*POST*/tags](#create-tag)

[*DEL*/tags/{id}](#delete-tag)

Appointments

[*GET*/appointments](#list-appointments)

[*GET*/appointments/{id}](#get-appointment)

Team

[*GET*/team](#get-team)

### 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](https://sumgenius.ai/docs/send-api), and [webhooks](https://sumgenius.ai/docs/chatgenius-webhook-api). 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.

| 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 |
| 500 | server_error | Unexpected 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.

> **Warning**
>
> **This cannot be undone.** To remove one thread and keep the person, use [delete conversation](#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](#get-team).
- updated_sincetimestampoptional
- limit · starting_afterpaginationoptional

> **Note**
>
> **`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](#get-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.

> **Warning**
>
> **This cannot be undone.** The contact survives; to erase the person entirely use [delete contact](#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](https://sumgenius.ai/docs/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.

> **Warning**
>
> A failed cold send still creates the conversation and contact before the delivery attempt. Repeats reuse the same records; remove them with [delete contact](#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 |

**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](#add-contact-tag).

### 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](https://sumgenius.ai/docs/appointment-booking). 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

> **Note**
>
> **`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](#assign-conversation) 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
}
```
