Documentation Index
Fetch the complete documentation index at: https://docs.credibill.tech/docs/llms.txt
Use this file to discover all available pages before exploring further.
For plans with usage-based pricing, you must report usage to Credibill so we know how much to charge.
Recording Usage Events
Report usage events for a subscription to track consumption. Each event is recorded with a quantity and metric type for billing purposes.
- Endpoint:
https://giant-goldfish-922.convex.site/api/v1/usage
- Method:
POST
- Authentication: Authorization:
Bearer <CREDIBILL_API_KEY>
Request Body Schema
| Field | Type | Required | Description |
|---|
| subscriptionId | string | Yes | The unique ID of the subscription to record usage for. |
| quantity | number | Yes | The quantity consumed (must be a positive number). |
| metric | string | Yes | The metric type (e.g., “api_calls”, “storage_gb”, “users”). |
| eventId | string | No | Unique event identifier for idempotency (prevents duplicate charges on retries). |
| metadata | object | No | Arbitrary JSON data for additional context about this usage event. |
Examples
// src/hooks/useRecordUsage.ts
import { useMutation, UseMutationOptions } from "@tanstack/react-query";
export type UsageEventData = {
subscriptionId: string,
quantity: number,
metric: string,
eventId?: string,
metadata?: Record<string, any>,
};
export type RecordUsageResponse = {
success: boolean,
usageEventId: string,
duplicate: boolean,
};
const useApiKey = () => {
// !! REPLACE THIS PLACEHOLDER !!
const apiKey = 'YOUR_CREDIBILL_API_KEY';
return apiKey;
};
const CREDIBILL_URL = 'https://giant-goldfish-922.convex.site';
const ENDPOINT = '/api/v1/usage';
type UseRecordUsageOptions = Omit<UseMutationOptions<
RecordUsageResponse,
Error,
UsageEventData
> , 'mutationFn'>;
/\*\*
- Custom React Query hook for recording usage events
- @param options - Optional configuration options for useMutation
- @returns The mutation object provided by TanStack Query
\*/
export const useRecordUsage = (
options?: UseRecordUsageOptions
) => {
const apiKey = useApiKey();
return useMutation<RecordUsageResponse, Error, UsageEventData>({
mutationFn: async (data: UsageEventData) => {
if (!apiKey) {
throw new Error("API Key is required for authentication.");
}
const response = await fetch(`${CREDIBILL_URL}${ENDPOINT}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`,
},
body: JSON.stringify(data),
});
if (!response.ok) {
const errorBody = await response.json();
throw new Error(errorBody.error || `HTTP error! Status: ${response.status}`);
}
return response.json();
},
...options,
});
};
Usage in a Component
import React, { useState } from 'react';
import { useRecordUsage } from './hooks/useRecordUsage';
export const RecordUsageForm = () => {
const [subscriptionId, setSubscriptionId] = useState('');
const [quantity, setQuantity] = useState('');
const [metric, setMetric] = useState('api_calls');
const [eventId, setEventId] = useState('');
const { mutate, isPending, isError, isSuccess, data, error, reset } = useRecordUsage({
onSuccess: (responseData) => {
if (responseData.duplicate) {
console.log('Event already recorded (duplicate prevention)');
} else {
console.log(`Usage recorded! Event ID: ${responseData.usageEventId}`);
}
},
});
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (isPending) return;
reset();
// Generate a unique event ID if not provided (for idempotency)
const finalEventId = eventId || `event-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
mutate({
subscriptionId: subscriptionId.trim(),
quantity: parseFloat(quantity),
metric: metric.trim(),
eventId: finalEventId,
});
};
return (
<div className="p-6 max-w-lg mx-auto bg-white rounded-xl shadow-lg space-y-4">
<h2 className="text-2xl font-bold text-gray-800">Record Usage Event</h2>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label htmlFor="subscriptionId" className="block text-sm font-medium text-gray-700">
Subscription ID (Required)
</label>
<input
id="subscriptionId"
type="text"
value={subscriptionId}
onChange={(e) => setSubscriptionId(e.target.value)}
placeholder="m90dro4hlizjt71cvwg64cj4juf9ze3e"
required
disabled={isPending}
className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
/>
</div>
<div>
<label htmlFor="quantity" className="block text-sm font-medium text-gray-700">
Quantity (Required)
</label>
<input
id="quantity"
type="number"
value={quantity}
onChange={(e) => setQuantity(e.target.value)}
placeholder="100"
required
disabled={isPending}
step="0.01"
min="0"
className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
/>
</div>
<div>
<label htmlFor="metric" className="block text-sm font-medium text-gray-700">
Metric Type (Required)
</label>
<input
id="metric"
type="text"
value={metric}
onChange={(e) => setMetric(e.target.value)}
placeholder="e.g., api_calls, storage_gb, users"
required
disabled={isPending}
className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
/>
</div>
<div>
<label htmlFor="eventId" className="block text-sm font-medium text-gray-700">
Event ID (Optional - For Idempotency)
</label>
<input
id="eventId"
type="text"
value={eventId}
onChange={(e) => setEventId(e.target.value)}
placeholder="Leave blank to auto-generate"
disabled={isPending}
className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
/>
<p className="mt-1 text-xs text-gray-500">
Unique ID prevents duplicate charges if the request is retried
</p>
</div>
<button
type="submit"
disabled={isPending}
className="w-full py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition duration-150 ease-in-out disabled:opacity-50"
>
{isPending ? 'Recording...' : 'Record Usage'}
</button>
</form>
{isSuccess && (
<div className="mt-4 p-3 bg-green-100 border border-green-400 text-green-700 rounded-lg">
<p className="font-semibold">Success!</p>
<p className="text-sm">Event ID: <span className="font-mono">{data?.usageEventId}</span></p>
{data?.duplicate && (
<p className="text-sm mt-1">⚠️ This was a duplicate event (same eventId)</p>
)}
</div>
)}
{isError && (
<div className="mt-4 p-3 bg-red-100 border border-red-400 text-red-700 rounded-lg">
Error: {error?.message}
</div>
)}
</div>
);
};
# Record a usage event with auto-generated event ID
curl -X POST 'https://giant-goldfish-922.convex.site/api/v1/usage' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer $YOUR_CREDIBILL_API_KEY' \
-d '{
"subscriptionId": "m90dro4hlizjt71cvwg64cj4juf9ze3e",
"quantity": 150,
"metric": "api_calls"
}'
# Record usage with idempotency key (prevents duplicates on retry)
curl -X POST 'https://giant-goldfish-922.convex.site/api/v1/usage' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer $YOUR_CREDIBILL_API_KEY' \
-d '{
"subscriptionId": "m90dro4hlizjt71cvwg64cj4juf9ze3e",
"quantity": 250,
"metric": "api_calls",
"eventId": "event-2024-01-15-abc123",
"metadata": {
"endpoint": "/api/users",
"region": "us-east-1"
}
}'
Response
When you record a usage event, the API responds with the event ID and duplicate status.
{
"success": true,
"usageEventId": "usage_event_xyz789",
"duplicate": false
}
Response Status Codes:
201 Created: New usage event recorded successfully
200 OK: Event already recorded (duplicate, no charge applied)
Aggregation Types
When configuring a metered component in a Plan, you choose how usage is aggregated over the billing period:
- Sum: Adds up all reported usage (e.g., API calls).
- Max: Uses the maximum value recorded (e.g., Peak Storage).
- Last: Uses the most recent value recorded (e.g., Number of Active Users).
Idempotency
To prevent double-billing due to network retries, you should provide an eventId when reporting usage. This ensures a specific event is counted only once, even if the request is retried multiple times. The system will return a duplicate: true flag if you send the same eventId again.