Skip to main content

Bitwarden

Bitwarden is a credential vault, which makes its event log a high-signal identity feed: who signed in, who read or copied which vault item, who was added to or removed from the organization, and who changed a collection's membership or an enterprise policy. Vault access is one of the few places where an attacker's first move after taking an account looks like an ordinary, successful action, so this feed pairs well with authentication data from the identity provider. Fluency collects it by polling the Bitwarden Public API on a schedule.

Authentication is an OAuth 2.0 Client Credentials exchange using the organization API key — a client_id and client_secret pair that already exists for every Teams or Enterprise organization. You do not create anything in Bitwarden; you read a key that is already there. The catch is who may read it: only an organization Owner, and only after re-entering their master password.

The setup has two halves:

  1. In Bitwarden — read the organization API key from the Admin Console and note which Bitwarden cloud the organization lives on.
  2. In Fluency — install the Bitwarden application template and paste those values in.

What arrives

Two different kinds of data, from the same credential.

Event logs come from GET /public/events and land in the data lake. Bitwarden records them against numeric type codes rather than names, and the API returns the code — 1000, not User_LoggedIn:

Event familyCodesExamples
Authentication10001011Logged in (1000), changed account password (1001), two-step login saved (1002) and turned off (1003), failed login with incorrect password (1005), failed login with incorrect two-step login (1006), individual vault exported (1007), device approval requested (1010)
Vault item access11001132Created (1100), edited (1101), permanently deleted (1102), viewed (1107), viewed password (1108), viewed hidden field (1109), copied password (1111), autofilled (1114), sent to trash (1115), viewed card number (1117), viewed TOTP seed (1118), copied bank account number (1119), copied passport number (1125), copied IBAN (1129)
Collections and groups13001302, 14001402Collection and group created, edited, deleted
Membership15001524Invited (1500), confirmed (1501), edited (1502), removed (1503), groups changed (1504), enrolled in account recovery (1506), first SSO login (1510), access revoked (1511), restored (1512), device approved (1513) or denied (1514), left organization (1516), revoked for two-factor non-compliance (1520), staged (1523)
Organization settings16001628, 1700Settings edited (1600), vault purged (1601), organization vault exported (1602), vault accessed by a managing provider (1603), SSO enabled (1604) or disabled (1605), Key Connector enabled (1606), collection-management restrictions changed (16101617), policy modified (1700)
Domains and providers18001803, 19001903, 20002003Provider user and provider organization changes, domain added, removed, verified, not verified
Secrets Manager21002305Secret retrieved (2100), created, edited, deleted; project and machine-account changes
Phishing blocker24002402Site accessed, site exited, bypassed
Send25002511Send created with or without password and email verification, edited, deleted, accessed (2510, 2511)

The table above is a map of the neighbourhoods rather than a complete index. For the full list, use Bitwarden's OpenAPI specification rather than its event log reference — see below.

Bitwarden's two published code lists disagree — use the specification

The EventType enum in the live API specification carries 149 codes. The prose list on Bitwarden's event log help page carries 113, and the difference is not only length:

  • 37 codes exist in the specification and are absent from the help page. Several matter for detection: revoked for two-factor non-compliance (1520) and for single-organization non-compliance (1521), the provider-user and provider-organization ranges (18001803, 19001903) including provider vault access (1903), the phishing blocker events (24002402), invite-link creation and use (1524, 16241628), secret permanently deleted and restored (2104, 2105), and the whole 11191132 block covering copying and revealing bank account numbers, licence numbers, passport numbers, SWIFT codes, IBANs and national identification numbers.
  • One code on the help page no longer exists. 2004 "Clicked vault banner button" has been renumbered to 1522.
  • One label is wrong. The help page gives 1118 as "viewed security code", which is a duplicate of its own entry for 1110. The specification has 1118 as revealing an item's TOTP seed — a materially different and more sensitive action.

A detection built from the help page's list will therefore miss real events and mislabel two of them. The specification is generated from the server's own enum; prefer it.

Organization resources come from the other four list endpoints — /public/members, /public/groups, /public/policies and /public/collections — and land on SIEM → Resources rather than in the lake, as the Members, Groups, Policies and Collections record sets described in Resources. They are not a second event feed. They are the lookup tables for the first one.

