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

# Pull Agent Usage into Your Warehouse

> Copy-paste recipes for exporting every agent's creator, consumers, sharing, and token usage from the Agent Usage Feed API.

Practical recipes for loading agent data into a BI tool or data warehouse. For the endpoint reference, see [Agent Usage Feed](/platform-api#agent-usage-feed). For a one-off answer without code, ask your AI client to use the [Runlayer MCP](/runlayer-mcp) `get_agent_usage_report` tool instead.

## Prerequisites

* A Runlayer instance (referred to as `RUNLAYER_URL` below)
* A personal API key from a user with both the **View company metrics** and **Export usage data** permissions. Use the least-privileged role that has both, **Analytics Admin**, rather than a Super Admin. Create it in **Settings → Personal API keys**. The key stops working if that user is deactivated, so give an ETL job a key from a user who will stay active
* Python 3.9+ for the scripted recipes (standard library only)

```bash theme={null}
export RUNLAYER_URL="https://<your-tenant>.runlayer.com"
export RUNLAYER_API_KEY="rl_..."
```

## Fetch One Page

```bash theme={null}
curl "$RUNLAYER_URL/api/v1/agents/metrics/usage?start_date=2026-09-01T00:00:00Z&end_date=2026-09-30T23:59:59.999999Z&limit=100" \
  -H "x-runlayer-api-key: $RUNLAYER_API_KEY"
```

The response has `count` (total agents), `next_cursor`, and `data` (one item per agent). Pass `next_cursor` back as `cursor` to get the next page, until it is `null`.

## Export Every Agent to CSV

Pages through the whole feed for one date range and writes one row per agent, plus one row per agent and consumer.

```python theme={null}
import csv
import json
import os
import urllib.parse
import urllib.request

BASE = os.environ["RUNLAYER_URL"].rstrip("/") + "/api/v1"
HEADERS = {"x-runlayer-api-key": os.environ["RUNLAYER_API_KEY"]}
USAGE_FIELDS = [
    "run_count", "input_tokens", "output_tokens", "cache_read_tokens",
    "cache_creation_tokens", "reasoning_tokens", "total_tokens", "cost_usd",
]


def get(path, **params):
    query = urllib.parse.urlencode({k: v for k, v in params.items() if v is not None})
    request = urllib.request.Request(f"{BASE}{path}?{query}", headers=HEADERS)
    with urllib.request.urlopen(request) as response:
        return json.load(response)


def agents(start, end):
    cursor = None
    while True:
        page = get("/agents/metrics/usage", start_date=start, end_date=end,
                   limit=100, consumer_limit=50, cursor=cursor)
        yield from page["data"]
        cursor = page["next_cursor"]
        if cursor is None:
            return


def consumers(agent_id, start, end):
    cursor = None
    while True:
        page = get(f"/agents/{agent_id}/metrics/consumers", start_date=start,
                   end_date=end, limit=500, cursor=cursor)
        yield from page["data"]
        cursor = page["next_cursor"]
        if cursor is None:
            return


def export(start, end, agents_csv="agents.csv", consumers_csv="agent_consumers.csv"):
    with open(agents_csv, "w", newline="") as a, open(consumers_csv, "w", newline="") as c:
        agent_rows, consumer_rows = csv.writer(a), csv.writer(c)
        agent_rows.writerow(["start_date", "end_date", "agent_id", "name", "is_disabled",
                             "created_at", "creator_email", "consumer_count",
                             "share_scope", "shared_with", *USAGE_FIELDS])
        consumer_rows.writerow(["start_date", "end_date", "agent_id", "user_id",
                                "user_email", *USAGE_FIELDS])
        for agent in agents(start, end):
            shared_with = "; ".join(f"{s['principal_type']}:{s['name']}" for s in agent["shared_with"])
            agent_rows.writerow([start, end, agent["id"], agent["name"], agent["is_disabled"],
                                 agent["created_at"], agent["creator"]["email"],
                                 agent["consumer_count"], agent["share_scope"], shared_with,
                                 *(agent["usage"][f] for f in USAGE_FIELDS)])
            # The feed carries the top consumers; page the rest only when there are more.
            people = (agent["consumers"] if agent["consumer_count"] <= len(agent["consumers"])
                      else consumers(agent["id"], start, end))
            for person in people:
                consumer_rows.writerow([start, end, agent["id"], person["user"]["id"],
                                        person["user"]["email"],
                                        *(person["usage"][f] for f in USAGE_FIELDS)])


if __name__ == "__main__":
    export("2026-09-01T00:00:00Z", "2026-09-30T23:59:59.999999Z")
```

## Load Daily Partitions

For a warehouse table partitioned by day, pull one day per request and replace the last two days on every sync. Tokens for a run are recorded when it finishes, so the most recent day can still change.

```python theme={null}
import datetime

# Reuses export() from the recipe above.
def sync(days=2):
    today = datetime.datetime.now(datetime.timezone.utc).replace(
        hour=0, minute=0, second=0, microsecond=0)
    for offset in range(days - 1, -1, -1):
        day = today - datetime.timedelta(days=offset)
        # end_date is inclusive: stop one microsecond before the next day so a
        # run that starts exactly at midnight lands in one partition, not two.
        end = day + datetime.timedelta(days=1) - datetime.timedelta(microseconds=1)
        export(day.isoformat(), end.isoformat(),
               agents_csv=f"agents_{day:%Y-%m-%d}.csv",
               consumers_csv=f"agent_consumers_{day:%Y-%m-%d}.csv")
        # Load each file into the warehouse, replacing that day's partition.


if __name__ == "__main__":
    sync()
```

Agent-level `usage` covers every consumer, so a day's agent totals equal the sum of that day's consumer rows.

## Handle Errors

| Status | What to do |
| - | - |
| `400` | The range reaches past the queryable window (90 days by default). Older usage isn't available from this API |
| `403` | The key's user lacks **View company metrics** or **Export usage data** |
| `422` with "narrow the date range or lower limit" | The feed page's agents ran more than 100,000 times in the range. Retry with a shorter range or a smaller `limit` |
| `422` with "This agent ran more often in range…" | From the consumers endpoint: that one agent ran more than 100,000 times. Retry with a shorter range; `limit` doesn't help |
| `422` with "Invalid cursor" | Restart paging from the first page without `cursor` |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.