OASA

Open Agent Spend Attribution

Version 0.1.2 (2026-10-08). Current version. Other versions: 0.1.3 (2026-10-22), 0.1.1 (2026-09-15).

Open Agent Spend Attribution (OASA) Specification

Version 0.1.2 · Initial public draft 2026-09-14 · Revised 2026-10-08 Published by Onaro (BrianOnAI LLC) · Licensed CC BY 4.0

Canonical HTML version: https://www.onaro.io/spec Pinned version: https://www.onaro.io/spec/v0.1.2


Abstract

OASA defines a canonical record format that attributes AI-agent resource consumption and spend to agent identity, task, and cost object, and joins runtime telemetry, normalized billing, and payment settlement into a single auditable ledger row. It is designed to interoperate with OpenTelemetry GenAI semantic conventions (telemetry), the FOCUS specification (billing), and x402 payment-rail records (settlement). It is not a payment protocol and not a billing format; it is the join between them.

Partial records are valid. Every field outside the envelope is optional so adapters can emit what they know and reconcile later. Within a major version, the schema is additive-only.

Design principles

The three-layer model

        ┌───────────────────────────────┐
        │  Telemetry — OTel GenAI       │
        └───────────────┬───────────────┘
                        │  trace_id
        ┌───────────────┴───────────────┐
        │  Attribution — OASA           │
        │  (canonical)                  │
        └───────────────┬───────────────┘
                        │  invoice_id + source_record_id
                        │  settlement_ref
        ┌───────────────┴───────────────┐
        │  Billing — FOCUS              │
        │  + Settlement — x402          │
        └───────────────────────────────┘

Join keys on the edges; attribution in the middle.


Record schema v0.1.2

Group 1 — Envelope (required)

Field Type Required Description
record_id string (UUIDv7) Yes Globally unique record identifier. Adapters with a stable source key SHOULD derive it deterministically (see Deterministic record IDs) so that a retried export produces the same ID.
record_type enum: usage | charge | settlement | allocation | outcome Yes One row = one event class.
schema_version string Yes Schema version string; "0.1" for this draft.
occurred_at timestamp (RFC 3339, UTC) Yes When the event happened.
recorded_at timestamp (RFC 3339, UTC) Yes When the record was ingested.
source_system string Yes e.g. otel-collector, openai-billing, x402-facilitator.
source_record_id string Yes Key in the source system.
currency string Yes ISO 4217, or CAIP-19 asset ID for on-chain assets.

source_system = otel-collector is the value emitted by oasa-otel-exporter, an OpenTelemetry Collector exporter that turns GenAI spans into OASA usage records.

Group 2 — Agent identity

Field Type Required Description
agent_id string No Stable internal agent ID.
agent_name string No Human-readable agent name.
agent_version string No Agent build or prompt version.
agent_owner string No Team or principal accountable.
parent_agent_id string No Parent in a sub-agent chain.
agent_framework string No e.g. langgraph, crewai, custom.
identity_scheme enum: internal | erc8004 | other No How external identity is named.
external_identity_ref string No e.g. ERC-8004 registration reference.

Group 3 — Task & session context

Field Type Required Description
session_id string No Runtime session identifier.
trace_id string No W3C Trace Context / OpenTelemetry trace id.
span_id string No Span id within the trace.
task_id string No Org-defined task identifier.
task_type string No Free taxonomy; org-defined.
workflow_id string No Workflow or pipeline id.
trigger enum: human | scheduled | agent | event No What started the run.
initiating_principal string No Human or agent that authorized the run.

Group 4 — Consumption

Field Type Required Description
provider string No Model or API provider.
service string No Service product name.
model string No Model identifier.
operation enum: chat | completion | embeddings | tool_call | inference | storage | api_call | other No Operation class.
input_tokens integer No Input token count.
output_tokens integer No Output token count.
cached_tokens integer No Cached / reused tokens when reported.
reasoning_tokens integer No Reasoning tokens when reported.
unit_type enum: tokens | requests | seconds | gb | custom No Unit of consumption.
units_consumed decimal No Quantity in unit_type.
tool_name string No Tool invoked, if any.
request_count integer No Request count for the event.

Group 5 — Cost

Field Type Required Description
list_cost decimal No List price; mirrors FOCUS ListCost semantics.
billed_cost decimal No Billed amount; mirrors FOCUS BilledCost.
effective_cost decimal No Effective cost after discounts; mirrors FOCUS EffectiveCost.
cost_source enum: measured | rated | invoiced | allocated No How cost was derived. allocated means the cost was assigned from a shared cost by an allocation rule rather than metered, rated, or invoiced for this record.
pricing_ref string No Price-sheet row ID.
invoice_id string No Vendor invoice identifier.
focus_charge_ref string No Pointer into a FOCUS dataset row.

