stalwartlabs/stalwart · error · trc::Error(AiEvent::ApiError)
OpenAPI request failed
Error message
OpenAPI request failed
What it means
The enterprise LLM (OpenAI-compatible API) client wraps any failure from its underlying POST (post_api) into an AiEvent::ApiError with details "OpenAPI request failed", preserving the original error as reason. It signals that the prompt could not be completed by the remote API.
Source
Thrown at crates/common/src/enterprise/llm.rs:94
pub model: String,
pub choices: Vec<TextCompletionChoice>,
}
#[derive(Deserialize, Debug)]
pub struct TextCompletionChoice {
pub index: i32,
pub finish_reason: String,
pub text: String,
}
impl AiApiConfig {
pub async fn send_request(
&self,
prompt: impl Into<String>,
temperature: Option<f64>,
) -> trc::Result<String> {
self.post_api(prompt, temperature).await.map_err(|err| {
trc::Error::new(trc::EventType::Ai(trc::AiEvent::ApiError))
.id(self.id.clone())
.details("OpenAPI request failed")
.reason(err)
})
}
async fn post_api(
&self,
prompt: impl Into<String>,
temperature: Option<f64>,
) -> Result<String, String> {
// Serialize body
let body = match self.api_type {
ApiType::ChatCompletion => serde_json::to_string(&ChatCompletionRequest {
model: self.model.to_string(),
messages: vec![Message {
role: "user".to_string(),
content: prompt.into(),View on GitHub (pinned to e962003857)
Solutions
- Inspect err.reason() for the underlying cause (HTTP status, connect error).
- Verify the API key and endpoint configuration for the LLM integration.
- Test connectivity to the endpoint with curl from the server host.
- Check quota/billing on the API provider account.
- Add retry with backoff for transient 429/5xx responses.
Example fix
// before (config) [enterprise.llm] api-key = "" // empty // after [enterprise.llm] api-key = "sk-..." url = "https://api.openai.com/v1/chat/completions"
Defensive patterns
Strategy: try-catch
Validate before calling
// before calling, verify config is complete
if llm.api_key.is_empty() || llm.url.is_empty() { return Err(anyhow!("LLM not configured")); } Try / catch
match llm.send_request(prompt, None).await {
Ok(reply) => reply,
Err(err) => { log::error!("LLM call failed: {:?} reason={:?}", err, err.reason()); fallback_response() },
} Prevention
- Validate API key and endpoint config at startup, not per-call.
- Pre-flight the endpoint with a cheap request during health checks.
- Retry with backoff on 429/5xx; fail fast on 401/403.
- Monitor provider quota and billing alerts.
When it happens
Trigger: Calling `send_request` when post_api fails: invalid/missing API key (401), unreachable host, timeout, quota exceeded, or malformed response from the OpenAI-compatible endpoint.
Common situations: Missing or expired OPENAI_API_KEY; enterprise LLM feature misconfigured (wrong base URL); network egress blocked; account out of credits.
Related errors
- StoreEvent::HttpStoreError
- unwrap_tls called on non-TLS acceptor
- Cluster::PublisherError
- Cluster::PublisherError
- Unspecified
AI-assisted analysis of stalwartlabs/stalwart@e962003857 (2026-09-06).
Data as JSON: /api/errors/abdf1ee77ccee63e.
Report an issue: GitHub.