@herrhelms/openai-token-cost-reports
v2.1.1
Published
Track OpenAI API token usage per Paperclip company, store daily rollups, render a dashboard, and export a monthly CSV for token-based billing. Pricing table covers GPT-5.5 / GPT-5.4 family and GPT-5.3 Codex.
Downloads
259
Maintainers
Readme
OpenAI Token Cost Reports
A Paperclip plugin that turns raw OpenAI API consumption into a token-priced client invoice.
Track OpenAI API token usage per Paperclip company, see who burned what across agents and models, and export a client-facing monthly invoice CSV in the currency you bill in. Daily FX snapshots and configurable margin.
Designed for operators on the OpenAI API who want to bill clients in a real currency without losing visibility on what API list price would have been.
Install
# From inside a Paperclip-enabled environment with the CLI installed:
paperclipai plugin install @herrhelms/openai-token-cost-reports
# Verify the install
paperclipai plugin list
# expect: key=openai-token-cost-reports status=ready version=2.1.1 id=<uuid>The host runs the plugin's database migrations automatically and registers the dashboard + settings page slots. No additional configuration is required to install — pricing and currency are set per-company in the Settings page after install.
The npm package is scoped (
@herrhelms/…) but the in-app plugin key is not — that's a Paperclip-host convention. To uninstall, use the unscoped key:paperclipai plugin uninstall openai-token-cost-reports
paperclipai plugin listprints the unscoped key next to each install, so you can always discover it from the host.
Requirements
- Paperclip host with
@paperclipai/plugin-sdk>=2026.609.0available. - Node.js 22+ on the host that runs the plugin worker.
- Outbound HTTP access from the host to
https://open.er-api.com(used by the hourly FX-rate job). - The plugin must be granted the capabilities listed in Capabilities. The Paperclip host prompts the operator on install.
Where it shows up after install
| Surface | Where to find it | What's there |
| --- | --- | --- |
| Dashboard | /$COMPANY_HANDLE/monthly-report-openai (in the company sidebar) | Usage KPIs, per-model bars, per-agent table, daily chart, monthly CSV export |
| Settings | /$COMPANY_HANDLE/company/settings/instance/plugins/<install-uuid> | Free-form pricing matrix (Add / Edit / Delete rows), margin, billing currency, FX-rate status, snapshot history timeline, cost adjustment multiplier |
The <install-uuid> is shown by paperclipai plugin list after install.
Quick start
After install, open the Settings page for any company:
- Pick a billing currency (10 supported). The hourly FX job will fetch today's USD→target rate and store one row per
(day, currency). - Set a margin % — the percentage you add on top of cost when invoicing the client.
- (Optional) Adjust per-model rates or add new model rows. Defaults are seeded from OpenAI's published list prices for the GPT-5.4 / 5.5 family, GPT-5.3 Codex, o3 / o4 reasoning models, ChatGPT (chat-latest), and computer-use-preview. For any model id you see the host emit that's missing from the table, click "Add rate" — type the exact model string and set input/output rates. The dashboard's "no rate set" chip surfaces these in real time.
- Backfill historical events. The Settings page has a
Backfill from historybutton (for the current period) andBackfill all history(since the company's first cost event). The plugin reads directly from the host'spublic.cost_eventstable via thecoreReadTableswhitelist, so historical data from before the plugin install is available immediately.
Then open /$COMPANY_HANDLE/monthly-report-openai — the dashboard reflects the configuration within a second.
What it does
- Subscribes to
cost_event.createdandagent.run.finishedand writes one row per event into a privateusage_eventstable. Keys arecost_event:<id>for cost events so the live subscription andBackfill from historyaction share a keyspace and dedupe idempotently. - Rolls up to
usage_dailyevery 15 minutes per company. - Fetches a daily USD→target FX rate from
open.er-api.comand stores one row per(day, currency)infx_rates. Only fetches for currencies at least one company has configured. - Cleans up automatically when a company is archived (purges
usage_events,usage_daily,pricing_config, currency state).
Dashboard at /$COMPANY/monthly-report-openai
- 5 KPI cards: total tokens, input, output, cost (pre-margin), price (chargeback).
- Per-model horizontal bar chart with native-currency cost and price.
- Per-agent table with totals + per-model breakdown (Runs / Input / Output / Cost / Price columns).
- Daily volume column chart — input + output stacked, peak label.
- Status chips for ingest health and FX staleness next to the title.
Settings at /$COMPANY/company/settings/instance/plugins/<install-uuid>
- Per-model rates (USD per 1M input / output) for the GPT-5.5 / GPT-5.4 / GPT-5.3-codex families.
- Margin %.
- Billing currency (10 currencies), with Refresh FX now and a status line showing the active rate.
The dashboard inherits the host's Paperclip theme (light/dark, shadcn-style cards) by referencing host CSS variables directly.
Billing math
For each event with model m, input tokens i, output tokens o:
list = (i × pricing[m].input + o × pricing[m].output) / 1_000_000 # USD, raw OpenAI
your_cost = list × effective_input_rate_multiplier # USD, post-adjustment
client_price = your_cost × (1 + margin.percent / 100) # USD, post-margin
row.price = client_price × fx_rate(month_end_day, currency) # Native currencyThe dashboard surfaces all three tiers explicitly. List price is what an API user would pay at OpenAI list. Your cost is what the operator effectively owes (post-adjustment). Client price is what the customer is billed (after operator margin). Per-model and per-agent cards show the same three numbers per row so reconciliation is explicit.
The monthly CSV emits only row.price — operator-internal numbers (list, multiplier, margin) stay off the file you send to the client.
Cost adjustment multiplier
The plugin has one knob: effective_input_rate_multiplier, default 1.0
(full list price). It scales the entire list price (input + output);
the variable name is a legacy artifact from earlier versions where it
applied to input only.
Useful when:
| Scenario | Multiplier | Why | | --- | --- | --- | | Default | 1.0 | Client pays full API list price | | High cache-hit ratio | 0.5 | OpenAI's cached input is ~10% of standard; if half your tokens are cache hits, the effective rate is roughly half — pick a value matching your workload | | Custom contract | any value in (0, 1] | Operator-specific arrangement |
Saving pricing in Settings replaces the active snapshot and reprices
every event in this company — past and future — so changing the multiplier
is reflected across the full report immediately. To preserve period-by-period
historical overrides, append snapshots explicitly via the addPricingSnapshot
action (advanced).
Monthly CSV export
GET /api/plugins/openai-token-cost-reports/api/export/monthly.csv?companyId=...&from=YYYY-MM-DD&to=YYYY-MM-DDColumns: period, month_start, month_end, model, input_tokens, output_tokens, total_tokens, currency, price.
Multi-month exports include a model = TOTAL row at the end of each month section. Filename: usage-<company-slug>-<from>-<to>-<currency>.csv.
Capabilities
The Paperclip host gates each of these on install. All are required for the plugin to function correctly.
| Capability | Why it's declared |
| --- | --- |
| events.subscribe | Receive cost_event.created, agent.run.finished, company.updated |
| costs.read | Gates delivery of cost_event.created |
| agents.read | Resolve agent display names for the per-agent breakdown |
| companies.read | Resolve company name for the CSV filename slug |
| database.namespace.migrate / .read / .write | Private SQL namespace |
| plugin.state.read / .write | Per-company pricing + currency config in ctx.state |
| jobs.schedule | rollup-daily (15 min) and fetch-fx-daily (hourly) |
| api.routes.register | Scoped CSV export route |
| ui.page.register | Dashboard page slot |
| instance.settings.register | Settings page slot |
| http.outbound | Daily FX fetch from open.er-api.com |
Reference
Data model
Private SQL namespace via ctx.db (plugin_openai_token_cost_reports_5d9ad52d0e):
usage_events(source_event_id PRIMARY KEY, company_id, agent_id, model, raw_model, provider, source, input_tokens, output_tokens, cached_input_tokens, cost_cents, occurred_at, day TEXT)— append-only event log.raw_modelpreserves the literal model id whilemodelholds the normalized key;providerandsource(api/subscription) drive the cost split.usage_daily(company_id, day TEXT, model, input_tokens, output_tokens, PRIMARY KEY(company_id, day, model))— rolled-up daily totals.pricing_config(company_id PRIMARY KEY, json TEXT)— kept for historical compatibility; live pricing lives inctx.state.fx_rates(day, currency, rate, source, fetched_at, PRIMARY KEY(day, currency))— daily USD-base FX snapshots.
Migrations: migrations/001_init.sql, migrations/002_costs_overview.sql, migrations/003_fx_rates.sql.
Core-read tables (declared in manifest): cost_events — used by the backfill action to import history from before the plugin install. Filtered by provider = 'openai'.
Plugin state keys
- Company-scoped:
pricing-config(rates + margin),currency-config(selected billing currency). - Instance-scoped:
active-currencies(string[] — drives which currencies the daily fetcher requests).
Data handlers (registered on ctx.data, called from UI via usePluginData)
getDailyUsage({ companyId, from, to })— daily rows with cost/price in USD and native currency. Drives the dashboard daily chart + KPIs.getMonthlySummary({ companyId, from, to })— calendar-month rollups (legacy aggregate, kept for the API surface).getPerModelForRange({ companyId, from, to })— per-model breakdown with cost → price in native currency.getPerAgentBreakdown({ companyId, from, to })— per-agent + per-model with runs, tokens, cost, price.getPricing({ companyId })— bare PricingConfig.getCurrencyConfig({ companyId })—{ currency, supported }.getFxStatus({ companyId })— current rate, day, source for the company's currency.getIngestStats({ companyId })— total + 24h ingest counts for the dashboard health chip.
Actions (registered on ctx.actions, called from UI via usePluginAction)
setPricing({ companyId, config })setCurrencyConfig({ companyId, currency })(best-effort prefetches FX)refreshFxNow({ companyId })backfillFromCostEvents({ companyId, from, to })backfillAllHistory({ companyId })
API routes
Mounted under /api/plugins/openai-token-cost-reports/api/*:
GET /export/monthly.csv?companyId=...&from=...&to=...— streams the client-facing monthly CSV.auth: board.
Naming and forking
Three names refer to the same thing; keep them aligned across npm, the host, and the database:
| Surface | Value | Where it's set |
| --- | --- | --- |
| npm package name | @herrhelms/openai-token-cost-reports | package.json name |
| In-app plugin key | openai-token-cost-reports | src/manifest.ts id |
| Private DB namespace | plugin_openai_token_cost_reports_5d9ad52d0e | derived by the host as plugin_<slug-with-underscores>_<sha256(slug)[0:10]> |
The 5d9ad52d0e suffix is the first 10 hex characters of sha256("openai-token-cost-reports"). Forks that rename the plugin must regenerate this suffix in every migration file — the host computes the namespace from the slug at install time, and a stale suffix in the SQL makes every migration fail with "schema X does not exist". A one-liner to recompute:
node -e "console.log(require('crypto').createHash('sha256').update('openai-token-cost-reports').digest('hex').slice(0,10))"Then sed -i '' 's/plugin_openai_token_cost_reports_5d9ad52d0e/plugin_<new_slug>_<new_hash>/g' migrations/*.sql. Tests do not catch this — the SQL runs at host install time, not at plugin build time.
Build from source
For developers and forks. Standalone plugin package; built against @paperclipai/plugin-sdk. TypeScript throughout; React + inline CSS for the UI (no Tailwind); esbuild for both the worker and the UI bundle.
pnpm install
pnpm typecheck # base + tests/ (chained via tsconfig.test.json)
pnpm test # 28 unit tests on the pure math + manifest
pnpm build # emits dist/manifest.js, dist/worker.js, dist/ui/index.js
# Install the locally built copy into the Paperclip host on this machine:
paperclipai plugin install -l .
paperclipai plugin listFor a clean reinstall during development:
paperclipai plugin uninstall openai-token-cost-reports --force
paperclipai plugin install -l .See docs/PRODUCTION-INSTALL-CHECKLIST.md for a verification flow to run after the first install on a non-dev Paperclip host.
License
MIT — see LICENSE.
Changelog
See CHANGELOG.md. The format follows Keep a Changelog and the project follows Semantic Versioning.
