PourlinesReal-time metering
pipelines · on tap
v0.4 · drip — now public

Counts at the speed
of light. Give or take.

API calls, AI tokens, compute seconds, credits. Pourlines reads your meterspec, ships the number to the customer's screen, and lets Claude / Codex / Cursor write the hooks for you. Or Terraform, for the enterprise side of the room.

forecourt · meter tokens.gpt5 on tap
184,293,017
tokens metered · last 60sstreaming · p99 4ms
How it pours

Meterspec in. Drip out.

Three moving parts. One file you commit, one stream the SDK subscribes to, one number on the customer's screen.

01 · Decant

Write the meterspec.

Declare your meters in a single file. Counters, gauges, deduplication keys, decimal precision. Versioned with your code.

meterspec.toml
02 · Pour

Send the events.

One pour.track() call per event. The number lands on the customer's screen before the request closes. Idempotent by default. Spills get reconciled, not double-counted.

@pour/sdk · 14kb
03 · Drip

Streamed, not polled.

One hook. The exact count streams into the customer's browser as it changes — no polling, no five-minute lag, no "refresh to see your balance." Same primitive powers usage dashboards, credit balances, and overage alerts.

usePourMeter() · streams live
The two files

A spec you commit.
A hook you subscribe to.

meterspec.tomltoml
# the schema artifact. lives in your repo.
[meter.tokens]
kind        = "counter"
unit        = "tokens"
dedupe_on   = "event_id"
precision   = 0
brim_at     = 1_000_000     # soft cap → ● spill

[meter.compute]
kind        = "gauge"
unit        = "seconds"
window      = "1m"
on_dry      = "alert.team"
app/usage.tsxtypescript
import { pour, usePourMeter } from "@pour/sdk";

// fire and forget. idempotent on event_id.
pour.track("tokens", { qty: 2_413, event_id });

// live counter, no polling.
function Balance() {
  const { value, status } = usePourMeter("tokens");
  return <Counter n={value} status={status} />;
  // ● on tap · ticks at p99 4ms
}
MeterSpec · one shape, many numbers

Events, aggregated.
Same compile.

If you can express it as events aggregated over time, grouped by something, MeterSpec compiles it. Euros, grams of CO₂, DORA incidents, SLA breaches, chargeback units — same abstraction, same audit-grade pipeline, same TypeScript document. Below: one event family, two projections — a Stripe-shaped billing meter and a CSRD-shaped reporting meter — sharing one row so the invoice and the regulator number cannot drift.

Fits the shape
  • Billing & usage
  • Carbon attribution · Scope 2/3
  • AI Act energy disclosure
  • Internal cost allocation · chargeback
  • SLA & error-budget tracking
  • DORA incidents · GDPR access requests
  • Inventory throughput · defect rates
Doesn't fit (we're honest)
  • Continuous signals — discretize first
  • Custom math beyond CEL · no UDFs
  • Predictions & forecasts
  • Pure analytics needing full history
  • High-cardinality blowups (group_by event_id)
01 · Event familyacme.platform.agent-run
[[families]]
name              = "acme.platform.agent-run"
event_id_field    = "agent_run_id"
event_time_field  = "completed_at"

  [families.schema.duration_ms]              # drives billing
  type = "integer"; required = true

  [families.schema.energy_joules]            # drives carbon
  type = "float";   required = true

  [families.schema.grid_intensity_g_per_kwh]
  type = "float";   required = true

  [families.schema.carbon_g]
  type = "float";   required = true

  [families.compliance]
  jurisdiction          = ["eu", "eu_restricted"]
  audit_retention_years = 7
02 · Project · billing→ Stripe
[[meters]]
name = "agent-hours-billing"
kind = "billing"                             # → Stripe

  [meters.source]
  kind = "family"; family = "acme.platform.agent-run"

  [meters.aggregate]
  fn       = "SUM"
  value    = "duration_ms / 3600000.0"
  window   = "billing_cycle"
  group_by = ["customer_id"]

  [meters.billing]
  model    = "combined"; currency = "EUR"
  included_units = 100
  tiers = [
    { up_to = 1000,       unit_amount = 0.45 },
    { up_to = "infinity", unit_amount = 0.30 },
  ]

  [meters.compliance]
  regulatory_regimes = ["dora"]
02 · Project · reporting→ CSRD · AI Act · 10y retention
[[meters]]
name = "agent-carbon-attribution"
kind = "reporting"                           # no Stripe, no entitlement

  [meters.source]
  kind = "family"; family = "acme.platform.agent-run"

  [meters.aggregate]
  fn       = "SUM"
  value    = "carbon_g / 1000.0"             # grams → kg CO2e
  group_by = ["customer_id", "region"]

    [meters.aggregate.window]                # 30-day rolling
    kind             = "rolling"
    duration_seconds = 2592000

  [meters.compliance]
  regulatory_regimes    = ["csrd", "ai_act"]
  audit_retention_years = 10
01

One event log.

Every agent-run writes once, to one Kafka topic, with both duration_ms and carbon_g on the same row. No second pipeline. No second schema. No second auditor evidence chain.

02

Two compiled pipelines.

Pourlines reads the document and emits a billing projection (Stripe-shaped, billing_cycle window) and a reporting projection (30-day rolling, customer × region). Both deterministic. Both replayable.

03

One compliance posture.

