pinecallDocs
Operating / Charging for it

Charging for it

A billing layer beside your runtime: plans as quotas, the usage feed, and what is not there yet.

The runtime charges nobody. It holds no plan, no price and no card: whoever runs a deployment and wants to bill for it writes a billing layer beside it, and the runtime gives that layer mechanisms to steer. With nothing set, a deployment has no limits and runs every call on its own vendor keys, which is what a deployment run for one's own agents wants.

 ┌───────────────────── the runtime ─────────────────────────────────────────┐
 │  admission   what a NEW org is given, per world         /v1/ops/admission │
 │  quotas      what an org may use and keep, per world    /v1/ops/orgs/{org}/quotas │
 │  lends       which of the box's vendor keys it runs on  (a quota)          │
 │  usage       every call's consumption, with a cursor    /v1/ops/usage      │
 │  the log     credits.exhausted when a quota refuses                        │
 └───────────▲────────────────────────────────────────────────────────────────┘
             │ the ops key, over HTTP, when money moves
 ┌───────────┴───────────── your billing layer ──────────────────────────────┐
 │  plans → quotas and lends · usage → invoices · a payment failed → quotas  │
 └────────────────────────────────────────────────────────────────────────────┘

Keep the payment provider out of process: a webhook, a retry or an outage at the provider must never fail a call. The runtime decides every call from rows it already holds, and your layer writes those rows when money moves.

1. What a new org is given — admission#

The admission row of the platform's settings, PUT /v1/ops/admission: {first: {production: quotas, sandbox: quotas}, later: {…} | null}. first is what a person's first org gets in each environment; later, when set, what any later org of the same person gets, which is how "one trial per person" is said. It is read at the moment an org is made, by sign-up or by the operator; an org already made keeps what it has. A deployment with no row gives a new org no limits.

2. What an org may use — quotas and lends#

Per org and per environment, set whole at PUT /v1/ops/orgs/{org}/quotas: minutes, messages, agents held, calls at once, memory facts, knowledge chunks, bought numbers, seats, model tokens, and a monthly budget_usd shown beside what was spent, and a new call is refused once the month's spend reaches it. lends says which of the platform's own vendor keys the org runs on: every one (null), none ([]), or named vendors and vendor/model prefixes. An org that brought its own key for a vendor runs on it whatever lends says. What each quota counts and when it bites: Limits and provider keys.

3. What an org consumed — the usage feed#

GET /v1/ops/usage?after=&org=&limit=, or the same as a stream with Accept: text/event-stream: one row per call.summary, call.score and evals.spent, each {cursor, org, agent, call, type, at, minutes, messages, input_tokens, output_tokens, characters, judge_calls, evals, simulated, cost_usd} (v1's shape, flat, and two more), and the totals per org, which count calls and simulations too. A call.score row carries its evals, the judges that answered the call on the platform's key (on the org's own key, 0); a call.summary row of a call a simulated caller played says simulated: true and counts no minutes, and a layer bills it as one simulation; a call an eval run opened and wrote counts no minutes either. A evals.spent row (on the agent's log, call empty) carries the evals of a judge tried on finished calls. The cursor is the store's position: a consumer that keeps the last one resumes and counts nothing twice. It skips nothing either: a row's position is given as it is written, not as it commits, so every write of a metered row takes one lock and holds it to its commit, and the positions a feed reads come in the order their rows became visible. cost_usd is what the vendors charged the operator, as the providers row's rates price it; what the operator charges is the layer's own business. An org reads its own rows at GET /v1/usage.

What a call's cost counts, a row per unit billed:

  • the model, the ears and the voice, as livekit metered them: tokens with their cache reads and writes, characters spoken, seconds heard;
  • the model that writes memory at hang-up: its tokens join the call's usage, so they are billed and counted with it;
  • each leg on the phone network: one row per leg, from when it joined the room until it left, in minutes begun billed whole, as a carrier bills. A leg is priced by the longest prefix of its number, as a carrier's own table is: the platform's number when the call came in (twilio-inbound/+1 is a local number, twilio-inbound/+1800 a toll-free one), the dialled one when it went out (twilio-outbound/+1907 is Alaska). The row names the prefix it matched, never the number. A transfer is a second leg, priced on its own.

The apps the platform hosts are a second meter, apart from calls: the time each app served, per UTC day, counted while a process of the org runs under one of its hosts (Hosted apps). GET /v1/ops/hosted-usage[?month=YYYY-MM] answers every org's month, {since, until, rows: [{org, env, name, day, seconds}]}, and an org reads its own at GET /v1/hosted/usage. A price per app-month, prorated by those seconds, is the layer's: Pinecall's cloud charges $5 an app a month.

The judges' tokens are on call.score (judge_cost_usd), apart from the call's, and a call judged again is a second row with its own evals. Not in any row: a WhatsApp message (Meta charges for templates only, and the runtime answers inside the 24 hours a person opens), a number's monthly rental, the embeddings of a lookup or a push.

The rates are the operator's, and a model or a leg without one is listed unpriced on the call, never priced at zero. The repository ships infra/seed/prices.csv, one row per model and unit (vendor,model,unit,usd,as_of,source: tokens per million; characters, seconds and minutes each), the models taken from voice-prices, Twilio's Elastic SIP Trunking from its US page, each checked against its source on the date its row says. They are list prices: a plan or a contract that pays less is an edit of the file, then pinecall-runtime providers prices infra/seed/prices.csv --apply. A trunk the platform reaches only by its address (a sip carrier account) is priced by rows named sip-inbound/… and sip-outbound/… the operator writes.

The platform's own compute is a row of the same file, pinecall,pinecall-compute,minutes,<usd>: a call's seconds on the worker, every one counted, priced beside the vendors' rows on each call (provider: "pinecall"), so a plan priced under what a call costs to run is visible. Nothing ships priced: the number is the operator's. Each call's facts keep its cost by stage (model, ears, voice, phone legs, the platform), and GET /v1/insights says per agent what a day cost by stage and per minute.

An org that spends strangely is said so: at each seal the org's spend today is held against its own usual day, the mean of its trailing four weeks in both environments, and once it is three times that (and the usual day is at least a dollar) spend.unusual {org, day, today_usd, usual_usd, multiple} is written on the agent's log, once a day, and pinecall_spend_unusual{org} stands on /metrics while it lasts, for the alert.

4. Where your orgs pay#

PINECALL_BILLING_URL is answered to every org in GET /v1/limits: the page where it buys more. A quota that refuses writes credits.exhausted {org, quota, used, limit} on the agent's log, the moment the layer or the console can offer it.

5. What is not in the runtime#

A plan, a price list, a trial's end date, an invoice, a card. They are the layer's, and a deployment run for one's own agents needs none of them.