The rule: meter, then delete
A deleted session’s usage cannot be read.GET /v1/sessions/{id}/usage
answers 404 once the session is gone. So the order is fixed — read, record,
delete — and a crash anywhere in between has to be recoverable without
recording twice.
Tag sessions when you create them:
The code
How a crash is survived
closeSession is safe to call again after failing at any step, because each
step leaves evidence the next attempt reads:
The session’s own
metadata is the ledger, so there is no database of your own
to keep in step. Stripe’s identifier — gobare-{session_id} — covers the one
window the mark cannot: Stripe enforces its uniqueness for at least 24 hours.
Which number to bill
GET .../usage returns several:
This page bills
sandbox_seconds, because it is the only number that is final
by the time you delete. It is your quantity to bill your customers with, not a
copy of Gobare’s bill to you. See sessions.
An idle session is still accruing. In testing, three idle sessions’ usage
rose from 23 to 30 seconds between reading the metrics and closing them. Close
sessions when the work is done, not when a sweep gets round to it.
Prometheus
metrics() is a scrape target. gobare_live_sandbox_seconds falls when a
session is deleted, so it is a gauge of what is running — alert on it, graph
it, but bill from the meter.
What was verified
Run against production, with a local mock of Stripe’sPOST /v1/billing/meter_events that refused a repeated identifier with a
400:
- Metrics. Three sessions for three customers produced one
gobare_sessionsand onegobare_live_sandbox_secondsline per customer. - A clean close recorded
25seconds forcus_ACMEand deleted the session. - A crash after marking skipped Stripe and deleted.
- A crash before marking re-sent, was refused as a duplicate, marked and deleted; Stripe kept the first attempt’s value.
- A third call on a deleted session returned without error.
identifier. Stripe
documents the uniqueness, not the response. The mock answers 400 with a
message naming the identifier, and meterEvent matches on that. Send a
duplicate in test mode once and adjust the match before relying on it.
Next
- limits — the concurrency ceiling a live session holds
- Run a fleet of ticket-driven agents in parallel — deleting on
turn.completed, which is wherecloseSessionbelongs