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_KEYThere 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.csvkey,tokens_input,tokens_output,cost_usd,invocations
user:usr_8421,1840221,402118,14.82,8412
user:usr_9013,918442,201044,7.41,4108Billing 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.