tinyhumansai/openhuman · error
integrationId must be a 24-char hex id
Error message
integrationId must be a 24-char hex id
What it means
Thrown by fetch_integration_tokens_handoff when the integration id is empty or not exactly 24 chars after trimming. Backend integration ids are MongoDB ObjectIds (24 hex chars) and the id is interpolated into auth/integrations/{id}/tokens. Note the code only checks length — the "hex" in the message is not verified, so a 24-char non-hex string passes here and fails server-side.
Source
Thrown at src/api/rest.rs:879
let integrations = value
.get("integrations")
.cloned()
.unwrap_or_else(|| value.clone());
serde_json::from_value(integrations).context("parse integrations response")
}
/// Fetches the decrypted OAuth tokens for a specific integration.
///
/// This is a one-time handoff process. The encryption key must match the
/// one used by the backend to encrypt the tokens.
pub async fn fetch_integration_tokens_handoff(
&self,
integration_id: &str,
bearer_jwt: &str,
encryption_key: &str,
) -> Result<IntegrationTokensHandoff> {
let id = integration_id.trim();
anyhow::ensure!(
!id.is_empty() && id.len() == 24,
"integrationId must be a 24-char hex id"
);
let body = serde_json::json!({ "key": encryption_key.trim() });
let value = self
.authed_json(
bearer_jwt,
Method::POST,
&format!("auth/integrations/{id}/tokens"),
Some(body),
)
.await
.context("integration tokens handoff")?;
let encrypted = value
.get("encrypted")
.and_then(Value::as_str)
.context("integration tokens response missing encrypted payload")?;
let plaintext = decrypt_handoff_blob(encrypted, encryption_key.trim())?;View on GitHub (pinned to a221052e0d)
Solutions
- Use the id verbatim from list_integrations — that is the backend ObjectId
- If you hold your own ids, map them to the backend integration id before calling
- Validate 24-char length (and hex-ness, to be strict) at the boundary
Example fix
// before
let h = client.fetch_integration_tokens_handoff(&my_uuid, &jwt, &key).await?;
// after
let id = list_integrations(&jwt).await?
.into_iter().find(|i| i.provider == wanted)
.map(|i| i.id)
.ok_or_else(|| anyhow!("no integration for {wanted}"))?;
let h = client.fetch_integration_tokens_handoff(&id, &jwt, &key).await?; Defensive patterns
Strategy: validation
Validate before calling
let id = integration_id.trim();
anyhow::ensure!(id.len() == 24, "integration id must be a 24-char backend ObjectId, got {} chars", id.len());
let h = client.fetch_integration_tokens_handoff(id, &jwt, &key).await?; Type guard
fn is_backend_integration_id(s: &str) -> bool {
let t = s.trim();
t.len() == 24 && t.chars().all(|c| c.is_ascii_hexdigit())
} Prevention
- Always source integration ids from list_integrations, never synthesize them locally
- Keep your own ids in a separate typed field so they cannot be passed where ObjectIds are expected
When it happens
Trigger: Passing a UUID (32/36 chars), an internal numeric id, or a blank string as integration_id — e.g. mixing up your own record key with the backend's integration ObjectId.
Common situations: Ids from two systems conflated after a refactor, trailing newline/whitespace in ids read from CSV or clipboard, or a placeholder like "test" used in a scratch script.
Related errors
- integration id is required
- API base URL must be an absolute http(s) URL with host
- provider is required
- login token is required
- channel is required
AI-assisted analysis of tinyhumansai/openhuman@a221052e0d (2026-08-16).
Data as JSON: /api/errors/78d04800ae906058.
Report an issue: GitHub.