Identity (/me) API
Endpoints under /api/v1/me/* return data scoped to the authenticated user, independent of any workspace. Account-scope API keys are the canonical authentication for these endpoints; workspace-scope keys may also call profile-only routes (GET/PATCH /me) but cannot enumerate cross-org data.
Returns the authenticated user's profile. Accepts both workspace and account-scope keys — no cross-org data is exposed.
Scope
profile:readResponse
{
"id": "us_abc",
"email": "you@example.com",
"emailVerified": true,
"displayName": "Alex Designer",
"username": "alex",
"displayNamePreference": "fullname",
"image": "/api/media/avatars/...",
"createdAt": 1762446710123
}Update profile fields. Strict allowlist: only displayName and displayNamePreference are patchable here. Username changes go through the dedicated change flow; avatar uploads through the dashboard.
Scope
profile:writeParameters
displayNamestring1-100 chars. When set, marks the name as user-customized so OAuth profile syncs no longer overwrite it.displayNamePreferencestring"username" or "fullname"List the workspaces the authenticated user belongs to. **Account-scope keys only.** A workspace-scope key cannot enumerate memberships outside the workspace it's bound to.
Scope
profile:readResponse
{
"organizations": [
{ "id": "org_abc", "slug": "acme", "name": "Acme", "role": "owner", "joinedAt": 1762446710123 },
{ "id": "org_def", "slug": "beta", "name": "Beta", "role": "member", "joinedAt": 1762556810123 }
]
}List the authenticated user's account-scope API keys. Read-only — key creation, rotation, and revocation live in the dashboard.
Scope
profile:readResponse
{
"apiKeys": [
{
"id": "ak_abc",
"name": "Personal CI",
"keyPrefix": "pf_p_a8d9e7f1...",
"permissions": ["profile:read"],
"audienceWorkspaceIds": [],
"audienceSnapshotMode": "explicit",
"createdAt": 1762446710123,
"lastUsedAt": 1762556810123,
"expiresAt": 1770000000000,
"revokedAt": null,
"graceEndsAt": null
}
]
}Cross-org notifications inbox for the authenticated user. **Account-scope keys only.** Per-row `organizationId` lets the client deep-link without a separate API call. Use `/api/v1/notifications` for the workspace-scoped variant.
Scope
notifications:readParameters
limitnumber1–200. Default 50.cursorstringOpaque pagination cursor. Forward-paginate by URL-encoding and passing `nextCursor` from the previous page.sincenumbercreatedAt time floor. Default: 30 days ago.Response
{
"notifications": [
{
"id": "nt_abc",
"type": "mention",
"title": "Alex mentioned you on POST-12",
"body": "Check this out…",
"organizationId": "org_acme",
"resourceType": "post",
"resourceId": "ps_12",
"postId": "ps_12",
"commentId": null,
"actorId": "us_alex",
"aggregateCount": null,
"read": false,
"createdAt": 1762556810123
}
],
"unreadCount": 3,
"hasMore": true,
"nextCursor": "enc:0ZAfYcGmL9xQKZ7p...",
"since": 1759964810123
}Cross-org unread sum for the authenticated user. **Account-scope keys only.**
Scope
notifications:readResponse
{ "unreadCount": 12 }Mark every unread notification as read. **Account-scope keys only.** Optional `?organizationId=X` limits the action to one workspace; if the user is not an active member of `X`, the mutation soft-skips (no writes) and returns `{ scope: 'organization', skipped: 'not_member' }`.
Scope
notifications:writeParameters
organizationIdstringWorkspace id. Membership is verified before any writes.Response
{ "scope": "account" }Set the read state on a single notification owned by the authenticated user. **Account-scope keys only.** Owner-mismatch returns 404 (existence of someone else's notification id is not leaked).
Scope
notifications:writeParameters
readbooleanrequiredtrue to mark read, false to mark unread.Response
{ "ok": true, "notificationId": "nt_abc", "read": true }Returns the user's account-default notification preferences plus a sparse list of per-workspace overrides. **Account-scope keys only.** Per-workspace overrides come from the workspace-scoped path (`/api/v1/notifications/preferences`); they appear here for read-only inspection so a client can render the full picture in one call.
Scope
notifications:readResponse
{
"account": {
"emailNewComment": true,
"emailDigest": "weekly",
"pushNewReaction": true,
"inAppMention": true,
"globalMute": false,
"quietHoursEnabled": false
},
"overrides": [
{
"organizationId": "org_acme",
"globalMute": true,
"createdAt": 1762000000000,
"updatedAt": 1762556810123
}
],
"hasAccountRow": true,
"overridesTruncated": false
}Update the user's account-default notification preferences. **Account-scope keys only.** Strict allowlist: unknown fields return a 400 listing them. Per-workspace overrides are intentionally NOT writable here — they go through the workspace-scoped `/api/v1/notifications/preferences` endpoint so workspace audit logs stay accurate. On first call, the account-default row is created by seeding the system defaults and applying the patch.
Scope
notifications:writeParameters
<any patchable field>boolean | stringEach patchable field is optional. Booleans for email/push/in-app/chat toggles, globalMute, quietHoursEnabled, inAppSoundEnabled, inAppTabIndicatorEnabled. Strings: quietHoursStart, quietHoursEnd, quietHoursTimezone. Enums: emailDigest (daily|weekly|never), chatDigest (none|daily|weekly), chatPlatformPreference (slack|discord|both).Response
{ "ok": true, "created": false, "id": "np_abc" }List the user's registered web-push subscriptions. **Account-scope keys only.** Returns a lean shape: id + userAgent + timestamps + failure counter. The per-device push credentials (`endpoint`, `p256dh`, `auth`) are intentionally omitted from the response — exposing them would let a leaked API key relay notifications to attacker servers.
Scope
profile:readResponse
{
"subscriptions": [
{
"id": "ps_abc",
"userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_5) Chrome/123",
"createdAt": 1762446710123,
"lastUsedAt": 1762556810123,
"consecutiveFailures": 0,
"vapidKeyId": "v2"
}
],
"truncated": false
}Revoke a single push subscription. **Account-scope keys only.** Owner-mismatch returns 404 (existence of someone else's subscription id is not leaked). Push subscription CREATION is intentionally NOT exposed via API key — it's a device-trust event that stays dashboard-session only, so a stolen API key cannot mint new push targets.
Scope
profile:writeResponse
{ "ok": true, "deletedId": "ps_abc" }