API reference

Health, identity and locations

Liveness, the caller's identity and the active locations.

Start here to find out who a token belongs to (GET /v1/me), which organizations and roles it has, and which locations resources can be created in.

GET /health

Liveness check. No authentication.

Auth: none

Response 200

{
  ok: true
}

GET /health/deep

Liveness check including a read of the serving region's database replica; returns the region. No authentication.

Auth: none

Response 200

{
  ok: true
  region: string
  dbMs: number
}

GET /v1/hooks/people/:userId/avatar

A person's avatar for <img src> anywhere: a redirect to the image (good for an hour), cached for ten minutes; 404 when they have none. The avatarUrl in profiles carries a version (?v=), so a new image is a new URL. No authentication.

Auth: none

Path parameterDescription
:userIdA member's user id (usr_…).

GET /v1/hooks/people/:userId/banner

A person's banner image for <img src> anywhere, as the avatar's hook: a redirect to the image, cached for ten minutes; 404 when they have none. bannerUrl carries a version. No authentication.

Auth: none

Path parameterDescription
:userIdA member's user id (usr_…).

GET /v1/me

The caller: the signed-in user (with organizations and roles), or the key (API or platform agent key) with its organization and scopes.

Auth: user access token or platform agent key

Response 200

{
  type: "agent"
  id: string
  orgId: string
  scopes: ("platform:admin" | "platform:infra" | "platform:analytics" | "platform:coverage" | "org:read" | "org:write" | "audit:read" | "tenant:read" | "tenant:write" | "members:read" | "members:write" | "projects:read" | "projects:write" | "deployments:read" | "deployments:write" | "env:read" | "env:write" | "keys:read" | "keys:use" | "keys:write" | "domains:read" | "domains:write" | "resources:read" | "resources:write" | "git:read" | "git:write" | "git:admin" | "logs:read" | "analytics:read" | "knowledge:read" | "knowledge:write" | "connectors:read" | "connectors:write" | "chat:use" | "mcp:connect" | "agents:read" | "agents:write" | "agents:run" | "mail:read" | "mail:send" | "mail:admin" | "calendar:read" | "calendar:write" | "contacts:read" | "contacts:write" | "issues:read" | "issues:write" | "issues:admin" | "maps:read" | "maps:write" | "drive:read" | "drive:write" | "crm:read" | "crm:write" | "crm:admin" | "marketing:read" | "marketing:write" | "marketing:send" | "marketing:admin" | "health:read" | "health:write" | "weather:read" | "weather:write" | "food:read" | "food:write")[]
  name?: string
  userId?: string
  creatorRole?: "owner" | "admin" | "developer" | "viewer"
  personal?: {
    kind: "photos"
    scopes: string[]
  }
  bot?: {
    appId: string
  }
} | {
  userId: string
  email: string
  name?: string
  avatarUrl?: string
  locale?: string
  orgs: {
    id: string
    slug: string
    name?: string
    kind: "platform" | "customer"
    role: "owner" | "admin" | "developer" | "viewer"
    teams?: string[]
    scopes?: string[]
    pending?: {
      requirement: "mfa" | "passkey" | "method"
      until: number
    }
  }[]
  held: {
    id: string
    slug: string
    name?: string
    reason: "blocked" | "guest_expired" | "mfa_required" | "passkey_required" | "method_not_allowed" | "reauthenticate" | "session_too_old"
  }[]
  isPlatformAdmin: boolean
  type: string
}

GET /v1/locales

The languages people can choose (locales: locale, label, spelling, weekStart), in order: English (Australia), the default, and English (US). Choose one with PATCH /v1/me/profile (locale).

Auth: user access token or platform agent key

Response 200

{
  locales: {
    locale: string
    label: string
    spelling: "au" | "us"
    weekStart: number
  }[]
}

GET /v1/me/org-access

Your organizations whose Cactive One access has ended (never subscribed, cancelled or unpaid), with the reason for each ({ ended: { [orgId]: reason } }). Their apps answer 402 with code: "access_ended" until an administrator fixes billing.

Auth: user access token or platform agent key

Response 200

{
  ended: {
    [key: string]: "canceled" | "unpaid" | "setup"
  }
}

GET /v1/locations

Active locations, for location pickers. Any authenticated caller.

