Uso medido (Meters)
Como cobrar por consumo deixando a conta com o Vipter, no formato dos Billing Meters da Stripe: criar um medidor, dar preço ao uso na oferta ou na assinatura, enviar eventos de uso, ler o período aberto e o que acontece quando o ciclo fecha, com exemplos em curl e Node.js, franquia, faixas de preço, limiar de cobrança e as diferenças em relação à Stripe.
Com o uso medido, o seu sistema só avisa o Vipter de cada consumo: uma chamada, um GB, um envio. O Vipter soma, aplica a franquia e o preço que você definiu, e cobra o total no cartão salvo da assinatura quando o ciclo fecha. É a alternativa à cobrança adicional por uso, em que você calcula o valor e chama POST /v1/subscriptions/{id}/charges.
Os endpoints seguem o formato dos Billing Meters da Stripe (billing.meter, billing.meter_event, event_summaries). O que muda está em Diferenças em relação à Stripe. Cada endpoint, com todos os parâmetros, está na referência da API.
Quando usar
| Você quer | Use |
|---|---|
| Manter a contagem no seu sistema e decidir quando e quanto cobrar. | Cobrança no cartão salvo: POST /v1/subscriptions/{id}/charges com o amount já calculado. |
| Só mandar os eventos e deixar o Vipter somar, aplicar a franquia, o preço e as faixas, e cobrar no fim do ciclo. | Esta página. |
| Cobrar na hora, a cada consumo. | Cobrança no cartão salvo. O uso medido não tem modo imediato: ele acumula e cobra no fechamento, ou ao atingir um limiar. |
Os dois caminhos geram o mesmo tipo de cobrança (subscription_charge) e o mesmo pedido pago. Uma assinatura pode usar os dois.
O modelo
medidor (meter) → eventos de uso (meter_event) → item de uso (usage_item) → período (usage_period) → cobrança (subscription_charge)- Medidor. Um tipo de consumo, identificado por um
event_name(api_calls,storage_gb). Diz como os eventos se agregam:sumsoma os valores,countconta os eventos,lastfica com o último valor. - Evento de uso. Um registro com o cliente (
payload.customer_id) e o valor (payload.value). Na chegada, o Vipter liga o evento à assinatura ativa do cliente que tem preço para esse medidor. - Item de uso. O preço de um medidor numa assinatura: valor por unidade, franquia, faixas, arredondamento e limiar. Nasce da oferta (configurado no painel) ou é definido pela API na assinatura.
- Período. O ciclo atual da assinatura (
current_period_start→current_period_end). Enquanto está aberto, o uso é recalculado a partir dos eventos a cada rodada. Quando fecha, vira uma cobrança. - Cobrança. Uma cobrança na assinatura com
source: "usage", feita uma vez por período, com uma linha por medidor. Ela vira um pedido pago combilling_reason: "usage".
Passo 1: criar o medidor
curl -X POST https://api.vipter.com/v1/billing/meters \
-H "Authorization: Bearer vk_live_…" \
-H "Content-Type: application/json" \
-d '{
"display_name": "Chamadas de API",
"event_name": "api_calls",
"default_aggregation": { "formula": "sum" }
}'{
"id": "mtr_4f8e2c1a9b7d6e5f3a2b1c0d",
"object": "billing.meter",
"display_name": "Chamadas de API",
"event_name": "api_calls",
"default_aggregation": { "formula": "sum" },
"customer_mapping": { "type": "by_id", "event_payload_key": "customer_id" },
"value_settings": { "event_payload_key": "value" },
"status": "active",
"status_transitions": { "deactivated_at": null },
"livemode": true,
"created": 1791100800,
"updated": 1791100800
}O event_name é o que os eventos usam para achar o medidor: só minúsculas, dígitos, _, . e -, até 100 caracteres, único na loja (repetir recebe 400 event_name_taken). Ele não muda depois; o display_name muda por POST /v1/billing/meters/{id} e é o nome que aparece na linha do pedido do comprador. Se o seu sistema já manda eventos para a Stripe com stripe_customer_id no payload, mantenha: o Vipter aceita essa chave como apelido de customer_id.
Passo 2: dar preço ao uso
O preço mora num item de uso, um por medidor. Há dois lugares para ele:
Na oferta, pelo painel. Na página da oferta, o card de uso medido lista os medidores da loja e aceita o preço por unidade, a franquia, as faixas, o arredondamento e o limiar, por moeda. Toda assinatura dessa oferta herda o item na primeira rodada do uso medido depois de ser criada (a rodada acontece a cada 10 minutos). A herança copia a linha da moeda da assinatura ou, se não houver, a da moeda da loja. É uma cópia: mudar o preço na oferta depois não altera as assinaturas que já herdaram.
Na assinatura, pela API. Para um preço negociado ou para uma assinatura que não veio de uma oferta com uso medido:
curl -X POST https://api.vipter.com/v1/subscriptions/sub_3c7a9e1f5b2d8c4e/usage_items \
-H "Authorization: Bearer vk_live_…" \
-H "Content-Type: application/json" \
-d '{
"meter": "mtr_4f8e2c1a9b7d6e5f3a2b1c0d",
"unit_amount": 0.4,
"included_units": 10000,
"rounding": "up",
"label": "Chamadas além da franquia"
}'{
"id": "usi_9d2e4f6a8b1c3d5e7f0a2b4c",
"object": "usage_item",
"meter": "mtr_4f8e2c1a9b7d6e5f3a2b1c0d",
"subscription": "sub_3c7a9e1f5b2d8c4e",
"offer": null,
"currency": "brl",
"unit_amount": 0.4,
"included_units": 10000,
"tiers": null,
"rounding": "up",
"billing_threshold": null,
"label": "Chamadas além da franquia",
"source": "api",
"livemode": true,
"created": 1791100900
}unit_amount é o preço de uma unidade, na menor unidade da moeda, com frações: 0.4 é R$ 0,004 por chamada; 50 é R$ 0,50 por GB. A moeda é sempre a da assinatura (outra recebe 400 currency_mismatch). Enviar de novo o mesmo meter substitui o item; um item que você definiu pela API nunca é sobrescrito pela herança da oferta. DELETE /v1/subscriptions/{id}/usage_items/{itemId} remove.
Passo 3: enviar os eventos
curl -X POST https://api.vipter.com/v1/billing/meter_events \
-H "Authorization: Bearer vk_live_…" \
-H "Content-Type: application/json" \
-d '{
"event_name": "api_calls",
"identifier": "req_01J9X3K7M2",
"payload": { "customer_id": "cust_9d2e4f6a8b1c3d5e", "value": 1 }
}'{
"id": "mev_1a5c9e3b7d2f6a8c0e4b2d6f",
"object": "billing.meter_event",
"event_name": "api_calls",
"identifier": "req_01J9X3K7M2",
"payload": { "customer_id": "cust_9d2e4f6a8b1c3d5e", "value": 1 },
"customer": "cust_9d2e4f6a8b1c3d5e",
"subscription": "sub_3c7a9e1f5b2d8c4e",
"value": 1,
"timestamp": 1791101000,
"livemode": true,
"created": 1791101000
}identifieré a idempotência do evento, por medidor. Repetir o mesmoidentifierdevolve o evento já gravado, com200, sem contar duas vezes. Use o ID da requisição, do job ou do registro no seu banco. Semidentifier, o Vipter gera um, e cada chamada conta.payload.valueé o valor somado (sum) ou guardado (last). Num medidorcount, o valor é ignorado e cada evento vale 1.timestampé opcional, em segundos Unix: até 35 dias atrás e até 5 minutos à frente, senão400 timestamp_out_of_range. Sem ele, vale a hora da chegada. É otimestampque decide em qual período o evento cai.subscriptionna resposta diz a quem o evento foi ligado.nullé um evento sem assinatura.
Em volume, use o lote: até 100 eventos por chamada em POST /v1/billing/meter_events/batch. Cada item é aceito ou recusado por conta própria, e results[i] responde events[i] com status accepted, duplicate ou error. A resposta é 200 sempre que ao menos um item entrou; 400 só quando nenhum entrou.
const API = 'https://api.vipter.com/v1';
const headers = { Authorization: `Bearer ${process.env.VIPTER_API_KEY}`, 'Content-Type': 'application/json' };
// Um evento por consumo. `identifier` é o ID do registro no seu lado: reenviar nunca conta duas vezes.
export async function reportUsage(record) {
const res = await fetch(`${API}/billing/meter_events`, {
method: 'POST',
headers,
body: JSON.stringify({
event_name: 'api_calls',
identifier: record.id,
timestamp: Math.floor(record.at.getTime() / 1000), // opcional; até 35 dias atrás
payload: { customer_id: record.vipterCustomerId, value: record.calls },
}),
});
const json = await res.json();
if (res.ok) return json; // 201 novo, 200 repetido
// 400 com error.code: no_meter_found, meter_inactive, invalid_payload, timestamp_out_of_range. 404: cliente não existe.
throw Object.assign(new Error(json.error.message), { code: json.error.code, status: res.status });
}
// Em lote: até 100 por chamada. Trate `results` item a item; `duplicate` é normal num reenvio.
export async function reportUsageBatch(records) {
const res = await fetch(`${API}/billing/meter_events/batch`, {
method: 'POST',
headers,
body: JSON.stringify({
events: records.map((r) => ({ event_name: 'api_calls', identifier: r.id, payload: { customer_id: r.vipterCustomerId, value: r.calls } })),
}),
});
const json = await res.json(); // { accepted, duplicates, errors, results[] }
json.results.forEach((r, i) => {
if (r.status === 'error') console.warn('evento recusado', records[i].id, r.error.code, r.error.message);
});
return json;
}Guarde o cust_… de cada usuário quando a assinatura nasce: ele vem em customer da sessão de checkout e da assinatura. Um customer_id que não existe na loja recebe 404 resource_missing.
Passo 4: ler o uso do período
curl https://api.vipter.com/v1/subscriptions/sub_3c7a9e1f5b2d8c4e/usage \
-H "Authorization: Bearer vk_live_…"{
"object": "list",
"url": "https://api.vipter.com/v1/subscriptions/sub_3c7a9e1f5b2d8c4e/usage",
"has_more": false,
"data": [
{
"id": "usp_7b3e9f1c2a8d4e6f0b2d4f6a",
"object": "usage_period",
"subscription": "sub_3c7a9e1f5b2d8c4e",
"status": "open",
"close_reason": null,
"period_start": 1790186400,
"period_end": 1792778400,
"currency": "brl",
"lines": [
{ "meter": "mtr_4f8e2c1a9b7d6e5f3a2b1c0d", "event_name": "api_calls", "quantity": 12300, "included": 10000, "billable": 2300, "unit_amount": 0.4, "amount": 920 }
],
"amount_total": 920,
"charge": null,
"computed_at": 1791101100,
"closed_at": null,
"livemode": true,
"created": 1790187000
}
]
}A lista traz os 12 períodos mais recentes, do mais novo para o mais antigo. O período aberto é calculado na hora da chamada, a partir dos eventos; os fechados vêm como ficaram no fechamento, com charge apontando para a cobrança. Cada linha é um medidor: quantity é o agregado, included a franquia, billable o que passou dela, amount o valor da linha em centavos. Use esta chamada para mostrar o consumo do mês no seu produto, em vez de somar do seu lado.
Para o agregado de um cliente num intervalo qualquer, inclusive por hora ou por dia, use GET /v1/billing/meters/{id}/event_summaries?customer=&start_time=&end_time=&value_grouping_window=day. Ele soma todos os eventos do cliente nesse medidor, ligados ou não a uma assinatura.
O que acontece no fim do ciclo
Uma rotina roda a cada 10 minutos e, para cada assinatura com itens de uso:
- Abre o período do ciclo atual, se ainda não existe: de
current_period_startacurrent_period_endda assinatura. Só assinaturasactive,trialingoupast_duetêm período aberto. - Recalcula o período aberto a partir dos eventos e grava
lineseamount_total. - Fecha o período quando o ciclo termina (
close_reason: "period_end"), quando o valor acumulado atinge o limiar (threshold) ou quando a assinatura deixa de ser cobrável (subscription_ended). - Cobra o período fechado com
amount_totalmaior que zero, uma vez só, no cartão salvo da assinatura: uma cobrança na assinatura comsource: "usage",Idempotency-Keyusage:{id do período}, descriçãoUso 2026-09-23 a 2026-10-23e uma linha por medidor com valor, com odisplay_namedo medidor. Um período com valor zero fecha sem cobrança e sem pedido.
A cobrança segue as regras das cobranças na assinatura:
- Aprovada: saem
invoice.paid, com o pedido (billing_reason: "usage",external_order_idcom osch_…), esubscription_charge.succeeded, com a cobrança (metadata.kind: "usage"emetadata.usage_periodcom ousp_…). O período ficaclosedcomchargepreenchido. - Recusada pelo cartão: saem
subscription_charge.failede, quando o provedor registrou um pedido,invoice.payment_failed. O Vipter não tenta de novo sozinho; o período fica fechado comchargeapontando para a cobrança recusada. Para cobrar o valor depois de o assinante trocar o cartão, usePOST /v1/subscriptions/{id}/chargescom oamount_totaldo período. - Em análise: a cobrança fica
pendinge os eventos saem quando o provedor decide. - Recusada antes de chegar ao cartão (assinatura sem cartão salvo, cartão num provedor que não aceita cobrança sem o cliente, valor abaixo de uma unidade da moeda, loja com a mensalidade em atraso): o período fecha com
charge: nulle o uso não é cobrado depois. Emsubscription_endedo uso já consumido ainda é cobrado no cartão salvo (é a fatura final da assinatura); só fica sem cobrança se o cartão não estiver mais disponível.
A renovação da assinatura não muda: a mensalidade continua sendo cobrada na data e no valor da oferta, e o uso vem numa cobrança separada. Cada cobrança de uso aprovada conta como um pedido pago na cota do plano Vipter da loja; os eventos de uso não contam.
Exemplos de preço
Os cálculos abaixo valem para uma linha. billable é o agregado menos included_units; o preço se aplica só a billable. O arredondamento acontece uma vez, no total da linha, para centavos inteiros: up arredonda para cima (padrão), nearest para o mais próximo.
Preço fixo com franquia
{ "meter": "mtr_…", "unit_amount": 0.4, "included_units": 10000 }12.300 chamadas: billable 2.300 × 0,4 = 920 centavos, R$ 9,20. 9.000 chamadas: billable 0, linha com amount 0.
Faixas graduadas
{
"meter": "mtr_…",
"unit_amount": 0,
"included_units": 0,
"tiers": [
{ "up_to": 1000, "unit_amount": 0 },
{ "up_to": 10000, "unit_amount": 0.5 },
{ "up_to": null, "unit_amount": 0.3 }
]
}Cada faixa cobre as unidades entre o up_to anterior e o seu, e a última precisa ter up_to: null. Com tiers, o unit_amount do item não entra no cálculo (envie 0). 25.000 unidades: 1.000 × 0 + 9.000 × 0,5 + 15.000 × 0,3 = 4.500 + 4.500 = 9.000 centavos, R$ 90,00. Uma faixa pode ter flat_amount, em centavos, cobrado uma vez quando alguma unidade cai nela. As faixas contam a partir de billable: com included_units: 1000, a primeira faixa começa na unidade 1.001 do consumo.
Arredondamento
unit_amount: 0.4 e 23 unidades: 9,2 centavos. Com rounding: "up", a linha vale 10 centavos; com nearest, 9. O arredondamento é por linha; o total do período é a soma das linhas já arredondadas.
Agregações
sum soma value (GB transferidos). count conta eventos e ignora value (chamadas, envios). last fica com o value do evento de timestamp mais recente do período (assentos ativos, GB armazenados): mande o total atual, não a diferença.
Limiar de cobrança
billing_threshold, em centavos, fecha e cobra o período antes do fim do ciclo quando o valor acumulado o atinge:
{ "meter": "mtr_…", "unit_amount": 0.4, "included_units": 10000, "billing_threshold": 20000 }Quando amount_total chega a R$ 200,00, o período fecha com close_reason: "threshold", é cobrado, e um período novo abre daquele momento até o fim do ciclo. A verificação acontece a cada rodada, então a cobrança pode passar um pouco do limiar. Com vários itens na assinatura, vale o menor limiar entre eles, sobre o total do período. Cada fechamento é uma cobrança e um pedido.
Eventos sem assinatura
Na chegada, o evento é ligado à assinatura do cliente que está active, trialing ou past_due e tem um item de uso para o medidor; com mais de uma, à mais recente. payload.subscription_id escolhe uma delas; se a escolhida não tem item para o medidor, o evento fica sem assinatura. A ligação é feita uma vez: um evento que chegou antes de a assinatura ter o item não é cobrado depois.
Um evento sem assinatura é gravado (subscription: null), aparece em event_summaries e não é cobrado. Enquanto isso acontece, o Vipter manda billing.meter.error_report_triggered no catálogo 2026-11-01, no máximo uma vez por medidor e por hora, com reason.error_count eventos sem assinatura na hora atual. As causas mais comuns:
- A assinatura é nova e ainda não herdou o item da oferta: a herança acontece na rodada seguinte, em até 10 minutos. Comece a mandar eventos depois que
GET /v1/subscriptions/{id}/usage_itemsdevolver o item, ou crie o item pela API na hora. - O item existe só na oferta antiga: uma assinatura herda uma vez; depois de uma troca de oferta, confira os itens.
- O
customer_idé de outro cliente, ou a assinatura paga por PIX e não tem cartão salvo (ela recebe o evento, mas a cobrança falha no fechamento).
Desativar um medidor
POST /v1/billing/meters/{id}/deactivate para de aceitar eventos (400 meter_inactive) na hora. Os períodos abertos continuam mostrando a quantity do medidor, mas o preço passa a zero. Os itens de uso que apontam para ele continuam existindo; remova-os se não quer mais vê-los. Não há reativação pela API.
Limites
| O quê | Limite |
|---|---|
timestamp do evento | Até 35 dias atrás e 5 minutos à frente. |
| Eventos por lote | 1 a 100. |
payload | Até 20 chaves; valores texto, número ou booleano. |
event_name, identifier | Até 100 caracteres. event_name: a-z, 0-9, _, ., -. |
display_name, label | Até 250 e 120 caracteres. |
tiers | 1 a 20 faixas, up_to crescente, a última null. |
Intervalo de event_summaries | Até um ano. |
GET …/usage | Os 12 períodos mais recentes. |
| Chamadas | Os limites de requisições da API. |
O que o comprador vê
A cobrança do período vira um pedido com a descrição Uso <início> a <fim> e uma linha por medidor, com o display_name do medidor e o valor da linha. O assinante vê o pedido na área do cliente, junto das renovações, e recebe o e-mail de confirmação de compra da loja, quando ele está ativo. No painel, a cobrança aparece na página da assinatura com a origem Uso, e a página da assinatura mostra o uso do período aberto.
Se o seu produto mostra o consumo em tempo real, leia GET /v1/subscriptions/{id}/usage em vez de refazer a conta: é o mesmo cálculo que vai para a cobrança.
Diferenças em relação à Stripe
- Não existe preço medido (
pricecomrecurring.usage_type: "metered"). O preço mora no item de uso da oferta (pelo painel) ou da assinatura (/usage_items), comunit_amountfracionário,included_units,tiers,roundingebilling_thresholdno mesmo objeto. - Não há fatura. O período fechado vira uma cobrança na assinatura com
source: "usage", cobrada na hora no cartão salvo, e um pedido combilling_reason: "usage". Os eventos sãoinvoice.paidesubscription_charge.*, nãoinvoice.createdeinvoice.finalized. - A franquia é um campo.
included_unitssubstitui a faixa gratuita; astierscontam depois dela. - A ligação com a assinatura é feita na chegada do evento, não no fechamento. Um evento sem assinatura não é cobrado depois.
billing.meter.error_report_triggeredtem um só tipo de erro (no_subscription_for_customer) e sai no máximo uma vez por medidor e por hora.event_summariesdevolve umalistsem paginação, só com os intervalos que têm eventos.- O
payloadaceitastripe_customer_idcomo apelido decustomer_id; o valor precisa ser ocust_…do Vipter. - Não existem
meter_event_adjustmentsnem reativação de medidor.
O que fazer a seguir
- Veja cada endpoint e cada objeto em Uso medido, na referência da API.
- Receba
invoice.paid,subscription_charge.*ebilling.meter.error_report_triggeredno seu servidor: catálogo de eventos. - Para cobrar um valor que você mesmo calculou, siga Cobrança adicional por uso.
Integrar com agentes de IA
O que o Vipter publica para que um agente de IA (Claude Code, Cursor, Codex, ChatGPT e similares) integre a API sem ajuda humana: a skill vipter-api no formato Agent Skills, o llms.txt com instruções, toda a documentação em Markdown, o OpenAPI 3.1 e a coleção Postman; como apontar o agente para cada um, o que pedir a ele e o que nunca entregar a ele.
Catálogo de eventos
Os tipos de evento que o Vipter envia por webhook, quando cada um dispara e o que vem em data.object, nos dois catálogos (os 21 nomes originais e os 19 no padrão Stripe, com as sessões de checkout, as cobranças na assinatura e o uso medido), com exemplos completos.