Billing Models

Document Metadata
Title
Billing Models
Description
Help query pricing, IPR charges, cross-peer RoC time credit transfers, settlement reconciliation, and multi-source billing breakdown.
Status
archived 2026-06-04 17:45
Access Level
9
Category
architecture
Product Area
platform
Audience
t4
Difficulty
advanced
Version
2.0
Author
steven
RoC Eligible
No
Vector Action
delete
Tags
docs billing ipr roc time-credits settlement transactions

(log in required)
Loading...
Loading vector metadata...

Help query pricing, IPR charges, cross-peer RoC time credit transfers, settlement reconciliation, and multi-source billing breakdown.

Advanced Last updated: 2026-06-04

Billing Models

Module: 10 | ← Agreement Routing | Next: LangGraph Migration β†’ Source: Addendum Β§B (help query pricing), Β§G (IPR and cross-peer RoC)


B. Help Query Pricing: Platform Hourly Rate = 0

B.1 Decision

@rosie help queries have hourly_rate = 0 and ipr_hourly_rate = 0 on the peer agreement with trustrosie-io. Help is a platform service, not a billable feature.

B.2 Transaction Recording

Even at zero cost, help queries are recorded as transactions for analytics:

  • Help query volume (admin KPIs: "how often are users asking for help?")
  • Hot Topics: the LLM tags each query with topic labels that feed the Hot Topics Dashboard. This applies to help queries as well β€” "what are users struggling with?" is valuable analytics.
  • Per-document retrieval frequency (which help docs are most useful?)
{
  "transaction_type": "query_usage",
  "platform_fee": 0.0,
  "ipr_cost": 0.0,
  "peer_agreement_id": "agr_trustrosie_help",
  "peer_org_id": "trustrosie_io",
  "is_help_query": true,
  "query_source": "https://trustrosie.io/help/"
}

No balance deduction. Anonymous users and users with zero balance can still get help.

B.3 Cost Absorption

The LLM inference cost for help queries is absorbed by the consumer site's platform (the RAG generation happens on the consumer's edge function using the consumer's LLM key). The vector retrieval cost is negligible. This is equivalent to hosting a help page.


C. Billing Duration Configuration

C.1 Duration Modes

The billable duration for each query is configurable per org via public.orgs.settings.pricing.billing_mode:

Mode billing_mode value What's billed Default
Response time "response_time" Full request after auth β€” includes retrieval, agreement routing, source grading, LLM generation. Excludes cold-start and auth overhead. Yes
LLM only "llm_only" Only LLM inference time β€” agreement routing LLM + topic extraction + response generation. Excludes retrieval, graph setup, persistence. No

C.2 Configuration

Set in public.orgs.settings JSON:

{
  "pricing": {
    "query_hourly_rate": 25,
    "currency": "CAD",
    "roc_split_percent": 60,
    "billing_mode": "response_time"
  }
}

When billing_mode is not set, defaults to "response_time".

C.3 Platform Fee Calculation

platform_fee = (billable_duration_seconds / 3600) Γ— query_hourly_rate

Example at $25/hr with a 5.5s response-time query: (5.5 / 3600) Γ— 25 = $0.038

C.4 Timeline of Duration Changes

Date Change
Pre-2026-03-14 Full request time including cold-start
2026-03-14 Billing clock moved to LLM invocation (excluded cold-start)
2026-03-16 Added agreement routing + topic extraction LLM time
2026-04-02 Made configurable per org. Default changed to response-time (post-auth)

C.5 Scope

Billing mode applies only to query_usage transactions (rag-v2). Other transaction types (llm_suggest, document_add, etc.) always bill on actual execution time.


G. Dual Billing: IPR and Cross-Peer RoC Transfers

G.1 The Distinction

Not all cross-peer content sharing involves IPR charges. There are two revenue models for peer-retrieved content, plus the free model for platform help:

