How MuapiClient Handles Asynchronous Generation Requests with Polling

The MuapiClient implements a two-step asynchronous pattern where it submits a generation task via POST to retrieve a request_id, then polls a result endpoint every 2 seconds until the server reports a terminal status such as completed, succeeded, or success.

The Open-Generative-AI repository by Anil-matcha provides a robust JavaScript client for the Muapi inference API that manages long-running generation workloads without blocking the execution thread. Understanding how MuapiClient handles asynchronous generation requests with polling is essential when building applications that generate images, videos, or other AI content through deferred processing pipelines.

Asynchronous Generation Architecture

The client abstracts complex inference workflows into a consistent two-step flow available in both class-based and functional forms. Every generation request follows the same pattern: an initial submission to <base>/api/v1/<endpoint> followed by repeated status checks against <base>/api/v1/predictions/<request_id>/result.

The Two-Step Submission Flow

  1. Task Submission: The client transmits the model payload (prompt, aspect ratio, seed, etc.) via POST and receives a response containing either a final result or a deferred request_id.
  2. Result Polling: Upon detecting a request_id, the client enters a polling loop that repeatedly queries the prediction endpoint until receiving a terminal status.

According to the source code in src/lib/muapi.js (lines 92-99) and packages/studio/src/muapi.js (lines 42-45), the client extracts response.request_id (falling back to response.id) immediately after submission. If this identifier exists, the client optionally invokes the onRequestId callback to expose the ID to the UI layer, then immediately initiates the polling sequence.

Class-Based vs Functional Implementation

The repository maintains two implementations with identical polling semantics:

  • Class-based client (src/lib/muapi.js): Operates as a singleton with methods like generateImage that encapsulate the entire flow.
  • Functional wrapper (packages/studio/src/muapi.js): Exposes composable functions submitAndPoll and pollForResult for server-side usage in Next.js API routes or Node.js backends.

Both versions delegate to pollForResult for the actual retry logic, but the functional version makes the polling parameters explicit at the call site, while the class version manages them through internal configuration defaults.

Polling Implementation Details

The core polling mechanism resides in the pollForResult function, which appears in both client variants with identical logic. This function manages the retry interval, status interpretation, and timeout boundaries.

Polling Loop and Terminal States

As implemented in src/lib/muapi.js (lines 117-129) and packages/studio/src/muapi.js (lines 6-27), the polling function executes the following logic:

// Conceptual representation based on packages/studio/src/muapi.js L6-L27
async function pollForResult(requestId, key, maxAttempts = 60, interval = 2000) {
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
    await new Promise(resolve => setTimeout(resolve, interval));
    
    const response = await fetch(
      `${base}/api/v1/predictions/${requestId}/result`,
      { headers: { 'Authorization': `Token ${key}` } }
    );
    const data = await response.json();
    
    if (['completed', 'succeeded', 'success'].includes(data.status)) {
      return data;
    }
    if (['failed', 'error'].includes(data.status)) {
      throw new Error('Generation failed');
    }
  }
  throw new Error('Generation timed out after polling.');
}

The loop continues until the server returns one of three success states (completed, succeeded, success) or two failure states (failed, error). All intermediate statuses (typically starting or processing) result in continued polling.

Configuration and Timeouts

Default polling parameters vary by generation type to accommodate different inference durations:

Parameter Images Default Videos/Long Jobs Default Description
maxAttempts 60 900 Maximum polling iterations before timeout
interval 2000 ms 2000 ms Delay between successive GET requests

These defaults translate to approximately 2 minutes for image generation and 30 minutes for video generation. The optional onRequestId callback fires before polling begins, allowing UI components to store the request identifier for progress indicators or cancellation workflows.

Code Examples

Using the Class-Based Client

The class-based implementation in src/lib/muapi.js (lines 27-71) provides a high-level API suitable for React components:

import { muapi } from '@/src/lib/muapi.js';

async function generatePortrait() {
  try {
    const result = await muapi.generateImage({
      model: 'sd-2',
      prompt: 'a futuristic portrait',
      aspect_ratio: '16:9',
      onRequestId: (id) => console.log('Request ID:', id)
    });
    console.log('Generated image URL:', result.url);
  } catch (e) {
    console.error('Generation failed:', e);
  }
}

