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:read

Response

{
  "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:write

Parameters

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:read

Response

{
  "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:read

Response

{
  "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:read

Parameters

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:read

Response

{ "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:write

Parameters

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:write

Parameters

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:read

Response

{
  "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:write

Parameters

<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:read

Response

{
  "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:write

Response

{ "ok": true, "deletedId": "ps_abc" }