openapi: 3.1.0
info:
title: Drip BillableMetrics Usage API
description: "\n# Drip API\n\n**Usage-based billing + execution ledger.**\n\n---\n\n## 60-Second Quickstart (Core SDK)\n\n### 1. Install\n\n```bash\nnpm install @drip-sdk/node\n```\n\n### 2. Set your API key\n\n```bash\n# Secret key - full API access (server-side only, never expose publicly)\nexport DRIP_API_KEY=sk_test_...\n\n# Use a secret key for the customer, billing, runs, pricing-plan, and webhook\n# flows documented in this reference.\n```\n\nOr use a \".env\" file (recommended):\n\n```bash\nnpm install dotenv\n# .env\nDRIP_API_KEY=sk_test_...\n```\n\nLoad your \".env\" at the top of your entry file:\n\n```typescript\nimport 'dotenv/config';\n```\n\n### 3. Create a customer and track usage\n\n```typescript\nimport 'dotenv/config';\nimport { drip } from '@drip-sdk/node';\n\n// Create a customer first\nconst customer = await drip.createCustomer({ externalCustomerId: 'user_123' });\n\n// Internal tracking only (no billing)\nawait drip.trackUsage({ customerId: customer.id, meter: 'api_calls', quantity: 1 });\n```\n\nThe `drip` singleton reads `DRIP_API_KEY` from your environment automatically.\n\n### Alternative: Explicit Configuration\n\n```typescript\nimport 'dotenv/config';\nimport { Drip } from '@drip-sdk/node';\n\n// Auto-reads DRIP_API_KEY from environment\nconst client = new Drip();\n\n// Or pass config explicitly with an Admin/Operator secret key\nconst clientWithSecret = new Drip({ apiKey: 'sk_test_...' });\n```\n\n### Full Example (Node.js)\n\n```typescript\nimport 'dotenv/config';\nimport { drip } from '@drip-sdk/node';\n\nasync function main() {\n // Verify connectivity\n await drip.ping();\n\n // Create a customer (at least one of externalCustomerId or onchainAddress required)\n const customer = await drip.createCustomer({ externalCustomerId: 'user_123' });\n\n // Internal tracking (no billing)\n await drip.trackUsage({\n customerId: customer.id,\n meter: 'llm_tokens',\n quantity: 842,\n metadata: { model: 'gpt-4o-mini' },\n });\n\n // Billable usage\n await drip.charge({\n customerId: customer.id,\n meter: 'api_calls',\n quantity: 1,\n });\n\n // Record an execution lifecycle\n await drip.recordRun({\n customerId: customer.id,\n workflow: 'research-agent',\n events: [\n { eventType: 'llm.call', quantity: 1700, units: 'tokens' },\n { eventType: 'tool.call', quantity: 1 },\n ],\n status: 'COMPLETED',\n });\n\n console.log(`Customer ${customer.id}: usage + run recorded`);\n}\n\nmain();\n```\n\n```python\nfrom drip import drip\n\n# Create a customer first\ncustomer = drip.create_customer(external_customer_id=\"user_123\")\n\n# Internal tracking only (no billing)\ndrip.track_usage(customer_id=customer.id, meter=\"api_calls\", quantity=1)\n```\n\nThe `drip` singleton reads `DRIP_API_KEY` from your environment automatically.\n\n### Alternative: Explicit Configuration (Python)\n\n```python\nfrom drip import Drip\n\n# Auto-reads DRIP_API_KEY from environment\nclient = Drip()\n\n# Or pass config explicitly with an Admin/Operator secret key\nclient_with_secret = Drip(api_key=\"sk_test_...\")\n```\n\n### Full Example (Python)\n\n```python\nfrom drip import drip\n\n# Verify connectivity\ndrip.ping()\n\n# Create a customer (at least one of external_customer_id or onchain_address required)\ncustomer = drip.create_customer(external_customer_id=\"user_123\")\n\n# Internal tracking (no billing)\ndrip.track_usage(\n customer_id=customer.id,\n meter=\"llm_tokens\",\n quantity=842,\n metadata={\"model\": \"gpt-4o-mini\"}\n)\n\n# Billable usage\ndrip.charge(\n customer_id=customer.id,\n meter=\"api_calls\",\n quantity=1\n)\n\n# Record execution lifecycle\ndrip.record_run(\n customer_id=customer.id,\n workflow=\"research-agent\",\n events=[\n {\"event_type\": \"llm.call\", \"quantity\": 1700, \"units\": \"tokens\"},\n {\"event_type\": \"tool.call\", \"quantity\": 1},\n ],\n status=\"COMPLETED\"\n)\n\nprint(f\"Customer {customer.id}: usage + run recorded\")\n```\n\n**Expected result:**\n- No errors\n- Events appear in your Drip dashboard within seconds\n\n**Install:** `npm install @drip-sdk/node` or `pip install drip-sdk`\n\n**Set API key:** `export DRIP_API_KEY=sk_test_...` or use a \".env\" file with `DRIP_API_KEY=sk_test_...` and load it with `import 'dotenv/config'` (for Node.js)\n\n---\n\n## REST API Quick Start\n\n### Step 1: Create a customer\n\n```bash\ncurl -X POST https://api.drippay.dev/v1/customers \\\n -H \"Authorization: Bearer sk_test_YOUR_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"externalCustomerId\": \"user_123\"}'\n```\n\n**You'll get back:**\n```json\n{\n \"id\": \"cmlxxxxxx...\",\n \"externalCustomerId\": \"user_123\",\n \"status\": \"ACTIVE\"\n}\n```\n\n### Step 2: Create pricing\n\nUse the same meter string in your pricing plan and your usage calls:\n\n```bash\ncurl -X POST https://api.drippay.dev/v1/pricing-plans \\\n -H \"Authorization: Bearer sk_test_YOUR_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"name\": \"API Calls\",\n \"unitType\": \"api_calls\",\n \"unitPriceUsd\": 0.001\n }'\n```\n\n### Step 3: Charge usage\n\n```bash\ncurl -X POST https://api.drippay.dev/v1/usage \\\n -H \"Authorization: Bearer sk_test_YOUR_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"customerId\": \"CUSTOMER_ID_FROM_STEP_1\",\n \"usageType\": \"api_calls\",\n \"quantity\": 100,\n \"idempotencyKey\": \"charge_001\"\n }'\n```\n\n---\n\n## SDK Methods\n\n### Node.js (`@drip-sdk/node`)\n\n| Method | Description |\n|--------|-------------|\n| `createCustomer()` | Create a customer |\n| `getCustomer()` | Get customer details |\n| `listCustomers()` | List all customers |\n| `trackUsage()` | Record internal usage without billing |\n| `charge()` | Record usage and charge (requires pricing plan) |\n| `emitEvent()` | Record an execution event |\n| `recordRun()` | Record a complete agent run |\n| `startRun()` | Start a run |\n| `endRun()` | End a run |\n| `getBalance()` | Get customer balance |\n\n### Python (`drip-sdk`)\n\n| Method | Description |\n|--------|-------------|\n| `create_customer()` | Create a customer |\n| `get_customer()` | Get customer details |\n| `list_customers()` | List all customers |\n| `track_usage()` | Record internal usage without billing |\n| `charge()` | Record usage and charge (requires pricing plan) |\n| `emit_event()` | Record an execution event |\n| `record_run()` | Record a complete agent run |\n| `start_run()` | Start a run |\n| `end_run()` | End a run |\n| `get_balance()` | Get customer balance |\n\n---\n\n## Two Modes\n\n| Mode | How | When |\n|------|-----|------|\n| **Tracking only** | `trackUsage()` / `track_usage()` | Pilots, analytics, no billing yet |\n| **Billing** | `charge()` + pricing plan | Ready to charge customers |\n\nBoth modes record usage in the ledger. The difference is whether a charge is created.\n\nUse `POST /v1/usage/internal` when you want tracking only.\n\n---\n\n## Authentication\n\n```\nAuthorization: Bearer sk_test_YOUR_KEY\n```\n\n### Key Types\n\n| Key Prefix | Use For |\n|------------|---------|\n| `sk_test_*` / `sk_live_*` | Server-side API access (secret key) |\n| `pk_test_*` / `pk_live_*` | Public key identifier. The role-protected endpoints in this reference require a secret key |\n\n### API Key Roles\n\nSecret keys (`sk_*`) are assigned a role that controls which endpoints they can access. Roles are hierarchical: higher roles include all permissions of lower roles.\n\n| Role | Level | Permissions |\n|------|-------|-------------|\n| `READONLY` | Lowest | List and read resources (customers, charges, pricing plans, events) |\n| `OPERATOR` | Mid | + Create customers, charge usage, track internal usage, emit events, manage runs |\n| `ADMIN` | Highest | + Create/update/delete pricing plans, manage contracts, manage API keys |\n\nPublic keys (`pk_*`) are always `READONLY`: they cannot create or modify resources.\n\n> **Important:** To create, update, or delete **pricing plans** and **contracts**, you must use a secret key (`sk_*`) with the **ADMIN** role. Using a lower-role key returns `403 Forbidden`.\n\n---\n\n## Idempotency\n\nAlways include `idempotencyKey` in POST requests to prevent duplicates:\n\n```json\n{\n \"customerId\": \"cmlxxxxxx...\",\n \"usageType\": \"api_calls\",\n \"quantity\": 1,\n \"idempotencyKey\": \"req_unique_12345\"\n}\n```\n\nSame key = same result. Safe to retry on network failures.\n\n---\n\n## Error Codes\n\n| Status | Meaning | Fix |\n|--------|---------|-----|\n| **400** | Bad request | Check request body matches schema |\n| **401** | Invalid API key | Verify `Authorization: Bearer sk_...` header |\n| **404** | Not found | Customer/pricing plan doesn't exist. Create customer first. |\n| **409** | Duplicate | Resource with this ID already exists |\n| **422** | Validation error | Check required fields and types |\n\n---\n\n## Important Notes\n\n- **Always create a customer first** for billable onboarding flows. You cannot use made-up customer IDs.\n- **`POST /v1/events`** records execution events (what happened). It does **not** auto-create charges.\n- **`POST /v1/usage`** records usage and creates charges if a matching pricing plan exists.\n- **`POST /v1/usage/internal`** records usage without billing.\n- **Numbers as strings**: Monetary amounts are returned as strings (e.g., `\"0.001000\"`) to preserve precision.\n"
version: 1.0.0
contact:
name: Drip Support
email: support@drippay.dev
x-logo:
url: https://drippay.dev/logo.svg
altText: Drip
servers:
- url: https://api.drippay.dev
description: Production
- url: http://localhost:3001
description: Development
security:
- bearerAuth: []
tags:
- name: Usage
description: Record metered usage. Creates charges when a pricing plan matches.
paths:
/v1/usage:
post:
operationId: recordUsage
summary: Record usage and charge
tags:
- Usage
description: "\nRecord a billable usage event and immediately charge the customer.\n\n> **Requires a secret key (`sk_*`) with at least the `OPERATOR` role.**\n\nThis endpoint waits for on-chain confirmation before returning, ensuring\nthe charge is settled. For faster responses, use `POST /v1/usage/async`.\n\n**x402 Payment Flow:**\nIf the customer has insufficient balance, returns 402 with payment headers.\nThe client can sign a payment with their session key and retry with\n`X-Payment-*` headers to complete the charge.\n\n**Idempotency:**\nInclude an `idempotencyKey` to safely retry requests without duplicate charges.\n "
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
customerId:
type: string
description: Drip customer ID (cus_*). One of customerId, externalCustomerId, or stripeCustomerId is required.
example: cus_abc123def456
externalCustomerId:
type: string
maxLength: 255
description: 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.
example: user_42
stripeCustomerId:
type: string
pattern: ^cus_[A-Za-z0-9]+$
maxLength: 255
description: 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.
example: cus_NffrFeUfNV2Hib
usageType:
type: string
description: Usage type matching a pricing plan (defaults to "generic" if omitted)
example: api_call
default: generic
quantity:
type: number
exclusiveMinimum: 0
description: Quantity of usage (defaults to 1 if omitted)
example: 100
default: 1
units:
type: string
maxLength: 50
description: Human-readable unit label for display (e.g., "tokens", "API calls", "seconds")
example: API calls
description:
type: string
maxLength: 500
description: Human-readable description for support/finance teams (e.g., "Chat completion for Customer XYZ")
example: 'Eligibility check for Pharmacy ABC (workflow: insurance_verify)'
idempotencyKey:
type: string
minLength: 1
maxLength: 128
description: Unique key to prevent duplicate charges. Required. Use a stable identifier like `{customerId}_{action}_{timestamp}` so retries produce the same key.
example: req_20240115_abc123
metadata:
type: object
additionalProperties: true
description: Optional metadata
example:
endpoint: /v1/chat
model: gpt-4
workflowId:
type: string
description: Link this usage to a workflow
runId:
type: string
description: Link this usage to an agent run
eventType:
type: string
enum:
- USAGE
- INFERENCE
- TOOL_CALL
- DELEGATION
- RETRIEVAL
- CUSTOM
description: Classify the type of usage event
actionName:
type: string
maxLength: 255
description: Name of the action performed (e.g., "chat_completion", "image_generation")
outcome:
type: string
enum:
- SUCCEEDED
- FAILED
- PENDING
- SKIPPED
- CANCELLED
description: Outcome of the action
explanation:
type: string
maxLength: 1000
description: Human-readable explanation of what happened
parentEventId:
type: string
description: ID of the parent event (for building causality trees)
retryOfEventId:
type: string
description: ID of the event this is retrying (for retry chain tracking)
attemptNumber:
type: integer
minimum: 1
description: Attempt number (1 = first try, 2 = first retry, etc.)
inputHash:
type: string
maxLength: 64
description: SHA-256 hash of the input for verification
outputHash:
type: string
maxLength: 64
description: SHA-256 hash of the output for verification
input:
type: object
additionalProperties: true
description: Raw input data (will be hashed for verification)
output:
type: object
additionalProperties: true
description: Raw output data (will be hashed for verification)
required:
- idempotencyKey
responses:
'201':
description: 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).
content:
application/json:
schema:
description: 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).
type: object
properties:
success:
type: boolean
example: true
usageEventId:
type: string
description: Usage event ID
isDuplicate:
type: boolean
description: True if this was a duplicate request matched by idempotencyKey
example: false
charge:
type: object
nullable: true
description: 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).
properties:
id:
type: string
description: Charge ID
amountUsdc:
type: string
description: Charge amount in USDC
amountToken:
type: string
description: Charge amount in token units
txHash:
type: string
nullable: true
description: Transaction hash
status:
type: string
enum:
- PENDING
- PENDING_SETTLEMENT
- CONFIRMED
mode:
type: string
enum:
- internal
nullable: true
description: Present when the request was recorded as internal/visibility usage.
autoPromoted:
type: boolean
nullable: true
description: 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:
type: string
nullable: true
description: Human-readable explanation of why the request was routed to the internal path.
x402:
type: object
nullable: true
description: Present if payment was via x402 flow
properties:
paymentVerified:
type: boolean
sessionKeyId:
type: string
'400':
description: Validation error
content:
application/json:
schema:
description: Validation error
type: object
properties:
error:
type: string
description: Error message
code:
type: string
description: Error code
details:
type: array
items:
type: object
properties:
path:
type: string
message:
type: string
description: Validation error details
required:
- error
- code
'401':
description: Unauthorized
content:
application/json:
schema:
description: Unauthorized
type: object
properties:
error:
type: string
description: Error message
code:
type: string
description: Error code
details:
type: array
items:
type: object
properties:
path:
type: string
message:
type: string
description: Validation error details
required:
- error
- code
'402':
description: Payment required - insufficient balance. Includes x402 payment details for automatic payment flow.
content:
application/json:
schema:
description: Payment required - insufficient balance. Includes x402 payment details for automatic payment flow.
type: object
properties:
error:
type: string
code:
type: string
example: PAYMENT_REQUIRED
payment:
type: object
description: Payment details for x402 flow. Sign with session key and retry with X-Payment-* headers.
properties:
amount:
type: string
description: Amount required in USDC
recipient:
type: string
description: Payment recipient address
currency:
type: string
example: USDC
chain:
type: string
example: base-sepolia
description:
type: string
description: Human-readable charge description
usageId:
type: string
description: Unique usage identifier
expiresAt:
type: integer
description: Unix timestamp (seconds) when payment expires
timestamp:
type: integer
description: Unix timestamp (seconds) when request was created
nonce:
type: string
description: Unique nonce for replay protection
x402:
type: object
description: Full x402 protocol payload for client signing
properties:
version:
type: string
example: 1.0.0
paymentRequest:
type: object
description: Complete payment request to sign via EIP-712
serverTime:
type: object
description: Server time for client clock synchronization
properties:
seconds:
type: integer
milliseconds:
type: integer
iso:
type: string
format: date-time
checkout_url:
type: string
nullable: true
description: URL for balance top-up
'403':
description: Account paused — usage blocked until balance is restored
content:
application/json:
schema:
description: Account paused — usage blocked until balance is restored
type: object
properties:
error:
type: string
description: Error message
code:
type: string
description: Error code
details:
type: array
items:
type: object
properties:
path:
type: string
message:
type: string
description: Validation error details
required:
- error
- code
'404':
description: Customer or pricing plan not found
content:
application/json:
schema:
description: Customer or pricing plan not found
type: object
properties:
error:
type: string
description: Error message
code:
type: string
description: Error code
details:
type: array
items:
type: object
properties:
path:
type: string
message:
type: string
description: Validation error details
required:
- error
- code
'429':
description: Rate limit exceeded. Retry after the duration specified in the Retry-After header.
content:
application/json:
schema:
description: Rate limit exceeded. Retry after the duration specified in the Retry-After header.
type: object
properties:
error:
type: string
description: Error message
code:
type: string
description: Error code
details:
type: array
items:
type: object
properties:
path:
type: string
message:
type: string
description: Validation error details
required:
- error
- code
'503':
description: Billing temporarily paused or dependency unavailable
content:
application/json:
schema:
description: Billing temporarily paused or dependency unavailable
type: object
properties:
error:
type: string
description: Error message
code:
type: string
description: Error code
details:
type: array
items:
type: object
properties:
path:
type: string
message:
type: string
description: Validation error details
required:
- error
- code
get:
operationId: listUsageEvents
summary: List usage events
tags:
- Usage
description: Retrieve recent usage events for your business, including associated charges.
parameters:
- schema:
type: integer
minimum: 1
maximum: 100
default: 100
in: query
name: limit
required: false
description: Maximum number of events to return
- schema:
type: string
in: query
name: customerId
required: false
description: Filter by customer ID
responses:
'200':
description: List of usage events
content:
application/json:
schema:
description: List of usage events
type: object
properties:
data:
type: array
items:
type: object
properties:
id:
type: string
customerId:
type: string
customer:
type: object
properties:
id:
type: string
onchainAddress:
type: string
externalCustomerId:
type: string
nullable: true
usageType:
type: string
quantity:
type: string
description:
type: string
nullable: true
description: Human-readable description of the usage event
metadata:
type: object
nullable: true
additionalProperties: true
createdAt:
type: string
format: date-time
charge:
type: object
nullable: true
properties:
id:
type: string
amountUsdc:
type: string
txHash:
type: string
nullable: true
status:
type: string
count:
type: integer
'401':
description: Unauthorized
content:
application/json:
schema:
description: Unauthorized
type: object
properties:
error:
type: string
description: Error message
code:
type: string
description: Error code
details:
type: array
items:
type: object
properties:
path:
type: string
message:
type: string
description: Validation error details
required:
- error
- code
/v1/usage/async:
post:
operationId: recordUsageAsync
summary: Record usage (async)
tags:
- Usage
description: "\nRecord a billable usage event and return immediately without waiting for settlement.\n\n> **Requires a secret key (`sk_*`) with at least the `OPERATOR` role.**\n\nThe charge will be processed in the background. Subscribe to webhooks\n(`charge.succeeded`, `charge.failed`) to get notified of the final status.\n\nUse this endpoint when you need fast response times and can handle\neventual consistency via webhooks.\n "
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
customerId:
type: string
description: Drip customer ID (cus_*). One of customerId, externalCustomerId, or stripeCustomerId is required.
example: cus_abc123def456
externalCustomerId:
type: string
maxLength: 255
description: 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.
example: user_42
stripeCustomerId:
type: string
pattern: ^cus_[A-Za-z0-9]+$
# --- truncated at 32 KB (65 KB total) ---
# Full source: https://raw.githubusercontent.com/api-evangelist/drippay/refs/heads/main/openapi/drippay-usage-api-openapi.yml