The generateImage method constructs the model-specific payload and delegates to the internal polling mechanism defined at lines 117-129.

Using the Functional Wrapper

For server-side contexts such as Next.js API routes, the functional wrapper in packages/studio/src/muapi.js offers explicit control:

import { generateImage } from '@/packages/studio/src/muapi.js';

export async function POST(req) {
  const { model, prompt } = await req.json();
  const apiKey = process.env.MUAPI_KEY;

  const result = await generateImage(apiKey, {
    model,
    prompt,
    onRequestId: (id) => console.log('Muapi request:', id)
  });

  return new Response(JSON.stringify({ url: result.url }), { status: 200 });
}

Here, submitAndPoll (lines 30-48) handles the POST request and extracts the request_id before invoking pollForResult (lines 6-27).

Manual Polling with Custom Parameters

For advanced use cases requiring custom timeout behavior:

import { pollForResult } from '@/packages/studio/src/muapi.js';

async function waitForResult(requestId, apiKey) {
  // 120 attempts at 1.5s intervals = 3 minute timeout
  const data = await pollForResult(requestId, apiKey, 120, 1500);
  console.log('Final payload:', data);
}

Error Handling and Retry Logic

The client implements differentiated error handling for the submission versus polling phases. During the initial POST request, non-2xx responses raise immediate exceptions containing the first 100 characters of the error response body.

During polling, the client silently retries on 5xx server errors while propagating 4xx client errors immediately to the caller. If maxAttempts exhausts without reaching a terminal state, the client throws "Generation timed out after polling." This distinction prevents transient infrastructure issues from failing valid generation requests while ensuring that authentication or permission errors surface immediately.

Summary

  • The MuapiClient uses a two-step asynchronous pattern: submit via POST to receive a request_id, then poll via GET until completion.
  • Default polling occurs every 2 seconds for up to 60 attempts (images) or 900 attempts (videos), translating to approximately 2 minutes and 30 minutes respectively.
  • Two implementations exist: a class-based singleton in src/lib/muapi.js and a functional wrapper in packages/studio/src/muapi.js, both utilizing the same pollForResult logic.
  • The client recognizes three terminal success statuses (completed, succeeded, success) and two failure statuses (failed, error).
  • Silent retries apply only to 5xx errors during polling; 4xx errors and timeouts throw immediately.
  • The optional onRequestId callback exposes the request identifier before polling begins, enabling UI progress tracking and cancellation workflows.

Frequently Asked Questions

What is the default timeout for MuapiClient generation requests?

The default timeout varies by content type. Image generation uses 60 polling attempts at 2-second intervals (approximately 2 minutes), while video generation and long-running jobs use 900 attempts (approximately 30 minutes). These defaults are implemented in the generateImage method at packages/studio/src/muapi.js (lines 50-66) but can be overridden by calling pollForResult directly with custom maxAttempts and interval parameters.

How does MuapiClient handle server errors during polling?

According to the source code in src/lib/muapi.js (lines 117-129), the polling logic distinguishes between server (5xx) and client (4xx) errors. The implementation silently retries on 5xx responses, assuming transient infrastructure issues, while immediately throwing on 4xx errors or malformed JSON responses. This prevents temporary API outages from failing valid generation requests while ensuring authentication failures surface immediately.

Can I retrieve the request_id before the generation completes?

Yes. Both the class-based and functional implementations accept an optional onRequestId callback that fires immediately after the initial POST request returns. As implemented in packages/studio/src/muapi.js (lines 42-45), this callback receives the request_id as its sole argument, allowing you to store the identifier for progress tracking or cancellation logic while the pollForResult loop continues in the background.

What statuses indicate a completed generation in MuapiClient?

The client recognizes three terminal success statuses: completed, succeeded, and success. Any of these values in the polling response trigger an immediate return of the result data. Conversely, the statuses failed and error cause the client to throw a generation error. All other statuses indicate ongoing processing and continue the polling loop until maxAttempts exhausts.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →