Skip to main content

Users

GET /api/users/me

Returns the authenticated user's profile.

Response — 200 OK

{
"id": "449b4cd2-...",
"externalUserId": "firebase|abc123",
"email": "praveen@example.com",
"displayName": "Praveen",
"photoUrl": null,
"createdAt": "2026-01-12T07:30:00Z",
"lastLoginAt": "2026-07-08T05:49:33.547Z",
"privacyPolicyVersion": "2026-08-07",
"privacyPolicyAcceptedAt": "2026-01-12T07:31:00Z",
"termsVersion": "2026-07-01",
"termsAcceptedAt": "2026-01-12T07:31:00Z",
"callRecordingDisclaimerVersion": "2026-07-01",
"callRecordingDisclaimerAcceptedAt": "2026-01-12T07:31:00Z",
"marketingOptIn": false,
"marketingOptInAt": null,
"plan": "FREE",
"plansIntroShownAt": null,
"isAdmin": false,
"accountStatus": "ACTIVE",
"accountStatusReason": null
}

displayName is critical — it's what SpeakerNameResolutionService uses to identify the user inside transcripts.

FieldTypeNotes
plan"FREE" | "PRO"Drives the limits in GET /api/users/me/limits. See Plans & billing.
plansIntroShownAtdatetime, nullableNull until the Android client's one-time post-login Plans screen has been shown.
isAdminbooleanTrue iff this account's email is on the admin allowlist. Not a stored field — computed per-request.
accountStatus"ACTIVE" | "SUSPENDED" | "DISABLED"Admin-controlled — see Admin console § Account status.
accountStatusReasonstring, nullableSet together with accountStatus by an admin. Null when accountStatus is ACTIVE.

UserProfileResponse also reports first-launch consent status — see PATCH /api/users/me/consent below.

PATCH /api/users/me

Update mutable profile fields.

Request

{ "displayName": "Praveen Kumar" }

Response — 200 OK

The updated UserProfileResponse.

PATCH /api/users/me/consent

Records first-launch consent acceptances: privacy policy, terms of service, and the call-recording legal disclaimer, plus the (non-required) marketing-notifications opt-in.

Designed for the Android app's post-login consent flow: shown right after sign-in/registration, before the signed-in home shell, whenever GET /api/users/me shows this account hasn't accepted the current versions yet. Sends all three required versions at once (the user just checked all three boxes to reach this call).

Request

{
"privacyPolicyVersion": "2026-08-07",
"termsVersion": "2026-07-01",
"callRecordingDisclaimerVersion": "2026-07-01",
"marketingOptIn": true
}

All fields optional. Only send a version the user just accepted — the server stamps the acceptance timestamp itself (never trusts a client-supplied one) and leaves any field not included in the request unchanged. marketingOptIn is independent of the other three: it is not a required consent, has no version, and can be toggled from a notification-preferences screen at any time, not just during onboarding.

Response — 200 OK

The updated UserProfileResponse, including:

FieldTypeNotes
privacyPolicyVersion / privacyPolicyAcceptedAtstring / datetimeNull until accepted.
termsVersion / termsAcceptedAtstring / datetimeNull until accepted.
callRecordingDisclaimerVersion / callRecordingDisclaimerAcceptedAtstring / datetimeNull until accepted.
marketingOptInbooleanDefaults false.
marketingOptInAtdatetimeNull until toggled at least once.

DELETE /api/users/me

Hard-delete the authenticated user's account.

Response — 204 No Content

This deletes:

  • Every call record, artifact, action item, and processing event owned by the user.
  • The voice profile.
  • The Firebase user (best-effort, if Admin SDK is configured).

Irreversible. Intended for GDPR-style "delete my data" requests.

GET /api/users/me/limits

Two independent limit systems, both surfaced here for a single client-facing "Limits" screen: the generic anti-abuse guard rails (see UploadAbuseGuardService, apply regardless of plan) and the plan-driven, billing-facing limits (see Plans & billing). Both would produce a rejection on POST /api/calls/analyze if exceeded — a 429 for either limit system, or a 403 if the account is suspended or disabled. Intended for a client-side "your limits" display, not for making upload decisions itself (the server is still authoritative).

Response — 200 OK

{
"maxUploadsPerHour": 20,
"uploadsThisHour": 6,
"maxConcurrentActiveCalls": 5,
"activeCalls": 1,
"maxMonthlyUploadBytes": 10737418240,
"uploadedBytesThisMonth": 1288490188,
"plan": "FREE",
"minutesUsedThisPeriod": 45,
"minutesLimit": 150,
"transcriptsToday": 1,
"dailyTranscriptLimit": 3,
"topupMinutesBalance": 0,
"topupTranscriptsBalance": 0
}
FieldTypeNotes
maxUploadsPerHourintRolling 1-hour cap on new uploads.
uploadsThisHourlongUploads created by this user in the last hour.
maxConcurrentActiveCallsintCap on calls in a non-terminal status at once.
activeCallslongThis user's calls currently QUEUED/PROCESSING/TRANSCRIBING/ANALYZING.
maxMonthlyUploadByteslongRolling 30-day upload volume cap, in bytes.
uploadedBytesThisMonthlongBytes uploaded by this user in the last 30 days.
plan"FREE" | "PRO"
minutesUsedThisPeriodlongComputed live from call_records, rolling 30 days — never a separate counter.
minutesLimitintFrom the plan catalog.
transcriptsTodaylong, nullableNull for PRO — no daily rail.
dailyTranscriptLimitlong, nullableNull for PRO.
topupMinutesBalance / topupTranscriptsBalanceintOne-time purchased/granted credit; see Plans & billing § Top-ups.

While billing_enabled is off, POST /api/calls/analyze never rejects for plan reasons — but this endpoint still reports the real numbers above regardless of the flag. The flag only gates enforcement, not this read.

A 403 from POST /api/calls/analyze with error code ACCOUNT_SUSPENDED or ACCOUNT_DISABLED means the account's admin-set status is blocking the request, not a plan/volume limit. See API · Admin for how that status gets set.

GET /api/users/me/stats

Lightweight aggregates suitable for a dashboard header.

Response — 200 OK

{
"totalCalls": 128,
"callsThisWeek": 14,
"openActionItems": 9,
"overdueActionItems": 2,
"completedActionItems": 87,
"averageCallDurationSeconds": 318
}