Group 6 — Allocation

Field Type Required Description
cost_center string No Cost center or department.
project_id string No Project identifier.
customer_id string No Customer or account identifier.
product_line string No Product line.
environment enum: prod | staging | dev | other No Runtime environment.
tags map<string,string> No Free-form tags.
allocation_method enum: direct | rule | split No How cost was allocated.
allocation_rule_id string No Rule that produced the allocation.
split_fraction decimal (0–1) No Share of a split allocation.

Group 7 — Settlement join

Field Type Required Description
settlement_rail enum: invoice | card | ach | wire | x402 | other_onchain No How money moved.
settlement_network string No CAIP-2 chain ID where applicable.
settlement_asset string No CAIP-19 asset identifier.
settlement_amount decimal No Settled amount.
settlement_ref string No Tx hash, statement line, or remittance ID.
settled_at timestamp No Settlement timestamp.
reconciliation_status enum: unmatched | matched | partial | disputed No Join status against billing/telemetry.

Group 8 — Control & governance

Field Type Required Description
budget_id string No Budget that constrained or funded the spend.
policy_id string No Policy identifier.
authorization_ref string No Mandate / verifiable-intent credential ID.
approval_type enum: policy_auto | pre_authorized | human_approved No How spend was authorized.

Group 9 — Outcome (optional, forward-looking)

Outcome capture is intentionally minimal in v0.1; it exists so cost-per-outcome queries have a landing zone.

Field Type Required Description
outcome_event_id string No Linked outcome event.
outcome_type string No Outcome class (org-defined).
outcome_value decimal No Numeric outcome value.
outcome_unit string No Unit for outcome_value.
business_metric_ref string No Pointer to a business metric definition.

Record schema v0.2.0 (additive)

schema_version "0.2" / "0.2.0" unlocks Group 10 and the capacity record type. v0.1.x records remain valid unchanged. Receivers that advertise known-minor 0.2 MUST reject infrastructure.* keys and record_type: capacity on schema_version < 0.2.

Extended record_type

Value Meaning
capacity Endpoint-level capacity observation over an interval (no caller). Agent and session/task fields MUST be absent.

Group 10 — Infrastructure

Wire names are flat (same style as token/cost fields). OTLP attribute reservations use the oasa.infrastructure.* namespace and map onto these wire names.

Field Type Cardinality Description
pool_id string, ≤128 Optional on usage; required on capacity Customer-defined cost pool / cluster identity. Opaque to the spec; org-scoped at the receiver.
endpoint_id string, ≤128 Optional on usage; required on capacity Serving endpoint identity (e.g. deployment / NAI endpoint name). Opaque.
gpu_seconds number ≥ 0 Forbidden on usage; required on capacity GPU time consumed over the record's interval. Endpoint grain only — not per-request.
gpu_busy_seconds number ≥ 0 Forbidden on non-capacity records; optional on capacity Portion of gpu_seconds during which the GPUs were busy. MUST be ≤ gpu_seconds.
energy_wh number ≥ 0 Forbidden on non-capacity records; optional on capacity Energy consumed by the endpoint over the record's interval, in watt-hours.
hosting enum: provider_api | self_hosted | dedicated_capacity Optional on any record type Signals that a vendor public price list may not apply.
interval_seconds number > 0 Optional Observation window length. occurred_at is the interval end. Added because usage has no interval/window fields.

Token and cost groups remain permitted (not required) on capacity records.

Draft OTLP attribute reservations (v0.2 binding)

OASA wire field Reserved OTLP attribute
pool_id oasa.infrastructure.pool_id
endpoint_id oasa.infrastructure.endpoint_id
gpu_seconds oasa.infrastructure.gpu_seconds
gpu_busy_seconds oasa.infrastructure.gpu_busy_seconds
energy_wh oasa.infrastructure.energy_wh
hosting oasa.infrastructure.hosting
interval_seconds oasa.infrastructure.interval_seconds

These reservations are draft until OASA 0.2.0 is published. They MUST NOT collide with OpenTelemetry GenAI semantic conventions.


Deterministic record IDs

Receivers deduplicate on record_id. An adapter that generates a fresh random ID on every attempt turns each retry into a duplicate row. Adapters whose source has a stable key SHOULD derive record_id from it with the procedure below; two implementations given the same inputs then produce the same ID, and the result is still a valid UUIDv7 under RFC 9562.

