Authentication
Every /api/v1 route needs credentials, except POST /api/v1/auth/login.
GET /api/openapi.yaml stays public. You can authenticate in either of two ways:
- HTTP Basic on each request:
curl -u admin:PASSWORD … - Bearer token from
POST /api/v1/auth/login:-H "Authorization: Bearer TOKEN"
A request without valid credentials gets:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Basic realm="weavster"
{"error":{"code":"UNAUTHORIZED","message":"authentication required"}}
First start: the admin account
When the server starts with no users, it creates one account named admin with every
permission. It picks the password in this order:
- The
WEAVSTER_BOOTSTRAP_ADMIN_PASSWORDenvironment variable. - The contents of the file named by
WEAVSTER_BOOTSTRAP_ADMIN_PASSWORD_FILE, for example a container secret at/run/secrets/weavster-admin. A trailing newline is ignored. - Otherwise, a random 24-character password, printed once to stderr:
First-run admin account created.
username: admin
password: q7M!xR...
This password is shown once and must be changed at first login (POST /api/v1/auth/password).
The password from option 1 or 2 must satisfy auth.passwordPolicy.
If it doesn't, the server exits 1 with Error: bootstrap: admin password rejected by auth.passwordPolicy.
Where users are stored
Users, password changes, and lockout state are saved in the configured store:
store.dialect |
Users after a restart |
|---|---|
postgres |
Kept. The first-start step runs only once per database. After that, the bootstrap variables are ignored and the printed password is never shown again. |
memory, disabled |
Lost. Every start repeats the first-start step, so a generated password changes each time. To keep a stable password, set WEAVSTER_BOOTSTRAP_ADMIN_PASSWORD or WEAVSTER_BOOTSTRAP_ADMIN_PASSWORD_FILE. |
If you lose a generated password on a PostgreSQL store, there is no API to reset it. Stop the server and drop the server's schema (this also deletes flows and messages), or keep the password somewhere safe when it is first printed.
Changing a generated password
Until you change a generated password, every route except /api/v1/auth/me,
/api/v1/auth/password, and /api/v1/auth/logout returns:
{"error":{"code":"PASSWORD_CHANGE_REQUIRED","message":"change your password with POST /api/v1/auth/password before using the API"}}
Change it:
curl -s -u 'admin:q7M!xR...' -H 'X-Weavster-CSRF: 1' \
-d '{"oldPassword":"q7M!xR...","newPassword":"A-Strong-Passw0rd"}' \
http://127.0.0.1:8080/api/v1/auth/password
This returns 204 on success. Errors:
| Response | Cause |
|---|---|
400 {"error":{"code":"OLD_PASSWORD_INCORRECT",…}} |
oldPassword is wrong. It counts as a failed attempt toward lockout. |
400 {"error":{"code":"PASSWORD_REJECTED",…}} |
newPassword fails auth.passwordPolicy or equals oldPassword. |
500 {"error":{"code":"INTERNAL",…}} |
The new password could not be saved (for example, the database is read-only), or another password change for the same user happened at the same moment. The old password still works; retry. |
A password change signs out every other session of that user. The token used for the change keeps working.
Log in, use a token, log out
curl -s -H 'X-Weavster-CSRF: 1' \
-d '{"username":"admin","password":"A-Strong-Passw0rd"}' \
http://127.0.0.1:8080/api/v1/auth/login
{"expiresAt":"2026-09-27T04:00:00Z","token":"5f0c…","user":{"username":"admin","permissions":["admin"],"mustChangePassword":false}}
This edition has no multi-factor authentication: an mfaCode field in the login body (or an
X-Weavster-MFA header) is accepted and ignored.
A token is valid for 12 hours, or until you log out, change your password from another
session, or the server restarts. The scheme name is case-insensitive (bearer works too).
curl -s -H 'X-Weavster-CSRF: 1' -H "Authorization: Bearer $TOKEN" http://127.0.0.1:8080/api/v1/auth/me
curl -s -X POST -H 'X-Weavster-CSRF: 1' -H "Authorization: Bearer $TOKEN" http://127.0.0.1:8080/api/v1/auth/logout # 204
A wrong username and a wrong password both return the same
401 {"error":{"code":"UNAUTHORIZED","message":"invalid username or password"}}.
Lockout
After auth.lockout.retryLimit failed attempts (default 5), the account is locked for
auth.lockout.lockoutPeriodSeconds (default 300). Failed attempts count on both the login
endpoint and Basic credentials. While the account is locked, even the correct password
returns 401. Set retryLimit: 0 to disable lockout. See Server configuration.
!!! warning "Lockout can shut out the only admin"
Anyone who can reach the API can lock admin by sending wrong passwords, and there is no
second account to unlock it. With store.dialect: postgres the lockout also survives a
restart, so you have to wait lockoutPeriodSeconds. Keep the server off untrusted
networks. Setting retryLimit: 0 removes this risk but allows unlimited password guessing.
Permissions
| Route | Required permission |
|---|---|
GET /api/v1/system and /api/v1/system/about, /password-requirements, /resources, /guid, GET /api/v1/auth/me, POST /api/v1/auth/password, POST /api/v1/auth/password/check, POST /api/v1/auth/logout |
any signed-in user |
GET /api/v1/flows, GET /api/v1/flows/{id}, GET /api/v1/flows/export, GET /api/v1/flows/connector-names, GET /api/v1/flows/ports-in-use, GET /api/v1/topology, GET /api/v1/topology/flows/{flowId} |
flows:view |
POST /api/v1/config/validate |
flows:edit |
POST /api/v1/flows, PUT /api/v1/flows, PUT /api/v1/flows/{id}, DELETE /api/v1/flows/{id}, POST /api/v1/flows/{id}/{enable,disable}, POST /api/v1/flows/import |
flows:edit |
GET /api/v1/messages, GET /api/v1/messages/{id}, GET /api/v1/messages/trends, GET /api/v1/system/prune |
messages:view |
GET /api/v1/messages/{id}/content, GET /api/v1/messages/export |
messages:content |
POST /api/v1/messages/import |
messages:import |
DELETE /api/v1/messages/{id}, DELETE /api/v1/messages, POST /api/v1/system/prune/{start,stop} |
messages:delete |
POST /api/v1/flows/{id}/messages, POST /api/v1/messages/{id}/reprocess, POST /api/v1/messages/requeue |
messages:send |
POST /api/v1/messages/{id}/requeue |
messages:view, messages:send |
POST /api/v1/flows/{id}/{deploy,undeploy,start,stop,pause,halt,resume}, POST /api/v1/flows/redeploy-all, POST /api/v1/flows/{deploy,undeploy,start,stop,pause,halt,resume}-all, POST /api/v1/flows/{id}/destinations/{name}/{start,stop}, POST /api/v1/flows/stats/reset, POST /api/v1/flows/{id}/stats/reset |
flows:deploy |
GET /api/v1/flows/{id}/stats, GET /api/v1/flows/stats, GET /api/v1/stats/series, GET /metrics |
flows:view |
GET /api/v1/events, /api/v1/events/{id}, /count, /max-id, /export |
events:view |
GET /api/v1/audit |
audit:view |
GET/POST /api/v1/users, GET/PUT/DELETE /api/v1/users/{name}, POST /api/v1/users/{name}/password |
users:admin |
GET/PUT /api/v1/users/{name}/preferences, GET /api/v1/users/{name}/loggedin |
the user named, or users:admin |
/api/v1/configmap, /api/v1/configmap/{name} (all methods) |
configmap:edit |
/api/v1/scripts, /api/v1/scripts/{name} (all methods) |
scripts:edit |
/api/v1/settings, /api/v1/settings/{name} (all methods) |
settings:edit |
/api/v1/snippets, /api/v1/snippet-libraries and their /{name} routes (all methods) |
snippets:edit |
/api/v1/alerts and every route under it |
alerts:edit |
GET /api/v1/lookups…, POST /api/v1/lookups/{group}/batch |
lookups:view |
PUT/DELETE /api/v1/lookups/{group}/{key}, DELETE /api/v1/lookups/{group}, POST /api/v1/lookups/{group}/import |
lookups:edit |
POST /api/v1/config/apply |
flows:view, flows:edit, alerts:edit, snippets:edit, scripts:edit, settings:edit, and configmap:edit |
POST /api/v1/config/plan |
flows:view, alerts:edit, snippets:edit, scripts:edit, settings:edit, and configmap:edit |
GET /api/v1/config/export |
flows:view, alerts:edit, snippets:edit, scripts:edit, and settings:edit; also configmap:edit with includeConfigMap=true |
POST /api/v1/config/import |
flows:edit, alerts:edit, snippets:edit, scripts:edit, and settings:edit; also flows:deploy unless nodeploy=true, and configmap:edit with overwriteConfigMap=true |
The admin permission grants everything. A signed-in user without the permission gets:
{"error":{"code":"FORBIDDEN","message":"missing permission flows:edit"}}
The other permissions are users:admin, flows:view, flows:edit, flows:deploy,
messages:view, messages:send, messages:content, messages:delete, messages:import,
events:view, alerts:edit, snippets:edit, scripts:edit, configmap:edit,
settings:edit, lookups:view, lookups:edit, and audit:view.
Manage users
An account with users:admin (or admin) manages the other accounts.
curl -s -u 'admin:PASSWORD' -H 'X-Weavster-CSRF: 1' -X POST http://127.0.0.1:8080/api/v1/users -d '{
"username": "ops", "password": "A-Temp-Passw0rd",
"permissions": ["flows:view", "flows:deploy", "events:view"],
"email": "ops@example.com", "org": "Radiology"
}'
{"username":"ops","email":"ops@example.com","org":"Radiology","permissions":["events:view","flows:deploy","flows:view"],"mustChangePassword":true,"locked":false}
| Request | What it does |
|---|---|
GET /api/v1/users, GET /api/v1/users/{name} |
Lists accounts, or shows one. Password data is never returned. locked is true during a lockout. |
POST /api/v1/users |
Creates an account (201), with optional email and org (organization). username is 1–64 characters from A-Z a-z 0-9 . _ @ -; permissions must be from the list above; the password policy applies. The user must choose a new password at the first login unless you send "mustChangePassword": false. |
PUT /api/v1/users/{name} |
Replaces permissions (required; [] for none). email and org change only when you send them. |
POST /api/v1/users/{name}/password |
Sets a new password ({"password":"…"}, 204). The user must change it at the next login, and a lockout ends. |
DELETE /api/v1/users/{name} |
Deletes the account (204). |
- Changing a user's permissions, setting their password, or deleting them ends all their open sessions (bearer tokens); they sign in again.
- You cannot delete your own account, and the last account with
admincannot be deleted or loseadmin(409). - An account with
users:adminbut notadmincan only grant permissions it holds itself, neveradmin, and cannot change, reset, or delete an account that hasadmin(403). - Editing your own account (
PUT) keeps your own session only when your permissions stay the same. Changing your permissions or setting your own password ends every session, yours included; sign in again before making another API request. - Invalid input returns
400with the reason, an existing username409, and an unknown user404.
Preferences, sign-in status, and password checks
Each user can keep their own preferences: a JSON object of names to text values, such as a UI
theme or a default flow.
PUT /api/v1/users/{name}/preferences replaces the whole set;
GET /api/v1/users/{name}/preferences returns it ({} when none).
curl -s -u 'ops:PASSWORD' -H 'X-Weavster-CSRF: 1' -X PUT \
http://127.0.0.1:8080/api/v1/users/ops/preferences -d '{"theme":"dark","dashboard.flow":"adt"}'
curl -s -u 'ops:PASSWORD' http://127.0.0.1:8080/api/v1/users/ops/preferences
{"dashboard.flow":"adt","theme":"dark"}
- Preferences are stored with the account, so they are kept across restarts on a PostgreSQL store, and deleted with the account.
- Up to 100 preferences; each name is 1–100 characters and each value text of at most 4096 bytes.
Anything else, or a value that is not a string, returns
400. - A user reads and changes only their own;
users:admincan reach anyone's, except that only an account withadmincan change the preferences of an account that hasadmin. Another user's gets403, and an unknown user404. - The request body is at most 1 MiB; a larger one returns
400withrequest body too large.
GET /api/v1/users/{name}/loggedin tells whether the user has an open login session (a bearer
token from /api/v1/auth/login that has not expired or been ended). Basic credentials are not
sessions. The same rule applies: the user themselves, or users:admin.
curl -s -u 'admin:PASSWORD' http://127.0.0.1:8080/api/v1/users/ops/loggedin
{"loggedIn":true,"sessions":1}
An unknown user returns 404. Sessions are held in memory, so
after a restart every user shows false until they sign in again.
POST /api/v1/auth/password/check tells any signed-in user whether a candidate password meets
auth.passwordPolicy, without setting it:
curl -s -u 'ops:PASSWORD' -H 'X-Weavster-CSRF: 1' -X POST \
http://127.0.0.1:8080/api/v1/auth/password/check -d '{"password":"short"}'
{"valid":false,"reason":"password shorter than 8 characters"}
A password that passes returns {"valid":true}. The check also works while the user must still
change their password, so a sign-in screen can check the new one first. It does not look at the
user's current password; a change to the same password is still refused by
POST /api/v1/auth/password.
CLI
Batch mode sends -u/-p as Basic credentials:
weavster -a http://127.0.0.1:8080 -u admin -p 'A-Strong-Passw0rd' -s script.txt
Without them, flow list fails with exit code 2 and prints the server's reply:
Error: server returned 401 Unauthorized: authentication required