You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The platform analytics endpoints added in #702 cannot produce finance-grade revenue or money-in numbers, because cloud-api does not record the financial facts needed:
No per-request credit attribution.organization_usage_log records what was consumed but not which credit funded it (grant vs payment, and which source). A first attempt classified an org by whether it currently has a payment credit and applied that to all history — which is time-inconsistent (past grant burn flips to "paid" after conversion). That split was removed in Add executive stats analytics endpoints #702.
No payment/credit ledger.organization_limits_history.spend_limit is a cap (ceiling), not a top-up/transaction record. There is no payments/transactions/refunds table in cloud-api; actual payments live in the billing service (Stripe via nearai-cloud-ui). So "money in", deferred revenue, and ARR run-rate cannot be derived here. The billing-summary endpoint was reduced to credit limits + consumption in Add executive stats analytics endpoints #702.
Goal
Make paid-vs-granted revenue and money-in accurate and auditable.
Proposed work (for discussion)
A. Per-request credit attribution (debit side)
Add columns to organization_usage_log: funded_credit_type (grant|payment), funded_source (e.g. stripe, nearai, hot-pay), and optionally price_version.
Populate at write time in the usage recording path (record_usage) using an explicit drain order (e.g. grants before payments) so each request maps to the bucket it actually drew from.
Backfill historical rows as unknown.
B. Credit/payment ledger (credit side)
Introduce a real transaction ledger (payments, grants, refunds, expirations) — either in cloud-api (synced from the billing service webhooks) or owned by the billing service and queried/exposed for analytics.
Distinguish provisioned vs consumed vs refunded/expired per credit type/source.
C. Analytics
Once A/B exist, restore paid-vs-granted revenue (from attribution), add real money-in/deferred-revenue, and consider daily/hourly rollups for the dashboard.
Dashboard UI changes (tracked with the admin-ui work).
Add: postpaid / invoiced customers (a third credit type)
Some orgs are postpaid — we extend NEAR AI credits now and invoice actual usage quarterly / end-of-month (net terms). Today the model only has grant | payment, so these get recorded as grant and are miscounted as free in any "paying customers" / revenue view. They are real revenue, just billed in arrears.
Decision: model them as a new credit_type = 'postpaid' (not an org-level flag, not a source hack) — consistent with the existing model; credit_type is VARCHAR(50) with no CHECK, so no constraint migration.
Scope to include in this ledger work:
Add CreditType::Postpaid (enum in crates/api/src/models.rs, admin parse in admin.rs, column comment in a migration).
Recognized vs billed diverges for postpaid: revenue is recognized on consumption (already in organization_usage_log); cash-in happens later via invoice — the ledger must record invoices/payments for these orgs, and spend_limit for them is a risk ceiling, not money-in.
One-off backfill of the few existing postpaid orgs from grant → postpaid.
The existing unique-active-per-(org, credit_type) index already supports an org having both a free grant and a postpaid arrangement — no index change.
Add: historical stability of the verifiable / provider split
The verifiable-vs-non-verifiable (and provider-type) breakdowns are computed by joining organization_usage_log to the currentmodels row (verifiable, provider_type). If a model's
verifiable flag or provider type changes later, historical periods shift retroactively — the same
class of bug we removed for paid-vs-granted. Today it's only labeled in the API/UI as "current model
metadata applied to historical usage."
Fold into this work: snapshot model metadata at usage time — store per usage row (or in the per-request
charge fact) the model id, canonical name, provider_type, and verifiable/attestation class (and ideally
price version), so verifiable-share and provider-mix are historically stable for an exec KPI.
Problem
The platform analytics endpoints added in #702 cannot produce finance-grade revenue or money-in numbers, because cloud-api does not record the financial facts needed:
organization_usage_logrecords what was consumed but not which credit funded it (grant vs payment, and which source). A first attempt classified an org by whether it currently has a payment credit and applied that to all history — which is time-inconsistent (past grant burn flips to "paid" after conversion). That split was removed in Add executive stats analytics endpoints #702.organization_limits_history.spend_limitis a cap (ceiling), not a top-up/transaction record. There is no payments/transactions/refunds table in cloud-api; actual payments live in the billing service (Stripe vianearai-cloud-ui). So "money in", deferred revenue, and ARR run-rate cannot be derived here. The billing-summary endpoint was reduced to credit limits + consumption in Add executive stats analytics endpoints #702.Goal
Make paid-vs-granted revenue and money-in accurate and auditable.
Proposed work (for discussion)
A. Per-request credit attribution (debit side)
organization_usage_log:funded_credit_type(grant|payment),funded_source(e.g.stripe,nearai,hot-pay), and optionallyprice_version.record_usage) using an explicit drain order (e.g. grants before payments) so each request maps to the bucket it actually drew from.unknown.B. Credit/payment ledger (credit side)
C. Analytics
References
nearai-cloud-ui(lib/webhook-utils.ts, Stripe webhook + credit sync).cloud-api/crates/database/src/repositories/organization_usage.rs(record_usage),migrations/sql/V0004*,V0044*(organization_limits_historycredit_type/source).Out of scope
Dashboard UI changes (tracked with the admin-ui work).
Add: postpaid / invoiced customers (a third credit type)
Some orgs are postpaid — we extend NEAR AI credits now and invoice actual usage quarterly / end-of-month (net terms). Today the model only has
grant | payment, so these get recorded asgrantand are miscounted as free in any "paying customers" / revenue view. They are real revenue, just billed in arrears.Decision: model them as a new
credit_type = 'postpaid'(not an org-level flag, not asourcehack) — consistent with the existing model;credit_typeisVARCHAR(50)with no CHECK, so no constraint migration.Scope to include in this ledger work:
CreditType::Postpaid(enum incrates/api/src/models.rs, admin parse inadmin.rs, column comment in a migration).credit_type IN ('payment','postpaid')everywhere it's derived (analytics.rs: paying count, billing-summary, org-revenuepayingCTE).organization_usage_log); cash-in happens later via invoice — the ledger must record invoices/payments for these orgs, andspend_limitfor them is a risk ceiling, not money-in.grant → postpaid.(org, credit_type)index already supports an org having both a free grant and a postpaid arrangement — no index change.Add: historical stability of the verifiable / provider split
The verifiable-vs-non-verifiable (and provider-type) breakdowns are computed by joining
organization_usage_logto the currentmodelsrow (verifiable,provider_type). If a model'sverifiable flag or provider type changes later, historical periods shift retroactively — the same
class of bug we removed for paid-vs-granted. Today it's only labeled in the API/UI as "current model
metadata applied to historical usage."
Fold into this work: snapshot model metadata at usage time — store per usage row (or in the per-request
charge fact) the model id, canonical name,
provider_type, and verifiable/attestation class (and ideallyprice version), so verifiable-share and provider-mix are historically stable for an exec KPI.