Skip to main content
Use this page to standardize how your M2M client handles non-2xx responses, network failures, polling timeouts, and user-facing fallbacks. The API Reference shows endpoint-level response schemas. This page focuses on client behavior.

Error response model

For failed requests, treat the HTTP status code as the primary contract. The response body may include an error field or a small JSON object with details:
{
  "error": "invalid payload"
}
Do not build production logic around exact error text. Exact wording can change. Use:
  • HTTP status
  • endpoint name
  • request context you own
  • retry count
  • latency
  • request_id when the response includes one

Status code handling

StatusMeaningClient behavior
400Invalid request payload, missing field, or malformed inputFix request construction. Do not retry without changing the request.
401Missing or invalid authenticationTreat as a configuration or credential incident. Do not retry repeatedly.
403Valid identity but action is not allowedCheck plan, tenant access, site access, or allowed capability. Do not blind-retry.
404Tenant, site, request, or document was not found in the requested scopeCheck identifiers and ownership. Do not blind-retry.
409Conflict, when returned by an endpointCheck whether the resource already exists or whether the operation is duplicated.
429Too many requestsRetry with bounded exponential backoff and jitter.
5xxTemporary server-side or upstream failureRetry with bounded exponential backoff for safely repeatable operations.

Retry decision table

OperationRetry on 429Retry on 5xxRetry on timeoutNotes
Read tenant or site informationYesYesYesSafe to retry with backoff.
Read sessions or statisticsYesYesYesSafe to retry with backoff.
Create text or Q&A documentYes, boundedYes, boundedUse careAvoid creating duplicates in your own sync process.
Upload file documentYes, boundedYes, boundedUse careKeep a source-to-document sync log in your system.
Sync chatYes, boundedYes, boundedUse careThe user may have already waited; show a fallback on final failure.
Async chat submitYes, boundedYes, boundedUse careStore local job state and avoid duplicate user notifications.
Async chat pollingYesYesStop at timeout budgetPolling should never run forever.
Feedback submissionYes, boundedYes, boundedUse careAvoid duplicate feedback events from the same UI event.
Session closeYes, boundedYes, boundedYesSafe if your app treats close as idempotent at the workflow level.

JavaScript error class

Use a custom error type so application code can make decisions without parsing strings.
export class UppzyApiError extends Error {
  constructor(message, { status, endpoint, details, retryable }) {
    super(message);
    this.name = "UppzyApiError";
    this.status = status;
    this.endpoint = endpoint;
    this.details = details;
    this.retryable = retryable;
  }
}

export function isRetryableStatus(status) {
  return status === 429 || (status >= 500 && status <= 599);
}

JavaScript client with timeout and retry

This example retries only retryable failures and applies a total timeout per request attempt.
import { UppzyApiError, isRetryableStatus } from "./uppzy-errors.js";

const BASE_URL = process.env.UPPZY_BASE_URL || "https://api.uppzy.com/api/v1";
const API_KEY = process.env.UPPZY_API_KEY;

function sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

function backoffDelay(attempt) {
  const baseMs = 300;
  const maxMs = 4000;
  const jitterMs = Math.floor(Math.random() * 250);
  return Math.min(maxMs, baseMs * 2 ** attempt) + jitterMs;
}

