---
title: "Record usage and charge"
method: POST
path: "/v1/usage"
tags: ["Usage"]
---

# Record usage and charge

`POST /v1/usage`

Record a billable usage event and immediately charge the customer.

> **Requires a secret key (`sk_*`) with at least the `OPERATOR` role.**

This endpoint waits for on-chain confirmation before returning, ensuring
the charge is settled. For faster responses, use `POST /v1/usage/async`.

**x402 Payment Flow:**
If the customer has insufficient balance, returns 402 with payment headers.
The client can sign a payment with their session key and retry with
`X-Payment-*` headers to complete the charge.

**Idempotency:**
Include an `idempotencyKey` to safely retry requests without duplicate charges.

## Request body

- object
  - `customerId` string — Drip customer ID (cus_*). One of customerId, externalCustomerId, or stripeCustomerId is required.
  - `externalCustomerId` string — Your own database's customer ID. If no Drip customer exists yet for (business, externalCustomerId), one is auto-provisioned as an internal customer on first use.
  - `stripeCustomerId` string — Stripe customer ID (`cus_…`) from your connected Stripe account. If no Drip customer exists yet for (business, stripeCustomerId), one is auto-provisioned and usage is forwarded to Stripe's Billing Meter Events. Intended for merchants who just finished Stripe OAuth, so you can start sending usage against Stripe IDs immediately without waiting for the background customer import.
  - `usageType` string — Usage type matching a pricing plan (defaults to "generic" if omitted)
  - `quantity` number — Quantity of usage (defaults to 1 if omitted)
  - `units` string — Human-readable unit label for display (e.g., "tokens", "API calls", "seconds")
  - `description` string — Human-readable description for support/finance teams (e.g., "Chat completion for Customer XYZ")
  - `idempotencyKey` string, required — Unique key to prevent duplicate charges. Required. Use a stable identifier like `{customerId}_{action}_{timestamp}` so retries produce the same key.
  - `metadata` object — Optional metadata
  - `workflowId` string — Link this usage to a workflow
  - `runId` string — Link this usage to an agent run
  - `eventType` 'USAGE' | 'INFERENCE' | 'TOOL_CALL' | 'DELEGATION' | 'RETRIEVAL' | 'CUSTOM' — Classify the type of usage event
  - `actionName` string — Name of the action performed (e.g., "chat_completion", "image_generation")
  - `outcome` 'SUCCEEDED' | 'FAILED' | 'PENDING' | 'SKIPPED' | 'CANCELLED' — Outcome of the action
  - `explanation` string — Human-readable explanation of what happened
  - `parentEventId` string — ID of the parent event (for building causality trees)
  - `retryOfEventId` string — ID of the event this is retrying (for retry chain tracking)
  - `attemptNumber` integer — Attempt number (1 = first try, 2 = first retry, etc.)
  - `inputHash` string — SHA-256 hash of the input for verification
  - `outputHash` string — SHA-256 hash of the output for verification
  - `input` object — Raw input data (will be hashed for verification)
  - `output` object — Raw output data (will be hashed for verification)

## Response `201`

Usage recorded. Either an on-chain charge was created (charge object populated) or the customer has no on-chain address and the request was auto-promoted to internal/visibility mode (charge=null, mode=internal).

- object — Usage recorded. Either an on-chain charge was created (charge object populated) or the customer has no on-chain address and the request was auto-promoted to internal/visibility mode (charge=null, mode=internal).
  - `success` boolean
  - `usageEventId` string — Usage event ID
  - `isDuplicate` boolean — True if this was a duplicate request matched by idempotencyKey
  - `charge` object, nullable — Populated when the customer has an on-chain address and a charge was created. `null` when the request was recorded as internal/visibility usage (no billing).
    - `id` string — Charge ID
    - `amountUsdc` string — Charge amount in USDC
    - `amountToken` string — Charge amount in token units
    - `txHash` string, nullable — Transaction hash
    - `status` 'PENDING' | 'PENDING_SETTLEMENT' | 'CONFIRMED'
  - `mode` 'internal', nullable — Present when the request was recorded as internal/visibility usage.
  - `autoPromoted` boolean, nullable — True when /usage auto-promoted to /usage/internal because the customer has no on-chain address. Explicit internal customers get mode=internal with autoPromoted=false.
  - `reason` string, nullable — Human-readable explanation of why the request was routed to the internal path.
  - `x402` object, nullable — Present if payment was via x402 flow
    - `paymentVerified` boolean
    - `sessionKeyId` string

## Other responses

- `400` — Validation error
- `401` — Unauthorized
- `402` — Payment required - insufficient balance. Includes x402 payment details for automatic payment flow.
- `403` — Account paused — usage blocked until balance is restored
- `404` — Customer or pricing plan not found
- `429` — Rate limit exceeded. Retry after the duration specified in the Retry-After header.
- `503` — Billing temporarily paused or dependency unavailable

---

[API](https://skmtc.net/usedrip/apis/drip-api.md) · [All operations](https://skmtc.net/usedrip/apis/drip-api/llms.txt) · [OpenAPI document](https://skmtc-service-staging.skmtc.workers.dev/v1/apis/usedrip/drip-api/revisions/2cb1567969ca/schema)
