Lararouter
API reference

Usage & billing

GET /v1/usage/entities. One endpoint for every token and cost question.

GET /v1/usage/entities?start=2026-08-01&end=2026-08-31 HTTP/1.1
Host: REGION.api.lararouter.com
Authorization: Bearer $LARAROUTER_API_KEY

There is one usage endpoint. Project totals, one customer's spend, and the intersection of two entities are all the same call with different filters. There's no separate aggregate route to remember and no export route that drifts out of sync with the reporting one.

Requires the usage:read scope.

Project totals

Omit entity entirely:

curl -G https://REGION.api.lararouter.com/v1/usage/entities \
  -H "Authorization: Bearer $LARAROUTER_API_KEY" \
  -d "start=2026-08-01" \
  -d "end=2026-08-31"

One entity

entity takes the same type:id form as the attribution header:

curl -G https://REGION.api.lararouter.com/v1/usage/entities \
  -H "Authorization: Bearer $LARAROUTER_API_KEY" \
  -d "entity=user:usr_8421" \
  -d "start=2026-08-01"

Two entities: the intersection

Pass entity twice and you get only the invocations attributed to both, which is how you answer "what did this user cost inside this team" rather than "what did this user cost anywhere."

curl -G https://REGION.api.lararouter.com/v1/usage/entities \
  -H "Authorization: Bearer $LARAROUTER_API_KEY" \
  -d "entity=user:usr_8421" \
  -d "entity=team:team_acme" \
  -d "start=2026-08-01"

Two is the cap

A third entity returns 400 invalid_request_error. Intersecting three ways is almost always a sign the question belongs in group_by instead.

Parameters

Prop

Type

start and end cannot span further back than your retention window: 90 days on Pay-as-you-go, 13 months on Enterprise. Reaching past it returns 400 retention_window_exceeded rather than a quietly truncated answer.

Response

{
  "object": "usage",
  "entities": ["user:usr_8421", "team:team_acme"],
  "start": "2026-08-01",
  "end": "2026-08-31",
  "group_by": "model",
  "totals": {
    "tokens_input": 1840221,
    "tokens_output": 402118,
    "cost_usd": 14.82,
    "invocations": 8412
  },
  "groups": [
    {
      "key": "llama-3.3-70b",
      "tokens_input": 1402118,
      "tokens_output": 388204,
      "cost_usd": 13.91,
      "invocations": 6120
    },
    {
      "key": "llama-3.1-8b",
      "tokens_input": 438103,
      "tokens_output": 13914,
      "cost_usd": 0.91,
      "invocations": 2292
    }
  ]
}

totals always reflects the filters. groups is only present when group_by is set.

Don't add group totals across entity types

An invocation attributed to both a user and a team counts fully toward each. Summing the user and team groups from group_by=entity_type double-counts. Compare within a type, not across.

Grouping

group_by=day gives you a time series to chart:

curl -G https://REGION.api.lararouter.com/v1/usage/entities \
  -H "Authorization: Bearer $LARAROUTER_API_KEY" \
  -d "entity=customer:cus_2291" \
  -d "start=2026-08-01" \
  -d "group_by=day"
{
  "object": "usage",
  "group_by": "day",
  "groups": [
    { "key": "2026-08-01", "cost_usd": 0.42, "invocations": 118 },
    { "key": "2026-08-02", "cost_usd": 0.51, "invocations": 141 }
  ]
}

group_by=batch reconciles a single job against your own records, though cost_usd on the batch object says the same thing without a second call:

curl -G https://REGION.api.lararouter.com/v1/usage/entities \
  -H "Authorization: Bearer $LARAROUTER_API_KEY" \
  -d "start=2026-08-01" \
  -d "group_by=batch"

group_by=entity is the one to reach for when you're building an invoice run. It returns every entity that spent anything in the window, in one request:

curl -G https://REGION.api.lararouter.com/v1/usage/entities \
  -H "Authorization: Bearer $LARAROUTER_API_KEY" \
  -d "start=2026-08-01" \
  -d "group_by=entity" \
  -d "tag=env=production"

CSV export

format=csv returns the same data as text/csv, streamed. There's no separate export endpoint and no job to poll.

curl -G https://REGION.api.lararouter.com/v1/usage/entities \
  -H "Authorization: Bearer $LARAROUTER_API_KEY" \
  -d "start=2026-08-01" \
  -d "group_by=entity" \
  -d "format=csv" \
  -o august-usage.csv
key,tokens_input,tokens_output,cost_usd,invocations
user:usr_8421,1840221,402118,14.82,8412
user:usr_9013,918442,201044,7.41,4108

Billing your own customers

The whole point of attribution is that a monthly invoice run is one request per billing period, not one per customer.

namespace App\Actions\Billing;

use App\Models\Customer;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Http;
use Throwable;

class ChargeMonthlyUsage
{
    public function handle(Carbon $month): void
    {
        try {
            $groups = Http::withToken(config('services.lararouter.key'))
                ->get('https://REGION.api.lararouter.com/v1/usage/entities', [
                    'start' => $month->copy()->startOfMonth()->toDateString(),
                    'end' => $month->copy()->endOfMonth()->toDateString(),
                    'group_by' => 'entity',
                ])
                ->throw()
                ->json('groups');

            foreach ($groups as $group) {
                [$type, $id] = explode(':', $group['key'], 2);

                if ($type !== 'customer') {
                    continue;
                }

                Customer::find($id)?->billFor($group['cost_usd'], $month);
            }
        } catch (Throwable $e) {
            report($e);

            throw $e;
        }
    }
}

Reconcile against totals.cost_usd before charging anyone. If the sum of your customer: groups is meaningfully below the project total, some traffic isn't attributed. See Attribution.

On this page