API Reference
Customers
💬Get free consultation

Customers and memory

ⓘ

Who can use this feature?

  • An admin on Pro or Plus creates a scoped API key. Reads need customers:read; writes need customers:write.
  • Deleting a customer additionally requires conversations:delete, because the customer's conversations are deleted too.
  • Exporting customer data requires customers:export.

All paths below use the Public API base URL. Send X-Api-Key; JSON writes also need Content-Type: application/json. Legacy keys without scopes are refused. These endpoints manage Chatty contacts, not Shopify customer records.

List and read profiles

Both reads require customers:read, Pro / Plus, and return 200. They take no request body.

MethodPathRequest exampleResponse example
GET/customers?limit=20&type=guest{"data":[],"count":0,"pagination":{"hasPre":false,"hasNext":false}}
GET/customers/{id}id=customer_123{"data":{"id":"customer_123","name":"Example Customer","email":"[email protected]","type":"guest"}}

List parameters are limit (default 20), after (the last returned customer's ID), type, and search (a prefix on the stored email field). This uses pagination.hasNext, not the conversation API's paging.nextCursor. Pass the last row's id as after to continue. Search does not search every profile field or the legacy email field, and is not case-normalized for you.

List rows are a smaller projection: id, email (with a legacy fallback), firstName, lastName, and shopifyId when stored. Fetch a profile to read the other fields. The detail/create/update projection allows id, shopifyId, name, firstName, lastName, email, phone, note, tags, type, createdAt, and updatedAt; absent fields may be omitted. Examples show only selected fields.

A missing or foreign customer returns 404 NOT_FOUND. Authentication, scope, version and rate errors apply to both reads. The existing GET /chat/customers/{customerUuid} has a different, wider contact profile; do not assume the two payloads match.

Create a guest contact

POST /customers — customers:write, Pro / Plus.

FieldRequiredValidation
emailEmail or phoneValid email, trimmed and lowercased.
phoneEmail or phoneNonblank after trimming, up to 50 characters.
firstName, lastNameNoStrings, up to 255 characters each.
noteNoString, up to 10,000 characters.
tagsNoString array; trimmed, deduplicated, up to 100 distinct tags of 1–50 characters each.

Unknown fields are rejected. name, type, and Shopify IDs are not create inputs. The display name is built from the first and last names, or null when both are blank; type is guest.

curl -X POST 'https://app.chatty.net/v1/customers' \
  -H 'X-Api-Key: sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' \
  -H 'Content-Type: application/json' \
  -d '{"email":"[email protected]","firstName":"Example","lastName":"Customer","tags":["vip"]}'

Example 201 response excerpt:

{"data":{"id":"customer_123","email":"[email protected]","firstName":"Example","lastName":"Customer","name":"Example Customer","type":"guest","tags":["vip"]}}

Tags are registered in the shop's customer tag catalog. The normal customer-created trigger emits customer.created. No Shopify write is made.

Invalid or missing contact details return 400 VALIDATION_FAILED. A duplicate email or phone returns 409 CUSTOMER_EXISTS with error.existingId:

{"error":{"code":"CUSTOMER_EXISTS","message":"Customer already exists","existingId":"customer_123"}}

Email duplicate checking searches the trimmed original input and lowercase form. Differently cased legacy email records may still escape that check. Reuse an existing ID when supplied; do not retry creation blindly.

Update a profile

PATCH /customers/{id} — customers:write, Pro / Plus.

Only name, email, phone, note, and tags are writable. Send only fields you want to change. A tags array replaces that contact's stored tags; it does not append. Register new catalog entries separately through /customer-tags when needed. Do not assume the create endpoint's normalization, duplicate checks, or validation are applied by PATCH.

curl -X PATCH 'https://app.chatty.net/v1/customers/customer_123' \
  -H 'X-Api-Key: sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' \
  -H 'Content-Type: application/json' \
  -d '{"name":"Example Customer","note":"Prefers email","tags":["vip","email-contact"]}'

200 returns the updated public profile in data, for example {"data":{"id":"customer_123","name":"Example Customer","note":"Prefers email","tags":["vip","email-contact"]}}. Unknown or read-only fields return 403 FIELD_NOT_PERMITTED; missing or foreign contacts return 404 NOT_FOUND.

Tracked profile changes emit customer.updated. Tags-only and note-only edits do not emit that event.

Delete a customer

DELETE /customers/{id} — both customers:write and conversations:delete, Pro / Plus. No body.

curl -X DELETE 'https://app.chatty.net/v1/customers/customer_123' \
  -H 'X-Api-Key: sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
{"data":{"success":true}}
!

This also removes the customer's associated conversations and memory. The normal cleanup removes their messages, activities, traces, memory profile, facts and summaries. It is a destructive Chatty operation, not a Shopify customer deletion, and has no public undo endpoint. Confirm the intended customer before sending it.

Success is 200. Missing either scope returns 403 FORBIDDEN; a missing or foreign customer returns 404 NOT_FOUND. Associated conversation cleanup emits conversation.removed with empty data; there is no customer.deleted webhook event. Repeating a completed deletion returns 404.

Customer tags

These are customer tags, separate from the conversation /tags catalog. Both endpoints use Pro / Plus.

MethodPathScopeRequestResponse example
GET/customer-tagscustomers:readNo body200 {"data":[{"id":"tag_123","title":"vip","createdAt":"2026-10-01T12:00:00.000Z"}]}
POST/customer-tagscustomers:write{"titles":["vip","email-contact"]}201 {"data":{"success":true}}

titles must be an array. Titles are trimmed and deduplicated; each must have 1–50 characters, with at most 100 distinct titles in a request. Invalid titles return 400 VALIDATION_FAILED. Creating catalog titles does not attach them to any contact; use PATCH on the contact for that. Shared authentication, scope and rate errors apply to both endpoints.

Read customer memory

GET /customers/{id}/memory — customers:read, Pro / Plus. No body or pagination parameters. 200 returns the profile and up to 20 most recent unexpired facts, newest first. It does not return a list of conversation summaries.

curl 'https://app.chatty.net/v1/customers/customer_123/memory' \
  -H 'X-Api-Key: sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
{
  "data": {
    "profile": null,
    "facts": [
      {"id":123,"fact":"Prefers email","factType":"preference","source":"manual","sourceConvoId":null,"expiresAt":null,"createdAt":"2026-10-01T12:00:00.000Z"}
    ]
  }
}

When present, profile contains summary, tags, avgSentiment, totalConversations, firstSeenAt, and lastSeenAt. A missing profile is null; a contact with no facts has facts: []. Missing or foreign contacts return 404 NOT_FOUND.

Add, update and remove facts

All three routes use customers:write, Pro / Plus. A fact belongs to both the shop and the customer in the URL; another customer's fact returns 404 NOT_FOUND.

MethodPathRequestSuccess response
POST/customers/{id}/memory/facts{"fact":"Prefers email","factType":"preference"}201 {"data":{"factId":"123"}}
PUT/customers/{id}/memory/facts/{factId}{"fact":"Prefers phone","factType":"contact"}200 {"data":{"id":123}}
DELETE/customers/{id}/memory/facts/{factId}No body200 {"data":{"id":123}}

fact must be nonblank and at most 500 characters before trimming. factType must be preference, size, behavior, demographic, issue, or contact. POST defaults an omitted type to behavior; PUT requires both fields. Added facts have source: "manual".

Use the ID returned at creation or read it from the memory response. Fact IDs must be positive decimal integers within JavaScript's safe-integer range; partial numeric strings are rejected. Invalid IDs, text or types return 400 VALIDATION_FAILED. Missing customers or facts return 404 NOT_FOUND.

Remove a conversation summary

DELETE /customers/{id}/memory/summaries/{conversationId} — customers:write, Pro / Plus. No body.

curl -X DELETE 'https://app.chatty.net/v1/customers/customer_123/memory/summaries/conv_123' \
  -H 'X-Api-Key: sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'

200 returns {"data":{"id":"conv_123"}}. This deletes the stored summary, not the conversation or its messages. The conversation must belong to this customer and shop, and a summary must exist; otherwise the response is 404 NOT_FOUND. A repeated delete returns 404.

Regenerate the memory profile summary

POST /customers/{id}/memory/regenerate — customers:write, Pro / Plus. No body. Limited to 10 requests per hour per key, in addition to shared API budgets.

curl -X POST 'https://app.chatty.net/v1/customers/customer_123/memory/regenerate' \
  -H 'X-Api-Key: sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'

200 returns the stored profile after the generation attempt, for example {"data":{"profile":null}} when none exists. The profile uses the same fields as the memory read above. Regeneration uses the customer's facts and conversation summaries; it does not create a conversation summary for every conversation.

If no source facts or summaries exist, or generation produces no new result, the response may contain the previous profile or null. A 200 is not a guarantee that the summary changed. Missing or foreign customers return 404 NOT_FOUND; exceeding the hourly budget returns 429 RATE_LIMITED.

Export customer data

Both routes need customers:export, Pro / Plus. This scope is separate: customers:write does not grant it. Grant it only to integrations that need to send customer data out of Chatty.

MethodPathRequestResponse
POST/customers/exportNo body200 {"queued":true,"recipient":"o***@example.com"}
POST/customers/{id}/exportNo body200 {"queued":true,"recipient":"o***@example.com"}

The export runs in the background and is emailed to the store's email address. You cannot choose the recipient: any body field returns 400 VALIDATION_FAILED, and a store without a valid email returns 400 VALIDATION_FAILED with Shop email is required. The response shows the recipient masked. queued: true means the job was queued, not that the email was sent.

  • POST /customers/export sends one CSV of every contact, with the columns Type, First name, Last name, Email, Phone, Address, Customer since, Total order, Total spent, Total conversation, Conversation IDs, Tag, Note and Last updated. The download link is valid for 1 day.
  • POST /customers/{id}/export sends one customer's profile fields and every message from their conversations, with the columns Section, Reference, Time, Field and Value. The download link is valid for 7 days. A missing or foreign customer returns 404 NOT_FOUND.

Each store can request 10 customer exports per hour across both routes and MCP. Requests that fail, including 404, still count. Over the limit: 429 RATE_LIMITED. Each request is recorded in the audit log with the calling key.

For a formal data request from a customer, follow Data and privacy.

Shared errors

All 15 endpoints apply authentication, scope, version and rate-limit checks before running. Errors use error.code and error.message; duplicate creation also returns error.existingId.

HTTPCodeMeaning
400INVALID_API_VERSIONInvalid X-Api-Version date.
400VALIDATION_FAILEDInvalid create, tag, or fact input.
401UNAUTHENTICATED / INVALID_API_KEYMissing, invalid, or revoked API key.
403FORBIDDENRequired scope missing, including the second scope on customer deletion.
403FIELD_NOT_PERMITTEDPATCH contains a field outside the writable profile allowlist.
404NOT_FOUNDCustomer, fact or summary is missing or belongs to another owner.
409CUSTOMER_EXISTSCreation found a duplicate; inspect error.existingId.
429RATE_LIMITEDShared limit or memory-regeneration budget reached.
500INTERNAL_ERRORUnexpected service failure. Inspect the resource before retrying an uncertain write.

There are no public operations here to delete every memory item at once or to edit a profile summary directly. Writes are recorded with the authenticated API identity for audit; this does not make repeated writes idempotent.