clockworklabs/SpacetimeDB · error · Unauthorized

{} is not authorized to perform action{}: {}

Error message

{} is not authorized to perform action{}: {}

What it means

The client API's authorization failure: the authenticated identity (subject) was denied permission for the requested Action on the given database. It maps to HTTP 403 Forbidden with this rendered message; the code documents that 401 is used only for missing or invalid credentials, so this 403 means 'valid identity, insufficient permission'.

Source

Thrown at crates/client-api/src/lib.rs:549

        database.map(|ident| format!(" on database {ident}")).unwrap_or_default(),
        action
    )]
    Unauthorized {
        subject: Identity,
        action: Action,
        // `Option` for future, non-database-bound actions.
        database: Option<Identity>,
        #[source]
        source: Option<anyhow::Error>,
    },
    #[error("authorization failed due to internal error")]
    InternalError(#[from] anyhow::Error),
}

impl axum::response::IntoResponse for Unauthorized {
    fn into_response(self) -> axum::response::Response {
        let (status, e) = match self {
            unauthorized @ Self::Unauthorized { .. } => (StatusCode::FORBIDDEN, anyhow!(unauthorized)),
            Self::InternalError(e) => {
                log::error!("internal error: {e:#}");
                (StatusCode::INTERNAL_SERVER_ERROR, e)
            }
        };

        (status, format!("{e:#}")).into_response()
    }
}

/// Action to be authorized via [Authorization::authorize_action].
#[derive(Clone, Copy, Debug)]
pub enum Action {
    CreateDatabase {
        parent: Option<Identity>,
        organization: Option<Identity>,
    },
    UpdateDatabase,

View on GitHub (pinned to 524b4487d9)

Solutions

  1. Check who you are on that server: `spacetime identity show` / `spacetime server whoami` against the right --server.
  2. Switch to the owning identity: `spacetime identity switch <identity-or-email>` or log in again.
  3. Verify the database name/address exists on the target server: `spacetime list`.
  4. If you own the database and still see this, confirm your server and identity correspond to the ones that published it.
  5. Otherwise ask the owner to grant access, or publish under a new name.
Defensive patterns

Strategy: try-catch

Validate before calling

const dbs = await fetch(`${server}/v1/databases`, { headers: { Authorization: `Bearer ${token}` } });
if (dbs.status === 403) {
  throw new Error('identity lacks rights on this server — run `spacetime identity show` and switch owner identity');
}

Try / catch

try {
  await spacetimePublish(dbName, modulePath);
} catch (e) {
  if (String(e).includes('is not authorized to perform action')) {
    // 403: valid identity, wrong permissions — do NOT retry blindly
    await spacetrySwitchIdentity(dbOwnerHint);
    throw new Error(`not owner of '${dbName}' — switch identity or ask owner for access`);
  }
  throw e;
}

Prevention

When it happens

Trigger: Publishing or updating a database owned by a different identity; calling a reducer or reading logs on a database the current identity does not own and has not been granted access to; using a token minted on server A against server B where the identity owns nothing; specifying an owner identity that does not match the logged-in identity during publish.

Common situations: Multiple identities in the keychain with the wrong one active; switching between local and testnet servers without re-login; team members sharing a database address without the owner granting publish rights; stale SPACETIMEDB_SPACETIME_TOKEN in CI.

Related errors


AI-assisted analysis of clockworklabs/SpacetimeDB@524b4487d9 (2026-08-16). Data as JSON: /api/errors/e9c0f78cd9d14604. Report an issue: GitHub.