REST API Reference
Build custom integrations from any platform using the public conversation REST API.
REST API Reference
The Premex Support REST API lets you build custom support integrations from any platform — mobile apps, desktop clients, IoT devices, or server-side services. No authentication tokens are required; customer identity is managed through an anonymous customerId.
Quick Start
Three requests are all you need to create a conversation and send a message.
1. Generate a customer ID (client-side)
// Generate once and persist (e.g. localStorage, Keychain, SharedPreferences)
const customerId = "cust_" + crypto.randomUUID();
2. Create a conversation
curl -X POST https://support.premex.se/api/conversations \
-H "Content-Type: application/json" \
-d '{
"productSlug": "your-product-slug",
"customerId": "cust_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"subject": "Login not working",
"message": "I cannot log in after resetting my password."
}'
# Response 200
# { "conversationId": "42", "createdAt": "2026-02-25T10:00:00.000Z" }
3. Post a follow-up message
curl -X POST https://support.premex.se/api/conversations/42/messages \
-H "Content-Type: application/json" \
-d '{
"productSlug": "your-product-slug",
"customerId": "cust_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"message": "I tried again and still see the same error."
}'
# Response 200
# { "id": "101", "createdAt": "2026-02-25T10:05:00.000Z" }
Authentication
The conversation API uses anonymous customer identity — no OAuth tokens or API keys are needed. Identity is established through a client-generated customerId.
- Format:
cust_<UUID>(e.g.cust_a1b2c3d4-e5f6-7890-abcd-ef1234567890) - Generate once per end user using
crypto.randomUUID()or equivalent. - Persist across sessions (localStorage, Keychain, SharedPreferences, etc.).
- Every API call requires the
customerId. The server validates it on every read and write to ensure strict tenant isolation.
Security note: The customerId must be opaque and non-guessable. Never use sequential IDs, email addresses, or other predictable values. The cust_<UUID> format provides sufficient entropy.
Base URL
https://support.premex.se
All endpoint paths below are relative to this base. For self-hosted deployments, replace with your own domain.
Endpoints
POST /api/conversations
Create a new support conversation. Returns the conversation ID for subsequent calls.
Request body
{
"productSlug": "your-product-slug", // required
"customerId": "cust_...", // required
"message": "I need help with...", // required — initial message body
"subject": "Login issue" // optional — conversation title
}
Response 200
{
"conversationId": "42",
"createdAt": "2026-02-25T10:00:00.000Z"
}
Errors
400— Missing required fields (productSlug,message, orcustomerId)404— Product not found for the given slug500— Internal server error (includesrequestIdfor debugging)
GET /api/conversations/list
List all conversations for a customer. Returns both open and closed conversations, sorted by most recent activity.
Query parameters
productSlug— requiredcustomerId— required
Response 200
{
"conversations": [
{
"id": "42",
"subject": "Login issue",
"status": "waiting-for-agent",
"isOpen": true,
"createdAt": "2026-02-25T10:00:00.000Z",
"updatedAt": "2026-02-25T10:05:00.000Z"
}
]
}
Status values
new— Just created, not yet triagedwaiting-for-agent— Customer replied, needs agent attentionwaiting-for-customer— Agent replied, waiting for customerresolved— Marked as resolvedclosed— Closed
Errors
400— Missing required parameters500— Internal server error
GET /api/conversations/find
Find an existing open conversation for a customer. Useful for checking whether a customer already has an active conversation before creating a new one. Prefers the first open conversation if multiple exist.
Query parameters
productSlug— requiredcustomerId— required
Response 200 (found)
{
"found": true,
"conversationId": "42"
}
Response 200 (not found)
{
"found": false
}
Tip: Call this endpoint before creating a new conversation to avoid duplicates. If found is true, navigate the user to the existing conversation instead.
Errors
400— Missing required parameters404— Product not found500— Internal server error
GET /api/conversations/[id]
Fetch a conversation with all its messages. The first message is returned as the issue body; subsequent messages are in the comments array.
Path parameters
id— Conversation ID
Query parameters
productSlug— requiredcustomerId— required
Response 200
{
"issue": {
"id": "42",
"title": "Login issue",
"body": "I cannot log in after resetting my password.",
"state": "open",
"labels": [],
"createdAt": "2026-02-25T10:00:00.000Z",
"updatedAt": "2026-02-25T10:05:00.000Z"
},
"comments": [
{
"id": "101",
"body": "Thanks for reaching out. Can you try clearing your cookies?",
"author": "Support Agent",
"createdAt": "2026-02-25T10:02:00.000Z",
"isCustomer": false
},
{
"id": "102",
"body": "That worked, thank you!",
"author": "Customer",
"createdAt": "2026-02-25T10:05:00.000Z",
"isCustomer": true
}
]
}
Note: The state field is "open" or "closed". For more granular status information, use the status field from the list endpoint.
Errors
400— MissingproductSlugorcustomerId404— Conversation not found (or does not belong to this customer)500— Internal server error
POST /api/conversations/[id]/messages
Post a new message to an existing conversation.
Path parameters
id— Conversation ID
Request body
{
"productSlug": "your-product-slug", // required
"customerId": "cust_...", // required
"message": "Here is more info...", // required
"clientMessageId": "msg_abc123" // optional — for deduplication
}
Response 200
{
"id": "103",
"createdAt": "2026-02-25T10:10:00.000Z"
}
Errors
400— Missing required fields (message,productSlug, orcustomerId)404— Conversation not found409— Conversation is closed (cannot post to a closed conversation)500— Internal server error
POST /api/conversations/[id]/close
Close or resolve a conversation. Use "resolve" when the customer's issue has been addressed, or "close" to close without marking as resolved.
Path parameters
id— Conversation ID
Request body
{
"productSlug": "your-product-slug", // required
"customerId": "cust_...", // required
"action": "resolve" // required — "resolve" or "close"
}
Response 200 (resolve)
{
"success": true,
"message": "Conversation marked as resolved"
}
Response 200 (close)
{
"success": true,
"message": "Conversation closed"
}
Errors
400— Missing required parameters or invalid action (must be"resolve"or"close")404— Conversation not found500— Internal server error
GET /api/conversations/[id]/stream
Open a Server-Sent Events (SSE) stream for real-time updates on a conversation. The connection stays open and pushes events as they occur.
Path parameters
id— Conversation ID
Query parameters
productSlug— requiredcustomerId— required
Response headers
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
Event types
connected— Sent immediately on connectionnew_comment— A new message was postedcomment_edited— A message was editedcomment_deleted— A message was deletedstate_update— Conversation opened/closedlabels_changed— Labels modified (status change)assignee_changed— Assignee modifiedissue_deleted— Conversation was deleted
Example SSE data
data: {"type":"connected","createdAt":"2026-02-25T10:00:00.000Z"}
data: {"type":"new_comment","data":{"id":"103","body":"We're looking into this.","author":"Support Agent","createdAt":"2026-02-25T10:02:00.000Z","isCustomer":false},"createdAt":"2026-02-25T10:02:00.000Z"}
data: {"type":"state_update","data":{"state":"closed"},"createdAt":"2026-02-25T10:10:00.000Z"}
Client example
const eventSource = new EventSource(
"https://support.premex.se/api/conversations/42/stream" +
"?productSlug=your-product-slug" +
"&customerId=cust_a1b2c3d4-e5f6-7890-abcd-ef1234567890"
);
eventSource.onmessage = (event) => {
const payload = JSON.parse(event.data);
switch (payload.type) {
case "connected":
console.log("SSE connected");
break;
case "new_comment":
appendMessage(payload.data);
break;
case "state_update":
updateConversationState(payload.data.state);
break;
}
};
eventSource.onerror = () => {
// The browser auto-reconnects with exponential backoff.
// Consider implementing manual reconnection for non-browser clients.
console.log("SSE connection lost, reconnecting...");
};
Reconnection: Browser EventSource handles reconnection automatically. For non-browser clients, implement exponential backoff starting at 1 second, doubling up to 30 seconds. After reconnecting, fetch the conversation detail to catch any events that were missed during the disconnection.
Errors
400— MissingproductSlugorcustomerId404— Conversation not found500— Internal server error
Message Metadata
Messages may contain hidden metadata embedded as HTML comments at the end of the body. This metadata is used internally for customer identity tracking and message deduplication.
Format
Message text here
<!-- cs:customerId=cust_abc123;clientMessageId=msg_def456 -->
The metadata block uses the <!-- cs:key=value;key=value --> format with URL-encoded values separated by semicolons.
Important: When displaying messages to end users, strip the metadata block. Look for the last occurrence of <!-- cs: and remove everything from that point to the closing -->, including any preceding whitespace. The built-in widget handles this automatically; you only need to strip metadata if building a custom client.
Deduplication
When posting messages, you can include an optional clientMessageId for safe retries and deduplication. This is especially important when using SSE, as the echo of your own message will arrive via the event stream.
Pattern
- Generate a unique
clientMessageIdper message (e.g.msg_<UUID>). - Include it in the POST request body.
- Show the message optimistically in the UI.
- When a
new_commentSSE event arrives, match it against theclientMessageIdto avoid showing duplicates. - If the POST fails, you can safely retry with the same
clientMessageId.
// Generate a client message ID
const clientMessageId = "msg_" + crypto.randomUUID();
// Include in the request
await fetch("/api/conversations/42/messages", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
productSlug: "your-product-slug",
customerId: "cust_...",
message: "My message",
clientMessageId,
}),
});
Error Handling
All error responses follow a consistent JSON format with an error field. Some errors include a requestId for debugging with the Premex Support team.
Error response format
{
"error": "Human-readable error description",
"requestId": "abc123" // included on 500 errors
}
Common HTTP status codes
200— Success400— Bad request (missing or invalid parameters)404— Resource not found (product, conversation)409— Conflict (e.g. posting to a closed conversation)500— Internal server error
Retry guidance: Requests that fail with 5xx status codes can be safely retried with exponential backoff. Use clientMessageId when posting messages to prevent duplicates on retry. Do not retry 4xx errors without fixing the request.
Rate Limiting
The API applies rate limiting to protect backend adapters. The default limit is 20 requests per second per product. Exceeding this limit may result in 429 Too Many Requests responses.
- Implement exponential backoff when receiving
429responses. - Batch operations where possible (e.g. avoid polling — use SSE instead).
- The rate limit window and maximum are configurable for self-hosted deployments.
Form Submissions
The built-in widget supports structured form types that produce markdown-formatted conversation bodies. You can replicate this in a custom client by constructing the subject and message fields in the same format when calling POST /api/conversations.
Support request (default)
// Subject: user-provided subject
// Body:
{
"productSlug": "your-product-slug",
"customerId": "cust_...",
"subject": "Cannot reset password",
"message": "**Name:** Jane Doe\n**Email:** jane@example.com\n\n---\n\nI tried the reset link but it expired."
}
Feature request
// Subject: [Feature] Dark mode support
// Body format:
{
"productSlug": "your-product-slug",
"customerId": "cust_...",
"subject": "[Feature] Dark mode support",
"message": "**Name:** Jane Doe\n**Email:** jane@example.com\n**Type:** Feature Request\n**Priority:** Important\n\n---\n\n### Problem or use case\nThe app is too bright at night.\n\n### Proposed solution\nAdd a toggle in settings for dark mode."
}
Bug report
// Subject: [Bug] Login button unresponsive
// Body format:
{
"productSlug": "your-product-slug",
"customerId": "cust_...",
"subject": "[Bug] Login button unresponsive",
"message": "**Name:** Jane Doe\n**Email:** jane@example.com\n**Type:** Bug Report\n**Environment:** Chrome 120, macOS 14\n\n---\n\n### Steps to reproduce\n1. Go to login page\n2. Enter credentials\n3. Click Login\n\n### Expected behavior\nUser should be logged in.\n\n### Actual behavior\nNothing happens when clicking the button."
}
Note: These body formats are conventions, not requirements. The API accepts any string as the message body. Using the structured format is recommended because it renders well in GitHub Issues and Slack threads, which are the default backend adapters.
Need help?
If you run into issues integrating with the API, include the requestId from error responses when contacting support. You can also use the DEBUG_WEBHOOKS=true environment variable on self-hosted deployments to enable detailed server-side logging.