> ## Documentation Index
> Fetch the complete documentation index at: https://docs.fynn.eu/llms.txt
> Use this file to discover all available pages before exploring further.

# Collect Usage Data

> Send usage events to Fynn for usage-based billing.

Usage events are the foundation of usage-based billing. Send them from your application to track customer consumption.

## Quick Start

Send a usage event:

```bash theme={null}
curl -X POST https://coreapi.io/api/usage-events \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "events": [{
      "transactionId": "req-abc-123",
      "eventName": "ai_request",
      "timestamp": "2026-01-15T14:30:00Z",
      "customerId": "550e8400-e29b-41d4-a716-446655440000",
      "properties": {
        "tokens_used": 350,
        "model": "gpt-4"
      }
    }]
  }'
```

***

## Event Structure

Each event requires these fields:

| Field           | Type     | Description                                                |
| --------------- | -------- | ---------------------------------------------------------- |
| `transactionId` | string   | Unique ID for idempotency                                  |
| `eventName`     | string   | Matches your Nutzungsmetrik's event name                   |
| `timestamp`     | ISO 8601 | When the event occurred                                    |
| `customerId`    | string   | The customer's Fynn UUID **or** external ID (`externalId`) |
| `properties`    | object   | Event data (tokens, bytes, etc.)                           |

<Tip>
  The `customerId` field accepts both the Fynn UUID and the customer's `externalId`. Fynn automatically detects which format is being used.
</Tip>

### Example: AI Token Usage

```json theme={null}
{
  "transactionId": "req-abc-123",
  "eventName": "ai_request",
  "timestamp": "2026-01-15T14:30:00Z",
  "customerId": "550e8400-e29b-41d4-a716-446655440000",
  "properties": {
    "tokens_used": 350,
    "model": "gpt-4",
    "endpoint": "/v1/completions",
    "status_code": 200
  }
}
```

### Example: API Call Tracking

```json theme={null}
{
  "transactionId": "call-xyz-456",
  "eventName": "api_call",
  "timestamp": "2026-01-15T14:31:00Z",
  "customerId": "550e8400-e29b-41d4-a716-446655440000",
  "properties": {
    "endpoint": "/api/users",
    "method": "GET",
    "response_time_ms": 145,
    "status_code": 200
  }
}
```

***

## Batch Import

Send up to 1000 events in one request:

```bash theme={null}
curl -X POST https://coreapi.io/api/usage-events \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "events": [
      {
        "transactionId": "req-001",
        "eventName": "ai_request",
        "timestamp": "2026-01-15T10:00:00Z",
        "customerId": "cust-123",
        "properties": { "tokens_used": 150, "model": "gpt-4" }
      },
      {
        "transactionId": "req-002",
        "eventName": "ai_request",
        "timestamp": "2026-01-15T10:01:00Z",
        "customerId": "cust-123",
        "properties": { "tokens_used": 300, "model": "gpt-4-turbo" }
      }
    ]
  }'
```

***

## Response

**Success (202 Accepted):**

```json theme={null}
{
  "ingested": 2,
  "failed": 0,
  "errors": []
}
```

**Partial Success:**

```json theme={null}
{
  "ingested": 1,
  "failed": 1,
  "errors": [
    {
      "index": 1,
      "transactionId": "req-002",
      "error": "Invalid customer ID"
    }
  ]
}
```

***

## Idempotency

Events with the same `transactionId` are only processed once. If you send the same event twice, Fynn returns the existing event instead of creating a duplicate.

<Tip>
  Use deterministic transaction IDs, e.g.: `${customerId}-${timestamp}-${eventName}` or UUIDs.
</Tip>

***

## Properties for Nutzungsmetriks

The `properties` object should contain all fields your Nutzungsmetriks need:

| Nutzungsmetrik | Aggregation  | Required Property |
| -------------- | ------------ | ----------------- |
| Token Usage    | Sum          | `tokens_used`     |
| API Calls      | Count        | - (counts events) |
| Storage        | Latest       | `storage_gb`      |
| Active Users   | Unique Count | `user_id`         |

### Grouping Properties

If your Nutzungsmetrik uses `groupBy`, include those fields:

```json theme={null}
{
  "properties": {
    "tokens_used": 350,
    "model": "gpt-4",       // groupBy field
    "region": "eu-west-1"   // another groupBy field
  }
}
```

### Filter Properties

If your Nutzungsmetrik has filters, include the filtered fields:

```json theme={null}
{
  "properties": {
    "tokens_used": 350,
    "status_code": 200  // filter: status_code in [200, 201, 204]
  }
}
```

***

## Legacy: Subscription Measurements

For existing integrations using subscription-based measurements:

```bash theme={null}
curl -X POST https://coreapi.io/subscription-measurements/batch \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "values": [
      {
        "subscription": "SUBSCRIPTION_ID",
        "subscriptionItem": "SUBSCRIPTION_ITEM_ID",
        "measurementCode": "used_traffic",
        "quantity": "1",
        "measuredAt": "2026-01-15T00:00:00Z"
      }
    ]
  }'
```

See: [API Reference](/api-reference/subscription-measurement/add-a-batch-of-subscription-measurement)

***

## Best Practices

### Timestamps

Always use UTC in ISO 8601 format:

```
✅ 2026-01-15T14:30:00Z
✅ 2026-01-15T14:30:00+00:00
❌ 2026-01-15 14:30:00
❌ 1705329000 (Unix timestamp)
```

### Batch Sizes

| Scenario           | Recommended Batch Size            |
| ------------------ | --------------------------------- |
| Real-time tracking | 10-100 events                     |
| Bulk import        | 500-1000 events                   |
| High volume        | Buffer and send every 1-5 seconds |

### Error Handling

```javascript theme={null}
async function sendEvents(events) {
  const response = await fetch('https://coreapi.io/api/usage-events', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${API_KEY}`
    },
    body: JSON.stringify({ events })
  });

  const result = await response.json();

  if (result.failed > 0) {
    // Retry failed events or log for manual review
    console.error('Failed events:', result.errors);
  }

  return result;
}
```

***

## Related Documentation

* [Nutzungsmetriken](/guide/catalogue/measurements/billable-metrics) - Konfiguriere was und wie gemessen wird
* [Nutzungsereignisse](/guide/catalogue/usage-events-import) - CSV Import und UI Optionen
* [Aggregationstypen](/guide/catalogue/measurements/aggregation-types/overview) - Summe, Anzahl, Maximum, etc.
