Skip to content

Commands

The alphone binary runs the server and every task you do by hand, such as creating an admin or minting an API token. In the container image it lives at /alphone. On the Install setup, put docker compose exec alphone /alphone in front of each command below, for example docker compose exec alphone /alphone account:list. Add -T after exec when you pipe input into the command, as in Install.

Every command reads the variables listed in Configuration. All of them except list, help, version and a seed preview need ALPHONE_DATABASE_URL.

Run with no command, the binary lists every command and exits:

Terminal window
alphone
AlphOne Version 0.27.0
Usage:
alphone <command> [flags] [arguments]
Every command answers -h. A command that offers -json answers one JSON document. A command that offers -yes is a dry run until -yes.
Available commands:
check check every setting, every plugin and every command name
help print the help of one command
list list every command
migrate apply every schema step
seed store the demo data
serve run the server
version print the version
account
account:create-admin create an account under a role
account:disable disable one account
account:enable enable one disabled account
account:grant-role give a role to every account holding none
account:list list every account with its role
account:records list who applied which change, the newest first
account:role set one account's role
token
token:create mint a token for one account and show its secret once
token:list list the tokens of one account or of every account
token:revoke revoke one token of one account
Every command is described at https://docs.alph.one/self-hosting/commands/

alphone list prints the same. alphone help followed by a command, or the command followed by -h, prints the help page of that command:

Terminal window
alphone help token:revoke
revoke one token of one account
Usage:
alphone token:revoke [flags]
Flags:
-email address
address of the account that owns the tokens
-id id
id of the token to revoke
-yes
apply the change, a dry run without it

A plugin can offer commands of its own, named after the plugin, and they show in the listing under the plugin’s name. A plugin that fails to load shows under Not loaded: after the commands, with the reason:

Not loaded:
plugin fields: ALPHONE_FIELDS_ENTRIES_MAX: must stand above zero, got "0"

seed, account:grant-role, account:role, account:disable, account:enable and token:revoke only show what they would change until you add -yes. Without it they change nothing, and they add one line on the error stream:

would set maria@example.com to admin
alphone: dry run, nothing changed, pass -yes to apply

Every command exits with one of three codes:

CodeMeaning
0The command ran, or showed what it would change.
1The command ran and failed, for example over a refused setting, an unknown account or a refused change. The reason follows alphone: on the error stream.
2The line itself is wrong, such as an unknown command, a missing flag or argument, a flag value it cannot read, or an unknown role. The reason follows alphone:, then the help page of the command when there is one.
Terminal window
alphone frobnicate
alphone: unknown command "frobnicate", run "alphone list" to see every command

Runs the server. It applies every migration first, the same ones migrate applies, then starts the plugins and listens on ALPHONE_ADDR. The container image runs serve when no command is named. If your compose file or your script names a command of its own, that command must be serve.

serve stops on SIGTERM or Ctrl+C. It first lets running requests and the plugins finish, within the three shutdown graces ALPHONE_SHUTDOWN_GRACE, ALPHONE_SHUTDOWN_CANCEL_GRACE and ALPHONE_SHUTDOWN_STOP_GRACE. Whatever stops it must wait longer than the three added together. Their defaults and the HTTP timeouts serve reads are under Timeouts and shutdown.

Applies every schema step and exits without serving: the accounts, the command records, the core and every plugin.

Terminal window
alphone migrate
migrated accounts
migrated records
migrated core
migrated plugins

Running it again changes nothing. Every migration waits for one database lock, so a starting server and migrate never migrate together.

Reads every setting and loads every plugin without connecting to the database:

Terminal window
alphone check
settings, plugins and command names are valid

A refused value exits 1 and names the variable:

Terminal window
ALPHONE_TOKEN_TTL_DAYS=soon alphone check
alphone: ALPHONE_TOKEN_TTL_DAYS: must be a whole number, got "soon"

Run it after you change the environment and before you restart the server.

Stores the demo data: demo accounts, contacts, tasks and WhatsApp conversations. Never run it against a production database, because the demo logins share the public password password1234. Without -yes it only says what it would do:

would store the demo data
alphone: dry run, nothing changed, pass -yes to apply

With -yes it applies every migration, stores the core demo data, then the demo data of each plugin, and starts no plugin:

Terminal window
alphone seed -yes
migrated accounts
migrated records
migrated core
migrated plugins
seeded the core demo data
login: admin@example.com / password1234 (admin)
login: maria@example.com / password1234 (member)
alphone: demo data is for development only, never seed a production database