The event stream carries IDs, not names — the resources are how you read it

Every actor and object in an event is a bare UUID. The EventResponseModel has actingUserId, memberId, itemId, collectionId, groupId and policyId, and no name or email field anywhere. A login event tells you that a9731c4c-… signed in; it does not tell you who that is.

Bitwarden's own web console resolves these for display, and its .csv export adds userName and userEmail columns. The JSON API does neither. Neither do the SIEM integrations Bitwarden ships — Splunk's app documents actingUserEmail, actingUserName and memberName as fields it adds, not fields Bitwarden sends.

This is what the Resources sync is for. The Members record set carries userId, name, email, status, twoFactorEnabled, resetPasswordEnrolled and externalId — exactly the fields of the Public API's member model — so actingUserId from an event joins to UserId on a member record. Confirm Members is populated before you write a detection that names people, or every alert you raise will identify its subject as a UUID.

Bitwarden does not vouch for the completeness of this log

Unusually, the vendor says in its own documentation that this feed may not be good enough for the purpose most people collect it for:

Event logs rely on user-reported and client-level data, which technically could be modified or suppressed. Because of this potential situation, Bitwarden event logs may not suffice for security, legal forensics, or auditing purposes for all users and organizations.

The mechanism is worth understanding, because it decides which events you can trust:

  • Server events are recorded instantly. Membership changes, policy edits, organization settings, SSO — anything the server performs, the server records.
  • Client events are batched and self-reported. They are "transmitted to the server every 60 seconds", and Bitwarden is explicit that "events cannot be recorded if the client loses API connectivity or is somehow modified to not send events."

The irony is that the client-reported half is the interesting half. Viewed password (1108), copied password (1111) and autofilled (1114) are the events that tell you a credential was actually taken, and they are precisely the ones reported by software running on the endpoint you are investigating. An attacker with control of that endpoint can stop them being sent, and nothing in the feed will record the silence.

There is no configuration that fixes this — it is a property of where the data comes from. What it changes is how you reason with the feed. Alert on what is present; never conclude from what is absent. An account with no 1108 events has not been shown to be safe. Corroborate item-access findings against server-side evidence — the endpoint agent, the identity provider, network egress — rather than treating this log as the record of what happened.

Before you begin

RequirementWhy, and how to check
A Teams or Enterprise organizationEvent logs and the Public API are both business-tier features. Free, Families and Premium have neither — there is no organization API key to find and nothing for the integration to read. Not a workaround case: the plan has to change.
An organization OwnerBitwarden restricts Manage API key to Owners alone. An Admin can read event logs in the console and still not be able to retrieve the key; an Enterprise custom role can be granted Access event logs and still not be able to retrieve the key. If you are not an Owner, this step belongs to someone who is.
That Owner's master passwordViewing the key re-prompts for it. An Owner signed in with an unlocked vault still gets the prompt, so it needs to be the person themselves, not a shared session.
Which Bitwarden cloud the organization is onLook at the web vault address the organization's members sign in to: vault.bitwarden.com is the US cloud, vault.bitwarden.eu is the EU cloud. This becomes the Region parameter, which opens empty and has to be typed, and it is not derivable from the credentials.
Self-hosted or Gov cloudThe template takes a Region, not a URL, so it cannot name a self-hosted server. Bitwarden also runs a US Government cloud (api.bitwarden-gov.com) that the two documented region values do not cover. In either case, confirm support with your Fluency contact before promising ingestion rather than guessing at a value.

Part 1 — Bitwarden

1. Open the Admin Console

The organization API key lives in the Admin Console, which is a different application from the vault most people see when they sign in. Log in to the web vault and use the product switcher at the foot of the left-hand navigation:

Bitwarden Password Manager with the product switcher expanded at the lower left, showing Password Manager, Secrets Manager and Admin Console

Select Admin Console. If the entry is not there, the account is not a member of a Teams or Enterprise organization.

2. Read the organization API key

In the Admin Console, go to Settings → Organization info and scroll to the API Key section:

Bitwarden Admin Console Organization info page with the Settings navigation expanded and the API Key section showing View API key and Rotate API key buttons

Select View API key. Bitwarden asks for your master password, then shows the credential:

Bitwarden API Key dialog showing client_id prefixed organization, a redacted client_secret, scope api.organization, grant_type client_credentials, and a warning that the API key has full access to the organization

Copy both values. They map to the Fluency form directly:

Bitwarden labelFluency parameter
client_idClientID
client_secretClientSecret

The dialog also shows scope: api.organization and grant_type: client_credentials. Both are fixed, and neither is something you enter anywhere — they are shown so you can confirm you are looking at the right key.

Unlike most credentials in these guides, this one is not a once-only reveal: an Owner can come back and read it again. What you cannot do is hold two live keys at once, which shapes Rotating the credential below.

Bitwarden has two API keys and only one of them works here

There is a personal API key at Settings → Security → Keys in the Password Manager. It has its own View API key button, its own master-password prompt, and a dialog that looks almost identical to the one above. It is not this key, and it will not authenticate against the Public API.

Two things tell them apart, both visible in the dialog:

Organization key (correct)Personal key (wrong)
client_id beginsorganization.user.
scopeapi.organizationapi

If the key you copied starts with user., you are in the Password Manager's settings rather than the Admin Console's. Go back to step 1.

This key administers the organization — it does not merely read the log

Bitwarden puts the warning in the dialog itself: "Your API key has full access to the organization. It should be kept secret." That is not boilerplate. The api.organization scope is the only scope this key has, and it covers the entire Public API — of its sixteen paths, thirteen accept writes.

The same credential Fluency uses to read events can create and remove members, revoke and restore their access, edit groups and their memberships, update and delete collections, change enterprise policies, bulk-import members and groups, and alter the organization's subscription. There is no read-only variant to drop to and no way to narrow it.

Treat it as an administrative credential, not a log-reader: hand it over only through a secure channel — Bitwarden suggests Bitwarden Send — never by email or in a ticket, and rotate it if it has ever been somewhere it should not have been.

Verify before you leave Bitwarden

Two calls prove the key, the region and the plan entitlement at once. The first exchanges the credentials for a token; the second uses it. Swap identity.bitwarden.com and api.bitwarden.com for the .eu hosts if the organization is on the EU cloud.

BW_CLIENT_ID='<client_id from step 2>'
BW_CLIENT_SECRET='<client_secret from step 2>'

TOKEN=$(curl -s -X POST https://identity.bitwarden.com/connect/token \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d "grant_type=client_credentials&scope=api.organization&client_id=$BW_CLIENT_ID&client_secret=$BW_CLIENT_SECRET" \
| python3 -c 'import json,sys; print(json.load(sys.stdin)["access_token"])')

curl -s -H "Authorization: Bearer $TOKEN" \
'https://api.bitwarden.com/public/events'

A working setup returns the last 30 days of events:

{"object":"list","data":[{"object":"event","type":1000,"itemId":null,"collectionId":null,"groupId":null,"policyId":null,"memberId":null,"actingUserId":"a2549f79-a71f-4eb9-9234-eb7247333f94","date":"2026-07-31T15:01:21.698Z","device":9,"ipAddress":"172.16.254.1"}],"continuationToken":"string"}
ResponseWhat it means
{"error":"invalid_client"} — HTTP 400The credential was rejected. See the note below — this one message covers several distinct causes.
HTTP 401, www-authenticate: Bearer error="invalid_token"The token is wrong or expired. Usually the first call failed and the shell carried on with an empty variable — run the token request on its own and read what it returns.
HTTP 404The organization has no Public API entitlement, or you called a host that does not serve this organization. Confirm the plan is Teams or Enterprise, and that the host matches the cloud from Before you begin.
{"object":"list","data":[],"continuationToken":null}Everything works; the organization has had no recorded activity in the last 30 days. Sign in to the web vault to generate a 1000 and try again.
HTTP 429Rate limited. Bitwarden meters in one-minute windows and reports the budget in x-rate-limit-remaining and x-rate-limit-reset response headers. Wait for the reset time.
One error message, several causes

Bitwarden's token endpoint answers with a bare {"error":"invalid_client"} and no error_description, and returns exactly that for every one of these:

  • a mistyped or stale client_id
  • a mistyped or stale client_secret
  • a personal API key used instead of the organization key
  • a missing or incorrect scope
  • a key from the other cloud — US credentials against identity.bitwarden.eu, or the reverse

