Connecting a Custom AI Provider to AI Spend
Last updated: September 21, 2026
A Custom Provider lets you bring AI spend into AI Spend that doesn't come from one of TeamOhana's natively supported providers — for example, an internal LLM gateway (Aperture, LiteLLM, Portkey), a vendor with no billing API, a reseller invoice, or a fixed-price subscription. You download a CSV template, fill it in from your own export, and upload it. Once imported, that spend behaves like any native provider: it shows up in the Dashboard, on employee pages, and in the AI Spend Classifier with the same per-employee attribution, model mix, and token detail.
You don't need an engineer to do this. The template uses plain column names, and the upload screen tells you exactly which rows need fixing before anything is imported.
Prerequisites
Role and permission: The Manage AI Providers permission (granted under Settings > Permissions > AI Spend Management). Admin and IT Admin have this by default.
An export from your provider or gateway containing, at minimum, a date and a dollar cost per row. Per-person emails and model names make the data far more useful.
A spreadsheet tool (Excel, Google Sheets, Numbers) or a script to rename columns and save the result as a .csv file.
File limits: 5 MB and 100,000 rows per file. For larger exports, split by month and upload each part — overlapping rows across files are merged, never double-counted.
Setup Steps
Step 1 — Open the Custom Provider drawer
In TeamOhana, go to Settings > Integrations.
Click the Custom Provider — Create a new provider card.
In the drawer, enter a Provider Name. This name appears on the Integrations page and in every spend view, so use the name your team already uses (for example "Aperture," "Azure OpenAI – EU reseller," or "Gemini Ultra seats").
Step 2 — Download the template
Click Download Template to get custom-provider-template.csv: one header row and four sample rows.
What the sample rows show:
Rows 1–2 — the ideal shape: one person, one model, one day, one tool. Two rows for the same person and day are fine when the model or the tool differs.
Row 1 reads: On 1 July 2026, jane.doe@acme.com spent $42.18 on claude-opus-4-8 through Claude Code (CLI). She sent 180,500 input tokens, received 9,600 output tokens, read 24.1M tokens from cache and wrote 1.22M tokens to cache, across 57 requests.
Row 2 reads: On the same day, jane.doe@acme.com also spent $3.75 on gpt-5.5 through Cursor: 52,000 input tokens, 8,100 output tokens, 64,000 reasoning tokens, 12 requests. No cache figures were available, so those cells are blank.
Row 3 — usage that belongs to a service key, not a person. employee_email is blank; api_key_name names the key.
Reads: On 1 July 2026, the key ci-pipeline-key spent $18.40 on claude-sonnet-4-6 over 88 requests. The note says it is shared CI usage.
Row 4 — a flat monthly fee. No model, no tokens, and a to_date so the $250 is spread across the whole month in your charts.
Reads: sam.lee@acme.com has a $250.00 platform fee covering 1 June to 30 June 2026.
Delete the sample rows before you upload. The sample emails are fake and will not match anyone in your roster.
Step 3 — Fill the template from your export
One row = what one person (or one key) spent on one model on one day. Every column except from_date and cost_usd can be left blank and the file will still import — you just lose the analytics that column powers.
Column guide
Column | Status | What to put in it |
|---|---|---|
from_date | Required | The day the usage happened, as YYYY-MM-DD, in UTC. For a row covering a longer period, put the first day here and the last day in to_date. A header named date is accepted as an alias. |
cost_usd | Required | What this row cost, in US dollars, as a plain decimal (e.g. 42.18). 0 is fine for included usage. No currency symbols or thousands separators — these are stripped with a warning. A decimal comma (42,18) is rejected. |
employee_email | Recommended | The work email of the person who used the AI — this is how spend is matched to people in your org chart automatically. Leave blank for shared or service usage and use api_key_name instead. |
model | Recommended | The model name exactly as your provider reports it. Powers "which models is my team on" analytics. Leave blank for fees not tied to a model. |
input_tokens | Optional | Whole number of tokens sent to the model for this row. |
output_tokens | Optional | Whole number of tokens received from the model for this row. |
cache_read_tokens | Optional | Tokens served from prompt cache. If your provider reports one combined "cached tokens" figure, it goes here. |
cache_write_tokens | Optional | Tokens written into prompt cache, any TTL. |
reasoning_tokens | Optional | Thinking/reasoning tokens, if your provider bills them separately. |
requests | Optional | Number of API calls this row aggregates. |
app_or_agent | Optional | The tool that generated the usage (e.g. Claude Code (CLI), Cursor, Codex CLI, curl, an internal bot name). Multiple rows per day/person/model with different tools are fine — they're summed, and the per-tool split is kept. |
api_key_name | Optional | If the usage isn't tied to a person, the name of the key or service it belongs to. See "How rows are attributed" below. |
to_date | Optional | Only for rows covering more than one day: the last day of the period, YYYY-MM-DD. Must be on or after from_date and within one year of it. Leave blank for daily rows. |
notes | Optional | Anything you want to remember about the row. Stored and shown on drill-down, never parsed. |
Note: The header row must be first; column order doesn't matter. Unknown columns are ignored, not rejected — the validation report lists any ignored headers, so a typo like employe_email won't silently drop a column. Save as UTF-8 CSV (Excel: File > Save As > CSV UTF-8 (Comma delimited); Google Sheets: File > Download > Comma Separated Values). All token and request columns must be whole numbers ≥ 0.
How rows are attributed to people
The row has… | What happens |
|---|---|
employee_email only | Matched automatically to the employee with that email in your roster. If there's no match, the spend lands unattributed in the Classifier for manual assignment. |
api_key_name only | Imported as that key's spend and sent to the Classifier, where you attach it to an agent or a person if not yet classified. |
Both | Treated as the key's spend and sent to the Classifier. The email is kept as a suggested owner — one click to accept, never applied silently, since a person's email next to a shared key is ambiguous. |
Neither | Lands in a single "‹Provider name› · unattributed" bucket you can classify later. |
Emails are matched case-insensitively; key names keep their case.
Fixed subscriptions and multi-day rows (to_date)
Use to_date when one row covers a period rather than a day — a monthly seat fee, a weekly invoice total, a prepaid subscription. TeamOhana stores the row once and amortizes it day-weighted across its span: a $200 seat fee for a 30-day month shows as roughly $6.67/day on daily charts and as $200 for that month on monthly charts. Rows without to_date post their full cost on from_date.
Step 4 — Upload and review
Drag your file onto the Upload CSV area, or click to choose it. Only .csv files up to 5 MB are accepted.
TeamOhana validates the file immediately — nothing is imported yet.
Read the result:
Errors (red banner) — the file cannot be imported until these are fixed. Each error names the row, the column, and what to change. The banner shows the first 10 errors and a count of the rest.
Warnings (amber banner) — the file can be imported as-is. The most common warning is that some emails weren't recognized, so that spend stays unclassified; you can import now and classify later, or fix the emails first.
When there are no errors, the Connect button becomes active. Click it.
The Import complete panel shows what landed: usage events, sources (people and keys), total spend, how many sources were auto-matched to employees, and how many need attribution in the Classifier. Click Done.
Your provider now appears on the Integrations page, and its spend flows into the dashboard, employee pages, and Classifier like any other provider.
Field Mappings — worked example (optional)
Aperture is an internal LLM gateway that fronts Anthropic, OpenAI, and Google models. Its dashboard exports one row per day × person × client app × model. Mapping its export to the TeamOhana template is mostly a rename, plus one date fix:
Aperture column | TeamOhana column | Note |
|---|---|---|
date_utc | from_date | Excel often exports this as a serial number (e.g. 46031). Format the column as YYYY-MM-DD first. |
login_name | employee_email | |
model | model | |
estimated_cost_dollars | cost_usd | |
input_tokens | input_tokens | |
output_tokens | output_tokens | |
cached_tokens | cache_read_tokens | One combined cache figure maps to cache read. |
reasoning_tokens | reasoning_tokens | |
request_count | requests | |
user_agent | app_or_agent | |
total_tokens, tool_use_count | (leave as-is) | Unknown columns are ignored and listed in the validation report. |
Several user_agent rows for the same person, day, and model are combined into one usage event, with the per-tool breakdown preserved. The same file can also carry a gateway's flat seat fees — just append one row per person per month with a to_date.
Managing a Custom Provider (optional)
Go to Settings > Integrations and click the settings (gear) icon on the provider's tile to open the Edit Provider drawer:
Overview tab — rename the provider, or upload another file. A new upload goes through the same validate → review → import flow.
Imports tab — every file you've uploaded, with its date range and import date. For each import, you can download the original CSV or delete the import.
Re-uploading and overlapping files
You can re-upload at any time — a fresh export of the same period, a corrected file, or a new month. Rows are matched on person-or-key × model × from_date × to_date, and matching rows are rewritten, never duplicated. The simplest routine is to export the full period from your provider each month and upload it whole.
Deleting an import
Deleting an import removes the usage rows that import was the latest source for. Rows that a later overlapping import rewrote belong to that later import and stay. Deleting a provider entirely removes all of its imports and spend.
Permissions Reference (optional)
Action | Requires |
|---|---|
Create, edit, or delete a Custom Provider; upload or re-upload a file | Manage AI Providers permission |
Troubleshooting
Message | Cause and fix |
|---|---|
Missing required column cost_usd | The header row doesn't contain from_date (or date) and cost_usd. Download the template and copy its header row. |
"46031" looks like an Excel serial date | Excel exported the date column as a number. Format it as YYYY-MM-DD before saving to CSV. |
"07-01-2026" is ambiguous | Locale-dependent date format. Use 2026-07-01. TeamOhana never guesses. |
"42,18" looks like a decimal comma | European number format. Use a dot as the decimal separator; commas are only accepted as thousands separators (e.g. 1,234.56). |
"$1,234.56" read as 1234.56 (warning) | Currency symbol or thousands separator was stripped automatically. No action needed. |
"…will be amortized — spread day-weighted across June (16/30) and July (14/30)" (info) | A row with to_date crosses a month boundary. This is expected; it tells you how the amount will be split. |
N rows name both a person and a key (warning) | See "How rows are attributed to people" above. |
input_tokens must be a whole number ≥ 0 | A token or request column contains a decimal, a negative number, or text. |
to_date is before from_date | Swap the two dates. |
Row spans more than 1 year | Split the row into shorter periods (e.g. one row per quarter). |
"jsmith" doesn't look like an email (warning) | The row still imports and lands unattributed. Use the full work email to get automatic matching. |
Rows share a person/model/date with another row (info) | Expected when several rows exist for the same person, model, and day, usually one per tool — they're summed and the per-tool split is kept. |
File has more than 100,000 rows | Split the export into smaller files, e.g. by month. |
File is not valid UTF-8 | Re-export as UTF-8 CSV (Excel: CSV UTF-8 (Comma delimited)). |
Frequently asked questions
What do you do with the file? TeamOhana stores the exact file uploaded so it can be downloaded later from the Imports tab. The rows become usage events attached to your custom provider. Nothing leaves your TeamOhana organization.
What personal data is in the file? Only what you put in it — work emails and the usage/cost figures next to them, used solely to attribute spend to people and teams. This is covered by the Data Processing Agreement already in force between TeamOhana and your organization.
Can I upload cost without tokens, or tokens without cost? Cost without tokens: yes — the row imports and shows in spend charts, just without token-volume detail. Tokens without cost: not currently supported; cost_usd is required on every row.
Can I mix models from different upstream providers in one custom provider? Yes — this is the normal case for a gateway. The provider name stays constant (e.g. "Aperture"), while the model column carries the upstream signal.
What happens to emails not in my roster? They import and wait in the Classifier. Once the person is added to your roster with that email, you can attribute the spend to them.
Does a custom provider sync automatically? No — custom providers are upload-only. Upload a fresh export whenever you want the data refreshed; re-uploads merge safely.
Can I remove data uploaded by mistake? Yes — delete the import from the Imports tab, or upload a corrected file for the same period to replace the earlier rows.
Can I use a different currency? Not yet. Convert to US dollars before uploading, and note the exchange rate used in the notes column if you want to keep a record.