DEV Community

Cover image for Let's Put Impact Metadata Into an Open API Specification
CarbonLayer
CarbonLayer

Posted on

Let's Put Impact Metadata Into an Open API Specification

We've spent the last few posts asking whether environmental impact should travel with an AI request.
We've looked at what the receipt could contain.
We've talked about provenance.
We've talked about measured versus modeled data.
We've talked about methodology.
We've talked about interoperability.
Now it's time to stop describing the idea.
Let's put the interface on the table.
Introducing the Impact Receipt Specification
I'm calling the initial proposal:
Impact Receipt Metadata — IRM/0.1
This is not being presented as an industry standard.
It's a proposal.
The point is to make it concrete enough for developers to implement, inspect, criticize, and improve.
The basic concept is simple:
AI RESPONSE
|
+----------------------+
| |
v v
Normal response Impact metadata
|
+-------+-------+
| | |
Energy Carbon Water
| | |
+-------+-------+
|
Provenance
|
Methodology
The environmental information becomes part of the API contract.
The proposed shape
Here is what an Impact Receipt looks like when no environmental measurement exists yet — which is the honest state of most systems today, including ours:
{
"response": "...",

"impact": {
"id": "impact_123",
"timestamp": "2026-09-27T18:00:00Z",

"model": "example-model",

"tokens": {
  "input": 600,
  "output": 200,
  "total": 800
},

"performance": {
  "latencyMs": 78
},

"energy": { "status": "unavailable" },
"carbon": { "status": "unavailable" },
"water":  { "status": "unavailable" },

"methodology": {
  "name": "example-methodology",
  "version": "0.1"
},

"data": {
  "source": "simulated",
  "degraded": false
}
Enter fullscreen mode Exit fullscreen mode

}
}
And here is the same shape carrying values — this example is fictional; every number in it was invented by hand to show the format, and is not a measurement of anything. Be precise about what that means: a truthful receipt could not carry these numbers under any provenance label — hand-invented values are not receipt data at all. The simulated tags in this illustration show only where provenance sits in the shape; in a live receipt, that label belongs exclusively to values a system actually generated. A modeled value — calculated from documented inputs by a stated method — is what real evidence looks like in this field. We don't have one to show you yet, and that absence is exactly the discipline this format exists to enforce.
"energy": {
"value": 0.2,
"unit": "kWh",
"source": "simulated",
"status": "available"
},
"carbon": {
"value": 10,
"unit": "gCO2e",
"source": "simulated",
"status": "available"
}
A developer should be able to look at both and understand exactly what they're receiving — and, just as importantly, what they are not receiving.
One terminology note, to keep ourselves honest: the numbers in that second example were invented by hand for this post. They are format illustrations, not receipt data — no real receipt has ever carried them, under any label. The simulated tag in the example shows where provenance belongs in the shape; in a live receipt, that label may only be used for values a system actually generated. And a general note for the whole post: the code snippets are fragments shown for readability unless presented as a complete receipt — a complete, schema-valid receipt would also carry id, timestamp, methodology, and data.
Why an impact object?
Because environmental metadata shouldn't destroy the existing API contract.
An application that doesn't care about environmental data can simply ignore the impact field altogether:
{ "impact": { } }
(That empty object is a placeholder sketch, not a valid receipt — a real ImpactReceipt must carry id, timestamp, methodology, and data, as the schema below requires. The point is the consumer's freedom: the normal response is unchanged, and nothing forces an application to read the impact object.)
A system that does care can consume it.
That means an existing AI API could theoretically add Impact Receipt metadata without forcing every consumer to redesign its application.
Explicit units
One of the first rules should be:
Never make the developer guess the unit.
Bad:
{ "carbon": 14.7 }
Better:
{
"carbon": {
"value": 14.7,
"unit": "gCO2e",
"source": "simulated",
"status": "available"
}
}
The same applies to energy, water, and carbon intensity. This sounds trivial. It isn't. Data gets moved between systems constantly. Explicit units prevent an entire class of integration mistakes. (The values in this section are format illustrations only — not measurements, and not receipt data. No truthful receipt would attach a provenance label to a hand-invented number; the simulated tag here shows only the field's position in the shape.)
Provenance is mandatory context
The proposal should distinguish at least:
measured — directly observed by an instrument or meter
modeled — calculated from documented inputs and a stated method
simulated — generated by a system that did not perform the underlying work. In a live receipt, this label is only valid for values a system actually generated; it is never a label for numbers a person simply made up.
{
"carbon": {
"value": …,
"unit": "gCO2e",
"source": "modeled",
"status": "available"
}
}
The value is elided on purpose: a modeled label is earned by documented inputs and a stated method, and a fragment can't show either — so this post won't borrow the label for an invented number. What the fragment does show is the shape every reported value must take: value, unit, provenance, status.
A measured value and a modeled value should not be treated as equivalent simply because the JSON structure is identical. That's the point of preserving provenance. And a simulated value must never wear a modeled label — simulation is how you test a format, not how you learn about the world.
What about derived values?
Suppose we have:
Energy = 0.2 kWh
Grid intensity = 50 gCO2e/kWh
We calculate: Carbon = 10 gCO2e.
The carbon result is derived. The underlying energy and grid-intensity evidence still matters. The specification should therefore make it possible to trace a value back to its inputs. That becomes increasingly important as receipts are aggregated. (Worked example with invented round numbers, for illustration.)
Unknown is not zero
This should be one of the strongest rules in the specification.
If water isn't available:
{ "water": { "status": "unavailable" } }
Not:
{ "water": { "value": 0 } }
Zero is a value — a claim that the quantity is none, and like any value, it needs a provenance.
Unavailable is a state — an admission that no value exists.
Those must never be conflated.
Methodology versioning
Every environmental metric should be interpretable in the context of a methodology:
{
"methodology": {
"name": "example-methodology",
"version": "0.1"
}
}
Why? Because methodologies evolve. Imagine a methodology changes how it allocates cooling energy. The new numbers might be better. But historical results calculated under the old method shouldn't silently appear as though they were calculated under the new method. Versioning makes that visible.
Data-source status
A receipt should also say something honest about its underlying data path:
{
"data": {
"source": "simulated",
"degraded": false
}
}
This matters because a system can have a perfectly valid API response while one of its upstream environmental datasets is unavailable. The API shouldn't hide that.
The minimum viable specification
I don't think IRM/0.1 should attempt to solve every environmental accounting problem. The first version could define a small core:
Required — id, timestamp, methodology, provenance (provenance = the source field on every reported value, plus the receipt's data-source status)
Recommended — model, tokens, energy, carbon
Optional — water, latency, location, grid intensity, renewable mix, uncertainty, hardware
The exact required/optional classification is something I want developers to challenge. The important thing is to start somewhere.
An OpenAPI-style schema
A first machine-readable definition could look like this. One clarification first: ImpactReceipt is the nested impact object — the thing this specification actually defines. It travels inside the provider's ordinary response envelope, which this spec does not try to standardize:
InferenceResponse: # the envelope — not standardized by this spec
type: object
properties:
response: { } # the provider's normal response, unchanged
impact: { $ref: '#/components/schemas/ImpactReceipt' }

components:
schemas:
ImpactReceipt:
type: object

  required:
    - id
    - timestamp
    - methodology
    - data

  properties:
    id:        { type: string }
    timestamp: { type: string, format: date-time }
    model:     { type: string }

    tokens:
      type: object
      properties:
        input:  { type: integer }
        output: { type: integer }
        total:  { type: integer }

    performance:
      type: object
      properties:
        latencyMs: { type: number }

    energy: { $ref: '#/components/schemas/ImpactMetric' }
    carbon: { $ref: '#/components/schemas/ImpactMetric' }
    water:  { $ref: '#/components/schemas/ImpactMetric' }

    location:    { type: object }
    methodology:
      type: object
      required: [name, version]
    data:
      type: object
      required: [source]
      properties:
        source:
          type: string
          enum: [live_grid, static_reference, simulated]

ImpactMetric:
  type: object
  required: [status]
  properties:
    value: { type: number }
    unit:  { type: string }
    source:
      type: string
      enum: [measured, modeled, simulated]
    status:
      type: string
      enum: [available, unavailable]

  # Conditional rules (JSON Schema if/then, as supported in OpenAPI 3.1),
  # combined under allOf so both rules apply:
  allOf:
    - if:
        properties: { status: { const: available } }
        required: [status]
      then:
        required: [value, unit, source]
    - if:
        properties: { status: { const: unavailable } }
        required: [status]
      then:
        allOf:
          - not: { required: [value] }
          - not: { required: [unit] }
          - not: { required: [source] }
Enter fullscreen mode Exit fullscreen mode

And the receipt-level data object — which is required on every receipt, as the schema's required list now shows — must itself carry its source field, so a receipt can never stay silent about its own data path.
Note the discipline built into that schema: status is required on every metric; when a value is present, its unit and its provenance are required with it; when status is unavailable, value, unit, and provenance are all forbidden — the metric is its status and nothing else. A metric reports either a value with its provenance, or status: unavailable. Never a bare number. That is what "provenance is mandatory" means mechanically, not just in prose.
Again: this is a proposal, not a finished standard.
The important part isn't the JSON
The most important part is the semantics.
carbon.value = 14.7 doesn't tell you enough. But value + unit + source + methodology + version + timestamp + location + data status starts to tell you what the number actually means. That is the difference between a metric and an auditable data point.
What could consume this?
Once the receipt has a predictable structure, other software could consume it. These are possibilities the format is designed to allow — not features anyone should assume exist today:
Observability — a request carrying latency, tokens, cost, energy, carbon, and water in one record.
CI/CD — inference tests producing an Impact Receipt, checked against a threshold before deployment.
Routing — an application policy comparing impact metadata across regions and choosing accordingly.
Reporting — many individual receipts could eventually become aggregate reporting. But the aggregate should retain the provenance of the underlying data. A dashboard total without evidence is still just a number.
And this is where CarbonLayer comes in — honestly
Here's the practical question. You can agree with the concept. You can agree that environmental metadata belongs next to latency and tokens. You can even implement your own receipt schema. But someone still has to do the unglamorous work of defining the format, the provenance rules, and the aggregation design. That's the work CarbonLayer is doing.
So here is exactly where CarbonLayer stands today, because this series does not publish claims we can't support:
CarbonLayer is a prototype. Its API returns a simulated receipt — not yet in the IRM format proposed here. The current receipt marks the request as simulated, records that no provider call occurred, and reports impact as unavailable rather than filling in invented numbers. IRM/0.1 is the format we are proposing to converge on — it is not a description of today's response shape.
A separate Playground can make a single direct provider call when configured. That provider response is distinct from the simulated inference receipt — the Playground does not produce a verified combined impact receipt — and without configuration it returns a simulated example.
What exists today is a working prototype receipt. This proposal codifies the honesty rules for what comes next: never present simulated as measured, never report unavailable as zero.
What does not exist yet: measured environmental values, production routing, and any savings calculation. Those are roadmap items, and this post does not announce them.
The prototype is designed around the problem this series describes. It is the beginning of the infrastructure, presented as exactly that.
The developer workflow we are aiming for
Instead of building this yourself:
AI provider → custom energy calculation → custom grid lookup → custom carbon
calculation → custom water calculation → custom provenance model → custom
database → custom dashboard
the goal is eventually:
Your application
|
v
CarbonLayer
|
+---- inference
+---- impact metadata
+---- provenance
+---- aggregation
|
v
Your application
That's the value proposition we are building toward: don't rebuild the impact layer from scratch every time you build an AI application. "Building toward" is doing real work in that sentence — the aggregation and provenance infrastructure is the roadmap, not the current product.
But CarbonLayer should not be the gatekeeper
This is important.
If this specification is useful, I want other developers to be able to implement it without becoming CarbonLayer customers. The schema should be open. The examples should be open. The discussion should be open. The criticism should be welcome.
CarbonLayer can provide an implementation. It doesn't have to own the concept. That's how this becomes infrastructure rather than marketing.
What I want to build next
The next step isn't another blog post. It's putting this proposal somewhere developers can work on it in the open. The structure we intend to publish for public review looks something like:
impact-receipt/
├── README.md
├── specification/
│ └── v0.1/
│ ├── schema.json
│ └── schema.yaml
├── examples/
│ ├── basic.json
│ ├── modeled.json
│ ├── unavailable-water.json
│ └── degraded-data.json
├── docs/
│ ├── provenance.md
│ ├── methodology.md
│ └── units.md
└── LICENSE
That repository does not exist yet, and this post doesn't promise a date for it. When it exists, we can ask developers to do something much more useful than "tell us what you think": implement it, break it, review it, open an issue, submit a pull request.
The CarbonLayer proposition
This is the point where the original idea comes full circle.
AI developers already have infrastructure for latency, tokens, errors, cost, and availability. But environmental impact is still often separated from the request that created it.
CarbonLayer is building the missing layer — starting with the part that has to come first: a receipt format that refuses to lie about what it knows.
AI APPLICATION
|
v
+----------------+
| CarbonLayer |
+----------------+
| | |
v v v
ENERGY CARBON WATER
| | |
+----+----+
|
PROVENANCE
|
METHODOLOGY
|
v
IMPACT RECEIPT
The goal isn't to give you another sustainability dashboard. The goal is to make environmental impact usable at the point where AI infrastructure decisions are actually made — with evidence attached, and with "unavailable" as an acceptable, honest answer.
Try the prototype — with the right expectations
CarbonLayer is currently a prototype, so the right way to evaluate it is as a prototype.
Call the API. Inspect the response. Look at the receipt structure. Look at the provenance fields. Watch the environmental fields report unavailable instead of inventing values — that behavior is the point. Question the methodology. Tell us what is missing.
CarbonLayer isn't asking you to blindly trust an environmental number. At this stage there is no environmental number to trust — there is a format, a set of honesty rules, and an open invitation to test both.
Where we go from here
This is the beginning of the next phase.
The Impact Receipt started as an idea: carbon should travel with every AI request.
Now it has become: a response format.
Then: a proposed schema.
Next: an open proposal, published for criticism and improvement.
And eventually, if developers find it useful: a piece of AI infrastructure.
If you're building AI applications, inference platforms, observability tools, or carbon-aware infrastructure, I'd like your feedback.
What field is missing?
What should be required?
What should never be reported as measured?
What would you need before you trusted an Impact Receipt in production?
And if you don't want to build this infrastructure yourself — that's exactly why CarbonLayer exists.
The next step is to stop estimating environmental impact after the fact and start carrying the evidence with the workload itself.

The series
Part 1 — The Impact Receipt: Carbon Should Travel With Every AI Request
Part 2 — I Built the First Version of an AI Impact Receipt
Part 3 — What Should an AI Impact Receipt Actually Contain?
Part 4 — Can an AI Impact Receipt Be Standardized?
Part 5 — Let's Put Impact Metadata Into an Open API Specification

Top comments (0)