ruvnet/ruflo · error

OpenAI API error: ${response.status} - ${error}

Error message

OpenAI API error: ${response.status} - ${error}

What it means

Inside callOpenAI(), a non-2xx response from POST to config.baseURL is read as text and thrown as 'OpenAI API error: <status> - <body>'. The surrounding retry loop re-attempts with exponential backoff (2^attempt * 100ms) and rethrows on the final attempt (maxRetries, default 3) — so whatever status surfaces has already been retried, including non-retryable 4xx.

Source

Thrown at v3/@claude-flow/embeddings/src/embedding-service.ts:348

        const response = await fetch(this.baseURL, {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
            Authorization: `Bearer ${this.apiKey}`,
          },
          body: JSON.stringify({
            model: this.model,
            input: texts,
            dimensions: config.dimensions,
          }),
          signal: controller.signal,
        });

        clearTimeout(timeoutId);

        if (!response.ok) {
          const error = await response.text();
          throw new Error(`OpenAI API error: ${response.status} - ${error}`);
        }

        return await response.json() as {
          data: Array<{ embedding: number[] }>;
          usage?: { prompt_tokens: number; total_tokens: number };
        };
      } catch (error) {
        if (attempt === this.maxRetries - 1) {
          throw error;
        }
        // Exponential backoff
        await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 100));
      }
    }

    throw new Error('Max retries exceeded');
  }
}

View on GitHub (pinned to fa13ee4ad6)

Solutions

  1. Map the status: 401 → fix config.apiKey; 429 → batch smaller, throttle, or raise config.maxRetries; 400 → check model/dimensions compatibility; 404 → verify config.baseURL and model name
  2. For 429s, add client-side throttling between embedBatch calls rather than relying on the built-in 3 retries
  3. Inspect the body text in the message — the provider error JSON names the offending parameter (e.g. invalid_request_error with the field)

Example fix

// before
const svc = new OpenAIEmbeddingService({ apiKey, model: 'text-embedding-ada-003' });
await svc.embedBatch(texts); // OpenAI API error: 404 - model not found

// after
const svc = new OpenAIEmbeddingService({ apiKey, model: 'text-embedding-3-small' });
await svc.embedBatch(texts);
Defensive patterns

Strategy: retry

Validate before calling

function classifyOpenAiStatus(message: string): 'auth' | 'rate' | 'request' | 'notfound' | 'unknown' {
  const m = message.match(/OpenAI API error: (\d{3})/);
  if (!m) return 'unknown';
  return { 401: 'auth', 403: 'auth', 429: 'rate', 400: 'request', 404: 'notfound' }[m[1]] ?? 'unknown';
}

Try / catch

for (let attempt = 0; attempt < 5; attempt++) {
  try {
    return await svc.embedBatch(texts);
  } catch (e) {
    const kind = classifyOpenAiStatus(e instanceof Error ? e.message : '');
    if (kind === 'auth' || kind === 'request') throw e;        // do not retry client errors
    if (attempt === 4) throw e;                                  // retries already done in-library
    await new Promise(r => setTimeout(r, 2 ** attempt * 500));   // extra backoff for 429/5xx
  }
}

Prevention

When it happens

Trigger: 401 invalid apiKey; 429 rate limit or quota exhausted; 400 invalid request (e.g. dimensions unsupported by the chosen model); 404 wrong baseURL path or model name; embedBatch() sending many uncached texts in one payload hitting size/rate limits.

Common situations: Missing/expired OpenAI key; bursty embedBatch calls hitting org rate limits; using the dimensions option with a model that does not support it; pointing baseURL at an Azure or proxy endpoint with a different path shape.

Related errors


AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18). Data as JSON: /api/errors/56c63833599df643. Report an issue: GitHub.