Work through them in that order rather than re-copying the secret repeatedly. The cheapest check is the client_id prefix: it must begin organization..

Part 2 — Fluency

Install the Application

Go to Platform → Applications → Install Application From Template and choose Bitwarden from the Cloud-based Business Softwares category. The form asks for three values, all required, and all three open empty:

ParameterNotes
RegionOpens empty, and is required. Type the cloud the organization belongs to — US for vault.bitwarden.com, EU for vault.bitwarden.eu. See the caution below.
ClientIDBitwarden's client_id from step 2. Begins organization..
ClientSecretBitwarden's client_secret from step 2. Masked once the application is installed.
datalakePre-filled managed.
datalake index namePre-filled Bitwarden. Two applications writing to the same index name in the same lake will collide, and the second one aborts.

The last two sit in the collapsed Advanced Configurations row below the parameters — expand it to reach them. The two credential fields carry bare parameter names on the form rather than the friendly labels most templates use, so they read as ClientID and ClientSecret rather than Client ID and Client Secret.

Setup New Bitwarden Application panel

Region opens empty and is typed, not chosen — and a wrong region looks like a bad secret

Region is a required free-text field that arrives empty: there is no default to accept, and no drop-down of valid values, so nothing catches a typo or a wrong cloud at install time. It selects which pair of Bitwarden hosts the connector talks to — identity.bitwarden.com and api.bitwarden.com, or their .eu counterparts — and the credentials are cloud-specific.

Point a correct US key at the EU cloud and Bitwarden answers invalid_client, the same message it gives for a wrong secret. The application installs cleanly and collects nothing, and the obvious next move — re-reading the key — will not help. Establish the region in Part 1 by looking at the web vault address the organization's members sign in to, then type it here.

An earlier version of this page said the field arrives pre-filled US. It does not. A US organisation has to type US like anyone else, and an install that left the field alone would not have got past the required-field marker.

Press Install. The application then appears in the Installed Applications view, where its badge reads Running once the pipeline is up.

Confirm it is running

The card is named after the template. A healthy install shows Running, but because this template writes resources as well as events, Running on its own is a weaker signal here than usual — check both halves.

The resources are the faster confirmation. Open SIEM → Resources. Once the application has run, a Bitwarden card appears with a button for each record set it collects — Members, Groups, Policies and Collections. Members is the one to check: it is the organization roster, it is never legitimately empty, and a non-zero count proves the credential and the region are both right.

Bitwarden Members resource list with Total Records 0, empty facet groups, and a No data found message

The capture above is the state you do not want: Total Records 0, every facet group reading (0), and No data found. Note that this is distinct from a Resource Load Failed message, which means the record set was never loaded at all. See Resources for the full page reference.

Then confirm events. Generate one that is easy to recognise — sign out and back in to produce a 1000, or view a password in an organization collection to produce a 1108. Two delays stack before it can arrive:

  • Bitwarden batches client events and transmits them every 60 seconds. A login recorded by the server appears sooner than an item view recorded by the browser extension.
  • The connector then polls on its own schedule.

Allow several minutes before concluding anything is wrong, and cross-check against Bitwarden's own view of the same window in Admin Console → Reporting → Event logs:

Bitwarden Admin Console Event logs page with a From and To date range, Update and Export buttons, and rows showing Timestamp, Client, Member and Event columns

If that page is also empty for the window, the integration is fine and the organization is simply quiet — a small organization can genuinely produce very little. If it shows events that never reach Fluency, work through Troubleshooting.

See Confirm data is arriving for the general procedure.

Maintenance

Rotating the credential

Bitwarden holds one active key per organization, so there is no overlap window: Rotate API key invalidates the old client_secret the moment it issues the new one. Bitwarden's own wording is that "active implementations of your current API key will need to be reconfigured with the new key before use" — this integration is one of those implementations, and so is anything else using the same key, including Directory Connector and any other SIEM.

Work in this order, and account for the other consumers before you start:

  1. Find out what else uses the organization API key. It is one key for the whole organization, not one per application, so rotating for Fluency rotates for everything.
  2. In the Admin Console, Settings → Organization info → Rotate API key, and copy the new client_secret.
  3. In Fluency, uninstall the Bitwarden application and reinstall it with the new secret. Template parameters are read-only after install, so there is no edit path.
  4. Reconfigure every other consumer found in step 1.
  5. Confirm events resume, and that Members on the Resources page is still populated.

