Skip to article
← Back to knowledge base

Budget: paid seats against observed use

Administration

What you pay for each tool, and how many of those seats anybody uses.

The detection pipeline answers who uses which AI tools. The Budget view adds the other half of the question: which of those does the organisation pay for, and do the two lists agree. Link a tool, record what its seats cost, give the portal the member list the plan covers, and each tool's card shows:

  • paid seats never observed - users on the vendor's member list that no detection surface has matched to activity. Downgrade candidates, with the caveat the card itself carries: a coverage gap looks identical to absence, so check the Sources view before acting.

  • observed users not matched to a seat - identities the estate has seen on the tool that match nobody on the member list.

  • personal-account use alongside the paid plan - the same rows the Personal accounts view holds, surfaced under the seats being paid for, because that is where the spend question lives.

  • the per-tier arithmetic: seats paid versus seats assigned to users, price per seat, monthly total, and the renewal date with a warning inside 30 days.

The subscriptions, user lists and connections are receiver state, every write is role-gated (viewers read, admins write) and lands in the audit trail, and the portal remains a page over data others hold.

Where the user list comes from

Three sources, per tool, and each card names which one its rows came from:

Source

How

Good for

Automatic

The vendor's admin API, synced on demand with a stored key

Anthropic (Claude Enterprise / Console), Fireflies

Import

Paste the member export from the vendor's admin page

ChatGPT Business, and any vendor without an admin API

Manual

Add members by hand on the card

Small teams, one-off seats

The three coexist: an API sync replaces the previous sync, a re-import replaces the previous import, and neither touches rows added by hand.

Automatic setup currently supports Anthropic and Fireflies. More vendors will be added for automatic configuration - if you want a particular tool supported, raise an issue on support.

ChatGPT Business is import-only, and that is OpenAI's boundary, not Nyxus's: the Business (formerly Team) plan exposes no admin API, and SCIM and the Compliance API are Enterprise-only. The workspace member list exports from Workspace settings → Members, and the import understands its email,role,seat type shape directly.

The Anthropic connection

The sync needs a scoped Admin API key with the read:members scope. The organisation's primary owner creates it:

  • Claude Enterprise orgs (claude.ai): Organization settings → API → Keys

  • Claude Console (platform.claude.com) orgs: Settings → Admin keys

Claude Team plans do not offer admin keys - the API section is simply absent from a Team workspace's organization settings, even for the primary owner. A Team workspace uses the Import path instead: Organization settings → Members has a CSV export (name, email, role, status, seat tier), and the import understands it directly - the seat tier column maps onto the subscription's tiers by name. Pasting the rows out of a spreadsheet works too; the parser handles tabs as well as commas. Include the header row in the paste: without it only the addresses can be mapped, and roles and seat tiers import empty (the preview warns when that is about to happen). Imports and syncs enrich rather than erase - a blank role, seat tier or name never overwrites a value already stored, which is also how tiers assigned by import survive an API sync that does not report them. Everything downstream behaves identically.

Select read-only scopes when creating it. The sync only ever lists members; a key that can also write members is more credential than this feature needs, and the receiver will happily use less.

The Fireflies connection

An API key from fireflies.ai → Integrations → Fireflies API. A team admin's key lists the whole team, and the sync also records the per-user usage the API reports (transcripts, minutes), which shows up in the users table.

Where the key lives, exactly

The vendor API key is stored by the receiver in its state database, alongside the log-store credential and under the same stated trade: recoverable by design, because the receiver must present it outward to sync. It is written by one admin request and never read back out by any route - the portal masks it, /admin/budget reports only that one is set, and a sync spends it server-side. Replacing the key resets the sync history, since what the old key last did says nothing about the new one.

How matching works, and what it will not claim

Findings deliberately carry account domains and identity hints, never full addresses - that is a privacy property of the platform, and Budget does not weaken it. A user's email is matched against observed identities (cloud sign-in names, and people attached to devices through the identity map) by normalising both sides to bare letters and digits, the same trick the identity-map suggestions use.

That makes the matching honest but fuzzy, and the cards phrase it accordingly: an unmatched member is "never observed", not "not using it"; an unmatched observed user is "not matched to a seat", not "unlicensed". Devices running the tool with no person attached are counted separately and excluded from both lists - an identity map (Sources view) is what turns those into names.

Seats nobody is using

Budget > Unused seats measures each seat against recorded use rather than the current window. A member list only says who holds a seat. When they last used it comes from the estate itself - the browser extension, endpoint collectors (through the identity map) and cloud sign-ins - recorded one finished day at a time in estate history. No vendor connection is needed: a CSV import gets the same answer.

A seat is

When

In use

its holder used a tool the plan covers within the plan's threshold

Unused

they did not, and they were seen doing something else in that time

Can't tell

the address matches nobody the estate has seen; the holder was not seen at all in that time; or fewer days are recorded than the threshold

The threshold is per plan, Unused after (days), 60 by default. Only unused seats are counted as money that could be saved. A holder not seen at all looks exactly like a laptop with no collector, so that seat is never called unused, and neither is one on a record too short to judge.