Inputs: occurred_at and source_record_id of the record being emitted.

  1. ms = occurred_at as Unix time in milliseconds, truncated (not rounded) to the millisecond.
  2. h = SHA-256 of the UTF-8 bytes of source_record_id.
  3. Build 16 bytes b:
    • b[0..5] = ms as a 48-bit big-endian integer.
    • b[6] = 0x70 | (h[0] & 0x0F) (version 7).
    • b[7] = h[1].
    • b[8] = 0x80 | (h[2] & 0x3F) (RFC 9562 variant).
    • b[9..15] = h[3..9].
  4. Format b as a lowercase hyphenated UUID string (8-4-4-4-12).

For OpenTelemetry spans, source_record_id is <trace_id>/<span_id> in lowercase hex and occurred_at is the span end time.

Test vector: occurred_at = 2026-09-01T15:04:05Z, source_record_id = 4bf92f3577b34da6a3ce929d0e0e4736/00f067aa0ba902b7 → record_id = 01a05d7f-b288-7532-8207-8cb53896d9e1.

Derived IDs are predictable from their inputs. They are identifiers, not secrets, and MUST NOT be used as access tokens.


Machine-readable schema

The record tables above are published as JSON Schema (draft 2020-12) for adapters to validate their output:

Schema identifiers ($id) live under https://oasaspec.org/schema/<major.minor>/ and never change once published; consumers may reference them directly. The schema is generated from this document by scripts/generate-schema.mjs and never edited by hand. A field change is a pull request against SPEC.md first; the schema is regenerated from it, and CI fails if the committed schema drifts from these tables. Run npm run validate to check every example.

Decimal fields, including every money field, are JSON numbers. Consumers SHOULD parse them with decimal precision rather than binary floating point.

The 0.2.0 draft (Group 10 and capacity) is not covered yet; it gets its own schema when 0.2.0 is published. The schema files, examples, and scripts are licensed Apache-2.0 (see LICENSE-APACHE); the specification text remains CC BY 4.0.

Batch envelope

Adapters that send records in bulk wrap them in this header. Every record in records is validated independently; the header carries no attribution.

Field Type Required Description
schema_version string Yes Schema version of the records in the batch; "0.1" for this draft.
source_system string Yes System that emitted the batch, e.g. otel-collector.
emitted_at timestamp (RFC 3339, UTC) Yes When the batch was sent.
count integer Yes Number of records in records.
records array of records Yes OASA records, each valid against the record schema.

Mapping tables

Verified against upstream docs on 2026-09-15. OpenTelemetry GenAI conventions are still experimental and may drift; attribute names here are current as of that date, and unmapped OASA fields are noted where no ratified equivalent exists. FOCUS token-economics columns track 1.4 (ratified) and 1.5 (scheduled). x402 V2 communicates requirements via PAYMENT-REQUIRED payloads (network, asset, amount, payTo).

OASA ↔ OpenTelemetry GenAI

Implemented by oasa-otel-exporter (OpenTelemetry Collector exporter, v0.1.0).

OASA OTel Notes
provider gen_ai.provider.name
input_tokens gen_ai.usage.input_tokens
output_tokens gen_ai.usage.output_tokens
cached_tokens gen_ai.usage.cache_read.input_tokens Cached/reused input tokens. Where a provider reports cache reads separately, map the cache-read count here; do not double-count into input_tokens if the provider already reports them inclusively. OTel also defines gen_ai.usage.cache_creation.input_tokens for cache writes.
reasoning_tokens gen_ai.usage.reasoning.output_tokens Output tokens used for reasoning (e.g. chain-of-thought). OTel says this value SHOULD be included in gen_ai.usage.output_tokens. Where a provider reports reasoning only in billing payloads, populate from the provider usage object.
model gen_ai.request.model The requested model. Where a provider returns a resolved or versioned model, prefer that value and record the requested model in tags if both matter.
operation gen_ai.operation.name
tool_name gen_ai.tool.name Emitted on tool-execution spans. Pair with operation = tool_call.
session_id gen_ai.conversation.id Where a runtime emits a conversation/thread identifier. OASA session_id is broader — a non-conversational agent run is still a session.
agent_id gen_ai.agent.id
agent_name gen_ai.agent.name
trace_id / span_id W3C Trace Context / OTel trace_id, span_id Join key to the Attribution layer; carried from W3C Trace Context.

OASA ↔ FOCUS

FOCUS 1.4 added token-economics columns; FOCUS 1.5 (Dec 2026 target) adds native token tracking and a Price Sheet dataset. OASA tracks those columns as they ratify.