The migrated lines and the last warning go to the error stream. Running it again repairs a half seeded database without duplicating anything, and an account that already exists keeps its password. The contacts a plugin’s demo data creates raise contact.created, so a webhook subscribed to that event receives them at the next start of the server.

Terminal window
alphone version
alphone 0.27.0

With -json it answers one document:

{
"name": "alphone",
"version": "0.27.0"
}

The account commands work on the whole deployment, on purpose. They find an account by its address whatever workspace it works in. Anyone who can run the binary next to the database already holds every workspace, so the commands do not narrow that.

Creates one account under a role, ready to log in. It needs -email, -name and -role, and reads the password, at least 12 characters, from the first line of its input. Typed at the Password: prompt or piped in, the result is the same:

Terminal window
printf '%s\n' "$ADMIN_PASSWORD" | alphone account:create-admin \
-email you@example.com -name "Your Name" -role admin
migrated accounts
migrated records
migrated core
Password: created user you@example.com

It first applies the account, record and core migrations, so it works on an empty database. It takes no -as and is not recorded, which makes it the way to create the first admin, and the way back in when no account can act. An unknown role exits 2 and names the roles, as in alphone: unknown role "owner", want admin or member. An address that is already taken exits 1 with alphone: gouncer: email already taken.

Lists every account with its role, a dash when it holds none, and whether it is enabled:

Terminal window
alphone account:list
admin@example.com admin enabled
invited@example.com member enabled
disabled@example.com member disabled
maria@example.com member enabled
you@example.com admin enabled

With -json it answers {"accounts": [...]}, each account with id, email, name, role and disabled.

Four commands change accounts that already exist. Each one only shows what it would change until you add -yes, and each one acts as the account you name with -as:

CommandWhat it changes
account:role <email> <role> -as <address>Sets the role of one account.
account:disable <email> -as <address>Disables one account.
account:enable <email> -as <address>Enables one disabled account.
account:grant-role -role <role> -as <address>Gives the role to every account holding none, and says how many it changed. With -yes, once the acting account passes its checks, it applies the account, record and core migrations before it gives the role.
Terminal window
alphone account:role maria@example.com admin -as you@example.com
alphone account:role maria@example.com admin -as you@example.com -yes
would set maria@example.com to admin
alphone: dry run, nothing changed, pass -yes to apply
set maria@example.com to admin

Without -as the command exits 2 with alphone: account:role wants -as <email>. The acting account must exist, be enabled, have been activated and hold a role that carries manage_users, which in AlphOne is the admin role. Otherwise the command exits 1 and changes nothing:

ErrorWhy
no account answers to nobody@example.comNo account has that address.
the account disabled@example.com is disabledThe acting account is disabled.
the account invited@example.com was never activatedThe acting account never set its password from its invitation.
the account maria@example.com holds the role member, which lacks manage_usersIts role may not manage users.
the account admin@example.com holds no role, so it lacks manage_usersIt holds no role at all.
the account you@example.com cannot change its own roleNo account changes its own role.
the account you@example.com cannot disable itselfNo account disables itself.

The acting account also reaches only as far as its own role. A change that gives a role carrying a capability the acting account’s role lacks is refused, and so is a change to an account whose role carries one, with a line such as the role <role> carries <capability>, which the account <address> lacks. With the roles AlphOne ships, an admin reaches every account. A role a plugin declares can carry more than admin does. It reaches an admin’s account, or gives the admin role, only when it also carries every capability admin carries, which are manage_users and manage_webhooks. A plugin role carrying manage_users alone can change members but not admins.

The last enabled admin always stays. When two changes race, the one that would leave no enabled admin is refused with <address> is the last enabled privileged account.

These four commands need the command records. On a database that does not hold them yet, such as one last migrated by a release from before the command line was rebuilt, they stop with the command records are missing, run migrate first, previews included. Run migrate, or start serve once. When nobody holds a role at all, Updates and backups shows the way back in.

Each change those four commands apply with -yes is recorded with its time, the acting address, the command, its arguments and its flags. Previews and refused attempts are not recorded. account:records lists the newest first, 50 of them unless -limit or ALPHONE_COMMAND_RECORDS_LIMIT asks for another number:

Terminal window
alphone account:records -limit 3
2026-10-04T15:24:55Z you@example.com account:enable maria@example.com
2026-10-04T15:24:55Z you@example.com account:disable maria@example.com
2026-10-04T15:24:55Z you@example.com account:role maria@example.com admin

With -json it answers {"records": [...]}, each record with applied_at, actor, account_id, command, args and flags.

A record is stored after its change. If storing it fails, for example because it takes longer than ALPHONE_COMMAND_RECORD_TIMEOUT, the command exits 1 with the change already made.

