dani-garcia/vaultwarden · error
Error creating organization API Key
Error message
Error creating organization API Key
What it means
A Rust .expect() panic raised when inserting a brand-new OrganizationApiKey row fails: the else-branch at organizations.rs:3221-3225 runs when no organization_api_key row exists yet for the org (first-time API key retrieval), generates a key, and .save() returns Err. Reached via POST /organizations/<org_id>/api-key (rotate=false) after admin auth and password/OTP validation succeed. The panic surfaces to the client as an HTTP 500 with no useful body.
Source
Thrown at src/api/core/organizations.rs:3224
err!("Organization not found", "Organization id's do not match");
}
let data: PasswordOrOtpData = data.into_inner();
let user = headers.user;
// Validate the admin users password/otp
data.validate(&user, true, &conn).await?;
let org_api_key = if let Some(mut org_api_key) = OrganizationApiKey::find_by_org_uuid(org_id, &conn).await {
if rotate {
org_api_key.api_key = crate::crypto::generate_api_key();
org_api_key.revision_date = chrono::Utc::now().naive_utc();
org_api_key.save(&conn).await.expect("Error rotating organization API Key");
}
org_api_key
} else {
let api_key = crate::crypto::generate_api_key();
let new_org_api_key = OrganizationApiKey::new(org_id.clone(), api_key);
new_org_api_key.save(&conn).await.expect("Error creating organization API Key");
new_org_api_key
};
Ok(Json(json!({
"apiKey": org_api_key.api_key,
"revisionDate": crate::util::format_date(&org_api_key.revision_date),
"object": "apiKey",
})))
}
#[post("/organizations/<org_id>/api-key", data = "<data>")]
async fn post_api_key(
org_id: OrganizationId,
data: Json<PasswordOrOtpData>,
headers: AdminHeaders,
conn: DbConn,
) -> JsonResult {
api_key(&org_id, data, false, headers, conn).awaitView on GitHub (pinned to 0cefa4cca7)
Solutions
- Read the server log for the underlying diesel error — 'no such table organization_api_key' means migrations are missing; 'readonly database' means file/mount permissions; a constraint error means a race or duplicate row.
- Run/verify database migrations for the deployed version so the organization_api_key table and its constraints exist and match the model.
- Fix storage access: ensure the data directory and SQLite file are writable by the service user (chmod/chown, correct Docker volume mount), or confirm the external DB grants INSERT.
- Convert the .expect() into error propagation so first-time key creation failures return a proper error response instead of a panic, and consider handling the unique-race by re-fetching the row on conflict.
Example fix
// before
let new_org_api_key = OrganizationApiKey::new(org_id.clone(), api_key);
new_org_api_key.save(&conn).await.expect("Error creating organization API Key");
new_org_api_key
// after (propagate as an API error instead of panicking)
let new_org_api_key = OrganizationApiKey::new(org_id.clone(), api_key);
new_org_api_key.save(&conn).await.map_err(|e| {
error!("Error creating organization API key for {}: {:#?}", org_id, e);
Error::new("Error creating organization API key", "Failed to save the new API key")
})?;
new_org_api_key Defensive patterns
Strategy: validation
Validate before calling
-- Pre-check that first-time key creation can succeed -- 1) table exists and is writable SELECT count(*) FROM organization_api_key; -- 2) no pre-existing row for this org (else the rotate path applies) SELECT * FROM organization_api_key WHERE org_uuid = '<org_id>'; -- 3) (SQLite) file and directory writable by the service user -- ls -l <data_dir>/db.sqlite3 && test -w <data_dir>
Try / catch
// Recommended: convert the panic into a returned error; on a unique-constraint
// race (two first-time requests), re-fetch the existing row instead of failing.
if let Err(e) = new_org_api_key.save(&conn).await {
if let Some(existing) = OrganizationApiKey::find_by_org_uuid(org_id, &conn).await {
return Ok(Json(json!({ "apiKey": existing.api_key, "revisionDate": crate::util::format_date(&existing.revision_date), "object": "apiKey" })));
}
return Err(Error::new("Error creating organization API key", "Database write failed"));
} Prevention
- Apply all migrations when upgrading so organization_api_key exists before any client requests an API key.
- Ensure the service account has INSERT privilege on the table (or write access to the SQLite file and its directory).
- Avoid concurrent first-time API-key requests from multiple clients — they race on the same insert.
- Prefer explicit error propagation over .expect() in request handlers so failures return structured 500s and keep the worker alive.
When it happens
Trigger: An org admin calls POST /organizations/<org_id>/api-key with valid credentials for an organization that has never generated an API key (find_by_org_uuid returns None), and the INSERT fails: missing organization_api_key table (migrations never run), unique-constraint violation on org_uuid from a concurrent first-time request racing to insert, DB connection loss, or a read-only/full disk.
Common situations: Upgraded instance where migrations were skipped so the table/columns do not exist; SQLite data directory mounted read-only or with wrong ownership in Docker; two clients requesting the key simultaneously on first generation; Postgres/MySQL out of disk or with revoked INSERT privileges; schema drift between the running binary and the database.
Related errors
- Error saving API key
- Error rotating organization API Key
- Error saving attachment
- Error updating attachment
- Grantee user should exist but does not!
AI-assisted analysis of dani-garcia/vaultwarden@0cefa4cca7 (2026-08-16).
Data as JSON: /api/errors/451c751fcc9a6420.
Report an issue: GitHub.