export async function uppzyRequest(path, {
  method = "GET",
  body,
  timeoutMs = 15000,
  maxRetries = 2,
} = {}) {
  for (let attempt = 0; attempt <= maxRetries; attempt += 1) {
    const controller = new AbortController();
    const timeout = setTimeout(() => controller.abort(), timeoutMs);

    try {
      const response = await fetch(`${BASE_URL}${path}`, {
        method,
        signal: controller.signal,
        headers: {
          "X-API-Key": API_KEY,
          "Content-Type": "application/json",
        },
        body: body ? JSON.stringify(body) : undefined,
      });

      const text = await response.text();
      const data = text ? JSON.parse(text) : null;

      if (response.ok) {
        return data;
      }

      const retryable = isRetryableStatus(response.status);

      if (!retryable || attempt === maxRetries) {
        throw new UppzyApiError(`Uppzy API returned HTTP ${response.status}`, {
          status: response.status,
          endpoint: path,
          details: data,
          retryable,
        });
      }

      await sleep(backoffDelay(attempt));
    } catch (error) {
      if (error instanceof UppzyApiError) {
        throw error;
      }

      const isAbort = error.name === "AbortError";
      const canRetry = isAbort && attempt < maxRetries;

      if (!canRetry) {
        throw new UppzyApiError(isAbort ? "Uppzy API request timed out" : "Uppzy API request failed", {
          status: null,
          endpoint: path,
          details: null,
          retryable: isAbort,
        });
      }

      await sleep(backoffDelay(attempt));
    } finally {
      clearTimeout(timeout);
    }
  }
}

Safe user-facing fallback

Do not expose raw API errors to end users. Map integration failures to a small set of safe messages in your product.
export function userFacingAssistantError(error) {
  if (error.status === 401 || error.status === 403) {
    return "The assistant is temporarily unavailable.";
  }

  if (error.status === 404) {
    return "The assistant could not find the requested support context.";
  }

  if (error.status === 429 || error.retryable) {
    return "The assistant is taking longer than expected. Please try again shortly.";
  }

  return "The assistant could not complete the request.";
}
Keep detailed error information in server-side logs only.

Async polling failure handling

For async chat, distinguish submit failures from polling failures.
import { uppzyRequest } from "./uppzy-request.js";

const SITE_ID = process.env.UPPZY_SITE_ID;

export async function waitForAsyncAnswer(requestId, {
  intervalMs = 2500,
  timeoutMs = 45000,
} = {}) {
  const startedAt = Date.now();

  while (Date.now() - startedAt < timeoutMs) {
    const result = await uppzyRequest(`/m2m/sites/${SITE_ID}/chat/requests/${requestId}`, {
      method: "GET",
      maxRetries: 1,
    });

    if (result.status === "completed") {
      return result;
    }

    if (result.status === "failed") {
      throw new Error(result.error || "Async chat failed");
    }

    await new Promise((resolve) => setTimeout(resolve, intervalMs));
  }

  throw new Error("Async chat polling timed out");
}
Recommended behavior after polling timeout:
  • Mark the local job as timed out
  • Store the request_id
  • Avoid endless polling
  • Let the user retry through your own product flow
  • Review timeout frequency in monitoring

Logging checklist

Log enough to debug without leaking sensitive data. Recommended fields:
  • Integration name
  • Environment
  • Endpoint path template
  • HTTP status
  • Tenant ID and site ID
  • Session ID
  • Request ID when available
  • Retry count
  • Latency
  • Timeout flag
Do not log:
  • API keys
  • Authorization headers
  • Full secret configuration
  • Raw private customer records
  • Full document content in normal production logs

Incident routing

Use these routing rules when alerts fire:
  • Repeated 401: check key rotation, deployment configuration, and revoked keys
  • Repeated 403: check plan level, tenant access, site access, and enabled capability
  • Repeated 404: check configured tenant_id, site_id, request_id, or document ID
  • Repeated 429: reduce concurrency, add backoff, and inspect traffic spikes
  • Repeated 5xx: keep bounded retries, monitor recovery, and pause non-critical bulk jobs if needed
  • Polling timeouts: check async job volume, user timeout budget, and worker concurrency

Production checklist

  • All HTTP calls have timeout settings
  • Retries are bounded
  • Retryable and non-retryable failures are separated
  • 401 and 403 create operational alerts instead of retry storms
  • Async polling has an overall timeout
  • User-facing errors are safe and non-technical
  • Server logs exclude API keys and sensitive customer records
  • Bulk document sync jobs can pause when error rate increases