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)

javascript
// Generate once and persist (e.g. localStorage, Keychain, SharedPreferences)
const customerId = "cust_" + crypto.randomUUID();

2. Create a conversation

bash
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

bash
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

json
{
  "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

json
{
  "conversationId": "42",
  "createdAt": "2026-02-25T10:00:00.000Z"
}

Errors

  • 400 — Missing required fields (productSlug, message, or customerId)
  • 404 — Product not found for the given slug
  • 500 — Internal server error (includes requestId for 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 — required
  • customerId — required

Response 200

json
{
  "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 triaged
  • waiting-for-agent — Customer replied, needs agent attention
  • waiting-for-customer — Agent replied, waiting for customer
  • resolved — Marked as resolved
  • closed — Closed

Errors

  • 400 — Missing required parameters
  • 500 — 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 — required
  • customerId — required

Response 200 (found)

json
{
  "found": true,
  "conversationId": "42"
}

Response 200 (not found)

json
{
  "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 parameters
  • 404 — Product not found
  • 500 — 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 — required
  • customerId — required

Response 200

json
{
  "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 — Missing productSlug or customerId
  • 404 — 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

json
{
  "productSlug": "your-product-slug",   // required
  "customerId": "cust_...",             // required
  "message": "Here is more info...",    // required
  "clientMessageId": "msg_abc123"       // optional — for deduplication
}

Response 200

json
{
  "id": "103",
  "createdAt": "2026-02-25T10:10:00.000Z"
}

Errors

  • 400 — Missing required fields (message, productSlug, or customerId)
  • 404 — Conversation not found
  • 409 — 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

json
{
  "productSlug": "your-product-slug",   // required
  "customerId": "cust_...",             // required
  "action": "resolve"                   // required — "resolve" or "close"
}

Response 200 (resolve)

json
{
  "success": true,
  "message": "Conversation marked as resolved"
}

Response 200 (close)

json
{
  "success": true,
  "message": "Conversation closed"
}

Errors

  • 400 — Missing required parameters or invalid action (must be "resolve" or "close")
  • 404 — Conversation not found
  • 500 — 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 — required
  • customerId — required

Response headers

Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive

Event types

  • connected — Sent immediately on connection
  • new_comment — A new message was posted
  • comment_edited — A message was edited
  • comment_deleted — A message was deleted
  • state_update — Conversation opened/closed
  • labels_changed — Labels modified (status change)
  • assignee_changed — Assignee modified
  • issue_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

javascript
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 — Missing productSlug or customerId
  • 404 — Conversation not found
  • 500 — 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

  1. Generate a unique clientMessageId per message (e.g. msg_<UUID>).
  2. Include it in the POST request body.
  3. Show the message optimistically in the UI.
  4. When a new_comment SSE event arrives, match it against the clientMessageId to avoid showing duplicates.
  5. If the POST fails, you can safely retry with the same clientMessageId.
javascript
// 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

json
{
  "error": "Human-readable error description",
  "requestId": "abc123"    // included on 500 errors
}

Common HTTP status codes

  • 200 — Success
  • 400 — 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 429 responses.
  • 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)

json
// 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

json
// 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

json
// 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.