Model When Revenue Flow Example
IPR Provider sets ipr_hourly_rate > 0 User pays platform_rate + ipr_rate. IPR revenue goes to provider peer's organisation. Premium content marketplace, licensed knowledge bases
RoC Transfer Agreement has roc_eligible = true, ipr_hourly_rate = 0 Consumer site calculates RoC for retrieved content. RoC amount is transferred to the author's account on the provider site. Individual contributor whose content is shared via their site's agreement
Free hourly_rate = 0, ipr_hourly_rate = 0, roc_eligible = false No cost, no RoC. Analytics only. Platform help documentation

G.2 The Problem with Requiring Author Accounts on Consumer Sites

A contributor authors a document on site-A.rosie.io (their home site). Site-A's governing body creates a sharing agreement with site-B.rosie.io. A user on site-B queries and retrieves the contributor's content.

The contributor has no account on site-B and shouldn't need one. They may not even know the agreement exists β€” their site's governing body negotiated it. It's not feasible to expect contributors to register on every site their content might be shared with.

The RoC must flow back to the contributor on their home site (the provider) via an inter-site transfer.

G.3 Cross-Peer RoC Transfer Flow

User on consumer-site (site-B) queries β†’ retrieves content from provider-site (site-A)

1. Consumer-site calculates RoC time credits for the retrieved content:
   roc_seconds = duration_seconds Γ— roc_split_percent Γ— attention_score

   This is the same RoC formula used for local contributors.
   The contributor's identity is the document's user_id from schema.documents β€” the user who uploaded the content.

2. Consumer-site records a local RoC credit transfer (non-cash redeemable time credits):
   {
     "transaction_type": "roc_credit_transfer_out",
     "roc_seconds": 3.6,                    ← time credit amount in seconds
     "peer_agreement_id": "agr_abc123",
     "provider_peer_org_id": "site_a_org_id",
     "contributor_user_id": "uuid-of-contributor",
     "source": "https://partner-site.example.com/compliance/data-governance/",
     "attention_score": 0.20,
     "status": "pending"
   }

3. Consumer-site calls provider-site's RoC transfer RPC:
   provider.rpc('receive_peer_transfer', {
     p_transfer_type: 'roc',  // or 'ipr'
     p_contributor_user_id: "uuid-of-contributor",
     p_roc_seconds: 3.6,
     p_source: "https://partner-site.example.com/compliance/data-governance/",
     p_consumer_org_id: "site_b_org_id",
     p_agreement_id: "agr_abc123"
   })

4. Provider-site validates the transfer:
   a. Verify the calling peer's agreement is active
   b. Verify the author exists on this site
   c. Verify the agreement has roc_eligible = true
   d. Credit the author's balance
   e. Record a "roc_credit_transfer_in" transaction
   f. Return confirmation

5. Consumer-site updates its local transaction status to "confirmed"

Consumer-side ledger: schema.peer_transfers table tracks outbound
transfers with status (pending β†’ confirmed / failed / unresolvable).
This is separate from schema.transactions.

Provider-side ledger: schema.transactions WHERE
transaction_type = 'peer_transfer_in'.

There is no peer_transfers table on the provider side.

Transfer timing: fire-and-forget from persistHistoryNode. The query
completes and the user receives their response before transfers
confirm. Failed transfers are tracked in peer_transfers (status:
'failed') and retried manually via the Network Health dashboard.

The sourcesContributed gate: transfers only fire when the LLM
determines that retrieved sources meaningfully contributed to the
answer. If sourcesContributed is false (low confidence, sources
not relevant), no transfers fire and no billing occurs for that query.

G.4 Provider-Side RPC: Receive Peer Transfer

A single RPC handles both RoC and IPR transfers. The p_transfer_type parameter determines behaviour ('roc' or 'ipr').

CREATE OR REPLACE FUNCTION schema.receive_peer_transfer(
  p_transfer_type text,                -- 'roc' or 'ipr'
  p_contributor_user_id uuid,          -- documents.user_id (schema.users.id, NOT auth_id)
  p_roc_seconds numeric(10,4) DEFAULT 0,
  p_ipr_amount numeric(10,4) DEFAULT 0,
  p_currency text DEFAULT 'CAD',
  p_source_url text DEFAULT '',
  p_attention_score numeric(5,4) DEFAULT 0,
  p_consumer_org_id text DEFAULT '',
  p_agreement_id text DEFAULT ''
)
RETURNS jsonb

Validation:

  1. Caller must be an active peer agreement user (auth.uid() β†’ agreements.auth_user_id)
  2. Transfer type must match agreement terms:
    • 'roc': agreement.roc_eligible must be true
    • 'ipr': agreement.ipr_hourly_rate must be > 0
  3. Contributor lookup: schema.users WHERE id = p_contributor_user_id (NOT auth.users, NOT users.auth_id β€” documents.user_id references users.id)

Balance crediting:

  • RoC: users.balance (same column as creditRocEarnings)
  • IPR: users.ipr_balance (separate column β€” income, not spending credits)

Transaction recording:

  • transaction_type: 'peer_transfer_in'
  • Amount stored in platform_fee column (transactions table has no 'amount' column)
  • Currency stored in metadata JSONB (transactions table has no 'currency' column)
  • Status stored in metadata JSONB (transactions table has no 'status' column)

Missing contributor handling:

  • If user not found, record transaction with status 'unresolvable' in metadata
  • Return accepted: true, escrowed: true (transfer accepted, funds held)

G.5 Edge Case: Deleted Contributors

Since contributor identity is schema.documents.user_id (always present when a document exists), the "unresolvable author" problem is eliminated for active documents. The only edge case is when a user account is deleted but their documents remain shared via peer agreements.

In this case, the RoC credit transfer is recorded with status: 'unresolvable' on the provider site. The provider admin can credit it to an organisational account or handle it per their retention policy. No escrow mechanism needed β€” this is a rare administrative edge case, not a systemic problem.

G.6 Agreement Table: Supporting Both Models

CREATE TABLE agreements (
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  scope text NOT NULL,                    -- 'peer', 'document', 'user', 'org'
  peer_org_id text,
  access_level_min int DEFAULT 0,
  access_level_max int DEFAULT 0,
  source_prefixes text[],
  peer_scopes text[],                     -- confidential scoping (Β§F)
  permissions text[] DEFAULT ARRAY['read'],
  auth_user_id uuid,                      -- Supabase auth user for this agreement

  -- Billing: one or both can apply
  hourly_rate numeric(10,4) DEFAULT 0,    -- platform rate for cross-peer queries
  ipr_hourly_rate numeric(10,4) DEFAULT 0, -- IPR charge (provider sets this)
  roc_eligible boolean DEFAULT false,     -- if true, cross-peer RoC transfers are enabled

  -- Agreement metadata
  description text,                       -- semantic description for LLM routing (Β§H)
  topic_tags text[],                      -- topic tags for LLM routing (Β§H)
  currency text DEFAULT 'CAD',
  status text DEFAULT 'active',
  created_at timestamptz DEFAULT now(),
  updated_at timestamptz DEFAULT now()
);

G.7 Billing Flow by Model

IPR model (ipr_hourly_rate > 0):

User queries content from provider-peer:
  Total cost = (duration / 3600) Γ— (platform_rate + ipr_hourly_rate)

  Distribution:
    Consumer platform β†’ platform_rate (balance deduction from user)
    Local contributors β†’ RoC time credits (non-cash, redeemable for query time) for local sources
    Provider contributor β†’ ipr_hourly_rate Γ— attention_score
      (for peer sources, to schema.documents.user_id via
      receive_peer_transfer RPC, credited to users.ipr_balance)

RoC transfer model (ipr_hourly_rate = 0, roc_eligible = true):

User queries content from provider-peer:
  Total cost = (duration / 3600) Γ— platform_rate   (no IPR surcharge to the user)

  Distribution:
    Consumer platform β†’ platform_rate (balance deduction from user)
    Provider contributor β†’ RoC time credits transferred to contributor's balance
      on the PROVIDER site via receive_peer_transfer RPC
      ↑ non-cash, redeemable for query time on the provider site

  The user pays the same platform rate as for local content.
  The difference is where the RoC credits go β€” to the provider site's contributor, not a local user.