token:create, token:list and token:revoke manage the tokens programs use to call the API, see GraphQL API. Each one acts on the account -email names, found by its address in upper or lower case. token:create and token:list -email act in the workspace that account works in, and token:revoke finds the account’s token in any workspace. token:list -all takes no -email and lists the tokens of every account in every workspace. They take no -as and are not recorded.

They apply no migration, so on a database no version has migrated yet they exit 1. After an update they still run, so run migrate, or start serve once, before you use them. Until then token:list can miss a token the update moves into its owner’s workspace.

Mints a token for one account and shows its secret once. It needs -email and -name:

Terminal window
alphone token:create -email you@example.com -name "my agent" \
-scope tasks:read -scope contacts:read
created token 01a10784-b944-72a3-b676-0f61036eb956
secret: a1_...
store it now, it is never shown again
scopes contacts:read tasks:read, expires 2027-01-02
  • -scope grants one area and whether the token may write there, such as tasks:read or tasks:write. Repeat it for each area. Without it the token holds every area, shown as scopes *. alphone help token:create lists the areas.
  • -ttl sets how many days the token lasts, or never. -ttl 0 mints a token that never expires too. Without -ttl the token lasts ALPHONE_TOKEN_TTL_DAYS days, ninety by default.

Without -name it exits 2 with alphone: token:create wants -name <name>. A -ttl that is neither never nor a whole number of days from 0 to 106751, or a -scope whose area no schema declares or whose access is neither read nor write, also exits 2, before any database is reached:

Terminal window
alphone token:create -email you@example.com -name "my agent" -ttl soon
alphone: token:create: invalid value "soon" for flag -ttl: want a whole number of days or never

A malformed ALPHONE_TOKEN_TTL_DAYS exits 1 instead, as every refused setting does.

With -email it lists the tokens of one account, their secrets left out:

Terminal window
alphone token:list -email you@example.com
01a10784-b944-72a3-b676-0f61036eb956 my agent scopes contacts:read tasks:read created 2026-10-04 last used never expires 2027-01-02

With -all it lists every token of every account in every workspace. Each line starts with the owner’s address and, after tenant, the id of the workspace the token is stored in. A token whose account no longer exists shows (no account) as its owner:

Terminal window
alphone token:list -all
admin@example.com tenant 00000000-0000-7000-8000-000000000001 01a10784-b957-7537-b4af-7d04cc2ce486 n8n scopes * created 2026-10-04 last used never expires never
you@example.com tenant 00000000-0000-7000-8000-000000000001 01a10784-b944-72a3-b676-0f61036eb956 my agent scopes contacts:read tasks:read created 2026-10-04 last used never expires 2027-01-02

Pass one of -email and -all. Both or neither exit 2. With -json it answers {"tokens": [...]}, each token with id, name, scopes, created_at, last_used_at and expires_at, the last two null when unset. With -all each token also carries owner, null when no account answers for it, and tenant_id.

When an account moves to another workspace, the tokens it held stay in the old one and still work. token:list -email no longer shows them, but token:list -all does, with the workspace that keeps them. token:revoke revokes them, while the API tokens tab of the Users page cannot yet.

Revokes one token of one account, in whichever workspace keeps it. It needs -email and -id, and only names the token until you add -yes:

Terminal window
alphone token:revoke -email admin@example.com -id 01a10784-b957-7537-b4af-7d04cc2ce486
would revoke token 01a10784-b957-7537-b4af-7d04cc2ce486 (n8n) of admin@example.com
alphone: dry run, nothing changed, pass -yes to apply

With -yes it answers revoked token and the id. An id the account does not hold exits 1 with alphone: apitoken: not found, with or without -yes. Without -id it exits 2 with alphone: token:revoke wants -id <id>, and an -id that is not a UUID exits 2 before any database is reached, both with or without -yes.

To find a token the account left in another workspace, run token:list -all and take the id from the line that starts with the account’s address.

Releases before the command line was rebuilt took other names. The old token create and token list spellings still work for now, and print a notice first, such as alphone: "token list" is deprecated, use "token:list".

BeforeNow
alphone alone started the serveralphone serve. A run with no command lists the commands.
alphone createadminalphone account:create-admin. Pass -role admin. The old name exits 2.
alphone grantrolealphone account:grant-role, which also needs -as and -yes. The old name exits 2.
alphone seedalphone seed -yes. Without -yes it only previews.
alphone token createalphone token:create. The old spelling still works.
alphone token listalphone token:list. The old spelling still works.
alphone token revokealphone token:revoke, which also needs -yes. The old spelling exits 2, so a preview is never mistaken for a revoke.