Auth: user access token or platform agent key

Response 200

{
  locations: {
    locationId: string
    label: string
    isDefault: boolean
    regions: string[]
    region?: string
  }[]
}

GET /v1/me/preferences

Your preferences, the same in every app and on every device: apps, the app switcher's order (order, tile keys such as cloud or mail:contacts) and the apps you hid (hidden), or null for the default switcher. People only.

Auth: user access token or platform agent key

Response 200

{
  apps: null | {
    order: string[]
    hidden: string[]
  }
}

Errors

StatusMessage
403Preferences belong to people.
403Change preferences in the app.

PUT /v1/me/preferences

Saves your app switcher (apps: { order, hidden }, up to 100 keys each; apps added later appear at the end). apps: null restores the default. People only.

Auth: user access token or platform agent key

Request body

FieldTypeRequiredNotes
appsobjectYescan be null
apps.orderstring[]Yesup to 100 items; each matches ^[a-z0-9][a-z0-9:_-]{0,39}$
apps.hiddenstring[]Yesup to 100 items; each matches ^[a-z0-9][a-z0-9:_-]{0,39}$

Response 200

{
  apps: null | {
    order: string[]
    hidden: string[]
  }
}

Errors

StatusMessage
403Preferences belong to people.
403Change preferences in the app.

GET /v1/me/preferences/widgets

The phone's widgets: what the Dynamic Island and the Lock Screen show, ranked per surface; null until the person arranges them.

Auth: user access token or platform agent key

Response 200

{
  widgets: null | {
    v: 1
    items: {
      id: string
      kind: "counter"
      title?: string
      config: {
        label: string
        step: number
        value: number
        goal: number
      }
    } | {
      id: string
      kind: "device"
      title?: string
      config: {
        show: "thermal" | "battery" | "memory" | "cpu"
      }
    } | {
      id: string
      kind: "endpoint"
      title?: string
      config: {
        label: string
        url: string
        path: string
        unit: string
        decimals: null | number
        refreshMinutes: number
      }
    } | {
      id: string
      kind: "photo"
      title?: string
      config: {
        photos: string[]
        rotate: "never" | "hourly" | "daily"
      }
    } | {
      id: string
      kind: "weather"
      title?: string
      config: {
        place: "saved" | "current"
        placeId: string
        name: string
        lat: null | number
        lon: null | number
        units: "auto" | "metric" | "imperial"
      }
    } | {
      id: string
      kind: "activity"
      title?: string
      config: {
        metric: "steps" | "distance"
        unit: "km" | "mi"
        goal: number
      }
    } | {
      id: string
      kind: "quote"
      title?: string
      config: {}
    } | {
      id: string
      kind: "reminders"
      title?: string
      config: {
        list: string
        overdue: boolean
      }
    } | {
      id: string
      kind: "weekday"
      title?: string
      config: {
        style: "short" | "long"
        showDate: boolean
      }
    } | {
      id: string
      kind: "flight"
      title?: string
      config: {
        radiusKm: number
      }
    } | {
      id: string
      kind: "dinner"
      title?: string
      config: {
        picture: boolean
      }
    } | {
      id: string
      kind: "dayProgress"
      title?: string
      config: {
        period: "month" | "day" | "year" | "week"
        start: string
        end: string
        weekStartsOn: "monday" | "sunday"
        show: "remaining" | "percent"
        photo: string
      }
    } | {
      id: string
      kind: "countdown"
      title?: string
      config: {
        label: string
        target: "date" | "endOfDay" | "endOfWeek" | "endOfMonth" | "endOfYear"
        at: null | number
        repeat: "none" | "yearly"
        weekStartsOn: "monday" | "sunday"
        show: "auto" | "days"
        photo: string
      }
    } | {
      id: string
      kind: "pomodoro"
      title?: string
      config: {
        focusMinutes: number
        shortBreakMinutes: number
        longBreakMinutes: number
        sprintsPerLongBreak: number
        autoStart: boolean
        dailyGoal: number
      }
    } | {
      id: string
      kind: "stocks"
      title?: string
      config: {
        symbols: string[]
        mode: "fixed" | "carousel"
        change: "amount" | "percent"
      }
    } | {
      id: string
      kind: "agenda"
      title?: string
      config: {
        events: boolean
        tasks: boolean
        phoneCalendars: boolean
        days: number
      }
    } | {
      id: string
      kind: "network"
      title?: string
      config: {
        speedTest: boolean
        sizeKb: number
      }
    } | {
      id: string
      kind: "stopwatch"
      title?: string
      config: {
        label: string
      }
    } | {
      id: string
      kind: "shopping"
      title?: string
      config: {
        count: number
      }
    } | {
      id: string
      kind: "progressBars"
      title?: string
      config: {
        rows: {
          label: string
          source: string
          color: "success" | "auto" | "warning" | "brand" | "destructive" | "chart1" | "chart2" | "chart3" | "chart4" | "chart5"
          max: number
        }[]
        weekStartsOn: "monday" | "sunday"
      }
    }[]
    island: {
      enabled: boolean
      order: string[]
    }
    lockScreen: {
      enabled: boolean
      order: string[]
      maxItems: number
    }
  }
}

