Skip to content

Administration

This page covers operations that affect the altero instance, accounts, groups or complete libraries.

For ordinary account settings: see Web interface.

What an instance administrator can do

Most altero permissions belong to a library. The instance administrator role is different: it can operate the server itself.

An instance administrator can manage accounts, retention, identity providers, registered applications and storage information. The role does not grant automatic access to other users' library contents. An administrator can count and operate libraries without being allowed to read their items, notes or files.

The first account that claims a fresh instance becomes an administrator. The last administrator cannot demote, suspend or delete themselves, so a working instance cannot be left without one through normal administration.

From the shell:

uv run altero user admin <username>
uv run altero user admin <username> --revoke

Common command-line tasks

The browser covers most routine administration. The command line remains useful for initial setup and recovery.

Accounts and credentials

uv run altero user add <username> [--display-name NAME] [--id N]
uv run altero user list
uv run altero user password <username>
uv run altero user admin <username> [--revoke]
uv run altero user disable <username> [--undo]
uv run altero user revoke <username>
uv run altero user delete <username> [--yes]

uv run altero key add <username> [--name LABEL] [--read-only] [--groups]
uv run altero key list
uv run altero key revoke <key>

uv run altero oauth add <client-id> --redirect-uri URL [--scope SCOPE] [--confidential]
uv run altero oauth list
uv run altero oauth disable <client-id>
uv run altero oauth rotate-key

Groups

uv run altero group add <name> --owner <username> [--public]
uv run altero group member <group-id> <username> [--role admin]
uv run altero group members <group-id>
uv run altero group role <group-id> <username> <member|admin>
uv run altero group remove <group-id> <username>
uv run altero group delete <group-id> [--yes]

Libraries and retention

uv run altero library list
uv run altero library set-version <user|group> <id> <version>
uv run altero library export <user|group> <id> <archive.zip>
uv run altero library import <archive.zip> [--replace]

uv run altero retention show
uv run altero retention run [--dry-run] [--trash DAYS]

Desktop login approval

uv run altero login list
uv run altero login approve <token> <username> [--key KEY]

Administration screens

The browser shows Administration only to an account with the instance-administrator role.

Overview

The overview reports the altero version, Web API version, database dialect, attachment directory and Alembic revision. It also shows account, library and group counts.

Storage

Storage reports both:

  • on disk — the physical bytes the instance stores; and
  • counted across libraries — the sum each library would account for separately.

The totals differ because altero stores the same attachment content once per digest even if several libraries reference it.

The screen also identifies:

  • unreferenced files; and
  • attachments whose expected bytes are missing.

An attachment can be missing its bytes because they were never here, because they were swept, or because the server removed a stored file that did not match what was uploaded. In each case the client offers the file again on its next sync.

Removing unreferenced files is an explicit administrator action, not a timed retention rule. Recent files are left alone because an upload can temporarily exist on disk before the database row that references it is committed. The same action removes files a write into the store never finished, under the same grace period.

Accounts

The Accounts screen can create an account, reset access, suspend it, revoke credentials or delete it.

Create an account

An administrator can create an account with a username and password. The initial password is shown once and should be handed to the user securely; the user can then change it.

Browser registration is available only when one of these conditions applies:

  • this is the first account on a fresh instance;
  • ALTERO_OPEN_REGISTRATION enables registration; or
  • the email address has been invited to a group.

Password reset

An administrator can either set a password or issue a single-use link that lets the account holder choose a new one.

The link is valid for 12 hours. If the account has a confirmed address and outgoing mail is configured, altero can email it. The administrator is also shown the link so an instance without mail can still recover an account.

Optional self-service password reset is controlled by ALTERO_PASSWORD_RESET and requires an SMTP relay.

Suspend an account

Suspension blocks every form of access:

  • browser sessions;
  • API keys used by Zotero Desktop; and
  • OAuth access tokens held by connected applications.

No library data is deleted. Re-enabling the account restores access with the existing credentials unless those credentials were separately revoked.

Revoke credentials

Credential revocation signs out browser sessions and invalidates API keys without disabling or deleting the account.

