> ## Documentation Index
> Fetch the complete documentation index at: https://docs.uppzy.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Playbook: Async Worker

> Use the Uppzy async chat endpoint from a background worker or queue-based application workflow.

This playbook is for applications that already process tasks through a queue or background worker.

Use async chat when:

* The caller should not wait for the final answer
* Your application has a job queue
* You want controlled polling and retry behavior
* You need to process longer-running assistant requests outside the web request cycle

Use sync chat when the caller needs the final answer immediately and the request volume is moderate.

## Flow

1. Web app creates a local job
2. Worker sends `POST /m2m/sites/{id}/chat/async`
3. Worker stores `request_id`
4. Worker polls `GET /m2m/sites/{id}/chat/requests/{requestId}`
5. Worker stores the completed answer or failure state
6. Application notifies the user through its own channel

## Job payload

Keep the queue payload minimal.

```json theme={null}
{
  "job_id": "job_48291",
  "session_id": "support_thread_48291",
  "message": "Can you summarize the refund policy?",
  "response_language": "en"
}
```

Do not put API keys in queue payloads. Workers should read API keys from protected runtime configuration.

## Worker example

This example uses placeholder queue functions. Replace them with your queue library.

```js theme={null}
import { uppzy } from "./uppzy-client.js";
import { getJob, markJobCompleted, markJobFailed, saveRequestId } from "./queue-client.js";

const SITE_ID = process.env.UPPZY_SITE_ID;

const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

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

  while (Date.now() - startedAt < timeoutMs) {
    const status = await uppzy(`/m2m/sites/${SITE_ID}/chat/requests/${requestId}`);

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

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

    await sleep(intervalMs);
  }

  throw new Error("Async chat polling timed out");
}

export async function processUppzyChatJob(jobId) {
  const job = await getJob(jobId);

  try {
    const request = await uppzy(`/m2m/sites/${SITE_ID}/chat/async`, {
      method: "POST",
      body: {
        session_id: job.session_id,
        message: job.message,
        response_language: job.response_language || "en",
      },
    });

    await saveRequestId(jobId, request.request_id);

    const result = await waitForRequest(request.request_id);

    await markJobCompleted(jobId, {
      session_id: result.session_id,
      request_id: result.request_id,
      answer: result.answer,
    });
  } catch (error) {
    await markJobFailed(jobId, {
      reason: error.message,
      retryable: error.status === 429 || error.status >= 500,
    });
  }
}
```

## Retry policy

Use retry only where it is safe:

* Retry `429` with backoff
* Retry `5xx` with backoff
* Do not automatically retry `401` or `403`
* Do not retry forever after polling timeout
* Keep a maximum retry count in your own queue system

## Polling policy

Start with a simple polling policy:

```js theme={null}
const pollingPolicy = {
  intervalMs: 2500,
  timeoutMs: 45000,
  maxQueueRetries: 3,
};
```

Increase the timeout only if your user experience and worker capacity can support it.

## Rollout checklist

* Keep API keys out of queue payloads
* Store `request_id` with the local job record
* Make retries bounded and visible
* Treat `401` and `403` as configuration incidents
* Track timeout rate after rollout
* Add feedback collection when users review the final answer
