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

# Send Usage Events

> Ingest usage events via API, CSV, or data integrations

# Send Usage Events

Send usage events to Alguna to power usage-based billing. Events are aggregated according to your billable metrics and automatically appear on customer invoices.

***

## Event Structure

Events are the basic unit of billable usage. Each event represents a single action a customer may be billed for.

### Required Fields

| Field        | Type     | Description                          |
| ------------ | -------- | ------------------------------------ |
| `uniqueId`   | string   | Unique identifier for deduplication  |
| `eventName`  | string   | Event type (matches your metric)     |
| `accountId`  | string   | Customer ID or alias                 |
| `timestamp`  | ISO 8601 | When the event occurred              |
| `properties` | object   | Event data for filtering/aggregation |

### Example Event

```json theme={null}
{
  "uniqueId": "evt_abc123xyz789",
  "eventName": "api.request",
  "accountId": "acc_customer123",
  "timestamp": "2024-01-20T10:30:00Z",
  "properties": {
    "endpoint": "/api/v1/users",
    "method": "GET",
    "response_time_ms": 45,
    "bytes_transferred": 2048,
    "status_code": 200
  }
}
```

***

## Send via API

The recommended method for real-time event ingestion.

### Single Event

```javascript theme={null}
await alguna.events.ingest({
  uniqueId: 'evt_' + crypto.randomUUID(),
  eventName: 'api.request',
  accountId: 'acc_customer123',
  timestamp: new Date().toISOString(),
  properties: {
    endpoint: '/api/v1/users',
    method: 'GET',
    bytes_transferred: 2048,
  },
});
```

### Batch Events

Send multiple events in a single request (recommended for high volume):

```javascript theme={null}
await alguna.events.ingestBatch([
  {
    uniqueId: 'evt_001',
    eventName: 'api.request',
    accountId: 'acc_customer123',
    timestamp: '2024-01-20T10:30:00Z',
    properties: { endpoint: '/api/v1/users', bytes: 1024 },
  },
  {
    uniqueId: 'evt_002',
    eventName: 'api.request',
    accountId: 'acc_customer123',
    timestamp: '2024-01-20T10:30:01Z',
    properties: { endpoint: '/api/v1/orders', bytes: 2048 },
  },
  {
    uniqueId: 'evt_003',
    eventName: 'api.request',
    accountId: 'acc_customer456',
    timestamp: '2024-01-20T10:30:02Z',
    properties: { endpoint: '/api/v1/products', bytes: 512 },
  },
]);
```

### cURL Example

```bash theme={null}
curl -X POST https://api.alguna.io/events/ingest \
  -H "Authorization: Bearer $ALGUNA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "events": [
      {
        "unique_id": "evt_abc123",
        "event_name": "api.request",
        "account_id": "acc_customer123",
        "timestamp": "2024-01-20T10:30:00Z",
        "properties": {
          "endpoint": "/api/v1/users",
          "method": "GET"
        }
      }
    ]
  }'
```

### Response

```json theme={null}
{
  "accepted": 3,
  "rejected": 0,
  "errors": []
}
```

***

## Send via CSV

Upload CSV files for batch ingestion or historical data.

### CSV Format

```csv theme={null}
unique_id,event_name,account_id,timestamp,properties
evt_001,api.request,acc_123,2024-01-20T10:00:00Z,"{""endpoint"":""/api/users"",""bytes"":1024}"
evt_002,api.request,acc_123,2024-01-20T10:01:00Z,"{""endpoint"":""/api/orders"",""bytes"":2048}"
evt_003,api.request,acc_456,2024-01-20T10:02:00Z,"{""endpoint"":""/api/products"",""bytes"":512}"
```

### Upload via Dashboard

1. Navigate to **Usage Metering** in the dashboard
2. Click **Upload CSV**
3. Select your CSV file
4. Map columns to fields
5. Click **Upload**

### Upload via API

```javascript theme={null}
const result = await alguna.events.uploadCsv({
  file: csvFile,
  mapping: {
    uniqueId: 'unique_id',
    eventName: 'event_name',
    accountId: 'account_id',
    timestamp: 'timestamp',
    properties: 'properties',
  },
});

console.log('Processed:', result.processed);
console.log('Errors:', result.errors);
```

### CSV Best Practices

* Maximum file size: 100MB
* Maximum rows per file: 1,000,000
* Use ISO 8601 timestamps
* JSON-encode properties column
* Include header row

***

## Send via Data Sources

Connect external data sources for automated ingestion.

### BigQuery Integration

```javascript theme={null}
await alguna.dataSources.create({
  type: 'bigquery',
  name: 'Production Usage',
  config: {
    projectId: 'your-gcp-project',
    datasetId: 'analytics',
    tableId: 'usage_events',
  },
  mapping: {
    uniqueId: 'event_id',
    eventName: 'event_type',
    accountId: 'customer_id',
    timestamp: 'created_at',
    properties: {
      bytes: 'bytes_transferred',
      endpoint: 'api_endpoint',
    },
  },
  schedule: '0 * * * *', // Every hour
});
```

