> For the complete documentation index, see [llms.txt](https://docs.harmony.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.harmony.io/integrations/saas-applications/bring-your-own-app/bring-your-own-app-usage.md).

# Usage Data

Field reference, deduplication rules, and examples for sending activity data to Harmony

A usage push tells Harmony **what people actually did** in an application. Where a [users push](/integrations/saas-applications/bring-your-own-app/bring-your-own-app-users.md) reports who has access, a usage push reports who used that access — which is what turns an inventory into a license optimization tool.

This page covers the usage endpoint in full. For setup, see [Bring Your Own App](/integrations/saas-applications/bring-your-own-app.md).

### How usage differs from users

Usage pushes are **additive**, not snapshots. Each push adds activity records to what Harmony already has. Nothing is removed by omitting it — there is no equivalent of the users snapshot rule here.

Instead of replacement, usage relies on **deduplication**: every record carries an `event_id` that you choose, and Harmony counts each `event_id` exactly once no matter how many times you send it. That is what makes usage pushes safe to retry.

{% hint style="info" %}
**Send a users push before your first usage push.** Activity is attributed to users Harmony already knows about. Reporting activity for someone who has never appeared in a users push means that activity is not counted.
{% endhint %}

### Endpoint

```
POST https://external.harmony.io/custom-push/v1/usage
Authorization: Bearer <access key>
Content-Type: application/json
```

### Request body

| Field             | Type   | Required | Description                                                                                                |
| ----------------- | ------ | -------- | ---------------------------------------------------------------------------------------------------------- |
| `external_app_id` | string | **Yes**  | The App ID you registered in Harmony. Must match the value used in your users pushes for this application. |
| `events`          | array  | **Yes**  | Between 1 and 100 activity records. See below.                                                             |
| `schema_version`  | string | No       | Defaults to `"1.0"`. Reserved for future versions of this API.                                             |

### Usage fields

Each object in the `events` array describes one activity observation.

| Field            | Type   | Required | Limit                      | Description                                                                                                                                                                                              |
| ---------------- | ------ | -------- | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `event_id`       | string | **Yes**  | 1–255 chars                | Your stable identifier for this record. This is the deduplication key — see [The event ID contract](#the-event-id-contract) below. Getting this right is the single most important part of a usage push. |
| `event_date`     | string | **Yes**  | `YYYY-MM-DD`               | The calendar day the activity happened, in UTC. Usage is tracked per day; there is no need for a timestamp.                                                                                              |
| `actor_email`    | string | **Yes**  | Valid email, max 254 chars | The email address of the person who performed the activity. This is how the record is attributed to a user and, through them, to an employee.                                                            |
| `actor_id`       | string | No       | 255 chars                  | The application's own identifier for that person — the same value you send as `external_id` in your users push. Recommended: it makes attribution reliable even if a user's email address changes.       |
| `action`         | string | **Yes**  | 1–255 chars                | What the user did, as a short label. Becomes a named activity metric in Harmony. Keep the set of values small and stable — `login`, `message_sent`, `report_generated`.                                  |
| `metric_value`   | number | **Yes**  | ≥ 0, finite                | How much of that action occurred. For a per-interaction record this is usually `1`; for a daily total it is the count for that day.                                                                      |
| `outcome_reason` | string | No       | See values below           | What `metric_value` measures. Defaults to `request_count`.                                                                                                                                               |
| `metadata`       | object | No       | 1 KB serialized            | Any additional key/value data you want to carry along. Free-form JSON.                                                                                                                                   |

#### `outcome_reason` values

| Value           | `metric_value` represents                                       |
| --------------- | --------------------------------------------------------------- |
| `request_count` | A number of actions or interactions. This is the default.       |
| `token_used`    | A quantity of consumed units, such as AI tokens or API credits. |
| `cost_usd`      | An amount of money in US dollars.                               |

Use this to report cost or consumption alongside plain activity counts. A single application can send several actions with different `outcome_reason` values — for example `action: "chat_message"` with `request_count`, and `action: "chat_message"` with `cost_usd` for the same day's spend.

### The event ID contract

`event_id` is how Harmony knows whether two records describe the same activity or two different activities.

* **Sending the same `event_id` twice is always safe.** The second one is ignored. Retry freely.
* **Sending the same real-world activity under a new `event_id` counts it twice.** This is the failure mode to avoid.

Because of that, an `event_id` must be **derived from the data**, never generated fresh at send time.

{% hint style="warning" %}
Never use a random value, a UUID generated per request, a timestamp of when your script ran, or a row counter. Any of these produce a new ID every run, so re-running your script double-counts everything it already sent.
{% endhint %}

There are two correct approaches. Pick the one that matches your data.

#### If your application has native event IDs

Some applications record every interaction individually with its own identifier — an audit log ID, an event ID, a message ID. Use it directly.

```json
{ "event_id": "evt_9f2ab41c7", "event_date": "2026-07-20", "actor_email": "alice@example.com", "action": "message_sent", "metric_value": 1 }
```

With native IDs, many records per user per day are expected and correct — one per interaction.

#### If you report daily totals

Most applications only expose aggregates: "Alice sent 47 messages on July 20th". Build a **natural key** from the values that make the record unique.

```
event_id = "{user}:{date}:{action}"
```

```json
{ "event_id": "alice@example.com:2026-07-20:message_sent", "event_date": "2026-07-20", "actor_email": "alice@example.com", "action": "message_sent", "metric_value": 47 }
```

Re-running your script for July 20th produces the same key, so the record is recognized and updated rather than added again. This is what makes daily backfills and overlapping date ranges safe.

{% hint style="info" %}
Event IDs only need to be unique within one application. You can use the same scheme across every application you push — Harmony keeps them separate.
{% endhint %}

### The 90-day activity window

Harmony's activity statistics are calculated over a rolling **90-day** window.

Usage records older than 90 days are accepted and stored, but they do not appear in activity charts or influence active/inactive user determination. If you are backfilling history, anything beyond 90 days will not surface in the interface.

For an initial load, pushing the last 90 days of activity gives you a complete picture immediately. Older history is optional.

### Response

`202 Accepted`:

```json
{
  "batch_id": "3d5e1f88-27ac-4b60-9de1-4f0c8a1b2e39",
  "accepted_rows": 2
}
```

| Field           | Description                                                                                 |
| --------------- | ------------------------------------------------------------------------------------------- |
| `batch_id`      | A unique identifier for this request. Useful when contacting support about a specific push. |
| `accepted_rows` | How many activity records were accepted.                                                    |

Unlike a users push, there is no `snapshot_id` — usage records are independent, so there is nothing to join across requests.

For error responses, see the [API reference](/integrations/saas-applications/bring-your-own-app/bring-your-own-app-api.md#errors).

### Examples

#### A minimal push

```json
{
  "external_app_id": "acme-crm",
  "events": [
    {
      "event_id": "alice@example.com:2026-07-20:login",
      "event_date": "2026-07-20",
      "actor_email": "alice@example.com",
      "action": "login",
      "metric_value": 1
    }
  ]
}
```

#### A fully populated push

Reporting both an activity count and its cost for the same day:

```json
{
  "external_app_id": "acme-ai",
  "events": [
    {
      "event_id": "alice@example.com:2026-07-20:completions",
      "event_date": "2026-07-20",
      "actor_email": "alice@example.com",
      "actor_id": "u-1001",
      "action": "completions",
      "metric_value": 312,
      "outcome_reason": "request_count",
      "metadata": { "workspace": "engineering", "model": "standard" }
    },
    {
      "event_id": "alice@example.com:2026-07-20:completions_cost",
      "event_date": "2026-07-20",
      "actor_email": "alice@example.com",
      "actor_id": "u-1001",
      "action": "completions",
      "metric_value": 4.72,
      "outcome_reason": "cost_usd"
    }
  ]
}
```

Note the two distinct `event_id` values. They describe two different measurements of the same day's work, so they must not collide.

#### A daily usage script

```python
import os
from datetime import date, timedelta

import requests

BASE_URL = "https://external.harmony.io"
APP_ID = "acme-crm"
ACCESS_KEY = os.environ["HARMONY_ACCESS_KEY"]


def fetch_daily_activity(day):
    """Return per-user totals for one day. Replace with your application's API."""
    ...


def push_usage(rows):
    for i in range(0, len(rows), 100):
        response = requests.post(
            f"{BASE_URL}/custom-push/v1/usage",
            headers={"Authorization": f"Bearer {ACCESS_KEY}"},
            json={"external_app_id": APP_ID, "events": rows[i:i + 100]},
            timeout=30,
        )
        response.raise_for_status()


yesterday = date.today() - timedelta(days=1)
day = yesterday.isoformat()

events = [
    {
        # Deterministic: re-running this script for the same day is a no-op
        "event_id": f"{row['email']}:{day}:{row['action']}",
        "event_date": day,
        "actor_email": row["email"],
        "actor_id": row["user_id"],
        "action": row["action"],
        "metric_value": row["count"],
    }
    for row in fetch_daily_activity(yesterday)
]

push_usage(events)
print(f"Pushed {len(events)} activity records for {day}")
```

Because the event IDs are deterministic, this script is safe to re-run, safe to run twice by mistake, and safe to point at an overlapping range of dates when catching up.

### Recommended push frequency

Daily is the usual pattern: each morning, push yesterday's activity. Applications that expose real-time events can push more frequently.

If your script misses a day, simply include the missed dates in the next run. Deterministic event IDs make overlapping ranges harmless.

### See also

* [**Users data**](/integrations/saas-applications/bring-your-own-app/bring-your-own-app-users.md) — reporting who has access
* [**API reference**](/integrations/saas-applications/bring-your-own-app/bring-your-own-app-api.md) — errors, retries, and limits


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.harmony.io/integrations/saas-applications/bring-your-own-app/bring-your-own-app-usage.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