Residency, jurisdiction, audit retention live on the family. Reporting meters can tighten retention (10y for CSRD), never loosen it. The compliance manifest is generated at compile time.

CSRD30-day rolling kg-CO2e per customer × region. Auditor walks the evidence chain back to the originating event.
AI Actenergy_joules + risk class declared on the family. Energy disclosure is a projection, not a side report.
DORAImmutable audit retention (7y on the family, 10y on reporting). Compliance manifest committed alongside the spec.
v1.2One-click evidence export · methodology replay. Shipping next.
A note on the words

Pourlines does not measure emissions. It attributes them — faithfully — from upstream signals you provide on the event (energy_joules × grid_intensity_g_per_kwh → carbon_g). The arithmetic is yours; the pipeline is ours.

First-class agent interface

When the meter moves,
your agent moves with it.

Hooks are typed events. Recipes on top, webhooks underneath. Wire them by hand with Terraform, or point Claude / Codex / Cursor at the Pour MCP and let the agent author and ship them. Every hook ships with a payload schema, an agent prompt, and a replay log.

on.balance.lowtop upbilling.topUp({ pack: $100 })
on.drycut offmeter.cutoff({ scope })
on.brimthrottlerate.set({ tier: 'soft' })
on.period.closebillinvoice.post({ lines })
payloadagent promptmcp / terraformreplay
{
  "id": "evt_01HZQ8TMK3...",
  "trigger": "on.balance.low",
  "at": "2026-05-04T18:32:14.041Z",
  "balance": { "before": 4.92, "after": 4.92 },
  "threshold": 5.00,
  "account": { "id": "acc_acme", "tier": "on_tap" },
  "idempotency_key": "balance.low:2026-05-04T18:32"
}
Claude · Codex · Cursor

One MCP. All the tools.

Install once. Your agent gets meter.read · billing.topUp · rate.set · hook.replay · invoice.post with typed args, examples, and golden tests. Docs are written for LLMs first.

$ npx @pour/mcp install
✓ wrote ~/.config/mcp/pour.toml
✓ workspace: acme/prod
Terraform

Or just commit it.

For the enterprise side of the room. Hooks as code, reviewed in PR, deployed by your existing pipeline. Same engine underneath.

resource "pour_hook" "topup_brim" {
  trigger = "on.balance.low"
  recipe { name = "topup", args = { pack = 100 } }
}
Billing models, not just one

Top up. Subscribe.
Or meter the overage.

Most metering vendors only know one shape of invoice. Pourlines ships three primitives so your pricing page can be whatever shape your product is.

Mode 01 · Top up

Prepaid credits.
Drip them down.

Customers buy a balance up front. Pourlines decrements the live counter as events arrive. Auto-refill thresholds, soft brims, hard cutoffs.

pack$100 · 550k pours
rate$0.18 / 1k
brim @$5 · auto top-up
top updripbrim
Mode 02 · Subscribe

Flat plans.
Live usage inside.

Monthly or annual subscriptions, with included quotas you can actually see go down. Upgrade, downgrade, or pause without exporting a CSV.

planOn Tap · $99 / mo
includes2.0M pours
cancelmonthly
subscribedecant planrenew
Mode 03 · Metered plan

Subscription plus
true overage.

Combine a base plan with usage-based overage. Pour computes the bill in-stream, so the customer's dashboard shows the same number that lands on the invoice.

base$99 / mo
includes2.0M pours
overage$0.20 / 1k
invoicebase + live overage
subscribemetertop up
Vs the jerry can

Batch reports tomorrow.
Or the number, now.

The jerry can

CSV exports. Cron jobs. Customer support tickets.

  • Counts settle once a day. Maybe.
  • Schema migrations require a maintenance window.
  • Dropped events show up in next month's invoice.
  • The dashboard is a Looker view someone built in 2022.
  • Pricing engines you can't read. Tiers you can't change.
Pourlines

Meterspec in. Live counter out. p99 4ms.

  • Counts in the customer's browser before they refresh.
  • Decant a meterspec without dropping a single event.
  • Spills are reconciled in-stream. No double counting.
  • Forecourt is the dashboard. The drip is the API.
  • Pricing tiers you ship in a PR.
Receipts

Numbers, not adjectives.

p99 ingest4ms
events / day2.4B
dropped (30d)0.0001%
status on tap
Pricing · Drip · On Tap · Refinery

Same tiers.
Three ways to pay.

Flat monthly. Live counters inside the plan. Cancel monthly.

Drip
$0 · free · forever
  • 100k pours / mo
  • 1 meterspec
  • 7d retention
  • community support
Subscribe
Refinery
$1,500+ · per month · 50M pours · custom
  • 50M+ pours / mo
  • private Forecourt
  • SOC2 BAA on request
  • named engineer on Slack
Subscribe
Private beta

Pour is opening in waves.
Reserve a spot.

We're onboarding a handful of design partners per week. EU-first, regulated workloads first. Tell us what you'd meter and we'll be in touch.

● No spam. One email per onboarding wave.
States, in two words

Empty states are
brand moments.

Healthy on tap
"Counting at the speed of light. Give or take."
— footer copy
Dry meter dry · 14d
"No meters yet. Pour one."
— empty state copy
Incident spill — investigating
"We spilled some events. They're being mopped up."
— error state copy
404
"This page is dry."
— Brimley, resident decanter