### Supported Data Sources

| Source         | Description                |
| -------------- | -------------------------- |
| **BigQuery**   | Google BigQuery tables     |
| **Snowflake**  | Snowflake data warehouse   |
| **PostgreSQL** | Direct database connection |
| **S3**         | AWS S3 buckets (CSV/JSON)  |
| **Segment**    | Segment events             |
| **Webhooks**   | Incoming webhooks          |

See [Data Ingestion](/integrations/data-ingestion/bigquery-copy) for detailed setup.

***

## Customer Identification

### Using Alguna Account ID

```javascript theme={null}
{
  accountId: 'acc_xyz789' // Alguna account ID
}
```

### Using External ID (Alias)

Use your own customer identifiers:

```javascript theme={null}
// First, set up alias on the customer
await alguna.accounts.update('acc_xyz789', {
  externalId: 'customer_123', // Your system's ID
});

// Then use in events
{
  accountId: 'customer_123' // Automatically resolved
}
```

### Using Multiple Aliases

```javascript theme={null}
await alguna.accounts.update('acc_xyz789', {
  aliases: [
    { type: 'stripe', value: 'cus_abc123' },
    { type: 'salesforce', value: '001ABC' },
    { type: 'internal', value: 'user_12345' },
  ],
});

// Use any alias
{ accountId: 'cus_abc123' }  // Stripe ID
{ accountId: '001ABC' }       // Salesforce ID
{ accountId: 'user_12345' }   // Internal ID
```

***

## Idempotency & Deduplication

### Unique ID Requirements

The `uniqueId` field prevents duplicate processing:

```javascript theme={null}
// Good: Unique per event
{ uniqueId: 'request_12345_2024-01-20T10:30:00Z' }

// Good: UUID
{ uniqueId: crypto.randomUUID() }

// Bad: Same ID for different events (will be deduplicated)
{ uniqueId: 'event' }
```

### Deduplication Window

Events with the same `uniqueId` are deduplicated within a 7-day window.

### Upsert Mode

Update existing events by using the same `uniqueId`:

```javascript theme={null}
// Initial event
await alguna.events.ingest({
  uniqueId: 'job_12345',
  eventName: 'compute.job',
  accountId: 'acc_123',
  timestamp: '2024-01-20T10:00:00Z',
  properties: { duration_seconds: 300 },
});

// Update with final duration
await alguna.events.ingest({
  uniqueId: 'job_12345', // Same ID
  eventName: 'compute.job',
  accountId: 'acc_123',
  timestamp: '2024-01-20T10:00:00Z',
  properties: { duration_seconds: 450 }, // Updated value
});
```

***

## Timestamps

### Requirements

* Must be ISO 8601 format
* Must be within the current or previous billing period
* Future timestamps are rejected

### Examples

```javascript theme={null}
// Valid formats
'2024-01-20T10:30:00Z'           // UTC
'2024-01-20T10:30:00+05:30'      // With timezone
'2024-01-20T10:30:00.123Z'       // With milliseconds

// Invalid
'2024-01-20'                      // Missing time
'01/20/2024 10:30 AM'            // Wrong format
'2025-01-20T10:30:00Z'           // Future date
```

### Backdating Events

Ingest historical events (within billing period):

```javascript theme={null}
await alguna.events.ingestBatch(
  historicalEvents.map(e => ({
    uniqueId: `historical_${e.id}`,
    eventName: 'api.request',
    accountId: e.customerId,
    timestamp: e.occurredAt, // Historical timestamp
    properties: e.data,
  }))
);
```

***

## Properties

Properties carry event data used for filtering and aggregation.

### Common Property Patterns

```javascript theme={null}
// API usage
{
  properties: {
    endpoint: '/api/v1/users',
    method: 'GET',
    status_code: 200,
    response_time_ms: 45,
    bytes_transferred: 2048,
  }
}

// Compute usage
{
  properties: {
    instance_type: 'gpu.large',
    duration_seconds: 3600,
    region: 'us-east-1',
  }
}

// Storage usage
{
  properties: {
    storage_class: 'standard',
    bytes_stored: 1073741824,
    region: 'eu-west-1',
  }
}

// AI/ML usage
{
  properties: {
    model: 'gpt-4',
    input_tokens: 1500,
    output_tokens: 500,
    latency_ms: 2300,
  }
}
```

### Property Types

| Type    | Example       | Use Case               |
| ------- | ------------- | ---------------------- |
| String  | `"us-east-1"` | Filtering, grouping    |
| Number  | `1024`        | Aggregation (sum, max) |
| Boolean | `true`        | Filtering              |

### Nested Properties

```javascript theme={null}
{
  properties: {
    request: {
      method: 'POST',
      path: '/api/users',
    },
    response: {
      status: 200,
      bytes: 1024,
    },
  }
}
```

***

## Error Handling

### Validation Errors

```javascript theme={null}
try {
  await alguna.events.ingest(event);
} catch (error) {
  if (error.code === 'VALIDATION_ERROR') {
    console.error('Invalid event:', error.details);
    // {
    //   field: 'timestamp',
    //   message: 'Invalid ISO 8601 format'
    // }
  }
}
```

### Common Errors

| Error                | Cause                       | Solution                         |
| -------------------- | --------------------------- | -------------------------------- |
| `INVALID_TIMESTAMP`  | Wrong format or future date | Use ISO 8601, current/past dates |
| `ACCOUNT_NOT_FOUND`  | Unknown accountId           | Create account or check alias    |
| `DUPLICATE_EVENT`    | Same uniqueId processed     | Use unique IDs per event         |
| `INVALID_PROPERTIES` | Non-JSON properties         | Ensure valid JSON structure      |

### Retry Strategy

```javascript theme={null}
async function ingestWithRetry(events, maxRetries = 3) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await alguna.events.ingestBatch(events);
    } catch (error) {
      if (error.status >= 500 && attempt < maxRetries) {
        // Server error, retry with backoff
        await sleep(Math.pow(2, attempt) * 1000);
        continue;
      }
      throw error;
    }
  }
}
```

***

## High-Volume Ingestion

### Batch Recommendations

| Volume           | Batch Size | Frequency           |
| ---------------- | ---------- | ------------------- |
| \< 1,000/min     | 1-100      | Real-time           |
| 1,000-10,000/min | 100-500    | Every 1-5 seconds   |
| > 10,000/min     | 500-1,000  | Continuous batching |

### Async Ingestion

For very high volume, use async mode:

```javascript theme={null}
const result = await alguna.events.ingestAsync({
  events: largeEventBatch,
  callbackUrl: 'https://yourapp.com/webhooks/ingestion',
});

// Returns immediately
console.log('Job ID:', result.jobId);
console.log('Status:', result.status); // 'processing'
```

### Rate Limits

| Tier       | Requests/sec | Events/request |
| ---------- | ------------ | -------------- |
| Standard   | 100          | 1,000          |
| Enterprise | 500          | 10,000         |

***

## Query Usage

### Current Period Usage

```javascript theme={null}
const usage = await alguna.usage.get({
  accountId: 'acc_customer123',
  metricCode: 'api_calls',
});

console.log('Usage:', usage.value);
console.log('Period:', usage.periodStart, '-', usage.periodEnd);
```

### Usage History

```javascript theme={null}
const history = await alguna.usage.history({
  accountId: 'acc_customer123',
  metricCode: 'api_calls',
  startDate: '2024-01-01',
  endDate: '2024-01-31',
  granularity: 'daily',
});
```

### Usage by Property

```javascript theme={null}
const breakdown = await alguna.usage.breakdown({
  accountId: 'acc_customer123',
  metricCode: 'api_calls',
  groupBy: ['endpoint'],
});
```

***

## Webhooks

| Event                      | Description              |
| -------------------------- | ------------------------ |
| `usage.recorded`           | Event processed (opt-in) |
| `usage.threshold_exceeded` | Usage exceeded threshold |

### Threshold Alerts

```javascript theme={null}
await alguna.alerts.create({
  accountId: 'acc_customer123',
  metricCode: 'api_calls',
  threshold: 80, // 80% of plan limit
  action: 'webhook',
  webhookUrl: 'https://yourapp.com/alerts',
});
```

***

## Best Practices

<CardGroup cols={2}>
  <Card title="Batch Events" icon="layer-group">
    Send events in batches for better performance.
  </Card>

  <Card title="Unique IDs" icon="fingerprint">
    Generate truly unique IDs to prevent duplicates.
  </Card>

  <Card title="Accurate Timestamps" icon="clock">
    Use the actual event time, not ingestion time.
  </Card>

  <Card title="Rich Properties" icon="tags">
    Include properties needed for filtering and aggregation.
  </Card>
</CardGroup>

***

## Troubleshooting

### Events Not Appearing

1. Check event timestamp is within billing period
2. Verify accountId exists or alias is configured
3. Confirm eventName matches your metric
4. Review API response for errors

### Usage Shows Zero

1. Verify events are being sent successfully
2. Check metric configuration and filters
3. Confirm event properties match metric requirements

### Duplicate Charges

1. Ensure uniqueId is truly unique per event
2. Check for duplicate event sends in your code
3. Review event logs for repeated ingestion

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Define Metrics" icon="ruler" href="/billable-metrics/define-metrics">
    Configure billable metrics.
  </Card>

  <Card title="Metrics Reference" icon="book" href="/billable-metrics/metrics-reference">
    Complete metrics configuration guide.
  </Card>
</CardGroup>
