Repository navigation
Networking
This doc will outline how bitwarden sends requests online.
Bitwarden has 4 endpoints:
- Main Endpoint (
https://vault.bitwarden.com) - API Endpoint (
https://api.bitwarden.com) - Vault Endpoint (
https://vault.bitwarden.com) - Icon Endpoint (
https://icons.bitwarden.net) - Websocket Endpoint (
wss://notifications.bitwarden.com)
Unlike Bitwarden, VaultWarden uses the same URL for the main, API and vault endpoint (https://someVault.com). VaultWarden uses the same Bitwarden icon endpoint but has a slightly different websocket endpoint (wss://someVault.com/notifications).
Bitwarden requires a valid useragent or it will result in random weird responses (like the kdfIterations always being at 600 000). Bitwarden also requires a Bitwarden-Client-Name which can be a web or desktop as well as a Bitwarden-Client-Version which looks like 2026.4.1. When sending requests, Bitwarden usually requires a authorization key as well unless you are trying to get an access token. All Bitwarden requests return with a 200 except for the WebSocket which returns a 101.
When logging into an account, Bitwarden sends a POST Prelogin password request to /identity/accounts/prelogin/password with a JSON email. Then it recieves a JSON response like:
{
"kdf": 0, // Or 1 for Argon2ID
"kdfIterations": 600001,
"kdfMemory": null,
"kdfParallelism": null
}
After sending a prelogin request upon login, it sends a GET request to /api/devices/knowndevice with a simple true or false response.
To retrieve a token upon login, VaultWarden sends a POST request to /identity/connect/token with a application/x-www-form-urlencoded; charset=utf-8 that contains:
-
scope:api offline_access -
client_id:web -
deviceType:10 -
deviceIdentifier: uniqueGUID -
deviceName: browser (firefox) -
grant_type:password -
username: email (a@a.com) -
password: Master Password Hash
The POST request then returns a JSON response with the Key, Kdf, KdfIterations, KdfMemory, KdfParallelism, access_token, expires_in and refresh_token.
To retrieve a token with a refreshToken, send a POST request to identity/connect/token and send a JSON request with the grant_type (refresh_token), client_id (web) and refresh_token which will pass back a JSON response with the access_token, expires_in and refresh_token.
When logging in, Bitwarden sends a POST request to <VWURL>/identity/accounts/prelogin/password with the email as the request in JSON format.
It then returns a JSON with the following information which will be required to decrypt the vault and make the masterPasswordHash. The response looks something like this:
{
"kdf": 0,
"kdfIterations": <KDF ITERATIONS>,
"kdfMemory": null, // Used for Argon2ID
"kdfParallelism":null // Used for Argon2ID
}
Note
If you provide an incorrect useragent, or an invalid one, you will always get 600 000 iterations or the request will fail.
It then sends a GET request to <VWURL>/api/devices/knowndevice with no request. Bitwarden will then return a true or false response.
It isnt very clear what this is used for.
Upon submitting the password, Bitwarden send a whole ton of requests.
First, it sends a POST request to <VWURL>/identity/connect/token with a content-type of application/x-www-form-urlencoded; charset=utf-8, with the following as the request data:
- scope "api offline_access"
- client_id "web"
- deviceType "10"
- deviceIdentifier
<Random GUID> - deviceName "firefox"
- grant_type "password"
- username
<Email> - password
<MasterPasswordHash>
Remember that the client_id can be web, desktop, etc and the deviceType must be one of the following (from Bitwarden Github):
export enum DeviceType {
Android = 0,
iOS = 1,
ChromeExtension = 2,
FirefoxExtension = 3,
OperaExtension = 4,
EdgeExtension = 5,
WindowsDesktop = 6,
MacOsDesktop = 7,
LinuxDesktop = 8,
ChromeBrowser = 9,
FirefoxBrowser = 10,
OperaBrowser = 11,
EdgeBrowser = 12,
IEBrowser = 13,
UnknownBrowser = 14,
AndroidAmazon = 15,
UWP = 16,
SafariBrowser = 17,
VivaldiBrowser = 18,
VivaldiExtension = 19,
SafariExtension = 20,
SDK = 21,
Server = 22,
WindowsCLI = 23,
MacOsCLI = 24,
LinuxCLI = 25,
DuckDuckGoBrowser = 26,
}
Bitwarden then responds with a JSON response with the following
{
"AccountKeys": {
"Object": "privateKeys",
"publicKeyEncryptionKeyPair": {
"Object": "publicKeyEncryptionKeyPair",
"publicKey": "...",
"wrappedPrivateKey": "..."
}
},
"ForcePasswordReset": false,
"Kdf": 0,
"KdfIterations": <KDF ITERATIONS>,
"KdfMemory": null,
"KdfParallelism": null,
"Key": "<VAULT Key>",
"MasterPasswordPolicy": {
"Object": "masterPasswordPolicy"
},
"PrivateKey": "...",
"ResetMasterPassword": false,
"UserDecryptionOptions": {
"HasMasterPassword": true,
"MasterPasswordUnlock": {
"Kdf": {
"Iterations": 600001,
"KdfType": 0,
"Memory": null,
"Parallelism": null
},
"MasterKeyEncryptedUserKey": "...",
"MasterKeyWrappedUserKey": "...",
"Salt": "a@a.com"
},
"Object": "userDecryptionOptions"
},
"access_token": "<ACCESS TOKEN>",
"expires_in": <EXPIRY NUM>,
"refresh_token": "<REFRESH TOKEN>",
"scope": "api offline_access",
"token_type": "Bearer"
}
VaultWarden then sends a GET request to <VWURL>/api/config with a JSON response like this:
{
"environment": {
"api": ...,
"cloudRegion": null,
"identity": ...,
"notifications": ...,
"sso": "",
"vault": ...
},
"featureStates": {
"pm-19148-innovation-archive": true
},
"gitHash": "f21a3ada",
"object": "config",
"push": {
"pushTechnology": 0,
"vapidPublicKey": null
},
"server": {
"name": "Vaultwarden",
"url": "https://github.com/dani-garcia/vaultwarden"
},
"settings": {
"disableUserRegistration": false
},
"version": "2025.12.0"
}
VaultWarden then sends a GET request to <VWURL>/api/sync which returns a JSON response like this:
{
"ciphers": [
{
"archivedDate": "2026-08-10T07:52:03.231119Z",
"attachments": [
{
"fileName": "2....",
"id": "XXXXXXXXXXXXXXXXXXXX",
"key": "2....",
"object": "attachment",
"size": "<NUM>>",
"sizeName": "XX.XX KB",
"url": "https://<URL>/attachments/<UUID>/<AttachmentID>?token=<TOKEN>"
}
],
"card": null,
"collectionIds": [],
"creationDate": "2026-07-31T06:51:25.742018Z",
"data": {
... // REMEMBER: Bitwarden requires a String as Data
},
"deletedDate": "2026-08-10T07:48:04.106107Z",
"edit": true,
"favorite": false,
"fields": [
{
"linkedId": null,
"name": "...",
"type": 0, // 0 for text, 1 for hidden, 2 for checkbox, 3 for linked
"value": "..."
}
],
"folderId": null,
"id": "<RANDOM GUID>",
"identity": null,
"key": "...",
"login": {
"autofillOnPageLoad": null,
"fido2Credentials": [
{
"counter": "2.XXX",
"creationDate": "<DATE>",
"credentialId": "2.XXX",
"discoverable": "2.XXX",
"keyAlgorithm": "2.XXX",
"keyCurve": "2.XXX",
"keyType": "2.XXX",
"keyValue": "2.XXX",
"response": null,
"rpId": "2.XXX",
"rpName": "2.X",
"userDisplayName": "2.XXX",
"userHandle": "2.XXX",
"userName": "2.XXX"
}
]
"password": "...",
"passwordRevisionDate": null,
"totp": "...",
"uri": "...",
"uris": [
{
"match": null,
"uri": "...",
"uriChecksum": "..."
}
],
"username": "..."
},
"name": "...",
"notes": "...",
"object": "cipherDetails",
"organizationId": null,
"organizationUseTotp": true,
"passwordHistory": [],
"permissions": {
"delete": true,
"restore": true
},
"reprompt": 0,
"revisionDate": "2026-08-10T07:48:34.271624Z",
"secureNote": null,
"sshKey": null,
"type": 1,
"viewPassword": true
}
],
"collections": [],
"deletedCiphers": [],
"deletedFolders": [],
"domains": {
...
},
"folders": [
{
"id": "<RANDOM GUID>",
"name": "...",
"object": "folder",
"revisionDate": "2026-07-31T06:46:39.236881Z"
}
],
"object": "sync",
"policies": [],
"profile": {
"_status": 0,
"avatarColor": "#8200db",
"creationDate": "2026-04-17T05:22:32.690513Z",
"culture": "en-US",
"email": "<EMAIL>",
"emailVerified": true,
"forcePasswordReset": false,
"id": "<RANDOM GUID>",
"key": "...",
"name": "<Name>",
"object": "profile",
"organizations": [],
"premium": true,
"premiumFromOrganization": false,
"privateKey": "...",
"providerOrganizations": [],
"providers": [],
"securityStamp": "<RANDOM GUID>",
"twoFactorEnabled": false,
"usesKeyConnector": false
},
"sends": [],
"userDecryption": {
"masterPasswordUnlock": {
"kdf": {
"iterations": <ITERATIONS>,
"kdfType": 0,
"memory": null,
"parallelism": null
},
"masterKeyEncryptedUserKey": "...",
"masterKeyWrappedUserKey": "...",
"salt": "<EMAIL>"
}
}
}
This vault JSON is used to sync vault ciphers or vault keys.
After getting the vault, VaultWarden then establishes a connection with the WebSocket URL <VWURL>/notifications/hub?access_token=.... Here are the notify types:
-
0: Cipher is updated -
1: Cipher is created -
2: Cipher is deleted -
3: Folder is deleted -
4: All Ciphers changed -
5: Whole vault changed -
6: Org Keys Changed -
7: Folder is created -
8: Folder is updated -
9: Cipher is binned -
10: Account settings changed -
11: Logout
It then sends a GET request to <VWURL>/api/accounts/profile which returns a JSON response:
{
"_status": 0,
"avatarColor": "#8200db",
"creationDate": "2026-04-17T05:22:32.690513Z",
"culture": "en-US",
"email": <EMAIL>,
"emailVerified": true,
"forcePasswordReset": false,
"id": <UNIQUE GUID>,
"key": "...",
"name": <NAME>,
"object": "profile",
"organizations": [],
"premium": true,
"premiumFromOrganization": false,
"privateKey": "...",
"providerOrganizations": [],
"providers": [],
"securityStamp": <UNIQUE GUID>,
"twoFactorEnabled": false,
"usesKeyConnector": false
}
After that, it sends a GET request to <VWURL>/api/accounts/revision-date that returns a number
It then sends a GET request to <VWURL>/api/auth-requests/pending which returns a JSON response like the following:
{
"continuationToken": null,
"data": [],
"object": "list"
}
First, VaultWarden retrieves a new accessToken via Refresh Token. Then it re-sync's the vault using a Sync Request and then retrieves profile info using a Profile Request. It then gets the revision date and pending info using a Revision Date Request and a Pending Request.