Skip to main content
This guide maps the five M2M chat endpoints to real integration patterns. Use it to understand which endpoint to call when.

Quick reference

EndpointMethodPurposeResponse TimeUse When
Chat (Sync)POST /m2m/sites/{id}/chatSend message and get answer immediately5-60sCaller needs immediate answer
Chat (Async)POST /m2m/sites/{id}/chat/asyncSubmit message for background processing100msCaller should not block
Chat StatusGET /m2m/sites/{id}/chat/requests/{requestId}Check status of async request10msPolling async completion
Chat FeedbackPOST /m2m/sites/{id}/chat/feedbackRate the response quality100msImproving answer quality
Session ListGET /m2m/sites/{id}/sessionsList user conversations100msTracking sessions
Session ClosePOST /m2m/sites/{id}/chat/session/closeMark session as closed100msEnding conversation

Pattern 1: Synchronous chat (blocking)

Use when the user is waiting for the answer.
User sends message

POST /m2m/sites/{id}/chat

[Assistant processes for 5-60 seconds]

Return answer + confidence immediately
Typical latency: 5-30 seconds. Simple answers faster, complex queries slower. Example: Customer support chatbot in a web form.
async function answerQuestion(siteId, userQuestion, userEmail) {
  const response = await fetch(
    `https://api.uppzy.com/api/v1/m2m/sites/${siteId}/chat`,
    {
      method: "POST",
      headers: {
        "X-API-Key": process.env.UPPZY_API_KEY,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        session_id: `user-${userEmail}`,
        email: userEmail,
        message: userQuestion,
      }),
    }
  );

  const data = await response.json();
  return data.answer;
}

Pattern 2: Asynchronous chat (non-blocking)

Use when you have a job queue or background processing system.
User sends message

POST /m2m/sites/{id}/chat/async

Return immediately with request_id (100ms)

[Processing continues in background]

App polls GET /m2m/sites/{id}/chat/requests/{requestId}

When status changes to 'completed', deliver answer
Typical latency: 100ms submit + 5-30 seconds processing + polling. Example: Background job processor, message queue consumer, webhook receiver.
async function processAsyncChat(siteId, sessionId, message, email) {
  // Step 1: Submit the request (returns immediately)
  const submitRes = await fetch(
    `https://api.uppzy.com/api/v1/m2m/sites/${siteId}/chat/async`,
    {
      method: "POST",
      headers: {
        "X-API-Key": process.env.UPPZY_API_KEY,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ session_id: sessionId, message, email }),
    }
  );

  const submitted = await submitRes.json();
  const requestId = submitted.request_id;

  // Step 2: Poll for completion (every 1 second, up to 2 minutes)
  let answer = null;
  for (let i = 0; i < 120; i++) {
    const statusRes = await fetch(
      `https://api.uppzy.com/api/v1/m2m/sites/${siteId}/chat/requests/${requestId}`,
      {
        headers: { "X-API-Key": process.env.UPPZY_API_KEY },
      }
    );

    const status = await statusRes.json();
    if (status.status === "completed") {
      answer = status.answer;
      break;
    }
    if (status.status === "failed") {
      throw new Error(`Chat failed: ${status.error}`);
    }

    // Wait 1 second before next poll
    await new Promise((r) => setTimeout(r, 1000));
  }

  if (!answer) throw new Error("Chat polling timed out");
  return answer;
}

Pattern 3: Collect feedback (quality monitoring)

Use after each completed response to track answer quality.
Response shown to user

User rates: "good" or "bad"

POST /m2m/sites/{id}/chat/feedback

Feedback stored in dashboard
Example: Chat widget with thumbs up/down buttons.
async function submitFeedback(siteId, sessionId, requestId, isSatisfied) {
  const feedback = isSatisfied ? "good" : "bad";

  const response = await fetch(
    `https://api.uppzy.com/api/v1/m2m/sites/${siteId}/chat/feedback`,
    {
      method: "POST",
      headers: {
        "X-API-Key": process.env.UPPZY_API_KEY,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        session_id: sessionId,
        request_id: requestId,
        feedback,
      }),
    }
  );

  return response.json();
}

Pattern 4: Retrieve conversation history

Use to look up past sessions for a user.
App needs to find user's conversations

GET /m2m/sites/{id}/sessions?email=user@example.com

Returns: [{ id, created_at, message_count, ... }]

App can display conversation list or resume
Example: Multi-session support ticket system.
async function getUserSessions(siteId, userEmail) {
  const response = await fetch(
    `https://api.uppzy.com/api/v1/m2m/sites/${siteId}/sessions?email=${encodeURIComponent(userEmail)}`,
    {
      headers: { "X-API-Key": process.env.UPPZY_API_KEY },
    }
  );

  const data = await response.json();

  // Find most recent active session
  const activeSession = data.sessions
    .filter((s) => s.closed_at === null)
    .sort((a, b) => new Date(b.updated_at) - new Date(a.updated_at))[0];

  return { allSessions: data.sessions, activeSession };
}

Pattern 5: Close sessions explicitly

Use when a conversation ends (user resolution, timeout, etc.).
Conversation ends (user resolved issue, or timeout)

POST /m2m/sites/{id}/chat/session/close

Session marked as closed in dashboard