Bitwarden retains event log data indefinitely and the API's start and end parameters can reach back up to 367 days, so a short gap is not inherently lost data — the records stay retrievable from Bitwarden's side. Do not count on the connector to fetch them for you: if the gap matters, export the period from Reporting → Event logs while you still know the dates.

The key does not expire on a timer, so there is no renewal date to record.

Removing the integration

  1. Uninstall the Bitwarden application in Fluency, so the connector stops calling Bitwarden.
  2. If nothing else uses the organization API key, rotate it in the Admin Console. That is the only way to revoke it — there is no per-application grant to withdraw, so a key left un-rotated remains a working administrative credential wherever a copy of it survives.

Deleting the Fluency application does not delete already-collected events from the lake, or the collected resource records.

Troubleshooting

SymptomLikely causeFix
invalid_client at install, or no data at allWrong client_id, wrong client_secret, a personal key, or the wrong RegionAll four give the same bare error. Check the client_id prefix is organization. first, then the region against the web vault address. See the note in Part 1.
Application installs, Resources shows no Bitwarden cardThe first collection has not completed, or the credential is rejectedGive it a poll interval, then run the verification calls yourself. A working key that returns members proves the fault is not the credential.
Members shows Total Records 0Credential or region wrong, or the sync has not runThe roster is never legitimately empty. Treat zero as a failure, not as quiet.
Resource Load Failed on a record setThat record set was never loadedDifferent from an empty list. Check the application's Actions list for what the install actually created.
Events arrive but every actor is a UUIDExpected — the API sends no namesJoin actingUserId to UserId on the Members record set. See What arrives.
Item-access events (1107, 1108, 1111) missing for a userClient-reported events were never transmittedThe client batches every 60 seconds and cannot report if it is offline or tampered with. Absence is not evidence — corroborate elsewhere. See the warning in What arrives.
Membership and policy events arrive, item events do notThe same cause, seen at scaleServer-side events are unaffected by client reporting; client-side ones are. A feed with only server events suggests clients are not reaching Bitwarden.
Worked yesterday, stopped todayThe API key was rotated without updating FluencyRotation is immediate and organization-wide. Reinstall with the new secret (Rotating the credential).
Stopped, and the key was not rotatedThe plan lapsed below TeamsPublic API access and event logs both disappear with the entitlement.
HTTP 429The organization's API budget is shared with every other tool using this keyBitwarden meters per minute and reports x-rate-limit-remaining and x-rate-limit-reset. Directory Connector and a second SIEM on the same key are the usual company.
Counts do not match Reporting → Event logsConsole and API do not span the same window by defaultThe console shows the date range in its From/To fields; the API defaults to the last 30 days. Compare like for like before treating a difference as loss.

Under the hood

Engineering reference — endpoints, pagination and response format

What this section describes

Everything below describes the Bitwarden API — documented, specified in OpenAPI, and exercised directly — rather than the collector's use of it. The settings that belong to your own install — its cadence, how far the first collection reaches back, which data lake index the events land in and the field names they land under — are recorded on the install itself: read the installed application's Actions list for where the data goes, and treat the Resources page as the confirmation that the resource half is working. Do not carry figures across from other pages on this site.

Authentication

POST https://identity.bitwarden.com/connect/token
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials
scope=api.organization
client_id=<Client ID>
client_secret=<Client Secret>

Returns {"access_token": "…", "expires_in": 3600, "token_type": "Bearer"}. Tokens last 60 minutes; an expired one returns 401. There is no refresh token in this flow — a new token is requested when the old one expires.

Hosts

CloudIdentity endpointAPI base
UShttps://identity.bitwarden.com/connect/tokenhttps://api.bitwarden.com
EUhttps://identity.bitwarden.eu/connect/tokenhttps://api.bitwarden.eu
Govhttps://identity.bitwarden-gov.com/connect/tokenhttps://api.bitwarden-gov.com
Self-hostedhttps://your.domain.com/identity/connect/tokenhttps://your.domain.com/api

