Current user¶
The current user is the account that presented the credential, represented as a user object. The routes here read that account, change it, and run its lifecycle operations.
Every route on this page except Get current user requires a user session (Authorization: Bearer <token>). A valid bot token or a valid OAuth2 access token is rejected with 403 ACCESS_DENIED.
| Route | Purpose |
|---|---|
GET /users/@me |
Get current user |
PATCH /users/@me |
Modify current user |
POST /users/@me/avatar/upload-url, POST /users/@me/avatar/confirm |
Change avatar |
POST /users/@me/banner/upload-url, POST /users/@me/banner/confirm |
Change banner |
DELETE /users/@me/authorized-ips |
Forget authorised IPs |
POST /auth/account/disable |
Disable current account |
POST /auth/account/delete |
Schedule current account deletion |
POST /users/@me/terms-acceptance |
Accept current policies |
Sudo verification¶
Security-sensitive changes (the username, lifecycle operations, forgetting authorised IPs, and several MFA routes) require proof of identity in the request body. The same three optional fields are accepted everywhere:
| Field | Type | Description |
|---|---|---|
| password? | string | The current password. Accepted only when the account has no TOTP authenticator and no registered passkey |
| mfa_code? | string | A current TOTP code or an unused backup code. Spaces are ignored |
| sudo_proof? | string | A one-time proof issued after a successful passkey assertion. See MFA |
The first non-empty field in the order sudo_proof, mfa_code, password is checked.
| Account state | Accepted proof |
|---|---|
| No TOTP authenticator and no passkey | password, mfa_code or sudo_proof |
| A TOTP authenticator or at least one passkey | mfa_code or sudo_proof only; password alone is rejected |
A request without an accepted proof fails with 403:
A wrong password or code fails with 401 UNAUTHORIZED. Password attempts are limited to 10 per 5 minutes per account (bucket auth:sudo_pwd) and code attempts to 10 per 15 minutes (bucket auth:sudo_mfa).
Sudo grants
POST /auth/sudo with the same proof fields grants a 5-minute sudo window that is bound to the session that requested it, and returns {"granted_at", "expires_at"}. DELETE /auth/sudo revokes it. Some routes on other pages check this grant instead of body fields, for example deleting your own messages in bulk and application management when no proof fields are sent. GET /auth/sudo/mfa-methods returns {"totp", "webauthn", "has_mfa"} so a client knows which proof to ask for. See MFA.
Get current user¶
GET /users/@me
Returns the current user object. This is the one route on this page that also accepts a bot token (Authorization: Bot <token>); a bot receives its own user object with bot: true.
Also accepts an OAuth2 access token with the identify scope.
Access token response¶
With an OAuth2 access token the response is the same user object, with private fields replaced by neutral values:
| Field | Value |
|---|---|
The address only when the token also holds the email scope; otherwise null |
|
| email_bounced, authenticator_types | Omitted |
| acls, traits, required_actions | [] |
| mfa_enabled, nsfw_allowed, premium_will_cancel, premium_badge_hidden, premium_badge_masked, premium_badge_timestamp_hidden, premium_badge_sequence_hidden, premium_purchase_disabled, premium_enabled_override, premium_perks_disabled, has_dismissed_premium_onboarding, has_ever_purchased, has_unread_gift_inventory | false |
| password_last_changed_at, premium_since, premium_until, premium_billing_cycle, premium_lifetime_sequence, pending_bulk_message_deletion | null |
| unread_gift_inventory_count | 0 |
Every other field is returned as it is for a session.
Response¶
| Status | Body | Condition |
|---|---|---|
| 200 | user object | Account was returned |
| 401 | error | Missing or invalid credential |
| 403 | error | An OAuth2 access token without the identify scope (MISSING_OAUTH_SCOPE) |
Rate limit¶
40 requests per 10 seconds per user, bucket user:settings:get, shared with Get current user settings.
Modify current user¶
PATCH /users/@me
Modifies the current account and returns the resulting user object. Every field is optional; an omitted field is left unchanged. A body that changes nothing is not an error and returns the current user.
The body is processed in three steps, and an error in an earlier step stops the request: account fields are validated first, then a username change runs, then profile fields are written, and finally account fields are stored.
JSON body¶
Username (requires sudo verification)
| Field | Type | Description |
|---|---|---|
| username? | string | New username. Lowercased, 3-32 characters of a-z, 0-9, _ and -. A username held by another account returns 409 CONFLICT |
| password? / mfa_code? / sudo_proof? | string | Sudo verification proof. Only read when username is present |
Profile
| Field | Type | Description |
|---|---|---|
| global_name? | ?string | Display name, 2-32 characters, or null to clear it. name is accepted as an alias and wins when both are sent |
| bio? | ?string | Biography, up to 4000 characters. Null or "" clears it |
| pronouns? | ?string | Pronouns, up to 40 characters. Null or "" clears them |
| accent_color? | ?integer | Accent colour as packed 24-bit RGB, or null to clear it |
| timezone? | string | IANA timezone identifier (up to 128 characters), trimmed. "" clears it. An unknown identifier returns 400 INVALID_FORM_BODY (INVALID_TIMEZONE_IDENTIFIER) |
| timezone_privacy_flags? | integer | Profile field privacy flags for the timezone. Setting a first timezone without this field sets it to EVERYONE |
| custom_status? | string | Custom status text, up to 100 characters. "" clears the text, emoji and expiry together |
| custom_status_emoji_id? | string | Custom status emoji: a custom emoji ID or a Unicode emoji (up to 100 characters) |
| custom_status_expires_at? | ISO8601 timestamp | When the custom status expires |
| image_url? | string | Only "" is meaningful: it removes the avatar. Upload a new avatar with Change avatar |
| banner_color? | string | Profile banner colour as #RRGGBB (exactly 7 characters) |
| banner_type? | string | Banner display type (up to 20 characters), for example color or image |
| banner_image_url? | string | Banner image URL (up to 512 characters); "" clears it. Upload a new banner with Change banner |
| active_frame_url? | string | Avatar frame to display (up to 512 characters); "" removes it |
| active_badge_url? | string | Premium badge to display; "" removes it. The account must hold TEBHOS Pro long enough for the badge's tier, otherwise 403 |
| active_badge_name? | string | Name of the selected premium badge |
| boost_badge_url? | string | Booster badge to display; "" removes it. Requires enough days of continuous guild boosting for the badge's tier, otherwise 403; an unknown badge returns 400 |
| boost_badge_name? | string | Name of the selected booster badge |
Account
| Field | Type | Description |
|---|---|---|
| mention_flags? | integer | Reply mention preference: 0, 1 or 2. Anything else returns 400 |
| premium_badge_hidden? | boolean | Hide the premium badge from the public profile |
| premium_badge_masked? | boolean | Show a lifetime badge as an ordinary subscription badge |
| premium_badge_timestamp_hidden? | boolean | Hide the premium start date |
| premium_badge_sequence_hidden? | boolean | Hide the lifetime sequence number |
| premium_enabled_override? | boolean | Staff only. Other accounts receive 403 MISSING_ACCESS |
| has_dismissed_premium_onboarding? | boolean | Only true has an effect: it records the dismissal time |
| has_unread_gift_inventory? | boolean | Only false has an effect: it marks the gift inventory as read |
Not accepted here
Email and password changes use the code-based flows on Email and password. Avatars and banners use the upload routes below. discriminator and date_of_birth cannot be changed.
Limits¶
| Change | Allowance | Error |
|---|---|---|
| Any request with profile fields | 20 per 60 seconds per user (bucket user:update) |
429 |
| Username | 5 per 3 hours per user (bucket username_change), counted after sudo verification |
429 |
| Biography to a different value | 25 per 30 minutes | 400 INVALID_FORM_BODY, BIO_CHANGED_TOO_MANY_TIMES on bio |
| Pronouns to a different value | 25 per 30 minutes | 400 INVALID_FORM_BODY, PRONOUNS_CHANGED_TOO_MANY_TIMES on pronouns |
| Accent colour to a different value | 25 per 30 minutes | 400 INVALID_FORM_BODY, ACCENT_COLOR_CHANGED_TOO_MANY_TIMES on accent_color |
Saving the value a field already holds does not count against the per-field allowances.
Response¶
| Status | Body | Condition |
|---|---|---|
| 200 | user object | Changes applied |
| 400 | error | A field failed validation |
| 401 | error | Wrong sudo password or code |
| 403 | error | Sudo proof missing (SUDO_MODE_REQUIRED), staff-only field, or badge not unlocked |
| 409 | error CONFLICT |
Username is taken |
| 429 | error | Rate limited |
Side effects¶
The account's own sessions receive USER_UPDATE with the private user object at the top level. Guild members and friends receive the profile change as well, and the Gateway derives GUILD_MEMBER_UPDATE for guilds whose cached member data changed. See Gateway events.
curl -X PATCH https://tebhos.com/api/v1/users/@me \
-H "Authorization: Bearer $TEBHOS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"global_name": "Aria", "pronouns": "she/her", "mention_flags": 2}'
Change avatar¶
Avatars are uploaded directly to storage with a pre-signed URL, then confirmed.
Create avatar upload URL¶
POST /users/@me/avatar/upload-url
| Field | Type | Description |
|---|---|---|
| filename | string | Original file name; must not be blank |
| content_type | string | image/png, image/jpeg, image/jpg, image/gif or image/webp |
| size | integer | File size in bytes, 1 to 8388608 (8 MiB) |
Returns:
| Field | Type | Description |
|---|---|---|
| upload_url | string | Pre-signed URL. Send the file with PUT and the same Content-Type |
| storage_key | string | Key to pass to the confirm route |
| url | string | Temporary public URL of the upload |
| expires_in | integer | Seconds the upload URL stays valid (300) |
Confirm avatar¶
POST /users/@me/avatar/confirm
| Field | Type | Description |
|---|---|---|
| storage_key | string | The storage_key from the previous step. It must belong to your account |
TEBHOS strips image metadata (EXIF, GPS and similar), stores the file under its content hash, and returns {"avatar_hash", "image_url"}. An animated avatar (GIF, APNG or animated WebP, detected from the bytes) requires TEBHOS Pro and otherwise fails with 403. Avatar changes are limited to 25 per 30 minutes (400 INVALID_FORM_BODY, AVATAR_CHANGED_TOO_MANY_TIMES). A successful change emits USER_UPDATE.
To remove the avatar, send {"image_url": ""} to Modify current user.
See Uploads for the general upload flow.
Change banner¶
POST /users/@me/banner/upload-url and POST /users/@me/banner/confirm
Same bodies, limits and flow as Change avatar. The confirm route returns {"banner_hash", "banner_image_url"}. An animated banner requires TEBHOS Pro. Banner changes are limited to 25 per 30 minutes (BANNER_CHANGED_TOO_MANY_TIMES). The banner hash appears in the user object only while the account has premium.
Forget authorised IPs¶
DELETE /users/@me/authorized-ips
Deletes every authorised IP record of the account, so the next sign-in from any address needs fresh IP authorisation. Requires sudo verification; the body carries the proof fields.
Response¶
| Status | Body | Condition |
|---|---|---|
| 204 | empty | Records were cleared |
| 403 | error SUDO_MODE_REQUIRED |
No accepted proof |
Rate limit¶
10 requests per 60 seconds per user, bucket user:authorized_ips:forget.
Disable current account¶
POST /auth/account/disable
Disables the account while keeping its data, and ends every session it owns, including the one that made the request. Signing in again re-activates the account. Requires sudo verification.
Response¶
| Status | Body | Condition |
|---|---|---|
| 204 | empty | Account was disabled |
| 401 | error | Wrong password or code |
| 403 | error SUDO_MODE_REQUIRED |
No accepted proof |
Rate limit¶
5 requests per hour per user, bucket user:account:disable.
Schedule current account deletion¶
POST /auth/account/delete
Schedules the account for deletion 14 days from now and ends every session. Signing in before the deadline cancels the deletion. Requires sudo verification.
Transfer guild ownership first
An account that owns a guild cannot be deleted; the request fails with 400 BAD_REQUEST. Transfer or delete each owned guild first.
Response¶
| Status | Body | Condition |
|---|---|---|
| 204 | empty | Deletion was scheduled |
| 400 | error | The account owns at least one guild |
| 403 | error SUDO_MODE_REQUIRED |
No accepted proof |
Rate limit¶
5 requests per hour per user, bucket user:account:delete.
curl -X POST https://tebhos.com/api/v1/auth/account/delete \
-H "Authorization: Bearer $TEBHOS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"mfa_code": "123456"}'
Accept current policies¶
POST /users/@me/terms-acceptance
Records acceptance of the current terms of service and privacy policy by setting terms_agreed_at and privacy_agreed_at to the request time. The body is ignored. Returns the updated user object and emits USER_UPDATE to the account's sessions. Repeating the call replaces both timestamps.
Rate limit¶
20 requests per 60 seconds per user, bucket user:update.
Legacy profile reads¶
The TEBHOS web client also uses two older, flat profile reads. New integrations should prefer Get current user.
| Route | Returns |
|---|---|
GET /auth/me |
A flat TEBHOS profile record of the current account (profile customisation, MFA timestamps, premium summary) |
GET /me |
The current account as a profile object, including email |