If user returns, create new session for fresh context
Example: Support ticket closure, conversation timeout.
async function closeConversation(siteId, sessionId) {
  const response = await fetch(
    `https://api.uppzy.com/api/v1/m2m/sites/${siteId}/chat/session/close`,
    {
      method: "POST",
      headers: {
        "X-API-Key": process.env.UPPZY_API_KEY,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ session_id: sessionId }),
    }
  );

  return response.json();
}

Session strategy

Before using any chat endpoint, define your session_id strategy:

Option A: Per-user sessions

Best for chat applications where one user = one long conversation.
const sessionId = `user-${userId}`;
Characteristics:
  • One session per user account
  • Conversation history grows over time
  • Follow-up questions maintain context
  • Close explicitly when user logs out

Option B: Per-thread sessions

Best for ticket or thread-based systems.
const sessionId = `ticket-${ticketId}`;
Characteristics:
  • One session per support ticket
  • Clear conversation boundaries
  • Easy to archive
  • Close when ticket resolved

Option C: Per-channel sessions

Best for multi-channel integrations (email, slack, etc.).
const sessionId = `slack-user-${userId}-${channelId}`;
Characteristics:
  • One session per channel + user combo
  • Separate context per channel
  • Can close after inactivity
  • Useful for omnichannel setups

Request/response flow

Sync chat flow

REQUEST:
  POST /m2m/sites/{siteId}/chat
  {
    "session_id": "user-123",
    "message": "How do I return an item?",
    "email": "user@example.com"
  }

RESPONSE (after 5-60 seconds):
  {
    "session_id": "user-123",
    "request_id": "req_xyz...",
    "answer": "Returns are accepted within 30 days...",
    "confidence_score": 92,
    "confidence_level": "high"
  }

Async chat flow

REQUEST 1:
  POST /m2m/sites/{siteId}/chat/async
  { "session_id": "...", "message": "..." }

RESPONSE 1 (100ms):
  {
    "request_id": "req_xyz...",
    "status": "pending"
  }

REQUEST 2 (after waiting):
  GET /m2m/sites/{siteId}/chat/requests/req_xyz...

RESPONSE 2 (when complete):
  {
    "request_id": "req_xyz...",
    "status": "completed",
    "answer": "Returns are accepted within 30 days..."
  }

Feedback flow

REQUEST:
  POST /m2m/sites/{siteId}/chat/feedback
  {
    "session_id": "user-123",
    "request_id": "req_xyz...",
    "feedback": "good"
  }

RESPONSE:
  {
    "status": "ok",
    "request_id": "req_xyz...",
    "feedback": "good",
    "feedback_submitted_at": "2024-06-13T14:32:45Z"
  }

Common patterns

Pattern: “Sync with fallback”

Try sync chat first, fall back to a template answer if it times out.
async function askWithFallback(siteId, question, userEmail) {
  try {
    // Try sync first
    const response = await fetch(
      `https://api.uppzy.com/api/v1/m2m/sites/${siteId}/chat`,
      {
        method: "POST",
        headers: {
          "X-API-Key": process.env.UPPZY_API_KEY,
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          session_id: `user-${userEmail}`,
          email: userEmail,
          message: question,
        }),
        signal: AbortSignal.timeout(15000), // 15 second timeout
      }
    );

    if (response.ok) {
      const data = await response.json();
      return data.answer;
    }
  } catch (err) {
    // Timeout or error occurred
  }

  // Fallback
  return "Thank you for your question. Our team will respond shortly.";
}

Pattern: “Async with webhook”

Submit async chat and notify via webhook when complete.
// Submit async request
const submitRes = await fetch(
  `https://api.uppzy.com/api/v1/m2m/sites/${siteId}/chat/async`,
  { method: "POST", ... }
);
const { request_id } = await submitRes.json();

// Store request_id in database
await db.insert("pending_chats", {
  request_id,
  user_id: userId,
  webhook_url: "https://myapp.com/api/chat-complete",
  created_at: new Date(),
});

// Background job polls and sends webhook
setInterval(async () => {
  const pendingChats = await db.query(
    "SELECT * FROM pending_chats WHERE completed_at IS NULL"
  );

  for (const chat of pendingChats) {
    const statusRes = await fetch(
      `https://api.uppzy.com/api/v1/m2m/sites/${siteId}/chat/requests/${chat.request_id}`,
      { headers: { "X-API-Key": API_KEY } }
    );

    const status = await statusRes.json();
    if (status.status === "completed") {
      // Send webhook
      await fetch(chat.webhook_url, {
        method: "POST",
        body: JSON.stringify({
          request_id: chat.request_id,
          answer: status.answer,
        }),
      });

      // Mark as completed
      await db.update("pending_chats", { completed_at: new Date() });
    }
  }
}, 2000); // Poll every 2 seconds

Error handling checklist

✓ Treat 400 as a payload error (fix request)
✓ Treat 401 as auth failure (check key, rotation)
✓ Treat 403 as access denied (check plan/permissions)
✓ Treat 404 as resource not found (check IDs)
✓ Retry 429 and 5xx with exponential backoff
✓ Don’t retry 4xx (except 429) without fixing the request
✓ Async polling has an overall timeout
✓ User-facing errors are safe and don’t expose API details
See Error Handling Catalog for complete guidance.

Production readiness

Before deploying chat integrations: ✓ All HTTP calls have timeouts
✓ Retries are bounded and selective
✓ Session IDs are stable across requests
✓ Feedback collection is wired correctly
✓ Async polling respects timeouts
✓ Monitoring and alerting are configured
✓ Logs do not contain API keys
✓ User-facing errors are safe
✓ Staging smoke test passed