This is appropriate for a lost device or leaked credential. The user can continue using the account after creating or receiving new credentials.

It does not disconnect third-party applications: their grants survive it. The account holder disconnects one under Settings → Connected applications, and an operator can stop a whole application for everybody with altero oauth disable <client-id>. Suspending the account stops its tokens along with everything else.

Delete an account

Deleting an account removes its personal library. It is refused while the account owns a group, because altero will not guess who should inherit that group.

Deletion is also refused for your own currently signed-in administrator account and where it would remove the last administrator.

Sign-in providers

Administration → Sign-in providers configures OpenID Connect and SAML 2.0 for browser sign-in.

Directory sign-in creates a browser session. The v3 API accepts API keys and OAuth access tokens, never that session cookie. Zotero Desktop still gets its API key from Settings → API keys.

Before adding a provider

Set ALTERO_PUBLIC_URL first. The administration screen shows the redirect/callback address that must be registered with the identity provider.

A callback mismatch is rejected by the provider before altero can complete sign-in.

OIDC and signed SAML sign-in have a real Keycloak integration test, including returning to the same account and keeping browser sessions out of the v3 API. The test setup documents its scope. Account creation, required-group checks and sign-in-time deprovisioning are exercised for both protocols, including retained and revoked API keys. SAML signing-key rollover, callback replay and the five-minute validity tolerance are also checked against Keycloak-issued responses. The clock tests adjust altero's validator clock; they do not test machine clock synchronization.

A second SAML integration test uses SimpleSAMLphp 2.5.3.1 on patched PHP 8.4.27, with both response and assertion signed. It covers persistent identity, required groups, provisioning policy, account linking, forced reauthentication and refusal of replayed or altered responses. This fixture does not establish compatibility with other providers or with encrypted assertions, artifact binding or signed AuthnRequests.

OpenID Connect

Store the client credentials and provider settings in the administration screen. Client secrets are write-only: the interface can replace a secret but does not display the existing value.

Copy the provider's exact issuer value from its discovery document, including any trailing slash. Issuers must match in configuration, discovery and ID tokens, as required by OpenID Connect Discovery. If a previous altero version removed that slash, edit the issuer and save it again; existing provider rows are not changed automatically.

SAML 2.0

A SAML provider requires:

  • the identity provider entity ID;
  • the sign-on URL; and
  • one or more signing certificates in PEM form.

Multiple certificates can be stored during signing-key rollover.

Register the screen's Callback address as the assertion consumer service (ACS), using HTTP-POST. For a provider whose short name is campus, this is <public_url>/web/auth/saml/campus/acs. The service provider entity ID is public_url without a trailing slash.

Linking an existing account and confirming identity return through a short-lived GET handoff after the assertion POST, so the browser can present its existing session cookie. The same signed-in account must complete it within two minutes; an expired or already-used handoff requires starting again. See the SAML session handoff.

The SAML implementation is deliberately limited to:

  • SP-initiated sign-in;
  • signed, unencrypted assertions; and
  • local sign-out only, with no SAML Single Logout.

Account creation and deprovisioning

Automatic account creation through a provider is off by default. Turning it on allows identities accepted by that provider to create local altero accounts.

A provider can also require a claim and value, such as an entitlement or group. On each sign-in, altero checks the requirement. If a previously linked user no longer has the required claim, the local account is suspended, which also stops existing Zotero API keys.

This check happens at sign-in time. It cannot detect a person who leaves the organization and never attempts to sign in again; administrators must handle that case through the Accounts screen or another operational process.

It also needs a token or assertion to check. If the directory refuses sign-in before returning one, altero cannot observe the missing claim and that attempt does not suspend existing local sessions or API credentials.

Removing a provider removes the account links to it, not the local accounts themselves.

Provider recipes

Use these settings with your own HTTPS addresses and client credentials. The examples use https://library.example.org for ALTERO_PUBLIC_URL. Choose a Short name before registering the callback; it becomes part of that address.

