Skip to content

feat(api): add usage email filters and members endpoint#4211

Open
evanjacobson wants to merge 9 commits into
mainfrom
feat/public-api-endpoints
Open

feat(api): add usage email filters and members endpoint#4211
evanjacobson wants to merge 9 commits into
mainfrom
feat/public-api-endpoints

Conversation

@evanjacobson

@evanjacobson evanjacobson commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Summary

External API consumers previously had to rely on internal user IDs and did not have a documented way to discover organization members for filtering usage analytics. This adds email-oriented usage filtering/display, a public organization members endpoint, and OpenAPI/Swagger coverage so clients can build against the API without depending on private UI assumptions.

Get Members API: /api/v1/organizations//members

Details
  • Adds email-based include/exclude filters and opt-in email display for usage analytics user dimensions while keeping user ID display as the default.
  • Adds a public organization members route that returns active and invited members without exposing invite tokens or invite URLs.
  • Extends the existing allowlisted OpenAPI generation pattern to include both tRPC usage procedures and the new REST endpoint.

Verification

No manual UI verification; this is API/schema behavior with no visual interaction changes.

Visual Changes

N/A

Reviewer Notes

Focus review on email-to-user identity scoping for usage analytics, the public member response PII boundary, and the OpenAPI shape for mixed tRPC/REST paths.

Comment thread apps/web/src/routers/usage-analytics-router.ts Outdated
@kilo-code-bot

kilo-code-bot Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Files Reviewed (5 files)
  • apps/web/src/app/api/docs/route.test.ts
  • apps/web/src/app/api/docs/route.ts
  • apps/web/src/lib/openapi/trpc-openapi.test.ts
  • apps/web/src/lib/openapi/trpc-openapi.ts
  • apps/web/src/lib/openapi/trpc-registry.ts
Previous Review Summaries (7 snapshots, latest commit e022ad4)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit e022ad4)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (2 files)
  • apps/web/src/routers/usage-analytics-router.ts
  • apps/web/src/routers/usage-analytics-router.test.ts

Previous review (commit 4db69ed)

Status: 2 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 2
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
apps/web/src/routers/usage-analytics-router.ts 1163 Adding scopedBreakdownUserIds on top of the existing self-scope kilo_user_id = ctx.user.id filter still excludes legacy oauth/... rows, so personal/self email breakdowns remain under-counted.
apps/web/src/routers/usage-analytics-router.ts 1177 Removing the SQL LIMIT for email-display breakdowns still requires materializing every per-user bucket before trimming to input.limit, which can turn top-N requests into unbounded scans on large orgs.

Fix these issues in Kilo Cloud

Files Reviewed (2 files)
  • apps/web/src/routers/usage-analytics-router.test.ts - 0 issues
  • apps/web/src/routers/usage-analytics-router.ts - 2 issues

Previous review (commit e2e18a2)

Status: 1 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
apps/web/src/routers/usage-analytics-router.ts 518 Mapping only the displayed label after limiting still returns duplicate and under-counted per-email buckets when one user has multiple raw IDs.

Fix these issues in Kilo Cloud

Files Reviewed (2 files)
  • apps/web/src/routers/usage-analytics-router.test.ts - 0 issues
  • apps/web/src/routers/usage-analytics-router.ts - 1 issue

Previous review (commit 52c4530)

Status: 1 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
apps/web/src/routers/usage-analytics-router.ts 515 Mapping the top-N raw Snowflake buckets to email labels after limiting still returns duplicate and under-counted per-user rows when one person has multiple IDs.

Fix these issues in Kilo Cloud

Files Reviewed (2 files)
  • apps/web/src/routers/usage-analytics-router.test.ts - 0 issues
  • apps/web/src/routers/usage-analytics-router.ts - 1 issue

Previous review (commit ce4959d)

Status: 1 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
apps/web/src/routers/usage-analytics-router.ts 1137 Dropping the SQL LIMIT for userDisplay=email makes user breakdowns fetch every grouped user row before client-side aggregation

Fix these issues in Kilo Cloud