Free model (help docs β€” hourly_rate = 0, ipr_hourly_rate = 0, roc_eligible = false):

No cost. No RoC. Recorded for analytics only.

G.8 Transaction Metadata for Multi-Source Queries

{
  "transaction_type": "query_usage",
  "platform_fee": 0.0236,
  "ipr_cost": 0.0028,
  "roc_distributions": [
    {
      "user_id": "local-contributor-uuid",
      "source": "local://policy-doc",
      "attention_score": 0.45,
      "roc_seconds": 3.8,
      "type": "local_roc"
    }
  ],
  "roc_transfers": [
    {
      "provider_peer_org_id": "site_a_org_id",
      "contributor_user_id": "uuid-of-contributor",
      "source": "https://partner-site.example.com/compliance/data-governance/",
      "attention_score": 0.2,
      "roc_seconds": 3.6,
      "transfer_status": "confirmed",
      "type": "cross_peer_roc_transfer"
    }
  ],
  "peer_sources": [
    {
      "peer": "site_a_org",
      "sources": 1,
      "attention": 0.2,
      "fee": 0.0056,
      "ipr": 0.0028,
      "roc_seconds": 3.6
    },
    {
      "peer": "trustrosie_io",
      "sources": 1,
      "attention": 0.35,
      "fee": 0,
      "ipr": 0,
      "roc_transfer": 0
    }
  ]
}

NOTE: The transactions table uses platform_fee (not amount) for the monetary value, and stores currency and status in the metadata JSONB field. The schema shown here represents the logical structure; see the actual table definition for column names.

Additional fields added during Phase 4:

{
  "ipr_cost": 0.0028,
  "peer_transfers_summary": {
    "total_roc_seconds_out": 3.6,
    "total_ipr_amount_out": 0.0028,
    "ipr_currency": "CAD",
    "transfer_count": 2
  },
  "actual_duration_seconds": 12.4
}

A separate 'transfer-summary' SSE event fires from persistHistoryNode milliseconds after the transaction-summary event, providing the actual ipr_cost and per-source transfer details (the transaction-summary fires from generateResponseNode with ipr_cost: 0 because transfers haven't been calculated yet).

G.9 Settlement and Reconciliation

RoC and IPR transfers fire in near-real-time (fire-and-forget per query). Failed transfers are tracked in the consumer-side peer_transfers table and surfaced through three channels:

  1. Stack logs β€” log.error() with full 15-field diagnostic context
  2. Google Chat webhook β€” immediate admin alerting
  3. In-app system notification β€” sendSystemNotification to admin panel
  4. Network Health dashboard β€” "Failed Transfers" attention card with [Retry] button

Reconciliation is admin-driven (not automated):

  • Admin reviews failed transfers in Network Health dashboard
  • Admin clicks [Retry Now] to manually retry all failed transfers
  • The retry endpoint (POST /adminReconcile/reconcile/retry-transfers) recreates peer clients and re-calls receive_peer_transfer

Automated reconciliation (pg_cron, exponential backoff, batch processing) can be added when production transfer volume justifies it.

Stale pending recovery: transfers stuck in 'pending' for 30+ minutes (Edge Function timeout/crash) are automatically marked 'failed' during manual retry.

G.10 Contributor Visibility

Contributors on the provider site can see in their dashboard:

  • RoC earned from local queries (existing)
  • RoC earned from cross-peer transfers (tagged with source: cross_peer)
  • IPR earned from cross-peer transfers (users.ipr_balance)
  • Which documents earned cross-peer RoC/IPR
  • They do NOT see which specific consumer sites queried their content (consumer_org_id and agreement_id are gated on access_level >= 9)

Balance separation:

  • users.balance: spending credits (prepaid query credits + RoC earned)
  • users.ipr_balance: income (IPR earnings from cross-peer content usage) These are displayed separately in the dashboard.

docs billing ipr roc time-credits settlement transactions
Version 2.0 Β· steven
Messages
Send a message to start a conversation with our support team.