Errors

StatusMessage
403Preferences belong to people.
403Change preferences in the app.

PUT /v1/me/preferences/widgets

Saves the phone's widgets (at most 16 KB as JSON); every phone signed in to the account follows.

Auth: user access token or platform agent key

Request body (up to 20 KB)

FieldTypeRequiredNotes
widgetsobjectYescan be null
widgets.v1Yes
widgets.itemsobject[]Yesup to 32 items
widgets.items[].idstringYesmatches ^[a-z0-9][a-z0-9_-]{0,31}$
widgets.items[].kindstringYesup to 32 characters
widgets.items[].titlestringNoup to 200 characters
widgets.items[].configobjectYesvalues: any JSON
widgets.islandobjectYes
widgets.island.enabledbooleanYes
widgets.island.orderstring[]Yesup to 32 items; each matches ^[a-z0-9][a-z0-9_-]{0,31}$
widgets.lockScreenobjectYes
widgets.lockScreen.enabledbooleanYes
widgets.lockScreen.orderstring[]Yesup to 32 items; each matches ^[a-z0-9][a-z0-9_-]{0,31}$
widgets.lockScreen.maxItemsintegerYes1–4

Response 200

{
  widgets: null | {
    v: 1
    items: {
      id: string
      kind: "counter"
      title?: string
      config: {
        label: string
        step: number
        value: number
        goal: number
      }
    } | {
      id: string
      kind: "device"
      title?: string
      config: {
        show: "thermal" | "battery" | "memory" | "cpu"
      }
    } | {
      id: string
      kind: "endpoint"
      title?: string
      config: {
        label: string
        url: string
        path: string
        unit: string
        decimals: null | number
        refreshMinutes: number
      }
    } | {
      id: string
      kind: "photo"
      title?: string
      config: {
        photos: string[]
        rotate: "never" | "hourly" | "daily"
      }
    } | {
      id: string
      kind: "weather"
      title?: string
      config: {
        place: "saved" | "current"
        placeId: string
        name: string
        lat: null | number
        lon: null | number
        units: "auto" | "metric" | "imperial"
      }
    } | {
      id: string
      kind: "activity"
      title?: string
      config: {
        metric: "steps" | "distance"
        unit: "km" | "mi"
        goal: number
      }
    } | {
      id: string
      kind: "quote"
      title?: string
      config: {}
    } | {
      id: string
      kind: "reminders"
      title?: string
      config: {
        list: string
        overdue: boolean
      }
    } | {
      id: string
      kind: "weekday"
      title?: string
      config: {
        style: "short" | "long"
        showDate: boolean
      }
    } | {
      id: string
      kind: "flight"
      title?: string
      config: {
        radiusKm: number
      }
    } | {
      id: string
      kind: "dinner"
      title?: string
      config: {
        picture: boolean
      }
    } | {
      id: string
      kind: "dayProgress"
      title?: string
      config: {
        period: "month" | "day" | "year" | "week"
        start: string
        end: string
        weekStartsOn: "monday" | "sunday"
        show: "remaining" | "percent"
        photo: string
      }
    } | {
      id: string
      kind: "countdown"
      title?: string
      config: {
        label: string
        target: "date" | "endOfDay" | "endOfWeek" | "endOfMonth" | "endOfYear"
        at: null | number
        repeat: "none" | "yearly"
        weekStartsOn: "monday" | "sunday"
        show: "auto" | "days"
        photo: string
      }
    } | {
      id: string
      kind: "pomodoro"
      title?: string
      config: {
        focusMinutes: number
        shortBreakMinutes: number
        longBreakMinutes: number
        sprintsPerLongBreak: number
        autoStart: boolean
        dailyGoal: number
      }
    } | {
      id: string
      kind: "stocks"
      title?: string
      config: {
        symbols: string[]
        mode: "fixed" | "carousel"
        change: "amount" | "percent"
      }
    } | {
      id: string
      kind: "agenda"
      title?: string
      config: {
        events: boolean
        tasks: boolean
        phoneCalendars: boolean
        days: number
      }
    } | {
      id: string
      kind: "network"
      title?: string
      config: {
        speedTest: boolean
        sizeKb: number
      }
    } | {
      id: string
      kind: "stopwatch"
      title?: string
      config: {
        label: string
      }
    } | {
      id: string
      kind: "shopping"
      title?: string
      config: {
        count: number
      }
    } | {
      id: string
      kind: "progressBars"
      title?: string
      config: {
        rows: {
          label: string
          source: string
          color: "success" | "auto" | "warning" | "brand" | "destructive" | "chart1" | "chart2" | "chart3" | "chart4" | "chart5"
          max: number
        }[]
        weekStartsOn: "monday" | "sunday"
      }
    }[]
    island: {
      enabled: boolean
      order: string[]
    }
    lockScreen: {
      enabled: boolean
      order: string[]
      maxItems: number
    }
  }
}