Files Reviewed (3 files)
  • apps/web/src/app/api/v1/organizations/[id]/members/route.test.ts - 0 issues
  • apps/web/src/routers/usage-analytics-router.test.ts - 0 issues
  • apps/web/src/routers/usage-analytics-router.ts - 1 issue

Previous review (commit 62c590f)

Status: 1 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
apps/web/src/routers/usage-analytics-router.ts 1146 userDisplay=email rewrites grouped breakdown keys after aggregation, which can emit duplicate per-user rows for legacy or unmapped IDs

Fix these issues in Kilo Cloud

Files Reviewed (2 files)
  • apps/web/src/routers/usage-analytics-router.test.ts - 0 issues
  • apps/web/src/routers/usage-analytics-router.ts - 1 issue

Previous review (commit d4343ec)

Status: 1 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
apps/web/src/routers/usage-analytics-router.ts 1234 userDisplay=email can still emit raw user IDs when a row has no email mapping

Fix these issues in Kilo Cloud

Files Reviewed (6 files)
  • apps/web/src/app/api/v1/organizations/[id]/members/route.test.ts - 0 issues
  • apps/web/src/app/api/v1/organizations/[id]/members/route.ts - 0 issues
  • apps/web/src/lib/organizations/organization-types.ts - 0 issues
  • apps/web/src/routers/usage-analytics-router.test.ts - 0 issues
  • apps/web/src/routers/usage-analytics-router.ts - 1 issue
  • apps/web/src/routers/usage-analytics-schemas.ts - 0 issues

Reviewed by gpt-5.4-20260305 · Input: 69.1K · Output: 7.2K · Cached: 321.6K

Review guidance: REVIEW.md from base branch main

Comment thread apps/web/src/routers/usage-analytics-router.ts Outdated
Comment thread apps/web/src/routers/usage-analytics-router.ts Outdated
Comment thread apps/web/src/routers/usage-analytics-router.ts Outdated
Comment thread apps/web/src/routers/usage-analytics-router.ts
@evanjacobson evanjacobson changed the title feat(api): add public usage filters feat(api): add usage email filters and members endpoint Jun 23, 2026
timeseries: rows.map(row => {
const rawLabel = toStringValue(row[2]);
const label = input.splitBy
? dimensionDisplayValue(input.splitBy, filters, userEmailsById, rawLabel)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moderate, Functional: The email mapping correctly avoids exposing raw identifiers, but it happens after Snowflake has grouped the timeseries by raw kilo_user_id. If one person has usage under both their canonical ID and a legacy oauth/... ID, the response contains two points with the same timestamp and email instead of one combined point. Clients may render duplicate points or overwrite one of them, which makes the visible total incomplete. Could we join the scoped email map before aggregation and group by the mapped email and time bucket, similar to the breakdown query? A test with canonical and OAuth rows for the same email and timestamp would cover this case.

const raw = row[dimIndexMap[d]];
dimensions[d] = typeof raw === 'string' ? raw : '';
const value = toStringValue(raw);
dimensions[d] = dimensionDisplayValue(d, filters, userEmailsById, value);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moderate, Functional: Mapping the returned user dimension to email avoids exposing raw identifiers, but the table query still groups and applies LIMIT using raw kilo_user_id values. A person with usage under both a canonical ID and a legacy oauth/... ID can therefore produce duplicate rows with the same visible dimensions. Those duplicate identity rows can also consume the limit and exclude rows for other users. Could we join the scoped email map in this query, group by the mapped email with the other requested dimensions, and apply the limit after that aggregation? Tests covering identity aggregation and a constrained limit would help preserve the expected table semantics.

},
},
},
'500': {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Low, Documentation: The shared REST operation documents the common error responses, but this members endpoint can also return HTTP 404. organizations.withMembers raises NOT_FOUND when the organization does not exist, and handleTRPCRequest preserves that status. Without a documented 404, generated clients and API consumers may treat a valid endpoint response as unexpected. Could the REST route metadata support route specific responses and add a 404 using RestErrorResponseSchema for this endpoint? An OpenAPI assertion for the members operation would keep the contract in sync.

@RSO

RSO commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Why did you not add this as a tRPC endpoint, like the rest? I'm not sure it makes sense to have some endpoints in the API docs as tRPC and then some others as REST.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants