bus-api-provider-usage — internal usage API provider

bus-api-provider-usage — internal usage API provider

bus-api-provider-usage exposes the internal usage-events API used by trusted backend collectors. It is not an end-user command module and does not provide a bus usage CLI.

Use this provider when a deployment needs an HTTP collector feed for usage events after API providers and integrations have recorded them. End users should not call these endpoints.

Authentication

Requests use Bearer JWT authentication with audience ai.hg.fi/internal.

Listing events requires usage:read. Deleting collected events requires usage:delete. Operators mint these trusted collector tokens with bus operator token issue or another deployment-controlled internal-token flow. The token must be HS256 signed with BUS_USAGE_JWT_SECRET, include sub, aud=ai.hg.fi/internal, space-separated scope, iat, and exp, and be sent as Authorization: Bearer <token>.

For a local auth-backed deployment, configure the auth provider to sign with the same HS256 secret as BUS_USAGE_JWT_SECRET, then mint a read/delete collector token through the auth provider internal-token endpoint: the auth provider or local gateway must already be running at http://127.0.0.1:8080/local-dev/v1.

mkdir -p ./local
export BUS_USAGE_JWT_SECRET=not-a-secret-local-development-hs256-key
printf '%s' 'not-a-secret-local-development-internal-key' > ./local/auth-internal-shared-key
# The auth provider must run with BUS_AUTH_HS256_SECRET="$BUS_USAGE_JWT_SECRET"
# and BUS_AUTH_INTERNAL_SHARED_KEY from ./local/auth-internal-shared-key.
bus operator token \
  --api-url http://127.0.0.1:8080/local-dev/v1 \
  --internal-key-file ./local/auth-internal-shared-key \
  --format token \
  issue \
  --subject usage-collector \
  --audience ai.hg.fi/internal \
  --scope "usage:read usage:delete" \
  --ttl 1h > ./local/usage-collector.token

Error Format

Errors use the common Bus API JSON envelope:

{
  "error": {
    "type": "invalid_auth",
    "message": "missing bearer token"
  }
}

The collector feed is internal infrastructure. Payment-provider export and quota bucket updates normally happen through bus-integration-usage and bus-integration-billing; collectors should not infer authorization or account ownership from caller-supplied data.

GET /api/internal/usage-events

Lists usage records for trusted collectors.

Responses can include LLM, runtime, and container usage records. Each item contains storage id, optional idempotency event_id, occurrence time, optional account_id, event type, and raw JSON data. Item fields are id (integer), event_id (string or omitted), occurred_at (RFC3339 string), account_id (UUID string or omitted), event_type (string), and data (JSON object or omitted). A non-empty page looks like:

{"items":[{"id":1,"event_id":"usage-doc-check","occurred_at":"2026-05-03T12:00:00Z","account_id":"00000000-0000-4000-8000-000000000001","event_type":"usage_recorded","data":{"total_tokens":1}}],"page":1,"page_size":100,"before":"2026-05-03T12:05:00Z","has_more":false}

Collectors should persist a page downstream before deleting it. Query parameters are before=<RFC3339 timestamp>, page=<n>, and page_size=<n>. Defaults are current time, page 1, and page size 1000; page size is capped at 10000. Results are ordered by occurred_at,id and return items, page, page_size, before, and has_more.

Example collector request:

before="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
curl -fsS \
  -H "Authorization: Bearer $(cat ./local/usage-collector.token)" \
  "http://127.0.0.1:8082/api/internal/usage-events?before=${before}&page=1&page_size=100"

A successful response is 200 OK with a deterministic page, for example {"items":[],"page":1,"page_size":100,"before":"...","has_more":false}.

DELETE /api/internal/usage-events

Deletes collected usage records.

Use the same pagination selector after the collector has safely persisted the page elsewhere. “Safely persisted” means the downstream database transaction, file write plus fsync, or provider export acknowledgement has completed and can be retried without losing records. With offset-style pages, repeatedly read, persist, and delete page=1 with the same fixed before value until the page is empty. Do not delete page 1 and then move to page 2, because deleting earlier rows can shift later records and skip items. The DELETE endpoint accepts the same before, page, and page_size selector as GET and deletes that deterministic page. Success returns {"deleted": <count>}.

Persist the GET page downstream before deleting it, then reuse the exact same selector:

curl -fsS -X DELETE \
  -H "Authorization: Bearer $(cat ./local/usage-collector.token)" \
  "http://127.0.0.1:8082/api/internal/usage-events?before=${before}&page=1&page_size=100"

The response is 200 OK with {"deleted":0} or the number of records removed from that page.

GET /readyz

Reports provider readiness.

If no database URL is configured, readiness returns a service-unavailable JSON response explaining that usage storage is unavailable.

Persistence

The provider stores collector feed data in PostgreSQL. It creates a small schema at startup when it is missing: accounts, usage_events, and indexes for deterministic pagination and non-empty event_id idempotency. The database is an intermediate collector feed and can be recreated from scratch; long-term billing records belong to downstream billing systems.

bus-integration-usage is the event-worker boundary for usage business logic and storage access. This provider remains the JWT-secured HTTP facade for trusted collectors.

Local Development

Use non-secret local configuration values only:

Start PostgreSQL first and ensure the database in BUS_USAGE_DATABASE_URL exists and is reachable.

BUS_USAGE_JWT_SECRET=not-a-secret-local-development-hs256-key \
BUS_USAGE_DATABASE_URL='postgres://bus:bus@127.0.0.1:5432/bus_usage?sslmode=disable' \
bus-api-provider-usage --addr 127.0.0.1:8082

Verify readiness with:

curl -fsS http://127.0.0.1:8082/readyz

A configured database returns 200 OK with an ok readiness body. Missing or unreachable storage returns a service-unavailable JSON error.

Plain JWT secret values are raw text even when they look like base64; use base64:<value> only for an intentionally base64-encoded secret.

The BusDK superproject compose.yaml starts this provider as bus-usage-api with BUS_USAGE_DATABASE_URL pointing at the local PostgreSQL service. Nginx exposes the trusted collector path at /api/internal/usage-events. The local usage worker writes usage records through Bus Events using PostgreSQL storage, and trusted collectors read or delete collected pages through this provider with internal-audience usage scopes.

Security Notes

Use internal-audience JWTs only. End-user aud=ai.hg.fi/api tokens are not valid for this provider. Do not expose the internal usage API through public routes unless an API gateway enforces the same internal audience and scope checks.

Usage records can contain operational metadata. Avoid placing bearer tokens, provider secrets, database URLs with passwords, SSH keys, or SMTP credentials inside usage data.