Errors

StatusMessage
403Preferences belong to people.
403Change preferences in the app.

GET /v1/me/preferences/calendar

Calendar's settings (week start, working hours, other time zones, new-event defaults, overlays, hidden calendars); null until changed.

Auth: user access token or platform agent key

Response 200

{
  calendar: null | {
    weekStart: null | number
    workStart: number
    workEnd: number
    workDays: number[]
    zones: string[]
    defaultReminder: null | number
    defaultLength: number
    showWeekends: boolean
    showDeclined: boolean
    showTasks: boolean
    showBirthdays: boolean
    hidden: string[]
    defaultView: "month" | "day" | "year" | "week" | "agenda"
  }
}

Errors

StatusMessage
403Preferences belong to people.
403Change preferences in the app.

PUT /v1/me/preferences/calendar

Saves Calendar's settings; the answer is what was saved (cleaned). Every device and the phone follow.

Auth: user access token or platform agent key

Request body (up to 16 KB)

FieldTypeRequiredNotes
calendarobjectYesvalues: any JSON; can be null

Response 200

{
  calendar: null | {
    weekStart: null | number
    workStart: number
    workEnd: number
    workDays: number[]
    zones: string[]
    defaultReminder: null | number
    defaultLength: number
    showWeekends: boolean
    showDeclined: boolean
    showTasks: boolean
    showBirthdays: boolean
    hidden: string[]
    defaultView: "month" | "day" | "year" | "week" | "agenda"
  }
}

Errors

StatusMessage
403Preferences belong to people.
403Change preferences in the app.
413Too many settings to save.

GET /v1/me/profile

