> ## 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.

# Usage Records

> Reporting metering data for billing

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

<Tabs>
  <Tab title="React">
    ```typescript theme={null}
    // 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

    ```typescript theme={null}
    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>
      );
    };
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={null}
    # 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"
    }
    }'

    ```
  </Tab>
</Tabs>

### Response

When you record a usage event, the API responds with the event ID and duplicate status.

```json theme={null}
{
  "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.