Protocol Address to register at the provider
OIDC https://library.example.org/web/auth/sso/<short-name>/callback
SAML, HTTP-POST ACS https://library.example.org/web/auth/saml/<short-name>/acs
SAML service provider entity ID https://library.example.org

For OIDC, create a confidential web client using authorization code and S256 PKCE, accepting client_secret_post at its token endpoint. Altero sends the secret in the form body. Set Scopes to profile email; openid is added automatically. The default claim names are preferred_username, name and email. Keep ID tokens unencrypted. Discovery and token endpoints must be reachable by the altero server, as well as the sign-in page by the browser.

Choose whether the provider may create accounts. With creation off, an existing user first connects the provider from Settings → Sign-in and security. Matching an email address never links accounts automatically. Required claim names a literal top-level claim or SAML attribute; Required value must match the string or one entry in its list exactly.

Keycloak

Verification: OIDC and SAML passed with Keycloak 26.8.1 in hosted CI at fcf8460, alongside the second SAML provider, SimpleSAMLphp. See the test scope for the exercised flows and browser limitations.

For OIDC, create an openid-connect client in the chosen realm. Enable client authentication and the standard authorization-code flow, require S256, and register the exact callback. Copy the client ID and generated secret into altero. Set Issuer to https://<keycloak-host>/realms/<realm>. Keycloak client settings describe these controls.

To require a group, add a group-membership mapper to this client's scope, emitting groups in the ID token or UserInfo. With full paths disabled, set altero's requirement to groups and the group name, such as researchers.

For SAML, create a saml client whose ID is altero's entity ID. Register the ACS, force POST responses and persistent NameIDs, enable assertion signing with RSA-SHA256, and disable assertion encryption and required client signatures. Copy the IdP issuer, HTTP-Redirect sign-on endpoint and signing certificate from the realm's /protocol/saml/descriptor into altero. Map user attributes to preferred_username, name and email as needed. The fixture's realm settings show the tested mapper and protocol choices.

Microsoft Entra ID

Unverified with altero. This recipe follows Microsoft's authorization-code flow and OIDC endpoints.

Register a single-tenant application in App registrations. Add a Web redirect URI with altero's exact OIDC callback, and create a client secret. Enter the application/client ID and the secret's value, not its identifier, in altero. Use https://login.microsoftonline.com/<tenant-id>/v2.0 as Issuer, with the actual tenant UUID. The common and organizations endpoints do not provide the fixed issuer this configuration requires. Use authorization code; implicit/hybrid ID-token delivery is not needed.

For an entitlement, define an app role with a value such as altero, assign test users to it, and set Required claim to roles and Required value to altero. The role must appear in the ID token. Altero does not resolve group-overage links through Microsoft Graph.

Okta

Unverified with altero. Follow Okta's web application registration. Choose OIDC and Web Application, enable authorization code, register the exact callback, and assign the intended users or groups to the application.

Configure the client's token_endpoint_auth_method as client_secret_post using Okta's client settings/API; its default is client_secret_basic, while altero sends form credentials. See client authentication. Enter the client ID and secret in altero.

Use the org issuer https://<your-okta-domain> for ordinary sign-in. If using a custom authorization server, copy its exact issuer, such as https://<your-okta-domain>/oauth2/<authorization-server-id>, and configure its access policy for this client. Do not assume /oauth2/default exists in every deployment; see authorization servers. Additional required claims must actually be emitted for this client and scope.

authentik

Unverified with altero. Create an application and an OAuth2/OIDC provider using the provider wizard. Choose a confidential client, set the exact callback as a strict redirect URI, and select the openid, profile and email scope mappings. Copy the client ID and secret into altero; leave token encryption disabled.

Keep the default per-provider issuer mode and a stable subject identifier. Set altero's Issuer to https://<authentik-host>/application/o/<application-slug>/, including the final slash. Copy the actual value from discovery. Global issuer mode uses a different discovery location from its issuer and does not fit altero's issuer-based discovery. See authentik's issuer modes and endpoints. Configure application access bindings and any required claim for your policy.

Shibboleth IdP