You: your profile (name, email, avatarUrl, bannerUrl, banner and accent colours, about (your About Me), effect, username, kind (personal, or org for an account made through an organization's invite) and usernameChangeAt (when it can change next, or null), locale (the language you chose, or null)), the language to use (locale: your choice, else en-US for a US Accept-Language, else en-AU), the languages you can choose (locales) and each organization with your scopes there, so native apps offer only the organizations an app can open. People only.

Auth: user access token or platform agent key

Response 200

{
  user: {
    userId: string
    name: string
    avatarUrl: null | string
    bannerUrl: null | string
    banner: null | string
    accent: null | string
    about: null | string
    effect: null | {
      id: string
      renderer: "sparkles" | "snow" | "hearts" | "glow" | "aurora" | "confetti"
      colors: string[]
    }
    username: null | string
    kind: "org" | "personal"
    badges: {
      kind: "early-supporter"
      since: null | number
    } | {
      kind: "subscriber"
      since: number
      tier: {
        form: "stone" | "cut" | "brilliant" | "radiant" | "spark"
        name: string
        months: number
        art: "quartz" | "bronze" | "silver" | "gold" | "platinum" | "diamond" | "emerald" | "ruby" | "opal" | "early"
        id: string
      }
    }[]
    email: string
    usernameChangeAt: null | number
    showBadges: boolean
    ownBadges: {
      kind: "early-supporter"
      since: null | number
    } | {
      kind: "subscriber"
      since: number
      tier: {
        form: "stone" | "cut" | "brilliant" | "radiant" | "spark"
        name: string
        months: number
        art: "quartz" | "bronze" | "silver" | "gold" | "platinum" | "diamond" | "emerald" | "ruby" | "opal" | "early"
        id: string
      }
    }[]
    locale: null | string
  }
  locale: string
  locales: {
    locale: string
    label: string
    spelling: "au" | "us"
    weekStart: number
  }[]
  orgs: {
    id: string
    slug: string
    name?: string
    kind: "platform" | "customer"
    role: "owner" | "admin" | "developer" | "viewer"
    scopes: ("platform:admin" | "platform:infra" | "platform:analytics" | "platform:coverage" | "org:read" | "org:write" | "audit:read" | "tenant:read" | "tenant:write" | "members:read" | "members:write" | "projects:read" | "projects:write" | "deployments:read" | "deployments:write" | "env:read" | "env:write" | "keys:read" | "keys:use" | "keys:write" | "domains:read" | "domains:write" | "resources:read" | "resources:write" | "git:read" | "git:write" | "git:admin" | "logs:read" | "analytics:read" | "knowledge:read" | "knowledge:write" | "connectors:read" | "connectors:write" | "chat:use" | "mcp:connect" | "agents:read" | "agents:write" | "agents:run" | "mail:read" | "mail:send" | "mail:admin" | "calendar:read" | "calendar:write" | "contacts:read" | "contacts:write" | "issues:read" | "issues:write" | "issues:admin" | "maps:read" | "maps:write" | "drive:read" | "drive:write" | "crm:read" | "crm:write" | "crm:admin" | "marketing:read" | "marketing:write" | "marketing:send" | "marketing:admin" | "health:read" | "health:write" | "weather:read" | "weather:write" | "food:read" | "food:write")[]
  }[]
}

Errors

StatusMessage
403A personal session is required.

PATCH /v1/me/profile

Changes your profile, the one people see across the platform (Mirage's cards, members and messages; every app's account menu): name, about (About Me, Markdown as in Mirage), banner and accent (hex colours such as #5865f2) effect (a profile effect's id from GET /v1/me/profile/effects) and locale (your language for dates, numbers and spelling in every app, email and text: one of GET /v1/locales, such as en-AU or en-US). null clears a field (not the name). Returns user and the fields that changed; name changes are recorded in your organizations' audit logs. People only, not assistants.

Auth: user access token or platform agent key

Request body

FieldTypeRequiredNotes
namestringNoup to 100 characters
aboutstringNoup to 190 characters; can be null
bannerstringNomatches ^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$; can be null
accentstringNomatches ^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$; can be null
effectstringNoup to 40 characters; can be null
localestringNoup to 20 characters; can be null
showBadgesbooleanNo

Also checked: Unknown fields are rejected.

Response 200

{
  user: {
    userId: string
    name: string
    avatarUrl: null | string
    bannerUrl: null | string
    banner: null | string
    accent: null | string
    about: null | string
    effect: null | {
      id: string
      renderer: "sparkles" | "snow" | "hearts" | "glow" | "aurora" | "confetti"
      colors: string[]
    }
    username: null | string
    kind: "org" | "personal"
    badges: {
      kind: "early-supporter"
      since: null | number
    } | {
      kind: "subscriber"
      since: number
      tier: {
        form: "stone" | "cut" | "brilliant" | "radiant" | "spark"
        name: string
        months: number
        art: "quartz" | "bronze" | "silver" | "gold" | "platinum" | "diamond" | "emerald" | "ruby" | "opal" | "early"
        id: string
      }
    }[]
    email: string
    createdAt: number
    usernameChangeAt: null | number
    showBadges: boolean
    ownBadges: {
      kind: "early-supporter"
      since: null | number
    } | {
      kind: "subscriber"
      since: number
      tier: {
        form: "stone" | "cut" | "brilliant" | "radiant" | "spark"
        name: string
        months: number
        art: "quartz" | "bronze" | "silver" | "gold" | "platinum" | "diamond" | "emerald" | "ruby" | "opal" | "early"
        id: string
      }
    }[]
    locale: null | string
  }
  changed: ("name" | "avatar" | "bannerImage" | "banner" | "accent" | "about" | "effect" | "username" | "locale" | "showBadges")[]
}

Errors

StatusMessage
403A personal session is required.
403Change your profile in the app.

POST /v1/me/profile/avatar/uploads

Where to upload a new avatar: give its type (PNG, JPEG, WebP or GIF) and size in bytes (up to 2 MB). PUT the image to url with headers within 15 minutes (exactly that type and size), then make it your avatar with PUT /v1/me/profile/avatar. People only.

Auth: user access token or platform agent key

Request body

FieldTypeRequiredNotes
type"image/png" | "image/jpeg" | "image/webp" | "image/gif"Yes
sizeintegerYes1–2097152

Also checked: Unknown fields are rejected.

Response 201

{
  uploadId: string
  url: string
  headers: {
    "content-type": "image/gif" | "image/jpeg" | "image/png" | "image/webp"
  }
  expiresAt: number
}

Errors

StatusMessage
403A personal session is required.
403Change your profile in the app.

PUT /v1/me/profile/avatar

Makes an uploaded image (uploadId) your avatar once it's checked: there, up to 2 MB and really a PNG, JPEG, WebP or GIF, whatever it was uploaded as. Anything else is deleted (400 or 413). The previous image is deleted. Returns user with the new avatarUrl. People only.

Auth: user access token or platform agent key

Request body

FieldTypeRequiredNotes
uploadIdstringYesmatches ^avt_[0-9a-z]{26}$

Also checked: Unknown fields are rejected.

Response 200

{
  user: {
    userId: string
    name: string
    avatarUrl: null | string
    bannerUrl: null | string
    banner: null | string
    accent: null | string
    about: null | string
    effect: null | {
      id: string
      renderer: "sparkles" | "snow" | "hearts" | "glow" | "aurora" | "confetti"
      colors: string[]
    }
    username: null | string
    kind: "org" | "personal"
    badges: {
      kind: "early-supporter"
      since: null | number
    } | {
      kind: "subscriber"
      since: number
      tier: {
        form: "stone" | "cut" | "brilliant" | "radiant" | "spark"
        name: string
        months: number
        art: "quartz" | "bronze" | "silver" | "gold" | "platinum" | "diamond" | "emerald" | "ruby" | "opal" | "early"
        id: string
      }
    }[]
    email: string
    createdAt: number
    usernameChangeAt: null | number
    showBadges: boolean
    ownBadges: {
      kind: "early-supporter"
      since: null | number
    } | {
      kind: "subscriber"
      since: number
      tier: {
        form: "stone" | "cut" | "brilliant" | "radiant" | "spark"
        name: string
        months: number
        art: "quartz" | "bronze" | "silver" | "gold" | "platinum" | "diamond" | "emerald" | "ruby" | "opal" | "early"
        id: string
      }
    }[]
    locale: null | string
  }
  changed: ("name" | "avatar" | "bannerImage" | "banner" | "accent" | "about" | "effect" | "username" | "locale" | "showBadges")[]
}

Errors

StatusMessage
403A personal session is required.
403Change your profile in the app.

DELETE /v1/me/profile/avatar

Removes your avatar (people see your initials) and deletes the image. People only.

Auth: user access token or platform agent key

Response 200

{
  user: {
    userId: string
    name: string
    avatarUrl: null | string
    bannerUrl: null | string
    banner: null | string
    accent: null | string
    about: null | string
    effect: null | {
      id: string
      renderer: "sparkles" | "snow" | "hearts" | "glow" | "aurora" | "confetti"
      colors: string[]
    }
    username: null | string
    kind: "org" | "personal"
    badges: {
      kind: "early-supporter"
      since: null | number
    } | {
      kind: "subscriber"
      since: number
      tier: {
        form: "stone" | "cut" | "brilliant" | "radiant" | "spark"
        name: string
        months: number
        art: "quartz" | "bronze" | "silver" | "gold" | "platinum" | "diamond" | "emerald" | "ruby" | "opal" | "early"
        id: string
      }
    }[]
    email: string
    createdAt: number
    usernameChangeAt: null | number
    showBadges: boolean
    ownBadges: {
      kind: "early-supporter"
      since: null | number
    } | {
      kind: "subscriber"
      since: number
      tier: {
        form: "stone" | "cut" | "brilliant" | "radiant" | "spark"
        name: string
        months: number
        art: "quartz" | "bronze" | "silver" | "gold" | "platinum" | "diamond" | "emerald" | "ruby" | "opal" | "early"
        id: string
      }
    }[]
    locale: null | string
  }
  changed: ("name" | "avatar" | "bannerImage" | "banner" | "accent" | "about" | "effect" | "username" | "locale" | "showBadges")[]
}

Errors

StatusMessage
403A personal session is required.
403Change your profile in the app.

POST /v1/me/profile/banner/uploads

Where to upload a new banner image (cropped to 5:2, 1500 × 600, first): give its type (PNG, JPEG, WebP or GIF) and size in bytes (up to 4 MB). PUT the image to url with headers within 15 minutes, then make it your banner with PUT /v1/me/profile/banner. People only.

Auth: user access token or platform agent key

Request body

FieldTypeRequiredNotes
type"image/png" | "image/jpeg" | "image/webp" | "image/gif"Yes
sizeintegerYes1–4194304

Also checked: Unknown fields are rejected.

Response 201

{
  uploadId: string
  url: string
  headers: {
    "content-type": "image/gif" | "image/jpeg" | "image/png" | "image/webp"
  }
  expiresAt: number
}

Errors

StatusMessage
403A personal session is required.
403Change your profile in the app.

PUT /v1/me/profile/banner

Makes an uploaded image (uploadId) your banner once it's checked: there, up to 4 MB and really a PNG, JPEG, WebP or GIF. Anything else is deleted (400 or 413). The previous image is deleted. Returns user with the new bannerUrl. People only.

Auth: user access token or platform agent key

Request body

FieldTypeRequiredNotes
uploadIdstringYesmatches ^avt_[0-9a-z]{26}$

Also checked: Unknown fields are rejected.

Response 200

{
  user: {
    userId: string
    name: string
    avatarUrl: null | string
    bannerUrl: null | string
    banner: null | string
    accent: null | string
    about: null | string
    effect: null | {
      id: string
      renderer: "sparkles" | "snow" | "hearts" | "glow" | "aurora" | "confetti"
      colors: string[]
    }
    username: null | string
    kind: "org" | "personal"
    badges: {
      kind: "early-supporter"
      since: null | number
    } | {
      kind: "subscriber"
      since: number
      tier: {
        form: "stone" | "cut" | "brilliant" | "radiant" | "spark"
        name: string
        months: number
        art: "quartz" | "bronze" | "silver" | "gold" | "platinum" | "diamond" | "emerald" | "ruby" | "opal" | "early"
        id: string
      }
    }[]
    email: string
    createdAt: number
    usernameChangeAt: null | number
    showBadges: boolean
    ownBadges: {
      kind: "early-supporter"
      since: null | number
    } | {
      kind: "subscriber"
      since: number
      tier: {
        form: "stone" | "cut" | "brilliant" | "radiant" | "spark"
        name: string
        months: number
        art: "quartz" | "bronze" | "silver" | "gold" | "platinum" | "diamond" | "emerald" | "ruby" | "opal" | "early"
        id: string
      }
    }[]
    locale: null | string
  }
  changed: ("name" | "avatar" | "bannerImage" | "banner" | "accent" | "about" | "effect" | "username" | "locale" | "showBadges")[]
}

Errors

StatusMessage
403A personal session is required.
403Change your profile in the app.

DELETE /v1/me/profile/banner

Removes your banner image (your banner colour shows) and deletes the image. People only.

Auth: user access token or platform agent key

Response 200

{
  user: {
    userId: string
    name: string
    avatarUrl: null | string
    bannerUrl: null | string
    banner: null | string
    accent: null | string
    about: null | string
    effect: null | {
      id: string
      renderer: "sparkles" | "snow" | "hearts" | "glow" | "aurora" | "confetti"
      colors: string[]
    }
    username: null | string
    kind: "org" | "personal"
    badges: {
      kind: "early-supporter"
      since: null | number
    } | {
      kind: "subscriber"
      since: number
      tier: {
        form: "stone" | "cut" | "brilliant" | "radiant" | "spark"
        name: string
        months: number
        art: "quartz" | "bronze" | "silver" | "gold" | "platinum" | "diamond" | "emerald" | "ruby" | "opal" | "early"
        id: string
      }
    }[]
    email: string
    createdAt: number
    usernameChangeAt: null | number
    showBadges: boolean
    ownBadges: {
      kind: "early-supporter"
      since: null | number
    } | {
      kind: "subscriber"
      since: number
      tier: {
        form: "stone" | "cut" | "brilliant" | "radiant" | "spark"
        name: string
        months: number
        art: "quartz" | "bronze" | "silver" | "gold" | "platinum" | "diamond" | "emerald" | "ruby" | "opal" | "early"
        id: string
      }
    }[]
    locale: null | string
  }
  changed: ("name" | "avatar" | "bannerImage" | "banner" | "accent" | "about" | "effect" | "username" | "locale" | "showBadges")[]
}

Errors

StatusMessage
403A personal session is required.
403Change your profile in the app.

GET /v1/me/profile/effects

The profile effects you can choose (effects: effectId, name, renderer, colors), in order. People only.

Auth: user access token or platform agent key

Response 200

{
  effects: {
    effectId: string
    name: string
    renderer: "sparkles" | "snow" | "hearts" | "glow" | "aurora" | "confetti"
    colors: string[]
  }[]
}

Errors

StatusMessage
403A personal session is required.
403Change your profile in the app.

GET /v1/me/profile/username/check

Whether a username (name) could be yours: available, or the reason it can't (taken, reserved, or not 2–32 lowercase letters, numbers, underscores and single full stops). People only.

Auth: user access token or platform agent key

Query parameterTypeRequiredNotes
namestringYesup to 64 characters

Response 200

{
  username: string
  available: boolean
  reason: null | string
}

Errors

StatusMessage
403A personal session is required.
403Change your profile in the app.

PUT /v1/me/profile/username

Claims or changes your username (username, unique regardless of case). After a change the next waits 30 days (429); your previous name is held for you for 14 days. Accounts made through an organization's invite have none (403); a taken name is 409. Recorded in your organizations' audit logs. People only, not assistants.

Auth: user access token or platform agent key

Request body

FieldTypeRequiredNotes
usernamestringYesup to 64 characters

Also checked: Unknown fields are rejected.

Response 200

{
  user: {
    userId: string
    name: string
    avatarUrl: null | string
    bannerUrl: null | string
    banner: null | string
    accent: null | string
    about: null | string
    effect: null | {
      id: string
      renderer: "sparkles" | "snow" | "hearts" | "glow" | "aurora" | "confetti"
      colors: string[]
    }
    username: null | string
    kind: "org" | "personal"
    badges: {
      kind: "early-supporter"
      since: null | number
    } | {
      kind: "subscriber"
      since: number
      tier: {
        form: "stone" | "cut" | "brilliant" | "radiant" | "spark"
        name: string
        months: number
        art: "quartz" | "bronze" | "silver" | "gold" | "platinum" | "diamond" | "emerald" | "ruby" | "opal" | "early"
        id: string
      }
    }[]
    email: string
    createdAt: number
    usernameChangeAt: null | number
    showBadges: boolean
    ownBadges: {
      kind: "early-supporter"
      since: null | number
    } | {
      kind: "subscriber"
      since: number
      tier: {
        form: "stone" | "cut" | "brilliant" | "radiant" | "spark"
        name: string
        months: number
        art: "quartz" | "bronze" | "silver" | "gold" | "platinum" | "diamond" | "emerald" | "ruby" | "opal" | "early"
        id: string
      }
    }[]
    locale: null | string
  }
  changed: ("name" | "avatar" | "bannerImage" | "banner" | "accent" | "about" | "effect" | "username" | "locale" | "showBadges")[]
}

Errors

StatusMessage
403A personal session is required.
403Change your profile in the app.