The template's Region parameter covers the first two. Bitwarden's OpenAPI specification lists the Gov cloud as a third server; for that cloud, or for a self-hosted server, see Before you begin.

Events endpoint

GET /public/events, with optional start, end, actingUserId, itemId, secretId, projectId and continuationToken query parameters.

Two behaviours matter for collection:

  • No filters means the last 30 days. The specification is explicit: "If no filters are provided, it will return the last 30 days of event for the organization." A collector that does not send start gets a 30-day window on every call, not everything since the last poll.
  • Pagination is by continuation token, 50 records to a page. Bitwarden documents that "a continuation token is provided for queries that return over 50 logs"; the next page is fetched by appending ?continuationToken=<value>. There is no page-size parameter.

Retention is unusual for this class of feed: event data is "retained indefinitely", with the constraint that "you can only view up to 367 days worth of data at a time". Unlike the short API windows on many other integrations, an outage here does not put the data permanently out of reach — the events remain fetchable with an explicit start.

Event object

{
"object": "event",
"type": 1000,
"itemId": null,
"collectionId": null,
"groupId": null,
"policyId": null,
"memberId": null,
"actingUserId": "a2549f79-a71f-4eb9-9234-eb7247333f94",
"installationId": null,
"date": "2026-07-31T15:01:21.698Z",
"device": 9,
"ipAddress": "172.16.254.1",
"secretId": null,
"projectId": null,
"serviceAccountId": null
}

Three properties of this object shape how it should be queried:

  • Fields not relevant to the event type are null. A login carries a type, a date, an actingUserId, a device and an ipAddress, and nulls for everything else. Filter on type first.
  • There is no event ID. The model has no unique identifier for the record itself, so there is no key to deduplicate on if an overlapping window is ever fetched twice. The nearest thing to a natural key is the whole tuple.
  • device is a numeric enum where 0 is meaningful. The specification's 27 values run 0 Android, 1 iOS, 2 ChromeExtension, 3 FirefoxExtension, 4 OperaExtension, 5 EdgeExtension, 6 WindowsDesktop, 7 MacOsDesktop, 8 LinuxDesktop, 9 ChromeBrowser, 10 FirefoxBrowser, 11 OperaBrowser, 12 EdgeBrowser, 13 IEBrowser, 14 UnknownBrowser, 15 AndroidAmazon, 16 UWP, 17 SafariBrowser, 18 VivaldiBrowser, 19 VivaldiExtension, 20 SafariExtension, 21 SDK, 22 Server, 23 WindowsCLI, 24 MacOsCLI, 25 LinuxCLI, 26 DuckDuckGoBrowser. Bitwarden's own sample payload shows "device": 0, which is Android rather than "unknown" — do not read a zero as a missing value.

Resource endpoints

The four record sets on SIEM → Resources correspond one-to-one with the Public API's other list endpoints, and their fields are the API's response models unchanged:

ResourceEndpointModel fields
MembersGET /public/membersid, userId, name, email, status, type, externalId, ssoExternalId, twoFactorEnabled, resetPasswordEnrolled, collections, permissions
GroupsGET /public/groupsid, name, externalId, collections
PoliciesGET /public/policiesid, type, enabled, data
CollectionsGET /public/collectionsid, externalId, groups

All four paginate by continuation token in the same way as events. The Members facets captured on the Resources page — Name, Email, Status, UserId, TwoFactorEnabled, ResetPasswordEnrolled, ExternalId — match the member model exactly, which is the evidence that the sync is a direct projection of this endpoint.

Rate limiting

Responses carry x-rate-limit-limit, x-rate-limit-remaining and x-rate-limit-reset headers. The window is one minute and x-rate-limit-reset is an absolute timestamp. Exceeding the budget returns 429, which Bitwarden documents as "too many requests hit the API too quickly. We recommend scaling back the number of requests."

The budget belongs to the organization's key rather than to this integration, so Directory Connector, another SIEM, or a script on the same key all draw from the same allowance.

References

Bitwarden

Fluency

  • Okta and Cisco Duo — identity feeds worth correlating with vault access, and the server-side corroboration this log cannot provide on its own.
  • Box.com — another feed whose vendor documents its own limits, in that case duplicates and ordering rather than completeness.