Unverified with altero. Use SAML 2.0. Give the IdP administrator the entity ID and HTTP-POST ACS above, and register the service provider in its metadata. Altero does not publish a metadata endpoint; this minimal descriptor is a starting point, with your own host and short name substituted:

<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
                  entityID="https://library.example.org">
  <SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"
                   AuthnRequestsSigned="false" WantAssertionsSigned="true">
    <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</NameIDFormat>
    <AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
        Location="https://library.example.org/web/auth/saml/shibboleth/acs"
        index="0" isDefault="true"/>
  </SPSSODescriptor>
</EntityDescriptor>

Enable a persistent NameID generator before the first sign-in; a transient NameID changes the identity between sessions. For this relying party, use the SAML2.SSO profile with signAssertions="true", encryptAssertions="false" and encryptNameIDs="false", and accept unsigned AuthnRequests. Apply these choices only to altero's relying party; see signing/encryption settings and SAML 2.0 profile options.

Copy the IdP entity ID, HTTP-Redirect sign-on URL and signing certificate from its metadata into altero. Arrange release of username, display-name and email attributes. If their Name or FriendlyName values are eppn, displayName and mail, use those as altero's three claim names; otherwise use the actual released names. Altero reads attributes from the signed assertion and makes no separate attribute-query request.

Check a configuration

Use a test account to sign in twice, signing out of altero between attempts. Both visits should reach the same local account. For an existing account with creation disabled, test linking from Settings first. Confirm that the provider also supports a fresh sign-in when Settings asks for proof to issue an API key.

If using a required claim, test both an allowed and a denied identity. Losing that claim after linking suspends the local account at its next sign-in; restoring the claim alone does not reinstate it. An administrator must reinstate the account. This is still a sign-in-time check, not continuous deprovisioning. See account creation and deprovisioning.

Third-party applications

Sign-in providers are how somebody proves who they are to altero. The authorization server is the other direction: how somebody else's application is given access to a library, without ever being handed a password or a long-lived API key.

An application has to be registered before it can ask for anything. Nothing self-registers, because the list of addresses an authorization code may be sent to is the only thing that keeps the code from going somewhere else.

Set ALTERO_PUBLIC_URL before registering anything. It decides the iss claim on every ID token, there is deliberately no fallback to the address a request arrived on, and without it these endpoints refuse to answer rather than guess.

uv run altero oauth add notebook \
    --name "Notebook" \
    --redirect-uri https://notebook.example.com/auth/callback \
    --scope openid --scope profile --scope library.read
uv run altero oauth list
uv run altero oauth disable notebook
uv run altero oauth rotate-key

A person approves an application in their browser and can disconnect it at any time under Settings → Connected applications. Disconnecting takes effect immediately.

Full details, including the scope table and what a client developer needs: Connecting other applications.

Retention

Retention starts conservatively: the default trash-retention period is never delete automatically.

Configuration values:

TRASH_RETENTION_DAYS = 0
ACTIVITY_RETENTION_DAYS = 0
UPLOAD_RETENTION_HOURS = 24
RETENTION_INTERVAL = 0

0 means “no automatic retention action” for the day-based settings and interval. Browser-configured values override the configuration file; clearing a browser override returns to the configured value.

Inspect and run retention from the shell:

uv run altero retention show
uv run altero retention run --dry-run
uv run altero retention run --dry-run --trash 30
uv run altero retention run

Why trash cleanup is a normal delete

When retention removes trashed Zotero objects, the library advances by one version and the deletions are recorded. A client that later asks /deleted?since= can therefore learn what disappeared.

Items restored or changed while a sweep waits are checked again before deletion.

Removing database rows without the deletion log would leave offline clients holding objects the server had silently forgotten.

Unreferenced attachment files are not deleted by the timed retention sweep. They are handled separately from the Storage screen to avoid racing uploads that have written their bytes before committing their item rows.

Rows that carry their own expiry

Browser sessions, sign-in codes, confirmation links, unanswered invitations and attachment download permissions have no retention period of their own. Each records when it expires, and the sweep removes it once it has: an expired session is already nobody's session, and an expired download permission opens nothing.