Use means an active signal: a sign-in, a visit, a call to the tool's model service. A tool only found installed on a machine, such as an editor nobody opens, is not use. A tool found signed in on a scan counts as use, which errs toward keeping a seat rather than cutting someone who uses it.

For each unused seat an admin records Reclaim, or Keep with a reason. Both land in the audit trail. Nyxus does not remove seats at the vendor.

Days recorded before this existed are read back once, from the tools each person was seen with. Those also counted a tool found installed, so a seat's last use can read later than it really was.

Renewals and the negotiation pack

Budget > Renewals lists every plan by renewal date with its seats paid, assigned, in use, unused and can't tell, and what it costs a month. Each plan's negotiation pack is the same figures seat by seat, to print or download as CSV. A cell a spreadsheet would run as a formula is written as text.

Chargeback by team

Budget > Chargeback charges each seat's monthly price, whole, to one team: the only team that covers the person holding it. Someone more than one team covers is listed until an admin chooses which team pays, and the choice stays until it is changed. Seats held by people in no team, seats whose address matches nobody, and seats paid for but assigned to nobody have lines of their own, so the totals always add up to the bill. Budget, chargeback included, is shown to organisation-wide accounts only.

One licence, several tools

A subscription is a licence, and a licence often entitles more than one tool: a Claude seat covers claude and claude-code, a ChatGPT Business seat covers chatgpt and codex. Each subscription therefore records what it covers, and the receiver refuses a tool covered by two subscriptions - one licence billed twice stops being a mistake anyone can make.

"Never observed" is one verdict judged against the whole covered set: a member seen on any covered tool is observed, full stop. Someone dark on claude but living in claude-code is not a downgrade candidate. The per-tool detail survives on the positive side - seen-on pills per member, and an "observed on: claude 8 · claude-code 5" line - telling you how the licence is used, never who is "missing" from one surface.

Tools sold as one licence share a license_group in the registry (shipped for the known bundles; the Tool registry view sets it on your own entries), which is how the wizard pre-ticks claude-code when you link claude, and how the page spots the same licence linked twice and says how to fold the two together: unlink the secondary first, then edit the primary and tick it under "also covers".

Per-tier coverage is how mixed plans are said precisely: premium covers claude and claude-code, standard covers claude alone. Two seat-economics findings follow from it - "premium seats never observed on claude-code" (the premium delta may be unused; framed as economics, these members still count as observed) and its inverse, "standard seat observed on claude-code" (either the coverage is recorded too narrowly or the seat grants more than it should).

Sharing it

The people who need this page mostly do not have a portal login: a finance owner approving the renewal, a manager deciding on a downgrade, a board pack. Share / export opens a report view of the same numbers, arranged to leave the browser:

  • Everything is expanded. Nothing a recipient needs sits behind a click they cannot make on paper.

  • It carries its own provenance: when it was generated, which version produced it, the observation window, and each licence's user-list source and date.

  • It closes with a "how to read this" note in plain language, so a recipient who has never seen the portal knows that spend is recorded, everything else is observed, and "never observed" is a lead rather than a verdict.

  • Print / save as PDF uses the browser's own print, with a stylesheet that drops the sidebar, topbar and buttons and forces a light palette, so a dark-theme portal does not produce a black PDF.

  • Download CSV writes one flat table for pivoting: a row_type column separates seats, observed users matched to no seat, and personal-account use. Cells that could be read as spreadsheet formulas are neutralised on the way out.

  • Withhold names replaces every person and device with a count, in the page and in the CSV (the file is named -anonymised). The report names individuals and their personal account domains, so circulating it more widely than the seat owner deserves one deliberate click rather than careful editing afterwards.

The report is a view, not a stored artifact: it is generated from current data each time it is opened, and #budget-report links straight to it for anyone with a login.

Currency

Each subscription records its own currency - the one the vendor actually invoices in - and those per-currency totals are always shown. Set a preferred currency (Settings > Display and alerting, or step 3 of the setup wizard) and the headline additionally converts into it using the ECB's daily reference rate, fetched by the receiver from the keyless Frankfurter API and cached for half a day. The conversion never hides its working: the rate date and the unconverted per-currency figures sit right beside the converted number, and if the rate source cannot answer - or a subscription uses a currency the ECB does not fix - the headline simply falls back to the per-currency breakdown, and says which of those it is: no preferred currency set, a rate source the deployment cannot reach, or a currency the ECB does not fix. Newly linked tools default to the preferred currency.

Seat tiers

Vendors sell mixed plans (Claude Team and ChatGPT Business both split standard from premium seats), so a subscription holds up to ten named tiers, each with a seat count and a per-seat monthly price. Tier names matter: a user row whose seat tier matches a tier's name counts against that tier's seats, which is how "8 premium seats paid, 3 assigned" falls out. The vendor APIs do not expose per-user tiers - a plan with a single tier therefore counts every member against it automatically (everyone on the plan is on that tier by definition, the one derivation that needs no vendor data). On multi-tier plans tier assignment comes from the CSV import or the card. Tier matching is forgiving: an imported "Premium seat" counts against a tier named "premium" (the tier's name just has to appear in the value), and whatever matches nothing is named under the table with a count, never silently dropped to zero.

© 2026 Aman Karir