OASA FOCUS
list_cost ListCost
billed_cost BilledCost
effective_cost EffectiveCost
provider ServiceProviderName (FOCUS 1.4; formerly ProviderName)
service ServiceName
tags Tags
charge rows (record_type=charge) ChargeCategory / charge rows

Full column-by-column mapping, including the Invoice Detail dataset and worked examples: OASA ↔ FOCUS 1.4 mapping.

OASA ↔ x402

OASA x402
settlement_network PaymentRequired.network (PAYMENT-REQUIRED)
settlement_asset PaymentRequired asset / token
settlement_amount PaymentRequired amount / price
settlement_ref on-chain tx / SettlementResponse reference
(payee context) PaymentRequired.payTo

Worked example

One agent tool-call as a usage row, later joined by a charge row and an x402 settlement row sharing trace_id / settlement_ref.

1. Usage (telemetry)

{
  "record_id": "0193f0a0-0000-7000-8000-000000000001",
  "record_type": "usage",
  "schema_version": "0.1",
  "occurred_at": "2026-09-01T15:04:05Z",
  "recorded_at": "2026-09-01T15:04:06Z",
  "source_system": "otel-collector",
  "source_record_id": "span-9f2c",
  "currency": "USD",
  "agent_id": "agent-support-47",
  "agent_name": "Support Triage",
  "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
  "span_id": "00f067aa0ba902b7",
  "task_type": "ticket_resolve",
  "provider": "openai",
  "model": "gpt-4.1",
  "operation": "tool_call",
  "tool_name": "crm.lookup",
  "input_tokens": 1200,
  "output_tokens": 340,
  "cached_tokens": 800,
  "session_id": "sess-2f91",
  "unit_type": "tokens",
  "units_consumed": 1540,
  "cost_source": "measured"
}

2. Charge (billing)

{
  "record_id": "0193f0a0-0000-7000-8000-000000000002",
  "record_type": "charge",
  "schema_version": "0.1",
  "occurred_at": "2026-09-02T00:00:00Z",
  "recorded_at": "2026-09-02T06:12:00Z",
  "source_system": "openai-billing",
  "source_record_id": "inv_line_88421",
  "currency": "USD",
  "agent_id": "agent-support-47",
  "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
  "invoice_id": "INV-2026-09-01",
  "focus_charge_ref": "focus:row:88421",
  "list_cost": 0.042,
  "billed_cost": 0.038,
  "effective_cost": 0.038,
  "cost_source": "invoiced"
}

3. Settlement (x402)

{
  "record_id": "0193f0a0-0000-7000-8000-000000000003",
  "record_type": "settlement",
  "schema_version": "0.1",
  "occurred_at": "2026-09-01T15:04:07Z",
  "recorded_at": "2026-09-01T15:04:08Z",
  "source_system": "x402-facilitator",
  "source_record_id": "pay_0xabc",
  "currency": "eip155:8453/erc20:0x",
  "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
  "settlement_rail": "x402",
  "settlement_network": "eip155:8453",
  "settlement_asset": "eip155:8453/erc20:0x",
  "settlement_amount": 0.038,
  "settlement_ref": "0xdeadbeef",
  "settled_at": "2026-09-01T15:04:07Z",
  "reconciliation_status": "matched"
}

References

Upstream specifications and standards this document maps to or depends on.

Reference Steward Link
OpenTelemetry GenAI semantic conventions OpenTelemetry (CNCF) https://opentelemetry.io/docs/specs/semconv/gen-ai/
FOCUS™ (FinOps Open Cost and Usage Specification) FinOps Foundation (Linux Foundation) https://focus.finops.org/
x402 x402 Foundation (Linux Foundation) https://x402.org/
W3C Trace Context W3C https://www.w3.org/TR/trace-context/
CAIP-2 (chain ID) / CAIP-19 (asset ID) Chain Agnostic Standards Alliance https://chainagnostic.org/
ERC-8004 (agent identity) Ethereum https://eips.ethereum.org/
RFC 3339 (timestamps) IETF https://www.rfc-editor.org/rfc/rfc3339
ISO 4217 (currency codes) ISO https://www.iso.org/iso-4217-currency-codes.html
UUIDv7 (RFC 9562) IETF https://www.rfc-editor.org/rfc/rfc9562

Referencing these specifications does not imply endorsement by their stewards. OASA is an independent publication of Onaro (BrianOnAI LLC).


Changelog

See CHANGELOG.md.

Cite this specification

Open Agent Spend Attribution (OASA) Specification, version 0.1.2. Onaro, 2026-09-14 (revised 2026-10-08). https://www.onaro.io/spec

License: CC BY 4.0. Attribution required; adaptations allowed.