The same sweep removes expired browser merge snapshots. A library keeps at most one, usable by its author for five minutes while the library remains unchanged. These private snapshots are not included in library archives.

Download permissions are the highest-volume of these. A client syncing its files is granted one per attachment it fetches, so the sweep is what keeps that table from growing for as long as the instance runs. See the file protocol for what they are and why the redirect cannot simply point at a URL carrying the API key.

Library transfer and recovery

Move a library to another altero instance

Export the library:

uv run altero library export user 1 library.zip

On the destination, create the owning account or group first, then import:

uv run altero user add <username> --id 1
uv run altero library import library.zip

The archive contains JSON metadata and attachment bytes, including object keys, object versions, the library version and the deletion log. Accounts and API keys are not included.

Exporting holds library writes until the archive is built, so its contents describe one consistent version. The lock is released before downloading. Restoring also locks the target library before checking or replacing its contents.

Importing into a non-empty target is refused unless --replace is used.

The browser exposes equivalent import/export operations under Settings → Import and export.

Move a personal library from zotero.org

This is a one-time migration. It does not keep the two servers synchronized afterwards. Finish syncing to zotero.org before importing, and pause edits until the clients have moved to altero. Continuous two-way sync is unsupported; a scheduled one-way refresh is future work. See the verdict and replacement-import limits.

If possible, create the altero account with the same numeric user ID that zotero.org uses:

uv run altero user add <username> --id <zotero.org-user-id>
uv run altero migrate zotero <username> --replace

The migration asks for a zotero.org API key with read access to the personal library. The key is prompted for rather than placed on the command line.

The migration preserves Zotero object keys and versions so an already-synchronized desktop client can continue from the same state.

If the local altero user ID differs from the zotero.org user ID, Zotero Desktop can require a reset and re-download because the client remembers which account number last synchronized that data directory.

Only the personal library is imported. Attachment bytes that zotero.org does not hold cannot be downloaded; the migration lists those items and brings the attachments across without their files.

Important

If attachment files were synchronized over WebDAV, zotero.org holds none of them. It answers 404 for every one, so the whole library arrives without files. The metadata is complete: item keys, md5 and mtime are all preserved. Either keep the WebDAV server for files, or move the files into altero from a client that still has them. See File syncing.

After recreating the database

A newly recreated library begins at a low library version, while existing clients may remember a much higher version. Zotero refuses to move its stored version backwards.

Raise the server library version above the version remembered by the client:

uv run altero library set-version user 1 100

This command only raises versions; lowering a working library version can lock clients out.

Raising the version does not recreate data that was lost with the database. Use Zotero's Restore to Server afterwards if you need the client to upload a complete copy.

Group policy

A group has three Zotero-compatible policy settings:

Setting Values Controls
libraryReading members, all Who may read the library
libraryEditing members, admins Who may edit objects
fileEditing none, members, admins Who may upload attachment files

The group type is Private, PublicOpen or PublicClosed.

A public group is readable without a key only when the group is public and libraryReading is all.

Membership remains a ceiling over key permissions: a key that grants access to all groups still cannot read a group the account does not belong to.

Per-member permissions

altero can apply an additional restriction to one member:

Permission Meaning
inherit Use the normal group policy
read Read only
add Create and change, but do not remove or trash
own Create freely; change or remove only items that member added

From the shell:

uv run altero group member <group> <username> --permission read
uv run altero group permission <group> <username> add
uv run altero group members <group>

These permissions can only restrict what the group policy already allows. They do not elevate a member above the group's normal policy.

Only the read-only state maps cleanly onto a permission the Zotero sync client understands. add and own are enforced server-side; a client that attempts a forbidden operation receives a sync error explaining the restriction. See Compatibility notes.

The desktop compatibility runner's resync scenario repeats this recovery with Zotero 10.0.6, including restoring an older archive before raising the counter and uploading the surviving desktop's authoritative copy. Raising the library counter alone does not rewind existing desktop objects to older object versions. See the desktop scenarios.