mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-18 13:35:42 +09:00
docs: rewrite reference prose and correct field code citations (#2592)
This commit is contained in:
1 parent
3d38d3f694
commit
2f008b8653
76 files changed
+680
-457
No files matched your search
@@ -10,9 +10,9 @@ An application is an OAuth2 client owned by one user account, and every bot acco
|
||||
|
||||
## Access rules
|
||||
|
||||
Every route except [Get public application](#get-public-application) requires a credential. A user-only route rejects a bot token and an OAuth2 bearer with 403 `ACCESS_DENIED`. An account with an outstanding required action is accepted everywhere here.
|
||||
Every route except [Get public application](#get-public-application) requires a credential. A user-only route rejects a bot token and an OAuth2 bearer with 403 `ACCESS_DENIED`. Fluxer accepts an account with an outstanding required action everywhere here.
|
||||
|
||||
The three sudo-gated routes read `X-Fluxer-Sudo-Mode-JWT`. A valid proof for the authenticated account satisfies [sudo mode](/http-api/users/mfa/#sudo-mode) on its own, and Fluxer echoes it back in the response header. An account with no authenticator proves sudo mode with `password` in the body, because Fluxer issues it no token.
|
||||
The three sudo-gated routes read `X-Fluxer-Sudo-Mode-JWT`. A valid proof for the authenticated account satisfies [sudo mode](/http-api/users/mfa/#sudo-mode) on its own, and Fluxer echoes it back in the response header. Fluxer issues no token to an account with no authenticator, so that account proves sudo mode with `password` in the body.
|
||||
|
||||
:::caution[Credentials are shown once]
|
||||
A client secret is returned only by [Create application](#create-application) and [Reset client secret](#reset-client-secret), and a bot token only by [Create application](#create-application) and [Reset bot token](#reset-bot-token). Both are stored as one-way hashes.
|
||||
@@ -173,11 +173,11 @@ The bot account's profile fields after an update. [Update bot profile](#update-b
|
||||
| 1 << 4 | FRIENDLY_BOT | The bot accepts friend requests from users |
|
||||
| 1 << 5 | FRIENDLY_BOT_MANUAL_APPROVAL | The bot requires manual approval for friend requests |
|
||||
|
||||
These two bits are a subset of the account's [public user flags](/http-api/users/#public-user-flags), so a bot account that also has `STAFF`, `PARTNER`, `BUG_HUNTER`, or `SPAMMER` reports that bit in the same field. A client MUST ignore a bit it does not recognise.
|
||||
These bits are a subset of the account's [public user flags](/http-api/users/#public-user-flags), so a bot account that also has `STAFF`, `PARTNER`, `BUG_HUNTER`, or `SPAMMER` reports that bit in the same field. A client MUST ignore a bit it does not recognise.
|
||||
|
||||
## Bot token reset object
|
||||
|
||||
The response to [Reset bot token](#reset-bot-token). The fields are the new token and the bot account it belongs to.
|
||||
The response to [Reset bot token](#reset-bot-token).
|
||||
|
||||
### Structure
|
||||
|
||||
|
||||
@@ -197,11 +197,15 @@ The parsed device metadata recorded for a session or a pending handoff.
|
||||
|
||||
<sup>1</sup> A native or Electron client reports null, and a session created by an unparseable request reports null. A handoff omits the member entirely
|
||||
|
||||
Fluxer recognises a native Fluxer client from a `User-Agent` beginning `Fluxer Android`, `Fluxer iOS`, `Fluxer Linux`, `Fluxer Desktop`, or `Fluxer Client`. Its `platform` is the instance product name followed by the resolved operating system, with `Lite` inserted before the operating system for a non-mobile one. A native client that resolves no operating system reports the product name followed by `Lite`, so `platform` is never null for a native client. An Electron client reports the product name followed by the resolved operating system, and the product name alone when no operating system resolves. Fluxer parses every other client from the recorded `User-Agent`, and that client's platform is the browser name, or the operating system name when no browser resolves, and null when neither resolves.
|
||||
Fluxer recognises a native Fluxer client from a `User-Agent` beginning `Fluxer Android`, `Fluxer iOS`, `Fluxer Linux`, `Fluxer Desktop`, or `Fluxer Client`.
|
||||
|
||||
- A native client's `platform` is the instance product name followed by the resolved operating system, with `Lite` inserted before the operating system for a non-mobile one. A native client that resolves no operating system reports the product name followed by `Lite`, so `platform` is never null for a native client.
|
||||
- An Electron client's `platform` is the product name followed by the resolved operating system, and the product name alone when no operating system resolves.
|
||||
- Every other client is parsed from the recorded `User-Agent`, and its `platform` is the browser name, or the operating system name when no browser resolves, and null when neither resolves.
|
||||
|
||||
Fluxer resolves `device` from the recorded `User-Agent` and, for a native client, from the operating system reported through `X-Fluxer-Client-Properties`. A native or Electron client is `mobile` only when the resolved operating system is iOS or Android. Every other client is `mobile` when the parsed platform type is mobile or tablet, and `desktop` otherwise.
|
||||
|
||||
A session reports a null `location` when the recorded IP address resolves to no location. A handoff always reports the object, and each of its three members is null when that component is unavailable. For a session, an address with a known region and country but no known city reports the region as `city`, the country as `region`, and null as `country`.
|
||||
A session reports a null `location` when the recorded IP address resolves to no location. A handoff always reports the object, and each of its members is null when that component is unavailable. For a session, an address with a known region and country but no known city reports the region as `city`, the country as `region`, and null as `country`.
|
||||
|
||||
## Client location object
|
||||
|
||||
@@ -240,7 +244,7 @@ The state of one IP authorisation ticket, as observed by the device whose sign-i
|
||||
| user_id?<sup>1</sup> | ?snowflake | The authenticated user ID |
|
||||
| user?<sup>1</sup> | ?[partial user](/http-api/users/#partial-user-object) object | The public representation of the authenticated account |
|
||||
|
||||
<sup>1</sup> The three fields are present together only when `completed` is true, and a still-pending ticket returns `completed` alone
|
||||
<sup>1</sup> The fields are present together only when `completed` is true, and a still-pending ticket returns `completed` alone
|
||||
|
||||
## Username suggestions object
|
||||
|
||||
@@ -300,7 +304,7 @@ The state of one desktop handoff as observed by the device that initiated it.
|
||||
|
||||
<sup>1</sup> An unknown code and an expired code both report `expired`
|
||||
|
||||
<sup>2</sup> The three fields are present together only when the status is `completed`
|
||||
<sup>2</sup> The fields are present together only when the status is `completed`
|
||||
|
||||
## Get SSO status
|
||||
|
||||
@@ -401,7 +405,7 @@ A verified provider email that adopts a bot account returns 403 `BOT_USER_AUTH_S
|
||||
|
||||
### Side effects
|
||||
|
||||
Fluxer consumes the SSO state exactly once, before it attempts the provider exchange. A failed exchange still burns the state. A client MUST then start a fresh flow. On first sign-in, the provider identity becomes exclusive to the new account. The account starts with a verified email, no password, no authenticator, and its default settings. Provisioning joins no guild and accepts no invite, so no [Guild Member Add](/gateway/events/#guild-member-add) is emitted.
|
||||
Fluxer consumes the SSO state exactly once, before it attempts the provider exchange. A failed exchange still spends the state. A client MUST then start a fresh flow. On first sign-in, the provider identity becomes exclusive to the new account. The account starts with a verified email, no password, no authenticator, and its default settings. Provisioning joins no guild and accepts no invite, so no [Guild Member Add](/gateway/events/#guild-member-add) is emitted.
|
||||
|
||||
Approval mode registration instead returns 403 `REGISTRATION_PENDING_APPROVAL` and creates no session. Otherwise the operation creates one authentication session and clears an expired temporary suspension.
|
||||
|
||||
@@ -415,7 +419,7 @@ Approval mode registration instead returns 403 `REGISTRATION_PENDING_APPROVAL` a
|
||||
|
||||
Creates an ordinary account. Returns an [authentication token response](#authentication-token-response) when the instance admits the account immediately and a [registration pending approval response](#registration-pending-approval-response-object) when it does not. Emits a [Guild Member Add](/gateway/events/#guild-member-add) Gateway event when an invite or instance community admission takes effect.
|
||||
|
||||
This is a local authentication operation and it verifies [CAPTCHA](/topics/captcha/) when CAPTCHA is enabled. Registration additionally permits 3 attempts per hour for each client IP address and 15 per hour for each client subnet, which is the IPv4 /24 or IPv6 /48 network. A supplied email address permits 3 attempts per 15 minutes of its own. Those three allowances are separate from the route bucket, and only a deployment that relaxes registration rate limits disables them.
|
||||
This is a local authentication operation and it verifies [CAPTCHA](/topics/captcha/) when CAPTCHA is enabled. Registration permits 3 attempts per hour for each client IP address and 15 per hour for each client subnet, which is the IPv4 /24 or IPv6 /48 network. A supplied email address permits 3 attempts per 15 minutes of its own. Those allowances are separate from the route bucket, and only a deployment that relaxes registration rate limits disables them.
|
||||
|
||||
### Request headers
|
||||
|
||||
@@ -570,7 +574,6 @@ Consumes an MFA ticket and validates a time-based one-time password or an uncons
|
||||
|
||||
This is a local authentication operation. MFA verification additionally permits 10 failed attempts per account in 15 minutes and destroys the ticket after 5 failed attempts.
|
||||
|
||||
|
||||
### JSON body
|
||||
|
||||
| Field | Type | Description |
|
||||
@@ -580,7 +583,7 @@ This is a local authentication operation. MFA verification additionally permits
|
||||
|
||||
<sup>1</sup> A validated authenticator code is claimed for 30 seconds, so the same code cannot be presented twice. A backup code is consumed permanently on the attempt that accepts it
|
||||
|
||||
An expired or unknown ticket returns the field code `SESSION_TIMEOUT` on `ticket`. An account with no TOTP enrolment returns the field code `TOTP_NOT_ENABLED` on `code`. An incorrect code, and any code presented after the per-account or per-ticket attempt allowance is exhausted, returns the field code `INVALID_CODE` on `code`. A ticket that resolves to a bot account returns 403 `BOT_USER_AUTH_ENDPOINT_ACCESS_DENIED`.
|
||||
An expired or unknown ticket returns the field code `SESSION_TIMEOUT` on `ticket`. An account with no TOTP enrolment returns the field code `TOTP_NOT_ENABLED` on `code`. Both an incorrect code and any code presented after the per-account or per-ticket attempt allowance is exhausted return the field code `INVALID_CODE` on `code`. A ticket that resolves to a bot account returns 403 `BOT_USER_AUTH_ENDPOINT_ACCESS_DENIED`.
|
||||
|
||||
### Response
|
||||
|
||||
@@ -659,7 +662,7 @@ This is a local authentication operation.
|
||||
|
||||
An expired or unknown ticket returns the field code `SESSION_TIMEOUT` on `ticket`. A challenge mismatch, an unknown credential, a stored public key that cannot be decoded, a signature counter that the authenticator did not advance, and a failed signature verification all return 401 `PASSKEY_AUTHENTICATION_FAILED`. A verified assertion whose reported signature counter cannot be read returns 500 `INVALID_WEBAUTHN_AUTHENTICATION_COUNTER`.
|
||||
|
||||
This operation shares the MFA attempt allowances of [complete login with TOTP](#complete-login-with-totp). It permits 10 failed attempts per account in 15 minutes and destroys the ticket after 5 failed attempts, and an exhausted allowance returns the field code `INVALID_CODE` on `ticket`.
|
||||
This operation shares the MFA attempt allowances of [complete login with TOTP](#complete-login-with-totp). It permits 10 failed attempts per account in 15 minutes and destroys the ticket after 5 failed attempts. An exhausted allowance returns the field code `INVALID_CODE` on `ticket`.
|
||||
|
||||
### Response
|
||||
|
||||
@@ -848,7 +851,7 @@ The operation creates one 64-character verification token bound to the account a
|
||||
|
||||
### Rate limit
|
||||
|
||||
10 requests per minute, on the `auth:verify:resend` bucket, and the target address additionally permits 3 messages in 15 minutes.
|
||||
10 requests per minute, on the `auth:verify:resend` bucket, and the target address permits 3 messages in 15 minutes.
|
||||
|
||||
## Request password recovery
|
||||
|
||||
@@ -898,7 +901,7 @@ Fluxer sends one 64-character reset token by email to an address that resolves t
|
||||
|
||||
### Rate limit
|
||||
|
||||
5 requests per minute, on the `auth:forgot` bucket, and recovery additionally permits 20 attempts per client IP address and 5 attempts per email address in each 30-minute window.
|
||||
5 requests per minute, on the `auth:forgot` bucket, and recovery permits 20 attempts per client IP address and 5 attempts per email address in each 30-minute window.
|
||||
|
||||
## Validate a password reset token
|
||||
|
||||
@@ -987,7 +990,7 @@ A successful reset terminates every authentication session on the account, inclu
|
||||
|
||||
The operation replaces the password, records the change time, clears an expired temporary suspension, terminates every authentication session on the account, and consumes the presented reset token. Every terminated session's Gateway connection receives Invalid Session with `d: false` and stays open, unauthenticated. Other outstanding reset tokens are not invalidated, so a second recovery link issued earlier still works.
|
||||
|
||||
An account with no second factor then receives one new session and its token. An account with a second factor receives a five-minute MFA ticket instead, and the session is created by the MFA completion.
|
||||
An account with no second factor then receives one new session and its token. An account with a second factor receives a five-minute MFA ticket instead, and the MFA completion creates the session.
|
||||
|
||||
### Rate limit
|
||||
|
||||
@@ -1016,7 +1019,7 @@ Completing a reversion terminates every authentication session, clears the accou
|
||||
|
||||
An unknown or already consumed token, and a token bound to an account that no longer exists, return the field code `INVALID_OR_EXPIRED_REVERT_TOKEN`. Account suspension returns 403 `ACCOUNT_SUSPENDED_TEMPORARILY` or 403 `ACCOUNT_SUSPENDED_PERMANENTLY`, and a bot account returns 403 `BOT_USER_AUTH_ENDPOINT_ACCESS_DENIED`.
|
||||
|
||||
The replacement password is checked against the same breached-password corpus described under [reset a password](#reset-a-password) before the token is spent.
|
||||
Fluxer checks the replacement password against the same breached-password corpus described under [reset a password](#reset-a-password) before it spends the token.
|
||||
|
||||
### Response
|
||||
|
||||
@@ -1204,7 +1207,7 @@ Fluxer sends the authorisation message to the address associated with the login
|
||||
|
||||
### Rate limit
|
||||
|
||||
5 requests per minute, on the `auth:ip_authorization_resend` bucket, and each ticket additionally permits exactly one resend, no earlier than 30 seconds after the ticket was issued.
|
||||
5 requests per minute, on the `auth:ip_authorization_resend` bucket, and each ticket permits exactly one resend, no earlier than 30 seconds after the ticket was issued.
|
||||
|
||||
## Poll IP authorisation
|
||||
|
||||
@@ -1329,7 +1332,7 @@ Reading the handoff information marks the code as inspected but does not approve
|
||||
|
||||
Anyone who can reach the API and knows the code can mark it inspected. Declining the request in a client discards only what that client is showing, and the inspected state remains until the handoff is completed or the code expires.
|
||||
|
||||
A code that is not exactly 12 characters from the handoff alphabet after hyphens and whitespace are removed returns 400 `INVALID_HANDOFF_CODE`. A code whose three lookups are already spent, and a request from a client IP address that has exhausted its failed-attempt allowance, return the same error code. An unknown or expired code returns 200 with the `expired` status and records one failed attempt.
|
||||
A code that is not exactly 12 characters from the handoff alphabet after hyphens and whitespace are removed returns 400 `INVALID_HANDOFF_CODE`. Both a code whose three lookups are already spent and a request from a client IP address that has exhausted its failed-attempt allowance return the same error code. An unknown or expired code returns 200 with the `expired` status and records one failed attempt.
|
||||
|
||||
### Response
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ A private call has no moderator and no permission overwrites. No account other t
|
||||
|
||||
Silencing another participant happens in the client. A client MAY mute a participant or change per-participant volume. The client MUST keep each setting local to the listening device, so neither reaches the Gateway or any other participant.
|
||||
|
||||
Two operations on this page name another recipient. [Ring call recipients](#ring-call-recipients) adds named recipients to the ringing set of a call and [Stop ringing call recipients](#stop-ringing-call-recipients) removes them from it. Any current recipient MAY call either one. Neither changes anything for a recipient who has already connected, and neither writes a voice state.
|
||||
Two operations on this page name another recipient. [Ring call recipients](#ring-call-recipients) adds named recipients to the ringing set of a call, and [Stop ringing call recipients](#stop-ringing-call-recipients) removes them from it. Any current recipient MAY call either one. Neither changes anything for a recipient who has already connected, and neither writes a voice state.
|
||||
|
||||
## Call eligibility object
|
||||
|
||||
@@ -72,7 +72,7 @@ Returns the [call eligibility object](#call-eligibility-object) for a direct mes
|
||||
|
||||
A direct message reports `ringable` as false when the other recipient's incoming call policy excludes the caller. That policy is the `incoming_call_flags` bitfield of the recipient's [user settings](/http-api/users/settings/). Fluxer reads the nobody flag, the friends-only flag, an existing friendship, a mutual friend, a mutual guild, and finally the everyone flag, in that order. The silent-everyone flag reports `silent` as true for a caller admitted by the mutual friend, mutual guild, or everyone branch.
|
||||
|
||||
A recipient who has never stored settings reports `ringable` as true and `silent` as false, and a direct message that has lost its other recipient reports the same pair. A group direct message reports `ringable` as true unless the caller is already connected to its call, and it always reports `silent` as false.
|
||||
For a recipient who has never stored settings, `ringable` is true and `silent` is false. A direct message that has lost its other recipient reports the same pair. In a group direct message `ringable` is true unless the caller is already connected to its call, and `silent` is always false.
|
||||
|
||||
Fluxer evaluates the same policy again on a later [Ring call recipients](#ring-call-recipients) request.
|
||||
|
||||
@@ -120,11 +120,18 @@ The caller does not have to be connected to the call.
|
||||
|
||||
<sup>2</sup> Validated for length and then discarded, so it never affects the selected region
|
||||
|
||||
Any other unknown or inaccessible `region` returns 400 `INVALID_FORM_BODY` with the validation code `INVALID_OR_RESTRICTED_RTC_REGION` on the `region` field. The body can be omitted. Fluxer treats a missing, empty, or whitespace-only body as an empty object, which requests no change. A body that is not valid JSON returns 400 `INVALID_FORM_BODY` with the validation code `INVALID_FORMAT` at the `body` path.
|
||||
Any other unknown or inaccessible `region` returns 400 `INVALID_FORM_BODY` with the validation code `INVALID_OR_RESTRICTED_RTC_REGION` on the `region` field.
|
||||
|
||||
The body can be omitted. Fluxer treats a missing, empty, or whitespace-only body as an empty object, which requests no change. A body that is not valid JSON returns 400 `INVALID_FORM_BODY` with the validation code `INVALID_FORMAT` at the `body` path.
|
||||
|
||||
A region identifier is the `id` of an [RTC region object](/http-api/channels/#rtc-region-object). No route enumerates the regions a call accepts, and [List RTC regions](/http-api/channels/#list-rtc-regions) answers only for a guild voice channel.
|
||||
|
||||
A region or voice server restricted to named guilds, to a guild feature, or to VIP voice is never selectable for a call. A region or voice server whose user allowlist excludes the caller is unselectable, and so is a region whose servers are all inactive or all closed to the caller. An instance with voice disabled accepts any region string.
|
||||
A region or voice server is unselectable for a call on any of these grounds:
|
||||
|
||||
- It is restricted to named guilds, to a guild feature, or to VIP voice.
|
||||
- Its user allowlist excludes the caller.
|
||||
|
||||
A region whose servers are all inactive or all closed to the caller is unselectable as well. An instance with voice disabled accepts any region string.
|
||||
|
||||
### Response
|
||||
|
||||
@@ -176,7 +183,7 @@ The body can be omitted. Fluxer treats a missing, empty, or whitespace-only body
|
||||
|
||||
An identifier that names a non-recipient or the caller itself returns 400 `INVALID_FORM_BODY` with the validation code `USER_NOT_IN_CHANNEL`. A repeated identifier is accepted and rings that recipient once.
|
||||
|
||||
A named recipient is only rung when the incoming call policy described by [Get call eligibility](#get-call-eligibility) admits the caller audibly, so a recipient admitted only under the silent-everyone flag is notified without being rung. A direct message ignores the named set when choosing whom to ring and always evaluates the policy against the other recipient.
|
||||
A named recipient is only rung when the incoming call policy described by [Get call eligibility](#get-call-eligibility) admits the caller audibly, so a recipient admitted only under the silent-everyone flag is notified without being rung. In a direct message Fluxer ignores the named set when choosing whom to ring and always evaluates the policy against the other recipient.
|
||||
|
||||
### Response
|
||||
|
||||
@@ -198,7 +205,9 @@ When no call exists, Fluxer then stores a call system message with the initial p
|
||||
|
||||
When a call already exists, Fluxer instead extends the ringing set and emits [Call Update](/gateway/events/#call-update) when the set grows. A recipient who is already connected to the call is never added to the ringing set.
|
||||
|
||||
Each rung recipient holds a ringing entry for 30 seconds. Fluxer then drops the entry and emits [Call Update](/gateway/events/#call-update) again. When that expiry leaves the call with no connected participant and no other ringing recipient, Fluxer removes the call in the same step, which emits [Call Delete](/gateway/events/#call-delete) and stamps the call system message with its ended timestamp. A call that rang at least one recipient and that nobody joined therefore ends 30 seconds after the ring. A call created with an empty ringing set arms no ring timer, so it ends on the 120 second idle timer instead. An explicit empty `recipients` array produces such a call, and so does a ring that admits no candidate audibly.
|
||||
Each rung recipient holds a ringing entry for 30 seconds. Fluxer then drops the entry and emits [Call Update](/gateway/events/#call-update) again. When that expiry leaves the call with no connected participant and no other ringing recipient, Fluxer removes the call in the same step, which emits [Call Delete](/gateway/events/#call-delete) and stamps the call system message with its ended timestamp. A call that rang at least one recipient and that nobody joined therefore ends 30 seconds after the ring.
|
||||
|
||||
Fluxer arms no ring timer for a call created with an empty ringing set, so that call ends on the 120 second idle timer instead. An explicit empty `recipients` array produces such a call, and so does a ring that admits no candidate audibly.
|
||||
|
||||
Whenever Fluxer removes a call, it rewrites that call's system message with the ended timestamp and with every account that ever connected, and publishes the rewritten message to every recipient of the private channel as [Message Update](/gateway/events/#message-update).
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ A channel object always has its identity and type. Every other field is present
|
||||
|
||||
<sup>4</sup> The value `0` means no occupancy limit
|
||||
|
||||
<sup>5</sup> A guild voice channel that stores no per-user connection limit reports the default `5`
|
||||
<sup>5</sup> Defaults to `5` on a guild voice channel that stores no per-user connection limit
|
||||
|
||||
<sup>6</sup> Null selects automatic routing. [List RTC regions](#list-rtc-regions) returns the available identifiers
|
||||
|
||||
@@ -61,9 +61,9 @@ A channel object always has its identity and type. Every other field is present
|
||||
|
||||
<sup>9</sup> A channel with no explicit override reports `nsfw` false and `nsfw_override` null
|
||||
|
||||
<sup>10</sup> A channel that inherits reports `content_warning_level` `0` and `content_warning_text` null
|
||||
<sup>10</sup> On a channel that inherits, `content_warning_level` is `0` and `content_warning_text` is null
|
||||
|
||||
<sup>11</sup> A channel that configures no slowmode reports `0`
|
||||
<sup>11</sup> The interval is `0` when the channel configures no slowmode
|
||||
|
||||
<sup>12</sup> Omitted when the group stores no nickname. Clearing a nickname removes its key from the map
|
||||
|
||||
@@ -408,13 +408,11 @@ Every field is optional, and an omitted field preserves its current value. The f
|
||||
|
||||
A `url` that is not an absolute `http` or `https` URL with a host returns 400 `INVALID_FORM_BODY` with the code `INVALID_URL_FORMAT` on the path `url`.
|
||||
|
||||
Three failures apply to `parent_id`. A parent that does not exist in the same guild returns 400 `INVALID_FORM_BODY` with the code `INVALID_PARENT_CHANNEL` on the path `parent_id`. A parent that is not a category returns `PARENT_MUST_BE_CATEGORY` the same way.
|
||||
A parent that does not exist in the same guild returns 400 `INVALID_FORM_BODY` with the code `INVALID_PARENT_CHANNEL` on the path `parent_id`. A parent that is not a category returns `PARENT_MUST_BE_CATEGORY` the same way.
|
||||
|
||||
Naming a category other than the current parent also checks capacity. A full category returns 400 `MAX_CATEGORY_CHANNELS` with the ceiling it reached, which is the deployment's [`max_channels_per_category`](/http-api/instance/#limit-keys) limit and defaults to 50.
|
||||
|
||||
Fluxer accepts an overwrite mask in two encodings. A decimal string holds at most 9223372036854775807, and a larger one returns 400 `INVALID_FORM_BODY` with the code `INTEGER_OUT_OF_INT64_RANGE`. A JSON integer holds at most 9007199254740991, and a number outside the safe integer range returns `INVALID_INTEGER_FORMAT`.
|
||||
|
||||
Every bit outside the defined permission set is discarded. A [feature-gated bit](/http-api/permissions/#feature-gated-permission-bits) the `X-Fluxer-Features` declaration does not name is copied from the stored overwrite.
|
||||
Fluxer accepts an overwrite mask as a decimal string or as a JSON integer. The decimal string holds at most 9223372036854775807, and a larger one returns 400 `INVALID_FORM_BODY` with the code `INTEGER_OUT_OF_INT64_RANGE`. The JSON integer holds at most 9007199254740991, and a number outside the safe integer range returns `INVALID_INTEGER_FORMAT`. Every bit outside the defined permission set is discarded.
|
||||
|
||||
A non-null `rtc_region` names a region [List RTC regions](#list-rtc-regions) returns for this guild. Any other value returns 400 `INVALID_FORM_BODY` with the code `INVALID_OR_RESTRICTED_RTC_REGION` on the path `rtc_region`. A deployment that configures no voice topology validates no region and stores any accepted string.
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ description: The hosted-only routes and the instance flags that report deploymen
|
||||
|
||||
A small set of routes exists only on the deployment Fluxer hosts. An operator runs the same release, and most of the HTTP API is identical on both.
|
||||
|
||||
A self-hosted deployment does not register those routes. A request to one returns 404 `NOT_FOUND` with no feature-specific code. A caller cannot tell an unavailable route from an unrecognised path.
|
||||
A self-hosted deployment does not register those routes. A request to one returns 404 `NOT_FOUND` with no feature-specific code, so a caller cannot tell an unavailable route from an unrecognised path.
|
||||
|
||||
The API decides registration once at process start from the deployment configuration. No credential, permission, premium state, or OAuth2 scope changes the answer. A client resolves the deployment kind from instance discovery.
|
||||
|
||||
@@ -20,7 +20,11 @@ Every deployment reports its kind in `self_hosted` on the [instance features obj
|
||||
Neither flag promises that a provider-dependent operation succeeds.
|
||||
:::
|
||||
|
||||
Without a provider client, the answer depends on the operation. An operation that has to reach the provider fails with 400 `STRIPE_PAYMENT_NOT_AVAILABLE`, and [Receive Stripe webhook](/http-api/billing/#receive-stripe-webhook) fails with 400 `STRIPE_WEBHOOK_NOT_AVAILABLE`. Three read operations report the absence in a 200 body instead. [Get refund eligibility](/http-api/billing/#get-refund-eligibility) reports `eligible` false with the reason `feature_unavailable`, [Get current subscription price](/http-api/premium/#get-current-subscription-price) reports null, and [Get price IDs](/http-api/premium/#get-price-ids) reports the configured price IDs with every amount null.
|
||||
Without a provider client, the answer depends on the operation. An operation that has to reach the provider fails with 400 `STRIPE_PAYMENT_NOT_AVAILABLE`, and [Receive Stripe webhook](/http-api/billing/#receive-stripe-webhook) fails with 400 `STRIPE_WEBHOOK_NOT_AVAILABLE`. These read operations report the absence in a 200 body instead:
|
||||
|
||||
- [Get refund eligibility](/http-api/billing/#get-refund-eligibility) reports `eligible` false with the reason `feature_unavailable`.
|
||||
- [Get current subscription price](/http-api/premium/#get-current-subscription-price) reports null.
|
||||
- [Get price IDs](/http-api/premium/#get-price-ids) reports the configured price IDs with every amount null.
|
||||
|
||||
## Hosted-only routes
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ Discovery is the public directory of guilds any account can browse and join. A g
|
||||
|
||||
On a guild an operator has marked unavailable, Fluxer refuses [Apply for discovery](#apply-for-discovery), [Edit discovery application](#edit-discovery-application), [Withdraw discovery application](#withdraw-discovery-application), and [Get discovery status](#get-discovery-status) with 403 `MISSING_ACCESS` before the route runs. The gate does not cover [Join discovery guild](#join-discovery-guild).
|
||||
|
||||
The four routes under `/v1/guilds/{guild_id}/discovery` require [MANAGE_GUILD](/http-api/permissions/), which is an [elevated permission](/http-api/permissions/#elevated-permissions). While the guild [MFA level](/http-api/guilds/#mfa-levels) is elevated, a caller other than the owner also needs an enrolled authenticator, and receives 400 `TWO_FACTOR_REQUIRED` without one. A guild that does not exist returns 404 `UNKNOWN_GUILD`. Both a non-member and a member without the permission return 403 `MISSING_PERMISSIONS`. The routes answer 503 `SERVICE_UNAVAILABLE` when the check cannot be admitted, and 504 `GATEWAY_TIMEOUT` when it does not answer in time.
|
||||
The routes under `/v1/guilds/{guild_id}/discovery` require [MANAGE_GUILD](/http-api/permissions/), which is an [elevated permission](/http-api/permissions/#elevated-permissions). While the guild [MFA level](/http-api/guilds/#mfa-levels) is elevated, a caller other than the owner also needs an enrolled authenticator, and receives 400 `TWO_FACTOR_REQUIRED` without one. A guild that does not exist returns 404 `UNKNOWN_GUILD`. Both a non-member and a member without the permission return 403 `MISSING_PERMISSIONS`. The routes answer 503 `SERVICE_UNAVAILABLE` when the check cannot be admitted, and 504 `GATEWAY_TIMEOUT` when it does not answer in time.
|
||||
|
||||
:::note[Discovery is an optional instance capability]
|
||||
Searching, applying, editing, withdrawing, and joining fail with 400 `DISCOVERY_DISABLED` while an operator has it disabled. [List discovery categories](#list-discovery-categories) and [Get discovery status](#get-discovery-status) keep answering.
|
||||
@@ -20,7 +20,7 @@ Searching, applying, editing, withdrawing, and joining fail with 400 `DISCOVERY_
|
||||
|
||||
## Discovery guild object
|
||||
|
||||
One search result. Every field except the two counts is read from the discovery search index.
|
||||
One search result. Every field except `member_count` and `online_count` is read from the discovery search index.
|
||||
|
||||
### Structure
|
||||
|
||||
@@ -39,7 +39,7 @@ One search result. Every field except the two counts is read from the discovery
|
||||
| features | array[string] | The [guild features](/http-api/guilds/#guild-features) the guild has |
|
||||
| verification_level<sup>4</sup> | integer | The effective [verification level](/http-api/guilds/#verification-levels) of the guild |
|
||||
|
||||
<sup>1</sup> The four listing fields come from the guild's approved application as it stood at index time, and an entry that stores no category is reported as category `0`
|
||||
<sup>1</sup> These listing fields come from the guild's approved application as it stood at index time, and an entry that stores no category is reported as category `0`
|
||||
|
||||
<sup>2</sup> Refreshed from the main Gateway at response time. When that refresh fails, the operation still succeeds and reports the indexed member count with an `online_count` of `0`
|
||||
|
||||
@@ -199,9 +199,7 @@ The release fixes the set, so an operator cannot add, rename, or remove a catego
|
||||
| 7 | MOVIES_AND_TV | Movies & TV |
|
||||
| 8 | OTHER | Other |
|
||||
|
||||
Every category value on the wire is one of these integers.
|
||||
|
||||
A value outside 0 through 8 fails body or query validation with the validation code `INVALID_FORMAT`, so [Apply for discovery](#apply-for-discovery) and [Edit discovery application](#edit-discovery-application) reject it before the listing is stored.
|
||||
Every category value on the wire is one of these integers. A value outside 0 through 8 fails body or query validation with the validation code `INVALID_FORMAT`, so [Apply for discovery](#apply-for-discovery) and [Edit discovery application](#edit-discovery-application) reject it before the listing is stored.
|
||||
|
||||
The display names above are the strings [List discovery categories](#list-discovery-categories) returns. They are not localised, so the same ID has the same name for every account.
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ Accepts a request for a single-use donation management link. Returns 204 with an
|
||||
A failure of any of those returns 400 `INVALID_FORM_BODY` with an `errors` entry on `email`.
|
||||
|
||||
:::caution[Every address answers the same 204]
|
||||
An address that resolves to no donor receives no email, and the response looks identical either way.
|
||||
An address that resolves to no donor receives no email.
|
||||
:::
|
||||
|
||||
### JSON body
|
||||
@@ -110,7 +110,7 @@ Fluxer creates a single-use token of 64 lowercase hexadecimal characters, valid
|
||||
|
||||
Consumes a donation management token. Answers 302 with `Location` set to the externally hosted billing portal for the matching donor. The token is the credential.
|
||||
|
||||
Fluxer checks the token in a fixed order. An unknown token returns 400 `DONATION_MAGIC_LINK_INVALID`. A token past its 15 minute lifetime returns 400 `DONATION_MAGIC_LINK_EXPIRED`. An already consumed token returns 400 `DONATION_MAGIC_LINK_USED`, and a token that is both expired and consumed is reported as expired. None of those three consumes the token.
|
||||
Fluxer checks the token in a fixed order. An unknown token returns 400 `DONATION_MAGIC_LINK_INVALID`. A token past its 15 minute lifetime returns 400 `DONATION_MAGIC_LINK_EXPIRED`. An already consumed token returns 400 `DONATION_MAGIC_LINK_USED`, and a token that is both expired and consumed is reported as expired. None of those refusals consumes the token.
|
||||
|
||||
A token whose address no longer resolves to a donor holding a payment provider customer is still consumed, and the redirect goes to the public donation page. For a donor holding a customer, a deployment with no configured payment provider returns 400 `STRIPE_PAYMENT_NOT_AVAILABLE` and a provider failure returns 400 `STRIPE_ERROR`, both after the token has already been consumed.
|
||||
|
||||
@@ -133,7 +133,7 @@ A token whose address no longer resolves to a donor holding a payment provider c
|
||||
|
||||
### Side effects
|
||||
|
||||
The token is consumed. A resolved donor holding a payment provider customer receives a portal session, and closing that session returns the donor to the public donation page. An address that resolves to no donor, or to a donor with no customer, redirects to the public donation page and creates no portal session.
|
||||
The token is consumed. A resolved donor holding a payment provider customer receives a portal session. Closing that session returns the donor to the public donation page. Where the address resolves to no donor, or to a donor with no customer, Fluxer redirects to the public donation page and creates no portal session.
|
||||
|
||||
### Rate limit
|
||||
|
||||
@@ -145,9 +145,9 @@ The token is consumed. A resolved donor holding a payment provider customer rece
|
||||
|
||||
Creates a one-off or recurring donation checkout session. Returns a [donation checkout](#donation-checkout-object) object on success.
|
||||
|
||||
An amount outside the selected currency's bounds fails validation with 400 `INVALID_FORM_BODY` and an `errors` entry on `amount_cents`. A deployment with no configured payment provider returns 400 `STRIPE_PAYMENT_NOT_AVAILABLE` before the address is resolved, and a provider failure while creating the session returns 400 `STRIPE_ERROR`.
|
||||
An amount outside the selected currency's bounds fails validation with 400 `INVALID_FORM_BODY` and an `errors` entry on `amount_cents`. Where the deployment has no configured payment provider, the route returns 400 `STRIPE_PAYMENT_NOT_AVAILABLE` before it resolves the address. A provider failure while creating the session returns 400 `STRIPE_ERROR`.
|
||||
|
||||
A recurring donation for an address that already holds an active recurring donation returns the public donation management page for that address, with the percent-encoded address in `email` and the literal value `active_subscription` in `alert`. An address holds an active recurring donation when its donor record has a subscription identifier and a current period end still in the future, and has no scheduled cancellation.
|
||||
A recurring donation for an address that already holds an active recurring donation returns the public donation management page for that address, with the percent-encoded address in `email` and the literal value `active_subscription` in `alert`. An address holds an active recurring donation when its donor record has a subscription identifier, a current period end still in the future, and no scheduled cancellation.
|
||||
|
||||
### JSON body
|
||||
|
||||
|
||||
@@ -22,13 +22,13 @@ Every other route on this reference takes the `/v1` form, and [HTTP API](/http-a
|
||||
|
||||
## Methods
|
||||
|
||||
Every route answers `GET`. Five registrations also bind `HEAD`: the two checksum routes, the two artifact routes, and the catch-all. [Get latest desktop version](#get-latest-desktop-version) and [List desktop versions](#list-desktop-versions) bind `GET` alone, and a `HEAD` still reaches them under the [shared rule for HEAD](/http-api/#request-format).
|
||||
Every route answers `GET`. The two checksum routes, the two artifact routes, and the catch-all also bind `HEAD`. [Get latest desktop version](#get-latest-desktop-version) and [List desktop versions](#list-desktop-versions) bind `GET` alone, and a `HEAD` still reaches them under the [shared rule for HEAD](/http-api/#request-format).
|
||||
|
||||
A `HEAD` on either JSON route runs the same resolution as the `GET` and returns its status and headers with no body. It can therefore answer 404.
|
||||
|
||||
On a checksum route, a `HEAD` returns 200 with no body. The headers are `Content-Type`, `Content-Disposition`, `Cache-Control`, and the `Content-Length` of the checksum line.
|
||||
A checksum route answers a `HEAD` with 200 and no body. The headers are `Content-Type`, `Content-Disposition`, `Cache-Control`, and the `Content-Length` of the checksum line.
|
||||
|
||||
A `HEAD` on an artifact route or the catch-all reads object metadata. It returns 200 with `Content-Type`, `Content-Disposition`, `Accept-Ranges`, `Cache-Control`, `Content-Length`, and, where storage reports them, `ETag` and `Last-Modified`. It ignores `Range` and answers neither 206 nor 416. It never redirects to a [presigned storage URL](#redirects). Fluxer resolves a [country redirect](#redirects) before it reads the method, so a `HEAD` receives that 302 exactly as a `GET` does.
|
||||
The artifact routes and the catch-all read object metadata for a `HEAD`. They return 200 with `Content-Type`, `Content-Disposition`, `Accept-Ranges`, `Cache-Control`, `Content-Length`, and, where storage reports them, `ETag` and `Last-Modified`. They ignore `Range`, answer neither 206 nor 416, and never redirect to a [presigned storage URL](#redirects). Fluxer resolves a [country redirect](#redirects) before it reads the method, so a `HEAD` receives that 302 exactly as a `GET` does.
|
||||
|
||||
## Release channels
|
||||
|
||||
@@ -155,7 +155,7 @@ Fluxer reads the hash from the sibling `.sha256` object or from the manifest ent
|
||||
|
||||
Fluxer resolves a coordinate to one storage key before it answers. It reads `manifest.json` under the coordinate prefix first and takes the filename that manifest records for the requested format. Fluxer falls back to a listing when that manifest is absent, is not valid JSON, is not a manifest object, describes another coordinate, or names a file storage does not hold. The fallback lists the objects under the prefix and takes the highest version whose filename parses for the requested format.
|
||||
|
||||
The listing skips any name containing `/`, the names `manifest.json`, `RELEASES.json`, and `releases.json`, and any name ending in `.sha256`, `.blockmap`, or `.yml`.
|
||||
The listing skips any name containing `/` and any name ending in `.sha256`, `.blockmap`, or `.yml`. It also skips `manifest.json`, `RELEASES.json`, and `releases.json`.
|
||||
|
||||
A coordinate that resolves no file returns 404 with the plain text body `Not Found`.
|
||||
|
||||
@@ -172,7 +172,12 @@ Every artifact response has `Accept-Ranges: bytes` and a `Content-Disposition` o
|
||||
| Any `desktop/` artifact on a deployment that configures country redirects | `private, no-store` |
|
||||
| A redirect to a presigned storage URL | `no-store` |
|
||||
|
||||
A release feed filename is `manifest.json`, a name ending in `.yml` or `.yaml`, a name beginning `RELEASES`, or a name beginning `releases` or `assets` and ending in `.json`.
|
||||
A release feed filename is any of these:
|
||||
|
||||
- `manifest.json`
|
||||
- A name ending in `.yml` or `.yaml`
|
||||
- A name beginning `RELEASES`
|
||||
- A name beginning `releases` or `assets` and ending in `.json`
|
||||
|
||||
## Redirects
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ The [instance discovery](/http-api/instance/#limit-keys) document publishes a `f
|
||||
|
||||
## Supported containers
|
||||
|
||||
The container is detected from the decoded bytes, so a filename, a `data:` media type, or any other declared type has no effect on the result. The detected container resolves to one of four stored extensions, and every other input fails with `ENTRANCE_SOUND_INVALID_FORMAT`.
|
||||
The container is detected from the decoded bytes, so a filename, a `data:` media type, or any other declared type has no effect on the result. The detected container resolves to a stored extension, and every other input fails with `ENTRANCE_SOUND_INVALID_FORMAT`.
|
||||
|
||||
| Detected container | Stored extension | Stored content type |
|
||||
| --- | --- | --- |
|
||||
@@ -122,7 +122,7 @@ A scope with no assigned sound has no selection object. Clearing a scope deletes
|
||||
| dms | Applies in direct message and group calls |
|
||||
| `guild:{guild_id}` | Applies in one named guild, where the ID is 1 through 20 digits |
|
||||
|
||||
`scope_id` must match one of the four forms exactly. Any other value fails with the validation code `INVALID_FORMAT` at the `scope_id` path.
|
||||
`scope_id` must match one of these forms exactly. Any other value fails with the validation code `INVALID_FORMAT` at the `scope_id` path.
|
||||
|
||||
:::note[Fluxer stores scopes but never resolves them]
|
||||
A selection records which sound belongs to which scope and nothing more. [Play entrance sound](#play-entrance-sound) names the sound explicitly, so the client decides which selection applies to a channel.
|
||||
@@ -194,7 +194,7 @@ Every failure below is a 400 `INVALID_FORM_BODY`. One response can report two of
|
||||
|
||||
The first four rows are checked before the quota, so an account already holding 8 clips receives `BASE64_LENGTH_INVALID` or `INVALID_BASE64_FORMAT` for a malformed or over-long payload.
|
||||
|
||||
Both duration bounds report the same code at the `audio` path, and the entry has no member naming which of the two was crossed.
|
||||
The validation entry has no member naming which duration bound the clip crossed.
|
||||
|
||||
### Response
|
||||
|
||||
@@ -343,7 +343,7 @@ Nothing is published to the media servers. Each recipient receives a Dispatch na
|
||||
Fluxer evaluates no channel permission and checks no guild membership.
|
||||
:::
|
||||
|
||||
An ID naming no channel is refused exactly like a channel the caller is not connected to, so the response tells the two apart in neither direction. The connection check runs before the clip is looked up, so a caller who is not connected never learns whether the named clip exists.
|
||||
An ID naming no channel is refused exactly like a channel the caller is not connected to. The connection check runs before the clip is looked up, so a caller who is not connected never learns whether the named clip exists.
|
||||
|
||||
### Side effects
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ The `message` field is rendered from a template, is translated per request, and
|
||||
|
||||
## Selecting the error code
|
||||
|
||||
An error response always has `code` and `message`. A failure with more to report adds its own members beside those two. Every added member sits at the top level of the response. A rate limit denial adds `global` and `retry_after`, a missing scope adds `required_scope`, and a validation failure adds `errors`.
|
||||
An error response always has `code` and `message`. A failure with more to report adds its own members beside those two, at the top level of the response. A rate limit denial adds `global` and `retry_after`, a missing scope adds `required_scope`, and a validation failure adds `errors`.
|
||||
|
||||
:::note[OAuth2 endpoints use a different envelope]
|
||||
An OAuth2 protocol failure raised by the [OAuth2 resource](/http-api/oauth2/) answers with the RFC 6749 shape. That body is `error` and `error_description` and nothing else, so its values appear in no registry here.
|
||||
@@ -22,7 +22,13 @@ An OAuth2 protocol failure raised by the [OAuth2 resource](/http-api/oauth2/) an
|
||||
|
||||
The error code determines which supplementary members a failure has, and most codes have none. A client reads only the members documented for the code it matched. `errors` is the list of field violations. `retry_after` is the delay before another attempt is admitted, `global` is `true` on a global rate limit denial and `false` on a route one, `required_scope` is the OAuth2 scope the request is missing, and `has_mfa` and `methods` are the [sudo mode](/http-api/users/mfa/#sudo-mode) proofs an account can supply.
|
||||
|
||||
The two IP ban codes have their own members. `GLOBAL_IP_BANNED` and `GLOBAL_IP_TEMPORARILY_BANNED` both have `ip_address` with the normalised client address, `appeal_email` with the address an appeal is sent to, `appeals_supported` which is `true` only for the permanent ban, and `ban_kind` which is `permanent` or `temporary_24h`. `expires_at` is an ISO 8601 timestamp when the ban records an expiry and `null` otherwise, including on every permanent ban.
|
||||
`GLOBAL_IP_BANNED` and `GLOBAL_IP_TEMPORARILY_BANNED` have their own members:
|
||||
|
||||
- `ip_address` is the normalised client address.
|
||||
- `appeal_email` is the address an appeal is sent to.
|
||||
- `appeals_supported` is `true` only for the permanent ban.
|
||||
- `ban_kind` is `permanent` or `temporary_24h`.
|
||||
- `expires_at` is an ISO 8601 timestamp when the ban records an expiry, and `null` otherwise, including on every permanent ban.
|
||||
|
||||
## Validation failure codes
|
||||
|
||||
@@ -56,7 +62,7 @@ The `path` of an element is the dot-joined position of the failed value, so a ne
|
||||
An empty or whitespace-only body becomes `{}`, so the response reports the fields the schema then finds missing. A body that does not parse as JSON returns 400 `INVALID_FORM_BODY` with one element at path `body` and code `INVALID_FORMAT`.
|
||||
:::
|
||||
|
||||
Fluxer normalises empty values on all four targets before validation runs. An empty string becomes `null` wherever it appears, including inside an array element. A nested object becomes `null` when it holds no members, and it becomes `null` when every one of its members is `null` after Fluxer has applied the same rule to each of them. The top-level object itself is never replaced, so a request that sends nothing still reaches the schema as an object and fails on the fields the schema requires.
|
||||
Fluxer normalises empty values on all four targets before validation runs. An empty string becomes `null` wherever it appears, including inside an array element. A nested object becomes `null` when it holds no members. It also becomes `null` when every one of its members is `null` after Fluxer has applied the same rule to each of them. The top-level object itself is never replaced, so a request that sends nothing still reaches the schema as an object and fails on the fields the schema requires.
|
||||
|
||||
:::caution[An enumerated validation failure answers 400 alone]
|
||||
A validation failure whose elements have enumerated codes answers 400 with its elements in `errors`, and its top-level code is `INVALID_FORM_BODY` everywhere except [Modify current user settings](/http-api/users/settings/#modify-current-user-settings). Any other status, retry guidance, or response header from the original failure is dropped.
|
||||
@@ -79,9 +85,9 @@ A boundary schema constraint can name its own [validation code](#validation-erro
|
||||
|
||||
## HTTP status fallback codes
|
||||
|
||||
A failure that has an HTTP status and no recognised Fluxer error code takes the fallback `code` its status selects below. An unclassified 401, 413, 422, or 429 falls back to `GENERAL_ERROR`. An error named by an operation or by one of the registries below keeps that more specific code.
|
||||
A failure that has an HTTP status and no recognised Fluxer error code takes the fallback `code` its status selects below. Where an operation or one of the registries below names the error, Fluxer keeps that more specific code. An unclassified 401, 413, 422, or 429 falls back to `GENERAL_ERROR`.
|
||||
|
||||
An unrecognised failure returns 500 with `INTERNAL_SERVER_ERROR` and a generic message that names no detail. A failure that has a registered API code but no status returns 400, except `GENERAL_ERROR`, which returns 500.
|
||||
Fluxer answers an unrecognised failure with 500 `INTERNAL_SERVER_ERROR` and a generic message that names no detail. A failure that has a registered API code but no status returns 400, except `GENERAL_ERROR`, which returns 500.
|
||||
|
||||
| Status | Code | Description |
|
||||
| --- | --- | --- |
|
||||
@@ -118,14 +124,14 @@ The window length, both thresholds, and the number of windows the score trigger
|
||||
|
||||
## API error code registry
|
||||
|
||||
These codes appear in the top-level `code` field of an error response, transmitted as the exact JSON string shown. The registry is closed and holds exactly 264 codes. Each entry states the leading sentence of the English source message, without its final full stop. Those messages call a [guild](/http-api/guilds/) a community.
|
||||
These codes appear in the top-level `code` field of an error response, transmitted as the exact JSON string shown. The registry is closed. Each entry states the leading sentence of the English source message, without its final full stop. Those messages call a [guild](/http-api/guilds/) a community.
|
||||
|
||||
:::note[The rendered `message` fills in the braced values]
|
||||
A description containing a value in braces is an ICU MessageFormat template. `You've reached the maximum of {count, plural, one {# emoji} other {# emojis}}` renders as a complete sentence with the applicable limit.
|
||||
:::
|
||||
|
||||
:::note[A rendered `message` can run longer than the registry entry]
|
||||
Several source messages append a further recovery sentence with a template value such as a maximum size, a format list, or a retry delay. A client branches on `code` and reads the members the operation documents.
|
||||
Several source messages append a further recovery sentence with a template value such as a maximum size, a format list, or a retry delay.
|
||||
:::
|
||||
|
||||
### `ACCESS_DENIED`
|
||||
@@ -1187,7 +1193,7 @@ You've reached the maximum of {count, plural, one {# WebAuthn credential} other
|
||||
|
||||
## Validation error code registry
|
||||
|
||||
These codes appear in the `code` field of an element in the top-level `errors` array on an `INVALID_FORM_BODY` response. Each names one specific input constraint. A schema failure whose constraint names no code of its own reports one of the [default schema failure codes](#default-schema-failure-codes). The registry is closed and holds exactly 236 codes. Each entry states the leading sentence of the English source message, the same way.
|
||||
These codes appear in the `code` field of an element in the top-level `errors` array on an `INVALID_FORM_BODY` response. Each names one specific input constraint. A schema failure whose constraint names no code of its own reports one of the [default schema failure codes](#default-schema-failure-codes). The registry is closed. Each entry states the leading sentence of the English source message, the same way.
|
||||
|
||||
### `ACCENT_COLOR_CHANGED_TOO_MANY_TIMES`
|
||||
|
||||
@@ -2142,4 +2148,4 @@ A failure raised before the request locale is resolved, such as an IP ban denial
|
||||
|
||||
Fluxer localises the `message` of a validation element that has a `code` the same way. A validation element with no `code` has the fixed English string written at the failure site. The `code` field is never localised, either in the envelope or in a validation element.
|
||||
|
||||
A code whose catalogue entry is missing for the resolved locale falls back to its English source template. A code with no registered template at all falls back to the message the failure supplied or to the code itself.
|
||||
A code whose catalogue entry is missing for the resolved locale falls back to its English source template. Where no template is registered at all, the `message` falls back to the one the failure supplied, or to the code itself.
|
||||
@@ -6,18 +6,16 @@ description: Cross-guild lookup of a custom emoji or sticker by its own ID.
|
||||
|
||||
import RouteHeader from '@/components/RouteHeader.astro';
|
||||
|
||||
An expression is a custom emoji or a sticker that one guild owns. The two routes here resolve an expression from its own identifier, without membership of the owning guild.
|
||||
An expression is a custom emoji or a sticker that one guild owns. The routes here resolve an expression from its own identifier, without membership of the owning guild.
|
||||
|
||||
| Object | Route | Owning resource |
|
||||
| --- | --- | --- |
|
||||
| [Emoji metadata](#emoji-metadata-object) | [Get emoji metadata](#get-emoji-metadata) | [Guild emojis](/http-api/guild-emojis/) |
|
||||
| [Sticker metadata](#sticker-metadata-object) | [Get sticker metadata](#get-sticker-metadata) | [Guild stickers](/http-api/guild-stickers/) |
|
||||
|
||||
Both routes are read-only, and neither writes an audit entry or emits a [Gateway Dispatch](/gateway/events/).
|
||||
Both routes are read-only, and neither writes an audit entry or emits a [Gateway Dispatch](/gateway/events/). Each one returns the metadata even when the owning guild has [UNAVAILABLE_FOR_EVERYONE](/http-api/guilds/#guild-features).
|
||||
|
||||
An identifier that names no stored expression returns 404 `UNKNOWN_EMOJI` or 404 `UNKNOWN_STICKER`. A stored expression whose guild no longer exists returns 404 `UNKNOWN_GUILD`.
|
||||
|
||||
Both routes return the metadata even when the owning guild has [UNAVAILABLE_FOR_EVERYONE](/http-api/guilds/#guild-features).
|
||||
When an identifier names no stored expression, Fluxer returns 404 `UNKNOWN_EMOJI` or 404 `UNKNOWN_STICKER`. A stored expression whose guild no longer exists returns 404 `UNKNOWN_GUILD`.
|
||||
|
||||
:::note[Tags and the uploader need a guild-scoped operation]
|
||||
Tags, the uploader, and the adult content classification come from the guild-scoped operations, which require membership of the owning guild.
|
||||
|
||||
@@ -53,7 +53,7 @@ Fluxer resolves only `url` from deployment configuration. `shards` and `session_
|
||||
|
||||
## Session start limit object
|
||||
|
||||
A session start limit reports how many new Gateway sessions a bot may open in a window. Fluxer publishes four constants here for client-library compatibility.
|
||||
A session start limit reports how many new Gateway sessions a bot may open in a window. Fluxer publishes constants here for client-library compatibility.
|
||||
|
||||
### Structure
|
||||
|
||||
@@ -70,7 +70,7 @@ A session start limit reports how many new Gateway sessions a bot may open in a
|
||||
|
||||
<sup>3</sup> Fluxer runs no Identify concurrency bucket, so a bot MAY identify its shards without pacing them against this value
|
||||
|
||||
The limits the Gateway actually enforces live in [Session lifecycle](/gateway/limits-and-rate-limits/#session-lifecycle). Fluxer budgets Identify per source address. It caps a user account at a fixed number of concurrent sessions. Neither bound is reported here.
|
||||
The limits the Gateway actually enforces are in [Session lifecycle](/gateway/limits-and-rate-limits/#session-lifecycle). Fluxer budgets Identify per source address and caps a user account at a fixed number of concurrent sessions. Neither bound is reported here.
|
||||
|
||||
:::caution[A remaining session start does not guarantee admission]
|
||||
A non-zero `remaining` reserves nothing, and neither does the constant `max_concurrency`. [Session admission](/gateway/limits-and-rate-limits/#session-lifecycle) can still hold or reject a connection.
|
||||
|
||||
@@ -18,13 +18,13 @@ A provider outage, a request that outlives its deadline, and an unreadable provi
|
||||
|
||||
## Locale and country
|
||||
|
||||
Every route takes an optional `locale` drawn from the [supported locale registry](/topics/locales/#supported-locales), in the query string on the four GET routes and in the body of [Register a GIF share](#register-a-gif-share). It defaults to `en-US`. A tag outside the registry fails with 400 `INVALID_FORM_BODY`.
|
||||
Every route takes an optional `locale` drawn from the [supported locale registry](/topics/locales/#supported-locales), in the query string on the GET routes and in the body of [Register a GIF share](#register-a-gif-share). It defaults to `en-US`. A tag outside the registry fails with 400 `INVALID_FORM_BODY`.
|
||||
|
||||
Fluxer also derives a two-letter country from the requesting address by geolocation, and uses `US` when geolocation resolves none. No route accepts the country as a parameter, so a client cannot override it. [Get GIF search suggestions](#get-gif-search-suggestions) is the one route that ignores it.
|
||||
|
||||
## Provider headers
|
||||
|
||||
Every response under `/gifs`, `/tenor`, and `/klipy` has these three headers, including error responses and responses to a path no route claims. The three values repeat the [GIF provider](/http-api/instance/#gif-provider-object) object, so a client follows a provider change without refetching `/.well-known/fluxer`.
|
||||
Every response under `/gifs`, `/tenor`, and `/klipy` has these headers, including error responses and responses to a path no route claims. The values repeat the [GIF provider](/http-api/instance/#gif-provider-object) object, so a client follows a provider change without refetching `/.well-known/fluxer`.
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
@@ -126,7 +126,7 @@ To open the category, a client sends `name` as the `q` of [Search GIFs](#search-
|
||||
|
||||
## Deprecated vendor paths
|
||||
|
||||
Two vendor-named prefixes serve the same five handlers as `/gifs`. Each answers exactly as its successor does and has the same [provider headers](#provider-headers).
|
||||
Two vendor-named prefixes serve the same handlers as `/gifs`. Each answers exactly as its successor does and has the same [provider headers](#provider-headers).
|
||||
|
||||
| Method | Deprecated path | Successor |
|
||||
| --- | --- | --- |
|
||||
@@ -142,10 +142,10 @@ Two vendor-named prefixes serve the same five handlers as `/gifs`. Each answers
|
||||
| POST | `/v1/klipy/register-share` | [Register a GIF share](#register-a-gif-share) |
|
||||
|
||||
:::caution[The vendor prefixes spell trending differently]
|
||||
Trending is `/trending` under `/gifs` and `/trending-gifs` under both vendor prefixes. The other four paths keep the same last segment on all three prefixes.
|
||||
Trending is `/trending` under `/gifs` and `/trending-gifs` under both vendor prefixes. The other paths keep the same last segment on all three prefixes.
|
||||
:::
|
||||
|
||||
A response from one of the ten has three further headers.
|
||||
A response from a deprecated path has these further headers.
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
@@ -223,7 +223,7 @@ Fluxer resolves the category list against the country `US` for every request, wh
|
||||
| 403 | [error response](/http-api/#error-response) | The instance has bound no provider key and the request returns `FEATURE_TEMPORARILY_DISABLED` |
|
||||
| 503 | [error response](/http-api/#error-response) | The provider failed, the request outlived its deadline, or the `gifs` service answered with a payload the API could not read, each returning `SERVICE_UNAVAILABLE` |
|
||||
|
||||
Fluxer fetches both halves in one operation. A failure on either one fails the whole request and returns no partial body.
|
||||
Fluxer fetches both halves in one operation. A failure on either `gifs` or `categories` fails the whole request and returns no partial body.
|
||||
|
||||
### Rate limit
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ A gift records no recipient, so Fluxer binds the code to whichever eligible acco
|
||||
|
||||
A gift records a duration. Fluxer computes the entitlement window at redemption time from the redeemer's existing state.
|
||||
|
||||
Both creation paths record a creator. A completed gift checkout records the purchaser, and an Admin API gift records the system account with ID `0` rather than the administrator that requested it. No operation unredeems a code.
|
||||
Both creation paths record a creator. A completed gift checkout records the purchaser. An Admin API gift records the system account with ID `0`, and no field names the administrator that requested it. No operation unredeems a code.
|
||||
|
||||
### Structure
|
||||
|
||||
@@ -77,7 +77,7 @@ A purchased gift is always whole months or whole years, because a purchase of tw
|
||||
|
||||
Reads a gift by its code. Returns the [gift](#gift-object) object on success.
|
||||
|
||||
A code that does not exist and a code that has been revoked both return 404 `UNKNOWN_GIFT_CODE`, so a revoked code is never distinguishable from one that was never issued.
|
||||
A code that does not exist and one that has been revoked both return 404 `UNKNOWN_GIFT_CODE`, so a revoked code is never distinguishable from one that was never issued.
|
||||
|
||||
A chargeback or a refund for the purchase revokes the gift when [Receive Stripe webhook](/http-api/billing/#receive-stripe-webhook) processes it and the gift is still unredeemed. The gift also leaves [List current user gifts](/http-api/users/gifts/#list-current-user-gifts), so the buyer has no route that reports the reversal. A gift that was already redeemed when the same event arrives stays readable, and Fluxer recomputes the redeemer's entitlement from their remaining redeemed gifts.
|
||||
|
||||
@@ -151,7 +151,14 @@ One redemption can be in flight for a code across the whole deployment, and a se
|
||||
|
||||
The gift becomes redeemed, so [Get gift](#get-gift) reports `redeemed` as true and [List current user gifts](/http-api/users/gifts/#list-current-user-gifts) shows the redemption time and the redeemer to the buyer.
|
||||
|
||||
A gift with a positive quantity extends recurring premium. Fluxer stacks the duration onto the payment provider subscription only when the account's premium type is subscription, its premium end is unset or in the future, it has a stored subscription identity, and the instance has a payment provider configured. Stacking adds the duration to the existing trial end, or to the current period end when no trial end is set, and applies the update without proration.
|
||||
A gift with a positive quantity extends recurring premium. Fluxer stacks the duration onto the payment provider subscription only when all of these hold:
|
||||
|
||||
- The account's premium type is subscription.
|
||||
- The account's premium end is unset or in the future.
|
||||
- The account has a stored subscription identity.
|
||||
- The instance has a payment provider configured.
|
||||
|
||||
Stacking adds the duration to the existing trial end, or to the current period end when no trial end is set, and applies the update without proration.
|
||||
|
||||
The stacked path and the unstacked path both set the gift extension end to the gift duration added to the latest of the current time, the current premium end and the existing gift extension end. Both also clear an active grace deadline and set the premium type when the account has none. The stacked path also records the account as having ever purchased.
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ An audit log entry records one change made to a guild and the account that made
|
||||
|
||||
An audit-capable route accepts the `X-Audit-Log-Reason` request header, listed with the other [standard request headers](/http-api/#standard-request-headers). Fluxer reads the value verbatim and never percent-decodes it, so a caller that percent-encodes the reason stores and reads back the percent-encoded form.
|
||||
|
||||
Fluxer trims the value. A value that is blank before trimming, empty after it, or longer than 512 characters after it counts as no reason at all. None of the three fails the request, so a reason that misses the bound is dropped and the operation still succeeds.
|
||||
Fluxer trims the value. A value that is blank before trimming, empty after it, or longer than 512 characters after it counts as no reason at all. None of those values fails the request, so a reason that misses the bound is dropped and the operation still succeeds.
|
||||
|
||||
The operation writes the accepted reason onto its entry, returns it as the entry `reason` field, and sends it in the [Guild Audit Log Entry Create](/gateway/events/#guild-audit-log-entry-create) Dispatch.
|
||||
|
||||
@@ -42,7 +42,7 @@ One page of audit history together with the accounts and webhooks it references.
|
||||
|
||||
## Guild audit log entry object
|
||||
|
||||
One recorded guild change. An entry is immutable once written, except for a run of message deletion entries, which [List guild audit logs](#list-guild-audit-logs) can replace with a single consolidated entry. The entry ID is a [snowflake](/snowflakes/), so it has the exact creation time.
|
||||
One recorded guild change. An entry is immutable once written, except for a run of message deletion entries, which [List guild audit logs](#list-guild-audit-logs) can replace with a single consolidated entry.
|
||||
|
||||
### Structure
|
||||
|
||||
@@ -193,7 +193,7 @@ One field a mutation changed, with the value on each side of the change.
|
||||
A change value is a string, a JSON number, a boolean, `null`, an array of strings, an array of numbers, or an object with `added` and `removed` string arrays. The last shape comes only from the [permissions_diff](#guild-role-change-fields) key of a role modification.
|
||||
|
||||
:::note[A 64-bit domain value is a decimal string]
|
||||
A permission mask or a snowflake is serialised as a decimal string. A bounded number such as a position or a colour remains a JSON number.
|
||||
Fluxer serialises a permission mask or a snowflake as a decimal string. A bounded number such as a position or a colour remains a JSON number.
|
||||
:::
|
||||
|
||||
:::caution[A change list holds only the differing fields]
|
||||
@@ -402,7 +402,7 @@ Recorded by [STICKER_CREATE](#audit-actions), [STICKER_UPDATE](#audit-actions),
|
||||
|
||||
## Audit log webhook object
|
||||
|
||||
A reduced [webhook object](/http-api/webhooks/#webhook-object). It omits the execution token, application ID, and creating user, and it names the avatar hash `avatar_hash` rather than `avatar`.
|
||||
A reduced [webhook object](/http-api/webhooks/#webhook-object). It omits the execution token, application ID, and creating user, and it names the avatar hash `avatar_hash` where the full object names it `avatar`.
|
||||
|
||||
### Structure
|
||||
|
||||
@@ -469,7 +469,7 @@ A request supplying neither `user_id` nor `action_type` deletes every run of two
|
||||
:::
|
||||
|
||||
:::note[The same consolidation also runs in the background]
|
||||
Fluxer also applies it to the 250 most recent entries about 30 seconds after a message deletion entry is written.
|
||||
Fluxer also applies consolidation to the 250 most recent entries about 30 seconds after a message deletion entry is written.
|
||||
:::
|
||||
|
||||
### Side effects
|
||||
|
||||
@@ -10,7 +10,7 @@ A guild channel is one of the rooms a guild is divided into, including the categ
|
||||
|
||||
## Access rules
|
||||
|
||||
A caller who is not a member of an existing guild receives 403 `MISSING_PERMISSIONS`. Every operation here returns 404 `UNKNOWN_GUILD` for a guild that does not exist.
|
||||
Every operation here returns 404 `UNKNOWN_GUILD` for a guild that does not exist. A caller who is not a member of an existing guild receives 403 `MISSING_PERMISSIONS`.
|
||||
|
||||
A request whose path names a guild that has [UNAVAILABLE_FOR_EVERYONE](/http-api/guilds/#guild-features) is refused with 403 `MISSING_ACCESS` before the operation runs.
|
||||
|
||||
@@ -189,7 +189,7 @@ Creates a guild channel and returns its [channel object](/http-api/channels/#cha
|
||||
|
||||
A value longer than 10000 characters is rejected with `STRING_LENGTH_INVALID` before normalisation runs. A parent that does not exist in this guild returns `INVALID_PARENT_CHANNEL`, and one that is not a category returns `PARENT_MUST_BE_CATEGORY`.
|
||||
|
||||
A parent category holds at most the instance-configured `max_channels_per_category` [limit](/http-api/instance/#limit-keys), defaulting to 50, and the guild holds at most `max_guild_channels`, defaulting to 500. Reaching the guild limit returns 400 `MAX_GUILD_CHANNELS` and reaching the category limit returns 400 `MAX_CATEGORY_CHANNELS`. Each message has the resolved limit. The new channel is created with no RTC region.
|
||||
The guild holds at most the instance-configured `max_guild_channels` [limit](/http-api/instance/#limit-keys), defaulting to 500, and a parent category holds at most `max_channels_per_category`, defaulting to 50. Reaching the guild limit returns 400 `MAX_GUILD_CHANNELS` and reaching the category limit returns 400 `MAX_CATEGORY_CHANNELS`. Each message has the resolved limit. The new channel is created with no RTC region.
|
||||
|
||||
The new channel takes a position derived from its siblings. A category, and a channel created with no parent, is placed after the highest position in the guild. A voice channel created inside a category is placed after the last voice sibling, or after the last text or link sibling when the category holds no voice channel. A text or link channel is placed after the last text or link sibling, and a channel with no such sibling is placed directly after the category itself. The rest of the guild is not renumbered, so two channels can hold the same stored position until the next [hierarchy update](#modify-guild-channel-positions) renumbers them.
|
||||
|
||||
|
||||
@@ -8,9 +8,9 @@ import RouteHeader from '@/components/RouteHeader.astro';
|
||||
|
||||
An emoji is a custom image that one guild owns. Members use it in messages and reactions. Its ID is a [snowflake](/snowflakes/) that is unique across every guild. [List guild emojis](#list-guild-emojis) is the only guild-scoped read, and [Get emoji metadata](/http-api/expressions/#get-emoji-metadata) resolves a single emoji without membership of its guild.
|
||||
|
||||
Every route names a guild in its path. A guild that has [UNAVAILABLE_FOR_EVERYONE](/http-api/guilds/#guild-features) rejects the request with 403 `MISSING_ACCESS` before the operation runs, and [UNAVAILABLE_FOR_EVERYONE_BUT_STAFF](/http-api/guilds/#guild-features) does the same for an account without the instance staff flag.
|
||||
Every route names a guild in its path. When that guild has [UNAVAILABLE_FOR_EVERYONE](/http-api/guilds/#guild-features), Fluxer rejects the request with 403 `MISSING_ACCESS` before the operation runs. Fluxer does the same for a guild with [UNAVAILABLE_FOR_EVERYONE_BUT_STAFF](/http-api/guilds/#guild-features) when the account has no instance staff flag.
|
||||
|
||||
A guild that does not exist returns 404 `UNKNOWN_GUILD`. A caller who is not a current member of an existing guild returns 403 `MISSING_PERMISSIONS`, so guild existence is distinguishable from guild membership. On [Modify guild emoji](#modify-guild-emoji) a non-member receives the same 403 `MISSING_PERMISSIONS`, and a guild that does not exist returns 404 `UNKNOWN_EMOJI`.
|
||||
A guild that does not exist returns 404 `UNKNOWN_GUILD`. Fluxer returns 403 `MISSING_PERMISSIONS` to a caller who is not a current member of an existing guild, so guild existence is distinguishable from guild membership. On [Modify guild emoji](#modify-guild-emoji) a non-member receives the same 403 `MISSING_PERMISSIONS`, and a guild that does not exist returns 404 `UNKNOWN_EMOJI`.
|
||||
|
||||
The instance phrase and URL blocklists screen a submitted emoji `name` before the operation runs, and a match returns 403 `CONTENT_BLOCKED`. A name of fewer than three characters is not scanned. Neither blocklist reads `image`. Fluxer checks its decoded bytes against the banned asset hash list when it stores them.
|
||||
|
||||
@@ -112,7 +112,7 @@ One item a bulk creation rejected, identified only by the name the caller submit
|
||||
|
||||
<sup>2</sup> Rendered in the locale of the authenticated account, so its value changes with the caller's locale
|
||||
|
||||
There is no machine-readable code, so a client that needs to branch on the reason retries the item on its own. An item that fails because the guild is full renders the emoji limit message with the resolved limit. Every other failure with a registered [error code](/http-api/errors/) renders that code's own message. An undecodable or unaccepted image renders the generic `INVALID_FORM_BODY` message with no per-field validation code. A banned image hash renders the `CONTENT_BLOCKED` message. A failure with no registered code, such as an object storage fault, renders a fixed unknown-error message.
|
||||
There is no machine-readable code, so a client that needs to branch on the reason retries the item on its own. An item rejected because the guild is full renders the emoji limit message with the resolved limit. Every other failure with a registered [error code](/http-api/errors/) renders that code's own message. An undecodable or unaccepted image renders the generic `INVALID_FORM_BODY` message with no per-field validation code. A banned image hash renders the `CONTENT_BLOCKED` message. A failure with no registered code, such as an object storage fault, renders a fixed unknown-error message.
|
||||
|
||||
## List guild emojis
|
||||
|
||||
@@ -175,7 +175,7 @@ The body is one [emoji create object](#emoji-create-object).
|
||||
|
||||
<sup>2</sup> The [error code](/http-api/errors/) is `CONTENT_BLOCKED` for a blocked name and for a banned image hash, `MISSING_ACCESS` for an unavailable guild, and `MISSING_PERMISSIONS` for a membership or permission failure
|
||||
|
||||
An image the decoder cannot read returns `INVALID_IMAGE_FORMAT`. The name scan runs before the route is reached, so it precedes the rate limit bucket and the credential check. The image hash check runs at upload time, after every other admission step has passed.
|
||||
The name scan runs before the route is reached, so it precedes the rate limit bucket and the credential check. Fluxer checks the image hash at upload time, after every other admission step has passed.
|
||||
|
||||
### Side effects
|
||||
|
||||
@@ -228,7 +228,7 @@ Every item name is scanned together before the route is reached, so one blocked
|
||||
|
||||
### Side effects
|
||||
|
||||
Each successful item consumes one guild emoji slot, stores its image, and writes an [`EMOJI_CREATE`](/http-api/guild-audit-logs/#audit-actions) audit entry with the supplied reason. One [Guild Emojis Update](/gateway/events/#guild-emojis-update) with the guild's complete emoji collection is emitted after the batch when at least one item succeeded, and it is emitted before the audit entries are written. A failed item leaves no emoji, audit entry, or slot consumption.
|
||||
Each successful item consumes one guild emoji slot, stores its image, and writes an [`EMOJI_CREATE`](/http-api/guild-audit-logs/#audit-actions) audit entry with the supplied reason. When at least one item succeeded, Fluxer emits one [Guild Emojis Update](/gateway/events/#guild-emojis-update) with the guild's complete emoji collection after the batch, then writes the audit entries. A failed item leaves no emoji, audit entry, or slot consumption.
|
||||
|
||||
### Rate limit
|
||||
|
||||
|
||||
@@ -144,7 +144,7 @@ Searches the guild member index. Returns a [guild member search response](#guild
|
||||
### Limitations
|
||||
|
||||
- The caller must also hold at least one of [MANAGE_GUILD](/http-api/permissions/), [MANAGE_ROLES](/http-api/permissions/), [MANAGE_NICKNAMES](/http-api/permissions/), [BAN_MEMBERS](/http-api/permissions/), [MODERATE_MEMBERS](/http-api/permissions/), or [KICK_MEMBERS](/http-api/permissions/).
|
||||
- Five of the six qualifying bits are [elevated permissions](/http-api/permissions/#elevated-permissions). In a guild whose [MFA level](/http-api/guilds/#mfa-levels) is elevated, a caller other than the guild owner who holds one of those five and has no enrolled authenticator receives 400 `TWO_FACTOR_REQUIRED`. `MANAGE_NICKNAMES` is not elevated, so a caller who qualifies through it alone never receives that code.
|
||||
- Every qualifying bit except `MANAGE_NICKNAMES` is one of the [elevated permissions](/http-api/permissions/#elevated-permissions). In a guild whose [MFA level](/http-api/guilds/#mfa-levels) is elevated, a caller other than the guild owner who holds one of those elevated bits and has no enrolled authenticator receives 400 `TWO_FACTOR_REQUIRED`. A caller who qualifies through `MANAGE_NICKNAMES` alone never receives that code.
|
||||
- A non-member of an existing guild receives the same 403 `MISSING_PERMISSIONS` as a member holding none of the qualifying bits, so a caller cannot tell the two apart.
|
||||
|
||||
A guild that does not exist returns 404 `UNKNOWN_GUILD`.
|
||||
@@ -193,9 +193,9 @@ Every field is optional, and an omitted filter is not applied. The route reads a
|
||||
|
||||
[Input normalisation](/http-api/#input-normalisation) rewrites `""` to null, which the `query` field rejects with 400 `INVALID_FORM_BODY`. `" "` is accepted.
|
||||
|
||||
With no `sort_by`, the route sorts by join time and defaults `sort_order` to `desc`. Two members with the same join time are ordered by descending `id`. `relevance` ignores `sort_order`. A Meilisearch instance orders `relevance` by its own ranking rules. An Elasticsearch instance orders `relevance` by descending `id`.
|
||||
With no `sort_by`, the route sorts by join time and defaults `sort_order` to `desc`. Two members with the same join time are ordered by descending `id`. `relevance` ignores `sort_order`. A Meilisearch instance orders a `relevance` search by its own ranking rules, and an Elasticsearch instance by descending `id`.
|
||||
|
||||
Text matching runs over the username, the discriminator, the global display name, the guild nickname, and the user ID, and a query can match the middle of a username as well as its start. Term matching and typo tolerance belong to the search backend. Filter matching is exact in every case.
|
||||
Text matching runs over the username, the discriminator, the global display name, the guild nickname, and the user ID. A query can match the middle of a username as well as its start. The search backend defines term matching and typo tolerance. Filter matching is exact in every case.
|
||||
|
||||
:::caution[A deep `offset` returns 200 with an empty page]
|
||||
Elasticsearch walks past its 10000 document window with an internal cursor and serves an arbitrary `offset`. Meilisearch caps the window at 10000 and returns an empty page beyond it.
|
||||
|
||||
@@ -13,7 +13,7 @@ A guild that no Gateway process serves returns 404 `UNKNOWN_GUILD`. An existing
|
||||
The two modify operations return the resulting membership, [Transfer guild ownership](#transfer-guild-ownership) returns the updated guild, and every other mutation returns 204 with an empty body.
|
||||
|
||||
:::note[Fluxer reads the target before authorising the caller]
|
||||
This applies to the two modify operations, [Remove guild member](#remove-guild-member), [Add guild member role](#add-guild-member-role), and [Remove guild member role](#remove-guild-member-role). A request whose target is not a member returns 404 `UNKNOWN_MEMBER` whether or not the caller can see the guild.
|
||||
A request whose target is not a member returns 404 `UNKNOWN_MEMBER` whether or not the caller can see the guild. The rule covers the two modify operations, [Remove guild member](#remove-guild-member), [Add guild member role](#add-guild-member-role), and [Remove guild member role](#remove-guild-member-role).
|
||||
:::
|
||||
|
||||
## Guild member object
|
||||
@@ -49,7 +49,7 @@ Every field except `user` describes state that belongs to the membership.
|
||||
|
||||
<sup>6</sup> Omitted while the stored value is 0, so absence means no flag is set and, for `mention_flags`, that the account-level preference applies
|
||||
|
||||
Premium sanitisation hides the stored avatar and banner hashes and keeps them on the membership. Fluxer applies it once the account has lost its premium entitlement, when the membership holds a guild avatar, banner, biography, or accent colour.
|
||||
Premium sanitisation hides the stored avatar and banner hashes and keeps them on the membership. Fluxer applies it after the account loses its premium entitlement, to each membership that holds a guild avatar, banner, biography, or accent colour.
|
||||
|
||||
A client compares `communication_disabled_until` against the current time, because a non-null value can name a moment that has already passed. A [communication timeout](/http-api/permissions/#communication-timeout) does not reduce the member's computed permission mask.
|
||||
|
||||
@@ -76,7 +76,7 @@ A client compares `communication_disabled_until` against the current time, becau
|
||||
|
||||
## Guild member profile flags
|
||||
|
||||
A guild profile asset has three states. With no flag and no hash the membership inherits the account-level asset. A stored hash sets a guild-specific asset. The flag below suppresses inheritance and renders the default.
|
||||
A guild profile asset has three states. A membership with no flag and no stored hash inherits the account-level asset. A stored hash sets a guild-specific asset. The flag below suppresses inheritance and renders the default.
|
||||
|
||||
| Value | Name | Description |
|
||||
| --- | --- | --- |
|
||||
@@ -127,7 +127,7 @@ The request body shared by [Modify current guild member](#modify-current-guild-m
|
||||
|
||||
A `roles` entry that does not resolve to an existing role of the guild is dropped from the replacement, so a request naming only unknown roles clears the member's role set.
|
||||
|
||||
An `avatar` or `banner` base64 payload longer than 13981016 characters returns `BASE64_LENGTH_INVALID`, and a malformed one returns `INVALID_BASE64_FORMAT`. The decoded bytes are then checked against the instance-configured `avatar_max_size` [limit](/http-api/instance/#limit-keys), whose stock value is 10485760, and the same ceiling covers both fields. The decoded image must also pass the format allowlist and the animation rules of the asset policy for the field it sets. Pixel dimensions are not enforced. A value that is too large returns `IMAGE_SIZE_EXCEEDS_LIMIT`, and one whose format or animation is not allowed returns `INVALID_IMAGE_FORMAT`.
|
||||
An `avatar` or `banner` base64 payload longer than 13981016 characters returns `BASE64_LENGTH_INVALID`, and a malformed one returns `INVALID_BASE64_FORMAT`. Fluxer then checks the decoded bytes against the instance-configured `avatar_max_size` [limit](/http-api/instance/#limit-keys), whose stock value is 10485760, and the same ceiling covers both fields. The decoded image must also pass the format allowlist and the animation rules of the asset policy for the field it sets. Pixel dimensions are not enforced. A value that is too large returns `IMAGE_SIZE_EXCEEDS_LIMIT`, and one whose format or animation is not allowed returns `INVALID_IMAGE_FORMAT`.
|
||||
|
||||
Null and any `communication_disabled_until` that is not in the future both clear the timeout. A time more than 365.25 days ahead returns `TIMEOUT_CANNOT_EXCEED_365_DAYS`, and a value that passes the schema but is not a real instant returns `INVALID_TIMEOUT_VALUE`. `timeout_reason` has no effect unless `communication_disabled_until` is also supplied.
|
||||
|
||||
@@ -240,7 +240,7 @@ A caller without `CHANGE_NICKNAME` does not fail the request. Fluxer discards th
|
||||
|
||||
A supplied `nick`, `bio`, or `pronouns` is scanned against the instance phrase, URL, and profile substring blocklists, and a match returns 403 `CONTENT_BLOCKED`.
|
||||
|
||||
Guild avatar, banner, biography, and accent colour additionally require the instance-configured `feature_per_guild_profiles` [limit](/http-api/instance/#limit-keys) for the calling account. Fluxer discards any of those four fields supplied without that capability and applies every other field. Pronouns, `profile_flags`, and `mention_flags` are not gated that way.
|
||||
Guild avatar, banner, biography, and accent colour additionally require the instance-configured `feature_per_guild_profiles` [limit](/http-api/instance/#limit-keys) for the calling account. Fluxer discards any of those fields supplied without that capability and applies every other field. Pronouns, `profile_flags`, and `mention_flags` do not require it.
|
||||
|
||||
A `channel_id` destination must be a guild voice channel, and the caller must hold both [VIEW_CHANNEL](/http-api/permissions/) and [CONNECT](/http-api/permissions/) there. Supplying `channel_id` for a member with no live voice connection returns 400 `USER_NOT_IN_VOICE`. Supplying `mute` or `deaf` for such a member succeeds, stores the flags, and emits no voice Dispatch.
|
||||
|
||||
@@ -283,7 +283,7 @@ The target's own sessions always receive the Dispatch. Other guild sessions rece
|
||||
|
||||
Supplying `channel_id` moves or disconnects every voice connection the member holds, or only the one named by `connection_id`. It records one [`MEMBER_MOVE`](/http-api/guild-audit-logs/#audit-actions) or [`MEMBER_DISCONNECT`](/http-api/guild-audit-logs/#audit-actions) audit entry for the whole request, whatever the number of connections it touched, and emits [Voice State Update](/gateway/events/#voice-state-update) to sessions that can view the affected channel. Supplying `mute` or `deaf` updates the membership, records no separate audit entry, and emits the same voice Dispatch once for each live connection.
|
||||
|
||||
Every audit entry also emits [Guild Audit Log Entry Create](/gateway/events/#guild-audit-log-entry-create). A request that fails after uploading a new avatar or banner leaves the previous asset in place.
|
||||
Fluxer emits [Guild Audit Log Entry Create](/gateway/events/#guild-audit-log-entry-create) for every audit entry. A request that fails after uploading a new avatar or banner leaves the previous asset in place.
|
||||
|
||||
### Rate limit
|
||||
|
||||
@@ -307,13 +307,13 @@ A supplied `nick` is scanned against the instance phrase, URL, and profile subst
|
||||
|
||||
Hierarchy authority over the target is not consulted for `roles`, so a caller who outranks every affected role can replace the role set of a member who outranks them. The everyone role in the array is rejected with the field code `INVALID_ROLE_ID`.
|
||||
|
||||
The target's own permissions in the destination are never consulted, so a member can be moved into a channel they could not join themselves. Disconnecting sets `channel_id` to null and requires the same permission.
|
||||
Fluxer never checks the target's own permissions in the destination, so a member can be moved into a channel they could not join themselves. Disconnecting sets `channel_id` to null and requires the same permission.
|
||||
|
||||
Supplying `channel_id` for a target with no live voice connection, or a `connection_id` that is not one of the target's own, returns 400 `USER_NOT_IN_VOICE`. Supplying `mute` or `deaf` for such a target succeeds and stores the flags. A destination that does not exist returns the field code `CHANNEL_DOES_NOT_EXIST`, and one that is not a voice channel `CHANNEL_MUST_BE_VOICE`.
|
||||
|
||||
`MANAGE_ROLES` and `MODERATE_MEMBERS` are [elevated permissions](/http-api/permissions/#elevated-permissions), so replacing `roles` or applying `communication_disabled_until` additionally requires an enrolled multi-factor authenticator when the guild [MFA level](/http-api/guilds/#mfa-levels) is elevated and the caller is not the guild owner.
|
||||
|
||||
A caller addressing their own user ID here falls under the self-targeted rules of [Modify current guild member](#modify-current-guild-member) for the guild profile fields, and the body remains the complete update object, so `roles` is accepted. Replacing their own role set still requires `MANAGE_ROLES` and hierarchy authority, and `communication_disabled_until` is still rejected with 403 `MISSING_PERMISSIONS`.
|
||||
A caller addressing their own user ID here falls under the self-targeted rules of [Modify current guild member](#modify-current-guild-member) for the guild profile fields. The body remains the complete update object, so `roles` is accepted. Replacing their own role set still requires `MANAGE_ROLES` and hierarchy authority, and `communication_disabled_until` is still rejected with 403 `MISSING_PERMISSIONS`.
|
||||
|
||||
:::note[Guild profile fields belong to their own account]
|
||||
`avatar`, `banner`, `bio`, `pronouns`, `accent_color`, `profile_flags`, and `mention_flags` describe the target's own profile, so a moderator cannot set them. Supplying one for another member leaves it unwritten, and the request still applies every field the caller may set.
|
||||
@@ -394,7 +394,7 @@ The operation retains the member's first join time, leave time, and any stored c
|
||||
|
||||
It deletes the membership row and decreases the guild's member count, so the guild nickname, guild avatar hash, guild banner hash, biography, pronouns, accent colour, and role set are lost. The removed account's read states and guild settings are left untouched, and the guild is not removed from that account's guild folders.
|
||||
|
||||
The operation records a [`MEMBER_KICK`](/http-api/guild-audit-logs/#audit-actions) audit entry with no change list and emits [Guild Audit Log Entry Create](/gateway/events/#guild-audit-log-entry-create). Remaining guild sessions receive [Guild Member Remove](/gateway/events/#guild-member-remove), subject to [event filtering](/gateway/event-filtering/). The removed account's sessions receive [Guild Delete](/gateway/events/#guild-delete).
|
||||
Fluxer records a [`MEMBER_KICK`](/http-api/guild-audit-logs/#audit-actions) audit entry with no change list and emits [Guild Audit Log Entry Create](/gateway/events/#guild-audit-log-entry-create). Remaining guild sessions receive [Guild Member Remove](/gateway/events/#guild-member-remove), subject to [event filtering](/gateway/event-filtering/). The removed account's sessions receive [Guild Delete](/gateway/events/#guild-delete).
|
||||
|
||||
### Rate limit
|
||||
|
||||
@@ -470,7 +470,7 @@ Adds one role to a member and returns 204 with an empty body. Requires membershi
|
||||
- The guild owner bypasses the permission and the hierarchy check.
|
||||
- The everyone role cannot be assigned and is rejected with the field code `INVALID_ROLE_ID`.
|
||||
|
||||
`MANAGE_ROLES` is an [elevated permission](/http-api/permissions/#elevated-permissions), so it additionally requires an enrolled multi-factor authenticator when the guild [MFA level](/http-api/guilds/#mfa-levels) is elevated and the caller is not the guild owner. Every accepted request emits [Guild Member Update](/gateway/events/#guild-member-update), including one that changes nothing.
|
||||
`MANAGE_ROLES` is an [elevated permission](/http-api/permissions/#elevated-permissions), so it additionally requires an enrolled multi-factor authenticator when the guild [MFA level](/http-api/guilds/#mfa-levels) is elevated and the caller is not the guild owner.
|
||||
|
||||
The guild owner receives 404 `UNKNOWN_ROLE` for a role that does not exist in the guild. Any other caller receives 403 `MISSING_PERMISSIONS`.
|
||||
|
||||
@@ -497,7 +497,7 @@ The guild owner receives 404 `UNKNOWN_ROLE` for a role that does not exist in th
|
||||
|
||||
### Side effects
|
||||
|
||||
When the member does not already hold the role, the operation adds it and makes a temporary membership permanent. It then updates member search results, records a [`MEMBER_ROLE_UPDATE`](/http-api/guild-audit-logs/#audit-actions) audit entry, emits [Guild Audit Log Entry Create](/gateway/events/#guild-audit-log-entry-create), and emits [Guild Member Update](/gateway/events/#guild-member-update), and it does all four whether or not the role set changed. A request naming a role the member already holds still writes an audit entry with an empty change list, published without a `changes` member.
|
||||
When the member does not already hold the role, the operation adds it and makes a temporary membership permanent. It then updates member search results, records a [`MEMBER_ROLE_UPDATE`](/http-api/guild-audit-logs/#audit-actions) audit entry, emits [Guild Audit Log Entry Create](/gateway/events/#guild-audit-log-entry-create), and emits [Guild Member Update](/gateway/events/#guild-member-update). All four run whether or not the role set changed. A request naming a role the member already holds still writes an audit entry with an empty change list, published without a `changes` member.
|
||||
|
||||
The audit log response names the role only through the change list.
|
||||
|
||||
@@ -516,7 +516,7 @@ Removes one role from a member and returns 204 with an empty body. Requires memb
|
||||
- The guild owner bypasses the permission and the hierarchy check.
|
||||
- The everyone role cannot be removed and is rejected with the field code `INVALID_ROLE_ID`.
|
||||
|
||||
`MANAGE_ROLES` is an [elevated permission](/http-api/permissions/#elevated-permissions), so it additionally requires an enrolled multi-factor authenticator when the guild [MFA level](/http-api/guilds/#mfa-levels) is elevated and the caller is not the guild owner. Every accepted request emits [Guild Member Update](/gateway/events/#guild-member-update), including one that changes nothing. A role that does not exist is reported exactly as it is for [Add guild member role](#add-guild-member-role).
|
||||
`MANAGE_ROLES` is an [elevated permission](/http-api/permissions/#elevated-permissions), so it additionally requires an enrolled multi-factor authenticator when the guild [MFA level](/http-api/guilds/#mfa-levels) is elevated and the caller is not the guild owner. A role that does not exist is reported exactly as it is for [Add guild member role](#add-guild-member-role).
|
||||
|
||||
### Path parameters
|
||||
|
||||
@@ -541,7 +541,7 @@ Removes one role from a member and returns 204 with an empty body. Requires memb
|
||||
|
||||
### Side effects
|
||||
|
||||
When the member holds the role, the operation removes it. It then updates member search results, records a [`MEMBER_ROLE_UPDATE`](/http-api/guild-audit-logs/#audit-actions) audit entry, emits [Guild Audit Log Entry Create](/gateway/events/#guild-audit-log-entry-create), and emits [Guild Member Update](/gateway/events/#guild-member-update), and it does all four whether or not the role set changed. Removing a role does not change whether the membership is temporary.
|
||||
When the member holds the role, the operation removes it. It then updates member search results, records a [`MEMBER_ROLE_UPDATE`](/http-api/guild-audit-logs/#audit-actions) audit entry, emits [Guild Audit Log Entry Create](/gateway/events/#guild-audit-log-entry-create), and emits [Guild Member Update](/gateway/events/#guild-member-update). All four run whether or not the role set changed. Removing a role does not change whether the membership is temporary.
|
||||
|
||||
### Rate limit
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ A ban also stores the target's last known IP address and the lower-cased address
|
||||
|
||||
Accepting an [invite](/http-api/invites/) compares the joining account against the banned account identifier, the stored address, and the stored email. The address comparison uses a normalised decision key: the exact address for IPv4, the mapped IPv4 address for an IPv4-mapped IPv6 address, and the `/64` network for every other IPv6 address. An address match refuses the join with 403 `USER_IP_BANNED_FROM_GUILD`.
|
||||
|
||||
Fluxer skips the address match when the joining address is on the instance exemption list, and also when the banned address is a single address and the joining address is reported as high mobile or carrier-grade NAT blast radius. The match runs when that report is unavailable or fails.
|
||||
Fluxer skips the address match when the joining address is on the instance exemption list. It also skips the match when the banned address is a single address and the joining address is reported as high mobile or carrier-grade NAT blast radius. The match runs when that report is unavailable or fails.
|
||||
|
||||
The email comparison is exact and runs only when an invite is accepted. An email match refuses the join with 403 `USER_BANNED_FROM_GUILD`. Every other path that adds a member compares the account identifier and the address alone, and several compare nothing. [Grant OAuth2 consent](/http-api/oauth2/#grant-oauth2-consent) installing a bot, the admin membership operations, the stock community auto-join, and the premium entitlement guild join all add the member without consulting the ban list.
|
||||
|
||||
|
||||
@@ -255,7 +255,7 @@ A creation template describes the roles and channels that [Create guild](#create
|
||||
|
||||
<sup>3</sup> The identifier is applied only when it resolves to a category in the same template, and every other value leaves the channel at the guild root
|
||||
|
||||
<sup>4</sup> The three voice fields are stored only on a voice channel and are null on every other channel type
|
||||
<sup>4</sup> These voice fields are stored only on a voice channel and are null on every other channel type
|
||||
|
||||
<sup>5</sup> An entry is applied only when its resolved `type` is 0 and its `id` resolves to a role in the same template, and the identifier `0` resolves to the everyone role. Fluxer skips every other entry, and a channel whose entries are all skipped is created with no overwrites
|
||||
|
||||
@@ -303,7 +303,7 @@ The guild owner, a bot, and any member holding at least one role bypass the chec
|
||||
|
||||
<sup>1</sup> Only the guild owner can change this value, the owner account needs a second factor already configured, and the change requires sudo mode
|
||||
|
||||
While the level is ELEVATED, a caller other than the guild owner exercises the nine [elevated permissions](/http-api/permissions/#elevated-permissions) only with an enrolled authenticator, and a bot inherits the enrolment state of its application owner. An operation that asserts an elevated permission the caller holds but cannot exercise returns 400 `TWO_FACTOR_REQUIRED` after the permission itself has been confirmed.
|
||||
While the level is ELEVATED, a caller other than the guild owner exercises the [elevated permissions](/http-api/permissions/#elevated-permissions) only with an enrolled authenticator, and a bot inherits the enrolment state of its application owner. An operation that asserts an elevated permission the caller holds but cannot exercise returns 400 `TWO_FACTOR_REQUIRED` after the permission itself has been confirmed.
|
||||
|
||||
## Splash card alignments
|
||||
|
||||
@@ -407,7 +407,7 @@ A feature is a capability or availability flag in the guild's `features` array.
|
||||
|
||||
<sup>1</sup> The feature is added or removed through [Modify guild](#modify-guild), as part of the complete array. Send every feature without this marker back to `features` exactly as the guild holds it
|
||||
|
||||
<sup>2</sup> The feature changes no HTTP API behaviour. An instance can name it in a filter of the ordered [limit configuration](/http-api/instance/#limit-keys), and the stock configuration names none of the three
|
||||
<sup>2</sup> The feature changes no HTTP API behaviour. An instance can name it in a filter of the ordered [limit configuration](/http-api/instance/#limit-keys), and the stock configuration names none of them
|
||||
|
||||
<sup>3</sup> The expression operations raise the slot ceiling directly, outside the instance limit configuration
|
||||
|
||||
@@ -465,7 +465,7 @@ These fields prove [sudo mode](/http-api/users/mfa/#sudo-mode) when an operation
|
||||
|
||||
<sup>2</sup> The MFA proof is accepted only when the account has a second factor, and it returns the field code `INVALID_MFA_CODE` on any failure
|
||||
|
||||
A bot credential satisfies sudo mode without any proof. An account that has no password hash and no second factor also satisfies it without any proof. Every other account that supplies no usable proof receives 403 `SUDO_MODE_REQUIRED`, whose error object has `has_mfa` and a `methods` object reporting whether `totp` and `webauthn` are available.
|
||||
A bot credential satisfies sudo mode without any proof, and so does an account that has no password hash and no second factor. Every other account that supplies no usable proof receives 403 `SUDO_MODE_REQUIRED`, whose error object has `has_mfa` and a `methods` object reporting whether `totp` and `webauthn` are available.
|
||||
|
||||
Fluxer issues a newly generated proof in the `X-Fluxer-Sudo-Mode-JWT` header of the success response, and only for an account that has a second factor. A proof supplied on the request is echoed back in that same header.
|
||||
|
||||
@@ -507,7 +507,7 @@ The `icon` base64 payload is bounded to 1 to 13981016 characters and is otherwis
|
||||
Without a template the guild is created with a `Text Channels` category holding a text channel named `general`, a `Voice Channels` category holding a voice channel named `General`, and an everyone role with the default permission set. The `general` channel becomes the system channel.
|
||||
|
||||
:::note[Embedded collections arrive on the Guild Create Dispatch]
|
||||
The creation response has none of them. Read [Get guild](#get-guild) afterwards to fetch roles, channels, emojis, stickers, and member counts over HTTP.
|
||||
The creation response has no roles, channels, emojis, stickers, or member counts. Read [Get guild](#get-guild) afterwards to fetch them over HTTP.
|
||||
:::
|
||||
|
||||
### Response
|
||||
@@ -549,7 +549,7 @@ Returns an array of [guild objects](#guild-object), one for every guild the auth
|
||||
|
||||
<sup>2</sup> A guild whose counts cannot be fetched is returned without them
|
||||
|
||||
A membership whose guild record can no longer be resolved is dropped before the cursor and `limit` are applied, so the page still has up to `limit` guilds when further guilds remain. This route runs no availability screen, and a guild with `UNAVAILABLE_FOR_EVERYONE` is still listed.
|
||||
Fluxer drops a membership whose guild record can no longer be resolved before it applies the cursor and `limit`, so the page still has up to `limit` guilds when further guilds remain. This route runs no availability screen, and a guild with `UNAVAILABLE_FOR_EVERYONE` is still listed.
|
||||
|
||||
:::caution[`permissions` needs a limit of 100 or lower]
|
||||
A page of more than 100 guilds, or a failed lookup, omits `permissions` from every guild. The request still succeeds.
|
||||
|
||||
@@ -20,7 +20,7 @@ The API applies no generic byte limit to a request body. An operation that accep
|
||||
|
||||
Every request counts against one in-flight request ceiling for the whole instance. A request that arrives while the instance is at that ceiling returns 503 `SERVICE_UNAVAILABLE` with `Retry-After: 1` before the operation runs. The `/_health`, `/_healthz`, and `/_metrics` probe paths are exempt.
|
||||
|
||||
A request to a path that matches no route returns 404 `NOT_FOUND`. A request whose path is registered but not for the request method is answered the same way, and the response has no `Allow` header. Routing is strict, so a trailing slash is significant.
|
||||
A request to a path that matches no route returns 404 `NOT_FOUND`. A request using a method the path does not register returns the same 404, and the response has no `Allow` header. Routing is strict, so a trailing slash is significant.
|
||||
|
||||
The `GET` registered for a path also serves `HEAD`. A path that registers no `GET` serves no `HEAD` either. A `HEAD` response has the status and headers that `GET` returns, with no body. The request still reports `HEAD` as its method, so the [same-host origin check](#cross-origin-requests) can refuse a `HEAD` that has no `Origin` where the identical `GET` succeeds.
|
||||
|
||||
@@ -45,7 +45,13 @@ Form bodies accept `application/x-www-form-urlencoded` and `multipart/form-data`
|
||||
|
||||
The legacy names `file` and `file` followed by an index are accepted as file fields as well, and a bare `file` takes the next free legacy index.
|
||||
|
||||
Five field-name failures are rejected with their own code. An index outside either bound returns `FILE_INDEX_EXCEEDS_MAXIMUM`. Any other name beginning with `files[` returns `INVALID_FILE_FIELD_NAME`. Two file fields resolving to the same index return `DUPLICATE_FILE_INDEX`. More than one file supplied for one index returns `MULTIPLE_FILES_FOR_INDEX_NOT_ALLOWED`. Where the resolved limit is 0, any file field at all returns `ATTACHMENTS_NOT_ALLOWED_FOR_MESSAGE`.
|
||||
Field-name failures are rejected with their own code:
|
||||
|
||||
- An index outside either bound returns `FILE_INDEX_EXCEEDS_MAXIMUM`.
|
||||
- Any other name beginning with `files[` returns `INVALID_FILE_FIELD_NAME`.
|
||||
- Two file fields resolving to the same index return `DUPLICATE_FILE_INDEX`.
|
||||
- More than one file supplied for one index returns `MULTIPLE_FILES_FOR_INDEX_NOT_ALLOWED`.
|
||||
- Where the resolved limit is 0, any file field at all returns `ATTACHMENTS_NOT_ALLOWED_FOR_MESSAGE`.
|
||||
|
||||
The `Content-Type` header supplies the multipart boundary. Each part's field name is in `Content-Disposition`. A body the multipart parser cannot read is rejected with `FAILED_TO_PARSE_MULTIPART_FORM_DATA`. A field name the operation does not recognise is ignored, and a `files[n]` part whose value is not a file is ignored once its index has been bounds-checked.
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ description: Instance discovery, client geolocation, the limit key registry, and
|
||||
|
||||
import RouteHeader from '@/components/RouteHeader.astro';
|
||||
|
||||
The Instance resource describes how one Fluxer deployment is set up. It publishes the discovery document, the client geolocation lookup, and the served OpenAPI document, and it owns the [limit key](#limit-keys) registry that every other page cites.
|
||||
The Instance resource describes how one Fluxer deployment is set up. It publishes the discovery document, the client geolocation lookup, and the served OpenAPI document, and it owns the [limit key](#limit-keys) registry.
|
||||
|
||||
[Get instance discovery](#get-instance-discovery) is the entry point of the API. A client that knows only a Fluxer origin reads `/.well-known/fluxer` first, and that one unauthenticated response has every field of the [instance discovery object](#instance-discovery-object). Those values include the API base URLs, the [main Gateway](/gateway/overview/) WebSocket URL, and the [Media Proxy](/media-proxy/overview/) base URL, and they override the [endpoint path defaults](#instance-endpoints-object) a deployment would otherwise serve from its canonical public origin.
|
||||
|
||||
@@ -239,13 +239,19 @@ The ordered rules a client evaluates to work out the limits that apply to an acc
|
||||
|
||||
<sup>2</sup> The value is computed from the release's default limit values alone, so an operator editing a rule leaves it unchanged. It changes when a release changes the defaults, which invalidates a resolution a client cached against the older ones
|
||||
|
||||
Each rule publishes only the keys whose values differ from the release defaults. The defaults themselves are not published in this document, so a client MUST hold its own copy of them. A client MUST also rebuild every rule into a full limit map before it resolves anything, taking the default value of every [limit key](#limit-keys) and overlaying that rule's `overrides` on it.
|
||||
Each rule publishes only the keys whose values differ from the release defaults. The defaults themselves are not published in this document, so a client MUST hold its own copy of them. Before it resolves anything, a client MUST also rebuild every rule into a full limit map, taking the default value of every [limit key](#limit-keys) and overlaying that rule's `overrides` on it.
|
||||
|
||||
A client starts from the default value of every key. It evaluates the rules from least specific to most specific, where specificity is the total number of trait and guild feature names in a rule's filters. Two rules of equal specificity keep their published order. A rule matches when every trait it names is present in the evaluation context and every guild feature it names is present.
|
||||
The client starts from the default value of every key. It evaluates the rules from least specific to most specific, where specificity is the total number of trait and guild feature names in a rule's filters. Two rules of equal specificity keep their published order. A rule matches when every trait it names is present in the evaluation context and every guild feature it names is present.
|
||||
|
||||
A rule that names no trait and no guild feature replaces the current value for each key it has. A rule that names at least one raises the current value to its own value when that is higher. Because a rebuilt rule has every key, an unfiltered rule returns each key it did not override to the default. A matching filtered rule raises each key it did not override to at least the default.
|
||||
|
||||
Two evaluation contexts exist. A user evaluation applies only keys whose scope is user or is both. A guild evaluation applies every key whose scope is both. It applies a user-scoped key only when the rule has no guild feature filter. It applies a guild-scoped key when the rule has a guild feature filter, or when the rule has no trait filter at all. The [limit key](#limit-keys) registry states the scope of each key.
|
||||
Two evaluation contexts exist. A client reads the key's scope and the rule's filters to decide which keys an evaluation applies. The [limit key](#limit-keys) registry states the scope of each key.
|
||||
|
||||
| Scope | User evaluation | Guild evaluation |
|
||||
| --- | --- | --- |
|
||||
| user | Applied | Applied only when the rule has no guild feature filter |
|
||||
| guild | Not applied | Applied when the rule has a guild feature filter, or when the rule has no trait filter at all |
|
||||
| both | Applied | Applied |
|
||||
|
||||
:::note[The operation applies its own authoritative bound]
|
||||
The document lets a client present the correct bounds before it attempts an operation. A request that exceeds the resolved value still fails with the operation's own documented error.
|
||||
@@ -255,7 +261,7 @@ The document lets a client present the correct bounds before it attempts an oper
|
||||
The document publishes neither the requesting account's traits nor any guild feature set. A client supplies that context from its own authenticated state.
|
||||
:::
|
||||
|
||||
`traitDefinitions` names the traits the deployment advertises. Nothing validates a rule's `traits` filter against it, so a rule can filter on a name the collection omits. A self-hosted deployment whose premium mode grants every account the stock limits publishes an empty collection and has no rule that filters on `premium`.
|
||||
Nothing validates a rule's `traits` filter against `traitDefinitions`, so a rule can filter on a name that collection omits. A self-hosted deployment whose premium mode grants every account the stock limits publishes an empty collection and has no rule that filters on `premium`.
|
||||
|
||||
## Limit rule object
|
||||
|
||||
@@ -347,11 +353,11 @@ Each key names one limit. A key whose name begins with `feature_` is a feature g
|
||||
| max_webhooks_per_guild | Maximum webhooks per guild, in guild scope |
|
||||
| sticker_max_size | Maximum file size for sticker uploads in bytes, in guild scope |
|
||||
|
||||
<sup>1</sup> Emoji limits are enforced as one shared total against `max_guild_emojis`, and the four aliases exist only so an older client reads a plausible value
|
||||
<sup>1</sup> Emoji limits are enforced as one shared total against `max_guild_emojis`, and the aliases exist only so an older client reads a plausible value
|
||||
|
||||
<sup>2</sup> Sticker limits are enforced as one shared total against `max_guild_stickers`
|
||||
|
||||
Where a rule sets `max_guild_emojis`, `max_guild_stickers`, or one of the five compatibility aliases, it republishes that key in its own `overrides` even when the value matches the default.
|
||||
Where a rule sets `max_guild_emojis`, `max_guild_stickers`, or one of the compatibility aliases, it republishes that key in its own `overrides` even when the value matches the default.
|
||||
|
||||
## Public application configuration object
|
||||
|
||||
@@ -397,7 +403,7 @@ Whether the deployment has finished its initial configuration, and where an oper
|
||||
|
||||
## Public legal configuration object
|
||||
|
||||
Two public URLs a client links from its registration form.
|
||||
The public URLs a client links from its registration form.
|
||||
|
||||
### Structure
|
||||
|
||||
@@ -437,7 +443,7 @@ The approximate location Fluxer resolved for the request, together with the regi
|
||||
|
||||
<sup>3</sup> The value is a decimal string
|
||||
|
||||
The two geo collections are fixed by the release, so they are identical in every response. A client resolves its own country and subdivision from the detected fields and then selects the matching entry itself.
|
||||
The geo collections are fixed by the release, so they are identical in every response. A client resolves its own country and subdivision from the detected fields and then selects the matching entry itself.
|
||||
|
||||
### Example
|
||||
|
||||
@@ -479,7 +485,7 @@ The path has no `/v1` prefix.
|
||||
|
||||
### Rate limit
|
||||
|
||||
60 requests per minute for each authenticated user, or for each client IP address when the request has no credential, on the `instance:info` bucket. [Get OpenAPI document](#get-openapi-document) draws on the same bucket, so the two operations share one allowance.
|
||||
60 requests per minute for each authenticated user, or for each client IP address when the request has no credential, on the `instance:info` bucket. [Get OpenAPI document](#get-openapi-document) shares this bucket.
|
||||
|
||||
## Get client geolocation
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ An invite is a code that admits an account into a guild or a group direct messag
|
||||
|
||||
## Invite object
|
||||
|
||||
An invite object describes one code and the target it admits into. The `type` selects the representation. A guild invite has the guild it admits into and that guild's online count. A group direct message invite exposes the group's current recipients through its partial channel.
|
||||
An invite object describes one code and the target it admits into. The `type` selects the representation. A guild invite has the guild it admits into and that guild's online count, and a group direct message invite lists the group's current recipients in its partial channel.
|
||||
|
||||
### Structure
|
||||
|
||||
@@ -299,7 +299,7 @@ Creates an invite for a channel, or returns an existing equivalent invite. Retur
|
||||
### Limitations
|
||||
|
||||
- A guild channel requires guild membership, [VIEW_CHANNEL](/http-api/permissions/), and [CREATE_INSTANT_INVITE](/http-api/permissions/) in that exact channel.
|
||||
- An age-restricted guild channel additionally requires an age-verified account.
|
||||
- An age-restricted guild channel also requires an age-verified account.
|
||||
- A private channel requires the caller to be a current recipient, and no ownership is required.
|
||||
|
||||
`CREATE_INSTANT_INVITE` is read directly from the main Gateway, so this operation never requires elevated multi-factor authentication.
|
||||
@@ -367,7 +367,7 @@ Returns the standard invites of one channel as an array of [invite metadata obje
|
||||
### Limitations
|
||||
|
||||
- A guild channel requires guild membership, [VIEW_CHANNEL](/http-api/permissions/) in that channel, and [MANAGE_CHANNELS](/http-api/permissions/) in the guild.
|
||||
- An age-restricted guild channel additionally requires an age-verified account.
|
||||
- An age-restricted guild channel also requires an age-verified account.
|
||||
- A group direct message requires the caller to be a current recipient and its owner.
|
||||
|
||||
`MANAGE_CHANNELS` is an [elevated permission](/http-api/permissions/#elevated-permissions), so a caller who is neither the guild owner nor enrolled in multi-factor authentication receives 400 [`TWO_FACTOR_REQUIRED`](/http-api/errors/) in a guild whose [MFA level](/http-api/guilds/#mfa-levels) is elevated.
|
||||
|
||||
@@ -14,7 +14,7 @@ The routes here are user-only and operate on the caller's own collection. Fluxer
|
||||
|
||||
Fluxer caps a collection at the instance-configured `max_favorite_memes` [limit](/http-api/instance/#limit-keys), with a fallback of 50. Both creation operations check the count before they touch the channel, the message, or the source URL, so an account at its limit is refused with 400 `MAX_FAVORITE_MEMES` even when the rest of the request would have failed. The error body has `max_favorite_memes` reporting the ceiling reached.
|
||||
|
||||
A meme has at most `max_favorite_meme_tags` tags, with a fallback of 10. A request that exceeds the allowance returns 400 `INVALID_FORM_BODY` with the validation code `MAX_FAVORITE_MEME_TAGS_EXCEEDED` at the `tags` path.
|
||||
A meme has at most `max_favorite_meme_tags` tags, with a fallback of 10. Fluxer rejects a longer list with 400 `INVALID_FORM_BODY` and the validation code `MAX_FAVORITE_MEME_TAGS_EXCEEDED` at the `tags` path.
|
||||
|
||||
Fluxer normalises and trims a tag before counting it, and the result must be 1 through 30 characters. A tag that normalises to the empty string fails with 400 `INVALID_FORM_BODY` and `STRING_LENGTH_INVALID`.
|
||||
|
||||
@@ -203,7 +203,7 @@ Fetches an absolute media URL, stores a durable Fluxer copy, and returns the cre
|
||||
|
||||
<sup>4</sup> Persisted only when the request resolved to a provider GIF and the map is non-empty, and discarded otherwise
|
||||
|
||||
The stored filename is the last path segment of `url` after percent-decoding. Fluxer appends an extension inferred from the resolved content type when that segment has no dot, then normalises the result so whitespace becomes `_` and every character outside letters, digits, combining marks, `_`, `.`, and `-` is dropped. A URL path with no last segment yields `media.{extension}`. A segment that normalises to nothing, or to only dots and underscores, yields `unnamed`, and the extension is `bin` when the content type maps to none.
|
||||
The stored filename is the last path segment of `url` after percent-decoding. Fluxer appends an extension inferred from the resolved content type when that segment has no dot, then normalises the result so whitespace becomes `_` and every character outside letters, digits, combining marks, `_`, `.`, and `-` is dropped. A URL path with no last segment yields `media.{extension}`. Where the segment normalises to nothing, or to only dots and underscores, the filename is `unnamed`. The extension is `bin` when the content type maps to none.
|
||||
|
||||
The stored `gif_provider` is always the configured provider's own name, so new provider GIFs are sourced from KLIPY even when the request names another provider.
|
||||
|
||||
@@ -267,7 +267,7 @@ When neither key selects anything, Fluxer takes the message's first attachment.
|
||||
|
||||
Only an `image/*`, `video/*`, or `audio/*` asset can be saved. `attachment_id` is matched only when the message or one of its snapshots has at least one attachment, and an ID matching none of them fails with `ATTACHMENT_ID_NOT_FOUND_IN_MESSAGE` at the `attachment_id` path. When the message has no attachment at all, Fluxer ignores the ID and takes the first usable embed. Only the selected attachment is tried, so a message whose chosen attachment has an unsupported type falls through to its embeds. A selection that resolves to no supported media fails with `NO_VALID_MEDIA_IN_MESSAGE` at the `media` path.
|
||||
|
||||
An embed selection uses the embed's image, video, or thumbnail, in that order. A URL the instance's own media endpoint serves is copied without a fetch. Any other URL is fetched over the network, and a fetch that resolves no metadata fails with 400 `MEDIA_METADATA_ERROR`.
|
||||
An embed selection uses the embed's image, video, or thumbnail, in that order. When the URL is one the instance's own media endpoint serves, Fluxer copies the asset without a fetch. Any other URL is fetched over the network, and a fetch that resolves no metadata fails with 400 `MEDIA_METADATA_ERROR`.
|
||||
|
||||
### Response
|
||||
|
||||
@@ -443,7 +443,7 @@ Fluxer derives a two-letter country from the requesting address by geolocation,
|
||||
|
||||
<sup>1</sup> Exactly one entry per submitted URL, in the submitted order, so a client may index the result positionally against its request
|
||||
|
||||
Fluxer resolves a URL in three stages. It offers the URL to the configured GIF provider, then reads it as direct external media, and then unfurls it as a page when the direct read produced no renderable image or video. A failure in any stage downgrades that URL's entry alone. A URL that none of the three stages resolves still returns an entry, with its signed proxy URL, an empty `media` map, and whatever the direct read reported. Dimensions are zero and `content_type` is the empty string when the direct read reported nothing.
|
||||
Fluxer resolves a URL in three stages. It offers the URL to the configured GIF provider, then reads it as direct external media, and then unfurls it as a page when the direct read produced no renderable image or video. When a stage fails, Fluxer downgrades that URL's entry alone. A URL that none of the three stages resolves still returns an entry, with its signed proxy URL, an empty `media` map, and whatever the direct read reported. Dimensions are zero and `content_type` is the empty string when the direct read reported nothing.
|
||||
|
||||
### Response
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import RouteHeader from '@/components/RouteHeader.astro';
|
||||
|
||||
A message is one post in a channel, together with the text, attachments, embeds, stickers, and reactions stored with it. Channel metadata and membership are defined by the [Channels resource](/http-api/channels/).
|
||||
|
||||
Two terms recur below. A text-bearing channel is a [channel type](/http-api/channels/#channel-types) that stores messages, which is every type except a guild category and a guild link channel. The message history cutoff is an instant a guild stores on itself, and a member without [READ_MESSAGE_HISTORY](/http-api/permissions/) reads nothing created before it.
|
||||
A text-bearing channel is a [channel type](/http-api/channels/#channel-types) that stores messages, which is every type except a guild category and a guild link channel. The message history cutoff is an instant a guild stores on itself, and a member without [READ_MESSAGE_HISTORY](/http-api/permissions/) reads nothing created before it.
|
||||
|
||||
## Channel resolution
|
||||
|
||||
@@ -117,7 +117,7 @@ A reply reference resolves into `referenced_message`, and a forward has none. Fl
|
||||
| 7 | USER_JOIN<sup>2</sup> | User-join system message |
|
||||
| 19 | REPLY<sup>1</sup> <sup>2</sup> | Message with a reply reference |
|
||||
|
||||
<sup>1</sup> Only `DEFAULT` and `REPLY` can be modified, pinned, unpinned, or used as the target of a reply reference. Any other type rejects modification, pinning, and unpinning with 400 `CANNOT_MODIFY_SYSTEM_WEBHOOK`, and rejects being replied to with 400 `CANNOT_REPLY_TO_SYSTEM_MESSAGE`
|
||||
<sup>1</sup> Only `DEFAULT` and `REPLY` can be modified, pinned, unpinned, or used as the target of a reply reference. Any other type rejects modification, pinning, and unpinning with 400 `CANNOT_MODIFY_SYSTEM_WEBHOOK`, and rejects being replied to with the field code `CANNOT_REPLY_TO_SYSTEM_MESSAGE`
|
||||
|
||||
<sup>2</sup> Only `DEFAULT`, `CHANNEL_PINNED_MESSAGE`, `USER_JOIN`, and `REPLY` can be deleted, and any other type rejects deletion with 403 `MISSING_PERMISSIONS`
|
||||
|
||||
@@ -133,7 +133,7 @@ A reply reference resolves into `referenced_message`, and a forward has none. Fl
|
||||
|
||||
<sup>2</sup> Setting this flag binds the message to the voice message contract described under [Create message](#create-message)
|
||||
|
||||
These three bits are also the complete sendable set. A create masks the supplied value down to them, and a modify replaces only them and leaves every other stored bit untouched. A bit outside this table is discarded, and the request still succeeds.
|
||||
These bits are also the complete sendable set. A create masks the supplied value down to them, and a modify replaces only them and leaves every other stored bit untouched. A bit outside this table is discarded, and the request still succeeds.
|
||||
|
||||
## Message attachment object
|
||||
|
||||
@@ -486,7 +486,7 @@ An allowed mentions object decides which mentions written in the message text be
|
||||
|
||||
<sup>1</sup> Defaults to every category when the object is absent, to the empty set when `users` or `roles` is supplied without it, and to the empty set when the object is present with none of its four fields
|
||||
|
||||
<sup>2</sup> A non-empty `parse` combined with a non-empty `users` or `roles` is rejected with 400 `PARSE_AND_USERS_OR_ROLES_CANNOT_BE_USED_TOGETHER`
|
||||
<sup>2</sup> A non-empty `parse` combined with a non-empty `users` or `roles` is rejected with the field code `PARSE_AND_USERS_OR_ROLES_CANNOT_BE_USED_TOGETHER`
|
||||
|
||||
<sup>3</sup> Ignored in a one-to-one direct message and in personal notes, and ignored when the referenced message has the same author as the new message
|
||||
|
||||
@@ -529,7 +529,7 @@ A new message can refer to a previously uploaded attachment or attach a direct m
|
||||
|
||||
<sup>2</sup> The presence of this key is the discriminator that selects this variant over the [direct multipart metadata](#direct-multipart-attachment-metadata-object) variant
|
||||
|
||||
<sup>3</sup> Both are required when the message has the `VOICE_MESSAGE` flag. A `waveform` on an attachment whose resolved media type is not `audio/*` fails with 400 `VOICE_MESSAGES_ATTACHMENT_MUST_BE_AUDIO`
|
||||
<sup>3</sup> Both are required when the message has the `VOICE_MESSAGE` flag. A `waveform` on an attachment whose resolved media type is not `audio/*` fails with the field code `VOICE_MESSAGES_ATTACHMENT_MUST_BE_AUDIO`
|
||||
|
||||
Only `IS_SPOILER` and `CONTAINS_EXPLICIT_MEDIA` survive from a caller-supplied `flags` value. `IS_ANIMATED` is set by media inspection alone.
|
||||
|
||||
@@ -548,7 +548,7 @@ Only `IS_SPOILER` and `CONTAINS_EXPLICIT_MEDIA` survive from a caller-supplied `
|
||||
| duration?<sup>2</sup> | ?integer | Voice media duration in seconds (0-2,147,483,647) |
|
||||
| waveform?<sup>2</sup> | ?string | Base64 waveform of 1 through 4,096 characters |
|
||||
|
||||
<sup>1</sup> An entry whose `id` matches no supplied file index and that has a `filename` fails with 400 `NO_FILE_FOR_ATTACHMENT_METADATA`. An entry whose `id` matches no supplied file and that has no `filename` is read as an [existing attachment reference](#existing-attachment-reference-object) instead
|
||||
<sup>1</sup> An entry whose `id` matches no supplied file index and that has a `filename` fails with the field code `NO_FILE_FOR_ATTACHMENT_METADATA`. An entry whose `id` matches no supplied file and that has no `filename` is read as an [existing attachment reference](#existing-attachment-reference-object) instead
|
||||
|
||||
<sup>2</sup> Both are required when the message has the `VOICE_MESSAGE` flag
|
||||
|
||||
@@ -570,11 +570,11 @@ Fluxer discards a key outside the table above and accepts the rest of the entry.
|
||||
|
||||
<sup>1</sup> A retained attachment is rebuilt from its stored record, and only `title` and `description` are written back onto it
|
||||
|
||||
<sup>2</sup> Rejected with 400 `CANNOT_EDIT_ATTACHMENT_METADATA` when the caller is a guild moderator editing a message they did not author, because that caller can supply only `id`, `title`, `description`, and `flags`
|
||||
<sup>2</sup> Rejected with the field code `CANNOT_EDIT_ATTACHMENT_METADATA` when the caller is a guild moderator editing a message they did not author, because that caller can supply only `id`, `title`, `description`, and `flags`
|
||||
|
||||
## Rich embed input objects
|
||||
|
||||
A rich embed input is an embed a client supplies on a message, and Fluxer always stores it with the `rich` [embed type](#embed-types). The number of embeds on one message is bounded by the resolved `max_embeds_per_message` limit, which defaults to 10, and exceeding it fails with 400 `TOO_MANY_EMBEDS`.
|
||||
A rich embed input is an embed a client supplies on a message, and Fluxer always stores it with the `rich` [embed type](#embed-types). The number of embeds on one message is bounded by the resolved `max_embeds_per_message` limit, which defaults to 10, and exceeding it fails with the field code `TOO_MANY_EMBEDS`.
|
||||
|
||||
### Rich embed object
|
||||
|
||||
@@ -616,7 +616,7 @@ A rich embed input is an embed a client supplies on a message, and Fluxer always
|
||||
|
||||
<sup>1</sup> Either an `http` or `https` URL, or an `attachment://<filename>` reference naming a `png`, `jpg`, `jpeg`, `webp`, or `gif` attachment on the same message, and from 1 through 2,048 characters in either form. Only `image` and `thumbnail` accept the `attachment://` form
|
||||
|
||||
An `attachment://` reference on a request that has no attachment fails with 400 `CANNOT_REFERENCE_ATTACHMENTS_WITHOUT_ATTACHMENTS`. A reference to a filename that no attachment on the request supplies fails with 400 `REFERENCED_ATTACHMENT_NOT_FOUND`, and a reference to an attachment whose extension is not a supported image extension fails with 400 `ATTACHMENT_MUST_BE_IMAGE`.
|
||||
An `attachment://` reference on a request that has no attachment fails with the field code `CANNOT_REFERENCE_ATTACHMENTS_WITHOUT_ATTACHMENTS`. A reference to a filename that no attachment on the request supplies fails with the field code `REFERENCED_ATTACHMENT_NOT_FOUND`, and a reference to an attachment whose extension is not a supported image extension fails with the field code `ATTACHMENT_MUST_BE_IMAGE`.
|
||||
|
||||
### Embed footer input object
|
||||
|
||||
@@ -1008,7 +1008,7 @@ Fluxer aborts the multipart upload and discards its parts when no part was uploa
|
||||
:::
|
||||
|
||||
:::caution[An unowned upload is reported as invalid input]
|
||||
Fluxer reports 400 `UPLOADED_ATTACHMENT_NOT_FOUND` for an upload key the identity does not own, one that belongs to another channel, one planned as singlepart, and one a message already consumed. A caller cannot tell the four apart, so it cannot probe another identity's upload state.
|
||||
Fluxer reports the field code `UPLOADED_ATTACHMENT_NOT_FOUND` for an upload key the identity does not own, one that belongs to another channel, one planned as singlepart, and one a message already consumed. A caller cannot tell them apart, so it cannot probe another identity's upload state.
|
||||
:::
|
||||
|
||||
### Side effects
|
||||
@@ -1034,9 +1034,9 @@ Creates a message from a JSON body or from multipart form data. Returns the crea
|
||||
- Slowmode applies to a non-bot caller unless they hold [BYPASS_SLOWMODE](/http-api/permissions/).
|
||||
- A one-to-one direct message additionally applies the recipient's direct message policy and relationship state, and a denial returns 400 `CANNOT_SEND_MESSAGES_TO_USER`.
|
||||
- An unclaimed account can send only to its own personal notes channel, and a send to any other channel is refused with 400 `UNCLAIMED_ACCOUNT_CANNOT_SEND_MESSAGES` before the channel is resolved.
|
||||
- A non-bot caller must have started a session, and one that has not is refused with 400 `MUST_START_SESSION_BEFORE_SENDING`.
|
||||
- A non-bot caller must have started a session, and one that has not is refused with the field code `MUST_START_SESSION_BEFORE_SENDING`.
|
||||
|
||||
The request body is read as a multipart form when `Content-Type` contains `multipart/form-data`, and as JSON otherwise. An empty JSON body is read as `{}`. A JSON body that is neither valid JSON nor valid against the schema fails with 400 `INVALID_MESSAGE_DATA` on the path `message_data`, which has no per-field detail. A multipart body whose merged payload fails the same schema instead fails with a per-field 400 `INVALID_FORM_BODY` naming each offending path.
|
||||
The request body is read as a multipart form when `Content-Type` contains `multipart/form-data`, and as JSON otherwise. An empty JSON body is read as `{}`. A JSON body that is neither valid JSON nor valid against the schema fails with the field code `INVALID_MESSAGE_DATA` on the path `message_data`, which has no per-field detail. A multipart body whose merged payload fails the same schema instead fails with a per-field 400 `INVALID_FORM_BODY` naming each offending path.
|
||||
|
||||
### Path parameters
|
||||
|
||||
@@ -1069,9 +1069,9 @@ The request body is read as a multipart form when `Content-Type` contains `multi
|
||||
|
||||
<sup>5</sup> Fluxer forces the value to false when the caller lacks [SEND_TTS_MESSAGES](/http-api/permissions/) in a guild, and the request still succeeds
|
||||
|
||||
<sup>6</sup> Bounded by the resolved `max_embeds_per_message` limit, which defaults to 10. Exceeding it fails with 400 `TOO_MANY_EMBEDS` on the path `embeds`
|
||||
<sup>6</sup> Bounded by the resolved `max_embeds_per_message` limit, which defaults to 10. Exceeding it fails with the field code `TOO_MANY_EMBEDS` on the path `embeds`
|
||||
|
||||
<sup>7</sup> Bounded by the resolved `max_attachments_per_message` limit, which defaults to 10. Exceeding it fails with 400 `TOO_MANY_FILES` on the path `attachments`
|
||||
<sup>7</sup> Bounded by the resolved `max_attachments_per_message` limit, which defaults to 10. Exceeding it fails with the field code `TOO_MANY_FILES` on the path `attachments`
|
||||
|
||||
The smallest body that works is one line of text:
|
||||
|
||||
@@ -1091,11 +1091,11 @@ A nonce is remembered for the authenticated identity for five minutes, so a clie
|
||||
|
||||
Reusing a nonce in the same channel inside that window returns the message the first request created. Reusing it in a different channel fails with 404 `UNKNOWN_MESSAGE`. The replay check runs after authorisation and body validation, so a retry that is otherwise invalid still fails, and a retry after the window has passed creates a second message.
|
||||
|
||||
A reply applies the caller's message history cutoff to its target, and a forward applies none to its source. A reply whose target is a system message fails with 400 `CANNOT_REPLY_TO_SYSTEM_MESSAGE`, and a reference that resolves to no message fails with 404 `UNKNOWN_MESSAGE`.
|
||||
A reply applies the caller's message history cutoff to its target, and a forward applies none to its source. A reply whose target is a system message fails with the field code `CANNOT_REPLY_TO_SYSTEM_MESSAGE`, and a reference that resolves to no message fails with 404 `UNKNOWN_MESSAGE`.
|
||||
|
||||
A `FORWARD` reference has `channel_id`. One that does not is refused by body validation before the operation runs, with 400 `INVALID_MESSAGE_DATA` on a JSON body and a per-field 400 `INVALID_FORM_BODY` on a multipart body.
|
||||
|
||||
A forward request has no `content`, `embeds`, `attachments`, or `sticker_ids`, and one that does fails with 400 `FORWARD_MESSAGES_CANNOT_CONTAIN_CONTENT`. A `guild_id` that disagrees with the source channel's guild fails with 400 `GUILD_ID_MUST_MATCH_REFERENCED_MESSAGE`. Reading the source channel requires [VIEW_CHANNEL](/http-api/permissions/) when that channel belongs to a guild.
|
||||
A forward request has no `content`, `embeds`, `attachments`, or `sticker_ids`, and one that does fails with the field code `FORWARD_MESSAGES_CANNOT_CONTAIN_CONTENT`. A `guild_id` that disagrees with the source channel's guild fails with the field code `GUILD_ID_MUST_MATCH_REFERENCED_MESSAGE`. Reading the source channel requires [VIEW_CHANNEL](/http-api/permissions/) when that channel belongs to a guild.
|
||||
|
||||
Snapshots with embeds require `EMBED_LINKS` in the destination guild channel, and snapshots with attachments require `ATTACH_FILES`, each returning 403 `MISSING_PERMISSIONS` when absent.
|
||||
|
||||
@@ -1116,24 +1116,37 @@ Snapshots with embeds require `EMBED_LINKS` in the destination guild channel, an
|
||||
|
||||
<sup>2</sup> The index runs from 0 through the resolved `max_attachments_per_message` limit minus one
|
||||
|
||||
A body that cannot be parsed as a multipart form fails with 400 `FAILED_TO_PARSE_MULTIPART_FORM_DATA`, and a `payload_json` field that is not a JSON string fails with 400 `INVALID_JSON_IN_PAYLOAD_JSON`. A field name beginning with `files[` that does not match `files[<N>]` fails with 400 `INVALID_FILE_FIELD_NAME`.
|
||||
A body that cannot be parsed as a multipart form fails with the field code `FAILED_TO_PARSE_MULTIPART_FORM_DATA`, and a `payload_json` field that is not a JSON string fails with the field code `INVALID_JSON_IN_PAYLOAD_JSON`. A field name beginning with `files[` that does not match `files[<N>]` fails with the field code `INVALID_FILE_FIELD_NAME`.
|
||||
|
||||
| File field condition | Error |
|
||||
| --- | --- |
|
||||
| Index below 0 or above 10000 | 400 `FILE_INDEX_EXCEEDS_MAXIMUM` reporting a `maxIndex` of 10000 |
|
||||
| Resolved attachment limit is 0 | 400 `ATTACHMENTS_NOT_ALLOWED_FOR_MESSAGE` |
|
||||
| Index at or above a non-zero resolved limit | 400 `FILE_INDEX_EXCEEDS_MAXIMUM` reporting that limit minus one |
|
||||
| Index repeated across fields | 400 `DUPLICATE_FILE_INDEX` |
|
||||
| More than one file under one index | 400 `MULTIPLE_FILES_FOR_INDEX_NOT_ALLOWED` |
|
||||
| More files than the resolved limit | 400 `TOO_MANY_FILES` |
|
||||
| Index below 0 or above 10000 | `FILE_INDEX_EXCEEDS_MAXIMUM` |
|
||||
| Resolved attachment limit is 0 | `ATTACHMENTS_NOT_ALLOWED_FOR_MESSAGE` |
|
||||
| Index at or above a non-zero resolved limit | `FILE_INDEX_EXCEEDS_MAXIMUM` |
|
||||
| Index repeated across fields | `DUPLICATE_FILE_INDEX` |
|
||||
| More than one file under one index | `MULTIPLE_FILES_FOR_INDEX_NOT_ALLOWED` |
|
||||
| More files than the resolved limit | `TOO_MANY_FILES` |
|
||||
|
||||
The `attachments` array inside `payload_json` uses [direct multipart attachment metadata](#direct-multipart-attachment-metadata-object), and an entry with `upload_filename` is instead read as a [pre-uploaded attachment](#pre-uploaded-attachment-object). An entry whose `id` matches a supplied file index supplies that file's metadata, and two entries claiming the same file fail with 400 `DUPLICATE_ATTACHMENT_IDS_NOT_ALLOWED`.
|
||||
Each is a field code inside a 400 `INVALID_FORM_BODY` body. A `FILE_INDEX_EXCEEDS_MAXIMUM` element reports a `maxIndex` member, 10000 against the absolute bound and the resolved limit minus one otherwise.
|
||||
|
||||
The `attachments` array inside `payload_json` uses [direct multipart attachment metadata](#direct-multipart-attachment-metadata-object), and an entry with `upload_filename` is instead read as a [pre-uploaded attachment](#pre-uploaded-attachment-object). An entry whose `id` matches a supplied file index supplies that file's metadata, and two entries claiming the same file fail with the field code `DUPLICATE_ATTACHMENT_IDS_NOT_ALLOWED`.
|
||||
|
||||
Fluxer permits index gaps and attaches the files in ascending index order. A supplied file that no metadata entry claims is attached with its own multipart filename and no title or description, so omitting `attachments` entirely attaches every supplied file that way.
|
||||
|
||||
A message with `VOICE_MESSAGE` set has exactly one attachment and nothing else. Each rule fails with its own 400 code.
|
||||
A message with `VOICE_MESSAGE` set has exactly one attachment and nothing else.
|
||||
|
||||
Any count other than one attachment returns `VOICE_MESSAGES_REQUIRE_SINGLE_ATTACHMENT`. A missing `waveform` returns `VOICE_MESSAGES_ATTACHMENT_WAVEFORM_REQUIRED` and a missing `duration` returns `VOICE_MESSAGES_ATTACHMENT_DURATION_REQUIRED`. Content, embeds, stickers, and a favourite meme return `VOICE_MESSAGES_CANNOT_HAVE_CONTENT`, `VOICE_MESSAGES_CANNOT_HAVE_EMBEDS`, `VOICE_MESSAGES_CANNOT_HAVE_STICKERS`, and `VOICE_MESSAGES_CANNOT_HAVE_FAVORITE_MEMES`. A duration above the resolved `max_voice_message_duration` limit, which defaults to 1200 seconds, returns `VOICE_MESSAGES_DURATION_EXCEEDS_LIMIT`.
|
||||
| Voice message condition | Error |
|
||||
| --- | --- |
|
||||
| Content present | `VOICE_MESSAGES_CANNOT_HAVE_CONTENT` |
|
||||
| Embeds present | `VOICE_MESSAGES_CANNOT_HAVE_EMBEDS` |
|
||||
| Favourite meme present | `VOICE_MESSAGES_CANNOT_HAVE_FAVORITE_MEMES` |
|
||||
| Stickers present | `VOICE_MESSAGES_CANNOT_HAVE_STICKERS` |
|
||||
| Any count other than one attachment | `VOICE_MESSAGES_REQUIRE_SINGLE_ATTACHMENT` |
|
||||
| Missing `waveform` | `VOICE_MESSAGES_ATTACHMENT_WAVEFORM_REQUIRED` |
|
||||
| Missing `duration` | `VOICE_MESSAGES_ATTACHMENT_DURATION_REQUIRED` |
|
||||
| Duration above the resolved limit | `VOICE_MESSAGES_DURATION_EXCEEDS_LIMIT` |
|
||||
|
||||
The resolved `max_voice_message_duration` limit defaults to 1200 seconds.
|
||||
|
||||
### Response
|
||||
|
||||
@@ -1176,7 +1189,7 @@ Modifies a message. Returns the updated [message](#message-object) object. Emits
|
||||
### Limitations
|
||||
|
||||
- The target must be a `DEFAULT` or `REPLY` message, and any other type fails with 400 `CANNOT_MODIFY_SYSTEM_WEBHOOK`.
|
||||
- A message that has [message snapshots](#message-snapshot-object) cannot be edited by its author and fails with 400 `MESSAGES_WITH_SNAPSHOTS_CANNOT_BE_EDITED`. A non-author moderator holding `MANAGE_MESSAGES` can still toggle `SUPPRESS_EMBEDS` and change existing attachment metadata on a forwarded message.
|
||||
- A message that has [message snapshots](#message-snapshot-object) cannot be edited by its author and fails with the field code `MESSAGES_WITH_SNAPSHOTS_CANNOT_BE_EDITED`. A non-author moderator holding `MANAGE_MESSAGES` can still toggle `SUPPRESS_EMBEDS` and change existing attachment metadata on a forwarded message.
|
||||
- The author can modify every supported field, and a timed-out author is refused with 403 `COMMUNICATION_DISABLED`.
|
||||
- A caller who is not the author can act only in a guild channel, must hold [MANAGE_MESSAGES](/http-api/permissions/), and can change only `SUPPRESS_EMBEDS` and the metadata of attachments that already exist. A non-author edit that does not satisfy all three conditions fails with 403 `CANNOT_EDIT_OTHER_USER_MESSAGE`.
|
||||
- `MANAGE_MESSAGES` is an [elevated permission](/http-api/permissions/#elevated-permissions), so a caller holding the bit without an enrolled authenticator receives 400 [`TWO_FACTOR_REQUIRED`](/http-api/errors/) in a guild whose [MFA level](/http-api/guilds/#mfa-levels) is elevated, unless they own the guild. A caller who does not hold the bit at all is treated as any other non-author and receives 403 `CANNOT_EDIT_OTHER_USER_MESSAGE`.
|
||||
@@ -1382,14 +1395,14 @@ Deletes from 1 through 100 messages in one guild channel. Returns 204 with an em
|
||||
|
||||
### JSON body
|
||||
|
||||
At least one of the two fields below must be present. Supplying neither fails with 400 `INVALID_FORM_BODY` on the path `message_ids`, and supplying both uses `message_ids` and ignores `messages`.
|
||||
At least one of the fields below must be present. Supplying neither fails with 400 `INVALID_FORM_BODY` on the path `message_ids`, and supplying both uses `message_ids` and ignores `messages`.
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| message_ids?<sup>1</sup> | array[snowflake] | Message IDs to delete, at most 100 entries |
|
||||
| messages?<sup>1</sup> | array[snowflake] | Alias for `message_ids` |
|
||||
|
||||
<sup>1</sup> An empty array fails with 400 `MESSAGE_IDS_CANNOT_BE_EMPTY` on the path `message_ids`. More than 100 entries is rejected by body validation with 400 `INVALID_FORM_BODY` before the operation runs
|
||||
<sup>1</sup> An empty array fails with the field code `MESSAGE_IDS_CANNOT_BE_EMPTY` on the path `message_ids`. More than 100 entries is rejected by body validation with 400 `INVALID_FORM_BODY` before the operation runs
|
||||
|
||||
:::danger[Bulk deletion removes attachments and search entries]
|
||||
Every selected message, with its reactions and attachments, is permanently deleted and removed from search.
|
||||
@@ -1458,7 +1471,7 @@ The operation deletes every message it finds, with no age boundary and no confir
|
||||
|
||||
The operation permanently deletes every personal note, its attachments, and its reactions and removes each from search. The caller's sessions receive one [Message Delete Bulk](/gateway/events/#message-delete-bulk) Dispatch for each page of at most 100 messages.
|
||||
|
||||
The operation emits no [Channel Pins Update](/gateway/events/#channel-pins-update) and no individual [Message Delete](/gateway/events/#message-delete), and it writes no audit log entry.
|
||||
It emits no [Channel Pins Update](/gateway/events/#channel-pins-update) and no individual [Message Delete](/gateway/events/#message-delete), and it writes no audit log entry.
|
||||
|
||||
### Rate limit
|
||||
|
||||
@@ -1503,9 +1516,9 @@ The deletion runs inside the request despite the 202 status, so every matching m
|
||||
| webauthn_response? | [WebAuthn assertion](/http-api/authentication/#webauthn-assertion) object | WebAuthn assertion for sudo verification |
|
||||
| webauthn_challenge? | string | Challenge bound to the sudo mode assertion |
|
||||
|
||||
<sup>1</sup> Accepted only when the account has no enrolled authenticator, and an incorrect password fails with 400 `INVALID_PASSWORD`. An account with neither a stored password nor an enrolled authenticator never reaches this check
|
||||
<sup>1</sup> Accepted only when the account has no enrolled authenticator, and an incorrect password fails with the field code `INVALID_PASSWORD`. An account with neither a stored password nor an enrolled authenticator never reaches this check
|
||||
|
||||
<sup>2</sup> Accepted only when the account has an enrolled authenticator. A failed verification returns 400 `INVALID_MFA_CODE`
|
||||
<sup>2</sup> Accepted only when the account has an enrolled authenticator. A failed verification returns the field code `INVALID_MFA_CODE`
|
||||
|
||||
:::danger[Every message the caller authored is destroyed]
|
||||
The deletion has no age boundary and no selection. It permanently removes every message the caller has authored in the channel, together with its attachments and reactions, and removes each from search. There is no cancellation and no restore.
|
||||
@@ -1699,7 +1712,7 @@ When the Dispatch fails, the request returns 502, 503, or 504 after the read-sta
|
||||
|
||||
The `emoji` path value is URI-encoded and is 1 through 64 characters. A custom emoji uses `name:id`, where `id` is the trailing decimal custom emoji snowflake and `name` is everything before the final colon. Any other decoded value is read as a Unicode emoji.
|
||||
|
||||
Fluxer validates the emoji in full only when a request would create a new reaction group on the message. At that point a Unicode value must be exactly one valid emoji, and one that is not fails with 400 `NOT_A_VALID_UNICODE_EMOJI`. A custom emoji must exist, and one that does not fails with 400 `CUSTOM_EMOJI_NOT_FOUND`. Every other reaction operation parses the value without validating it, so removing a reaction with a nonsensical emoji succeeds silently.
|
||||
Fluxer validates the emoji in full only when a request would create a new reaction group on the message. At that point a Unicode value must be exactly one valid emoji, and one that is not fails with the field code `NOT_A_VALID_UNICODE_EMOJI`. A custom emoji must exist, and one that does not fails with the field code `CUSTOM_EMOJI_NOT_FOUND`. Every other reaction operation parses the value without validating it, so removing a reaction with a nonsensical emoji succeeds silently.
|
||||
|
||||
## List reaction users unpaged
|
||||
|
||||
@@ -1795,9 +1808,9 @@ Adds the authenticated identity's ordinary reaction. Returns 204 with an empty b
|
||||
- The caller must be able to view the text-bearing channel, must not be timed out, and must reach the message under the message history cutoff.
|
||||
- A non-bot caller must have a verified email, and one that does not receives 403 `REACTION_EMAIL_VERIFICATION_REQUIRED`.
|
||||
- Creating a new emoji reaction group in a guild requires [ADD_REACTIONS](/http-api/permissions/), while adding to an existing group does not.
|
||||
- A custom emoji whose source guild is not the channel's guild requires the `feature_global_expressions` entitlement, and a caller without it receives 400 `CUSTOM_EMOJIS_REQUIRE_PREMIUM_OUTSIDE_SOURCE`.
|
||||
- A custom emoji whose source guild is not the channel's guild requires the `feature_global_expressions` entitlement, and a caller without it receives the field code `CUSTOM_EMOJIS_REQUIRE_PREMIUM_OUTSIDE_SOURCE`.
|
||||
- In a guild channel a custom emoji additionally requires [USE_EXTERNAL_EMOJIS](/http-api/permissions/).
|
||||
- A non-bot caller must have started a session, and one that has not receives 400 `MUST_START_SESSION_BEFORE_SENDING`.
|
||||
- A non-bot caller must have started a session, and one that has not receives the field code `MUST_START_SESSION_BEFORE_SENDING`.
|
||||
- An unclaimed account can react only in its personal notes channel, and elsewhere receives 400 `UNCLAIMED_ACCOUNT_CANNOT_ADD_REACTIONS`.
|
||||
- The operation is idempotent, and this route applies no direct message send policy.
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ An unrecognised scope value is rejected when the authorisation request is comple
|
||||
Scope order has no semantic meaning, so a client MUST compare scopes as a set.
|
||||
:::
|
||||
|
||||
The `scope` string of an [OAuth2 token](#oauth2-token-object) and of an [OAuth2 introspection](#oauth2-introspection-object) is sorted into the registry order above. The `scopes` array of a [current OAuth2 authorisation](#current-oauth2-authorisation-object) has the stored scopes in no defined order, with unrecognised values removed. The `scopes` array of an [OAuth2 authorisation](#oauth2-authorisation-object) is in no defined order, and it can hold `bot` and an unrecognised value once more than one refresh token contributes.
|
||||
Fluxer sorts the `scope` string of an [OAuth2 token](#oauth2-token-object) and of an [OAuth2 introspection](#oauth2-introspection-object) into the registry order above. The `scopes` array of a [current OAuth2 authorisation](#current-oauth2-authorisation-object) has the stored scopes in no defined order, with unrecognised values removed. The `scopes` array of an [OAuth2 authorisation](#oauth2-authorisation-object) is in no defined order, and it can hold `bot` and an unrecognised value once more than one refresh token contributes.
|
||||
|
||||
## OAuth2 error response object
|
||||
|
||||
@@ -73,7 +73,7 @@ The failure envelope the OAuth2 routes return.
|
||||
|
||||
<sup>1</sup> Always present, and some descriptions are a bare API error code identifier
|
||||
|
||||
These two members are the whole body, so the routine that reads the ordinary [error response](/http-api/#error-response) cannot parse it. Fluxer returns every OAuth2 failure with status 400. A client MUST key on `error` and never on `error_description`.
|
||||
These members are the whole body, so the routine that reads the ordinary [error response](/http-api/#error-response) cannot parse it. Fluxer returns every OAuth2 failure with status 400. A client MUST key on `error` and never on `error_description`.
|
||||
|
||||
The consent, token, introspection, and revocation routes return this object for a grant, client authentication, scope, redirect, response type, or permission mask failure. Every other failure, including request validation, authentication, authorisation, and rate limiting, uses the ordinary [error response](/http-api/#error-response) with an [API error code](/http-api/errors/).
|
||||
|
||||
@@ -319,7 +319,7 @@ Fluxer builds the URL from the registered redirect URI the request resolved to,
|
||||
|
||||
<RouteHeader method="GET" path="/v1/oauth2/authorize" unauthenticated />
|
||||
|
||||
Starts an authorisation request. A query that passes request validation always answers with a redirect, and authentication is optional.
|
||||
Starts an authorisation request. Fluxer returns a redirect for every query that passes request validation. Authentication is optional.
|
||||
|
||||
### Query parameters
|
||||
|
||||
@@ -427,11 +427,11 @@ A guild installation emits [Guild Create](/gateway/events/#guild-create), [Guild
|
||||
|
||||
Consent issues one authorisation code bound to the application, user, resolved redirect URI, granted scopes, and PKCE challenge, and returns the callback URL that has it.
|
||||
|
||||
With the `bot` scope and a guild target, consent adds the bot to the guild with the bot-invite join source and the caller recorded as inviter, bypassing both the bot account's own guild count limit and the guild ban list. The guild member limit still applies. When a non-zero permission mask is requested, it also creates one role with exactly those permissions, names the role after the application, and assigns it to the bot.
|
||||
With the `bot` scope and a guild target, consent adds the bot to the guild with the bot-invite join source and the caller recorded as inviter. Consent bypasses both the bot account's own guild count limit and the guild ban list. The guild member limit still applies. When a non-zero permission mask is requested, consent also creates one role with exactly those permissions, names the role after the application, and assigns it to the bot.
|
||||
|
||||
That role is created at position 1 with no colour and is neither hoisted nor mentionable. It consumes a guild role slot and is not removed when the bot leaves. The installation records bot add, role create, and member role update [audit log](/http-api/guild-audit-logs/) entries.
|
||||
|
||||
With the `bot` scope and a group direct message target, it adds the bot as a recipient and stores the recipient-add system message.
|
||||
With the `bot` scope and a group direct message target, consent adds the bot as a recipient and stores the recipient-add system message.
|
||||
|
||||
When any part of the installation fails, Fluxer cancels the newly issued authorisation code, so the caller never receives a usable code.
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ A permission grants a member one ability in a guild, such as sending a message o
|
||||
|
||||
Only [List guild roles](#list-guild-roles) accepts an OAuth2 bearer credential, and every other route here rejects one with 403 `ACCESS_DENIED`. A guild with [UNAVAILABLE_FOR_EVERYONE](/http-api/guilds/#guild-features) rejects an authenticated request with 403 `MISSING_ACCESS`, and [UNAVAILABLE_FOR_EVERYONE_BUT_STAFF](/http-api/guilds/#guild-features) does the same for an account without the instance staff flag.
|
||||
|
||||
Fluxer resolves the guild before any role. A guild that does not exist returns 404 `UNKNOWN_GUILD`. A caller who is not a member of an existing guild returns 403 `MISSING_PERMISSIONS`, so guild existence is visible to any authenticated caller. A route that names a role asserts `MANAGE_ROLES` before it reads the role. A caller without the permission receives 403 `MISSING_PERMISSIONS` even for a role that does not exist.
|
||||
Fluxer resolves the guild before any role. A guild that does not exist returns 404 `UNKNOWN_GUILD`. When the guild exists and the caller is not a member, the request returns 403 `MISSING_PERMISSIONS`, so guild existence is visible to any authenticated caller. Every route that names a role asserts `MANAGE_ROLES` before it reads the role. A caller without the permission receives 403 `MISSING_PERMISSIONS` even for a role that does not exist.
|
||||
|
||||
## Permissions
|
||||
|
||||
@@ -86,7 +86,7 @@ Fluxer discards a bit it does not define. A role mutation intersects the request
|
||||
|
||||
### Feature-gated permission bits
|
||||
|
||||
Fluxer gates some bits on a client feature declaration. A client sends the feature set in the `X-Fluxer-Features` request header as a comma-separated list.
|
||||
Fluxer gates some permission bits on a feature declaration, which a client sends in the `X-Fluxer-Features` request header as a comma-separated list.
|
||||
|
||||
One bit is gated today. `VIEW_CHANNEL_MEMBERS` requires the feature name `view_channel_members_permission`.
|
||||
|
||||
@@ -94,7 +94,7 @@ On a role or overwrite write that does not declare the matching feature, Fluxer
|
||||
|
||||
Fluxer trims and lowercases each name in the header before matching it. A name that is empty, longer than 64 characters, or containing a character outside `a-z`, `0-9`, and `_` is ignored. Fluxer reads at most 64 names from one header. An absent or empty header declares no features.
|
||||
|
||||
A [Create guild role](#create-guild-role) request that supplies `permissions` without declaring the feature always creates the role with `VIEW_CHANNEL_MEMBERS` cleared. A request that omits `permissions` copies the everyone role mask without applying the gate.
|
||||
On [Create guild role](#create-guild-role), a request that supplies `permissions` without declaring the feature always creates the role with `VIEW_CHANNEL_MEMBERS` cleared. A request that omits `permissions` copies the everyone role mask without applying the gate.
|
||||
|
||||
## Elevated permissions
|
||||
|
||||
@@ -116,13 +116,13 @@ The guild owner receives the complete 64-bit mask. A user who is not a member re
|
||||
|
||||
Otherwise Fluxer starts from the permissions of the everyone role, whose [snowflake](/snowflakes/) equals the guild snowflake, then adds the permissions of every role assigned to the member with a bitwise union. If the result contains `ADMINISTRATOR` at that point, Fluxer returns the complete 64-bit mask and runs no further step.
|
||||
|
||||
When no channel was named, the union is the final result. When a channel was named, Fluxer applies that channel's stored [permission overwrites](/http-api/channels/#permission-overwrite-object) to the union in three steps:
|
||||
When no channel was named, the union is the final result. When a channel was named, Fluxer applies that channel's stored [permission overwrites](/http-api/channels/#permission-overwrite-object) to the union in this order:
|
||||
|
||||
1. The everyone role overwrite, by removing its denied bits and then adding its allowed bits.
|
||||
2. The overwrites targeting roles assigned to the member, accumulated into one union of allowed bits and one union of denied bits and applied as a single step in the same order. An allow on any one of the member's roles defeats a deny on another.
|
||||
3. The member overwrite, again denied bits first and allowed bits second.
|
||||
|
||||
A named channel that does not exist in the guild leaves the guild-level union unchanged.
|
||||
When the named channel does not exist in the guild, Fluxer leaves the guild-level union unchanged.
|
||||
|
||||
:::caution[`ADMINISTRATOR` grants every bit and skips overwrites]
|
||||
No deny overwrite in any channel restricts a member who holds the bit. Removing `ADMINISTRATOR` from every role they hold is the only remedy.
|
||||
@@ -134,7 +134,16 @@ A guild channel is visible when its channel-scoped mask contains `VIEW_CHANNEL`,
|
||||
|
||||
A communication timeout stops a member from communicating in a guild until it expires. It does not change the computed mask. A member whose [communication_disabled_until](/http-api/guild-members/#guild-member-object) is in the future keeps every permission their roles grant, and Fluxer applies the restriction as a separate precondition on each communicating operation.
|
||||
|
||||
Sending a message, editing the caller's own message, adding a reaction, requesting an attachment upload, starting a typing indicator, and changing the caller's own nickname each fail with 403 `COMMUNICATION_DISABLED` while the timeout is in the future. A timeout has no effect in a private channel.
|
||||
While the timeout is in the future, these operations fail with 403 `COMMUNICATION_DISABLED`:
|
||||
|
||||
- Sending a message
|
||||
- Editing the caller's own message
|
||||
- Adding a reaction
|
||||
- Requesting an attachment upload
|
||||
- Starting a typing indicator
|
||||
- Changing the caller's own nickname
|
||||
|
||||
A timeout has no effect in a private channel.
|
||||
|
||||
The order of the timeout precondition and the permission check varies by operation, so a caller who is both timed out and short of the permission can receive either `COMMUNICATION_DISABLED` or `MISSING_PERMISSIONS`.
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ An account holds at most one read state per channel, keyed by the account and th
|
||||
|
||||
<sup>3</sup> Present on every entry the API returns. The value is always `0`
|
||||
|
||||
An entry is created by an acknowledgement, by [Acknowledge pins](/http-api/messages/#acknowledge-pins), and by the server when a mention arrives in a channel the account has no entry for. A pin acknowledgement writes only `last_pin_timestamp`, and the entry it creates reports `last_message_id` as null. An entry the server creates for a mention starts from the channel's own baseline watermark, which is the snowflake of the channel ID itself, so every message already in the channel stays unread.
|
||||
An acknowledgement creates an entry. So does [Acknowledge pins](/http-api/messages/#acknowledge-pins), and so does the server when a mention arrives in a channel the account has no entry for. A pin acknowledgement writes only `last_pin_timestamp`, and the entry it creates reports `last_message_id` as null. The entry the server creates for a mention starts from the channel's own baseline watermark, which is the snowflake of the channel ID itself, so every message already in the channel stays unread.
|
||||
|
||||
[Clear channel read state](/http-api/messages/#clear-channel-read-state) deletes the whole entry, so it drops the watermark and the mention count along with the pin timestamp.
|
||||
|
||||
@@ -38,7 +38,7 @@ An entry is created by an acknowledgement, by [Acknowledge pins](/http-api/messa
|
||||
There is no HTTP read for the read state aggregate. The complete entry array arrives once as `read_states` in the main Gateway [Ready](/gateway/events/#ready) payload.
|
||||
:::
|
||||
|
||||
Only an acknowledgement produces a later Dispatch. A message acknowledgement emits [Message ACK](/gateway/events/#message-ack), and a pin acknowledgement emits [Channel Pins ACK](/gateway/events/#channel-pins-ack). A server-side mention increment and [Clear channel read state](/http-api/messages/#clear-channel-read-state) both change the stored entry and emit nothing, so the Dispatch stream is not a complete change feed. A client that needs the authoritative aggregate reconciles from a new Ready, or from the entries [Acknowledge read states](#acknowledge-read-states) returns.
|
||||
Only an acknowledgement produces a later Dispatch. A message acknowledgement emits [Message ACK](/gateway/events/#message-ack), and a pin acknowledgement emits [Channel Pins ACK](/gateway/events/#channel-pins-ack). Both a server-side mention increment and [Clear channel read state](/http-api/messages/#clear-channel-read-state) change the stored entry and emit nothing, so the Dispatch stream is not a complete change feed. A client that needs the authoritative aggregate reconciles from a new Ready, or from the entries [Acknowledge read states](#acknowledge-read-states) returns.
|
||||
|
||||
### Example
|
||||
|
||||
|
||||
@@ -256,7 +256,14 @@ The snapshot also records a conversation window of up to 25 messages before the
|
||||
|
||||
10 requests per hour, on the `report:create` bucket, which is shared with [Report user](#report-user) and [Report guild](#report-guild).
|
||||
|
||||
Four further allowances apply, each counted over one hour. One reporter creates at most 5 reports across all report types and at most 3 message reports in one channel, and one message receives at most 20 reports across all reporters. When the reported message is in a guild, one reporter creates at most 4 message reports in that guild. A duplicate submission returns 409 `CONFLICT` before any of the four is consumed.
|
||||
Further allowances apply, each counted over one hour:
|
||||
|
||||
- One reporter creates at most 5 reports across all report types.
|
||||
- One reporter creates at most 3 message reports in one channel.
|
||||
- One message receives at most 20 reports across all reporters.
|
||||
- When the reported message is in a guild, one reporter creates at most 4 message reports in that guild.
|
||||
|
||||
A duplicate submission returns 409 `CONFLICT` before any of them is consumed.
|
||||
|
||||
## Report user
|
||||
|
||||
@@ -315,7 +322,13 @@ Creates and returns a [report object](#report-object) for a guild.
|
||||
- A `guild_id` that resolves to no guild returns 404 `UNKNOWN_GUILD`.
|
||||
- The guild owner reporting their own guild returns 400 `CANNOT_REPORT_OWN_GUILD`.
|
||||
|
||||
Fluxer establishes access to the guild in one of three ways, tried in that order. A current member needs nothing further. Any reporter can report a guild with the `DISCOVERABLE` [guild feature](/http-api/guilds/#guild-features). Any other reporter supplies `invite_code` for an invite pointing at that exact guild. A reporter satisfying none of the three returns 403 `CANNOT_REPORT_GUILD`.
|
||||
Fluxer establishes access to the guild in one of these ways, tried in order:
|
||||
|
||||
1. A current member needs nothing further.
|
||||
2. Any reporter can report a guild with the `DISCOVERABLE` [guild feature](/http-api/guilds/#guild-features).
|
||||
3. Any other reporter supplies `invite_code` for an invite pointing at that exact guild.
|
||||
|
||||
A reporter satisfying none of them returns 403 `CANNOT_REPORT_GUILD`.
|
||||
|
||||
### JSON body
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ A key with a different segment count, a non-numeric channel segment, an empty co
|
||||
|
||||
[Modify stream region](#modify-stream-region), [Get stream preview](#get-stream-preview), and [Delete stream preview](#delete-stream-preview) resolve the channel from the channel segment of the key. [Upload stream preview](#upload-stream-preview) and [Create stream preview upload URL](#create-stream-preview-upload-url) instead resolve it from the `channel_id` in their request body and compare that value against the key afterwards.
|
||||
|
||||
The scope segment is checked against the resolved channel. A guild-scoped key whose channel turns out to be private, a `dm` key whose channel turns out to belong to a guild, and a guild-scoped key whose guild segment is not the resolved channel's guild each return 400 `STREAM_KEY_SCOPE_MISMATCH`.
|
||||
Fluxer checks the scope segment against the resolved channel. A guild-scoped key whose resolved channel is private returns 400 `STREAM_KEY_SCOPE_MISMATCH`, and so does a `dm` key whose resolved channel belongs to a guild. A guild-scoped key whose guild segment is not the resolved channel's guild returns the same code.
|
||||
|
||||
The route then compares the channel segment against the resolved channel ID, and a mismatch returns 400 `STREAM_KEY_CHANNEL_MISMATCH`. On the two routes that have a body `channel_id`, a caller MUST send the same value in the body and in the key.
|
||||
|
||||
@@ -46,13 +46,13 @@ Two boundaries apply across this page.
|
||||
|
||||
Read access matches [Get channel](/http-api/channels/#get-channel) for the [resolved channel](#stream-key). A guild channel additionally requires the [CONNECT](/http-api/permissions/) permission. The caller does not have to own the stream, so any member who can join the channel can read its preview.
|
||||
|
||||
Mutation access adds two requirements to read access. A guild channel also requires the [STREAM](/http-api/permissions/) permission. The caller must hold a voice state in exactly the resolved channel and the connection identifier the key names. Fluxer rejects a caller that holds no such voice state with 403 `ACCESS_DENIED`, even when it owns the channel.
|
||||
Mutation access requires everything read access does. A guild channel also requires the [STREAM](/http-api/permissions/) permission. The caller must hold a voice state in exactly the resolved channel and the connection identifier the key names. Fluxer rejects a caller that holds no such voice state with 403 `ACCESS_DENIED`, even when it owns the channel.
|
||||
|
||||
:::caution[A preview can exist with no live stream]
|
||||
A caller can record a region and upload a preview for any voice connection it holds, whether or not it is streaming.
|
||||
:::
|
||||
|
||||
A guild channel resolves before its guild does. A channel row that outlives its guild returns 404 `UNKNOWN_GUILD`. A guild record that exists but whose membership state cannot be resolved returns 403 `ACCESS_DENIED`, and a caller that is not a member or that lacks [VIEW_CHANNEL](/http-api/permissions/) returns 403 `MISSING_PERMISSIONS`. An unsatisfied age restriction on a guild text, voice, or link channel returns 403 `NSFW_CONTENT_AGE_RESTRICTED`.
|
||||
A guild channel resolves before its guild does. A channel row that outlives its guild returns 404 `UNKNOWN_GUILD`. Where the guild record exists and Fluxer cannot resolve the membership state, the route returns 403 `ACCESS_DENIED`, and a caller that is not a member or that lacks [VIEW_CHANNEL](/http-api/permissions/) returns 403 `MISSING_PERMISSIONS`. An unsatisfied age restriction on a guild text, voice, or link channel returns 403 `NSFW_CONTENT_AGE_RESTRICTED`.
|
||||
|
||||
A private channel whose recipient set does not contain the caller returns 404 `UNKNOWN_CHANNEL`. An absent channel returns the same code, so recipient membership is never distinguishable from private channel existence.
|
||||
|
||||
@@ -204,7 +204,7 @@ When the image should travel out of band, use [Create stream preview upload URL]
|
||||
|
||||
<sup>3</sup> A value containing `jpeg` or `jpg` in any case is accepted without inspecting the bytes. Any other value, and an absent field, is accepted only when the decoded bytes begin with `FF D8` and end with `FF D9`
|
||||
|
||||
Fluxer stores the accepted `content_type` verbatim and returns it from [Get stream preview](#get-stream-preview). An absent field stores the preview as `image/jpeg`.
|
||||
Fluxer stores the accepted `content_type` verbatim and returns it from [Get stream preview](#get-stream-preview). When the field is absent, the stored media type is `image/jpeg`.
|
||||
|
||||
A `thumbnail` that is not canonical base64 returns 400 `INVALID_STREAM_THUMBNAIL_PAYLOAD`. Fluxer re-encodes the decoded bytes and rejects the value when the result is not what the client sent. The 2000000 character ceiling admits 1500000 decoded bytes, so an oversized JPEG reaches the byte check and returns `FILE_SIZE_TOO_LARGE`. The format check runs before the size check. An oversized payload that is not a JPEG returns `PREVIEW_MUST_BE_JPEG`.
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import RouteHeader from '@/components/RouteHeader.astro';
|
||||
|
||||
A theme is a custom CSS stylesheet stored under a short code, and anyone holding the code can fetch it. The HTTP API exposes one operation, which stores a theme. The [Media Proxy](/media-proxy/overview/) serves the stored stylesheet back through [Get theme CSS](/media-proxy/routes/#get-theme-css).
|
||||
|
||||
The single route here is user-only. A bot token and an OAuth2 bearer credential are both rejected with 403 `ACCESS_DENIED`.
|
||||
The route is user-only. A bot token and an OAuth2 bearer credential are both rejected with 403 `ACCESS_DENIED`.
|
||||
|
||||
## Theme identifier
|
||||
|
||||
@@ -43,11 +43,11 @@ An account holds no theme quota. The route bucket is the only bound on how many
|
||||
|
||||
The instance-wide content filter screens the submitted document before the route runs. It checks a `css` value of at least 3 characters against the instance phrase blocklist and the instance URL blocklist. A match on either returns 403 `CONTENT_BLOCKED`. A shorter value is never screened.
|
||||
|
||||
Fluxer scans a body under every content type except `multipart/form-data` and `application/x-www-form-urlencoded`. A body that does not parse as JSON is skipped as well. The route parses the body from the raw request text and reads no content type, so a JSON document sent under either form content type is stored without passing the blocklists.
|
||||
Fluxer scans a body under every content type except `multipart/form-data` and `application/x-www-form-urlencoded`. The filter also skips a body that does not parse as JSON. The route parses the body from the raw request text and reads no content type, so a JSON document sent under either form content type is stored without passing the blocklists.
|
||||
|
||||
The screen precedes the rate limit bucket, the credential check, and the request schema, so a blocked document returns `CONTENT_BLOCKED` even when the request has no credential.
|
||||
|
||||
The screen is the only inspection the document receives. Fluxer stores the CSS verbatim and never parses, validates, minifies, or rewrites it.
|
||||
Nothing else inspects the document. Fluxer stores the CSS verbatim and never parses, validates, minifies, or rewrites it.
|
||||
|
||||
## Create theme
|
||||
|
||||
@@ -93,7 +93,7 @@ The object has the identifier and nothing else.
|
||||
|
||||
Fluxer stores the UTF-8 encoding of the submitted document with the content type `text/css; charset=utf-8`. The identifier addresses the document by the time the caller receives it.
|
||||
|
||||
Nothing Fluxer stores links a theme to the account that created it. The [Media Proxy](/media-proxy/overview/) then serves the stored document from `/themes/{id}.css`, and that route accepts no credential.
|
||||
Nothing Fluxer stores links a theme to the account that created it. The [Media Proxy](/media-proxy/overview/) serves the stored document from `/themes/{id}.css`, and that route accepts no credential.
|
||||
|
||||
:::danger[Theme CSS is public and permanent]
|
||||
The identifier is the only thing guarding a stored document, and the read route is unauthenticated. The HTTP API replaces or deletes no stored theme. A caller MUST NOT put a credential, a token, or any other secret in a submitted document.
|
||||
|
||||
@@ -8,7 +8,7 @@ import RouteHeader from '@/components/RouteHeader.astro';
|
||||
|
||||
Unfurl resolves one URL into the preview a message would show for it. That preview is an array of [embed](/http-api/messages/#embed-object) objects, the same objects the ordinary message path attaches.
|
||||
|
||||
The single route here is user-only. Fluxer rejects a bot token and an OAuth2 bearer credential with 403 `ACCESS_DENIED`.
|
||||
The route is user-only. Fluxer rejects a bot token and an OAuth2 bearer credential with 403 `ACCESS_DENIED`.
|
||||
|
||||
## Site resolvers
|
||||
|
||||
@@ -48,7 +48,15 @@ A resolved embed has at most one nested embed in `children`, and a nested embed
|
||||
|
||||
## Network policy
|
||||
|
||||
Fluxer applies its network policy before every fetch and again to every redirect target. It refuses a URL that is empty, exceeds 8192 characters, contains a control character, uses a scheme other than HTTP or HTTPS, or has user information. Fluxer also refuses a URL whose port is `0` or whose host is not a valid public hostname. A host that resolves to a loopback, private, link-local, or otherwise reserved address is refused, and so is a host that resolves to no address at all.
|
||||
Fluxer applies its network policy before every fetch and again to every redirect target. It refuses a URL on any of these grounds:
|
||||
|
||||
- The URL is empty, exceeds 8192 characters, or contains a control character.
|
||||
- The scheme is neither HTTP nor HTTPS.
|
||||
- The URL has user information.
|
||||
- The port is `0`.
|
||||
- The host is not a valid public hostname.
|
||||
- The host resolves to a loopback, private, link-local, or otherwise reserved address.
|
||||
- The host resolves to no address at all.
|
||||
|
||||
Every HTTP request has a timeout of at most 10 seconds. The FxTwitter timeout is 8 seconds, and a Hacker News, Wikipedia, Bluesky, ActivityPub, oEmbed, MediaWiki extract, or media metadata call has 5 seconds. Every fetch identifies itself as `Mozilla/5.0 (compatible; Fluxerbot/1.0; +https://fluxer.app)`.
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ A system account never receives the deleted representation. An account only sche
|
||||
|
||||
## Reply mention preferences
|
||||
|
||||
The account-wide preference applied when another account replies to one of this account's messages. `mention_flags` is exactly one of the three values below, and it is omitted from a serialised user while the stored value is `NO_PREFERENCE`.
|
||||
The account-wide preference applied when another account replies to one of this account's messages. `mention_flags` is exactly one of the values below, and it is omitted from a serialised user while the stored value is `NO_PREFERENCE`.
|
||||
|
||||
| Value | Name | Description |
|
||||
| --- | --- | --- |
|
||||
@@ -159,7 +159,7 @@ The private representation of the current account, returned by [Get current user
|
||||
|
||||
<sup>5</sup> For an OAuth2 bearer credential without the `email` [scope](/http-api/oauth2/#oauth2-scopes), `email` is `null`
|
||||
|
||||
<sup>6</sup> Phone numbers are no longer stored on the account record. The field is retained so an older client keeps parsing the response
|
||||
<sup>6</sup> The account record stores no phone number. The field is retained so an older client keeps parsing the response
|
||||
|
||||
<sup>7</sup> The pair is present only while the account has the staff flag, and it is absent for every other account
|
||||
|
||||
@@ -169,7 +169,7 @@ The private representation of the current account, returned by [Get current user
|
||||
|
||||
<sup>10</sup> The value is forced to `0` and `premium_since` is forced to `null` while premium entitlements are not currently active
|
||||
|
||||
<sup>11</sup> The value is the later of the subscription end and any stacked gift extension, and it is reported even when premium entitlements are no longer active
|
||||
<sup>11</sup> `premium_until` is the later of the subscription end and any stacked gift extension. Fluxer reports it even when premium entitlements are no longer active
|
||||
|
||||
<sup>12</sup> While the value is set, premium entitlements remain active until it passes, and it replaces the default grace interval that otherwise follows `premium_until`
|
||||
|
||||
@@ -438,7 +438,7 @@ One entry of the saved, user-owned guild sidebar layout. Joining a guild prepend
|
||||
|
||||
## Guild sensitive media filters
|
||||
|
||||
Guild channels accept only these two values.
|
||||
Guild channels accept only these values.
|
||||
|
||||
| Value | Name | Description |
|
||||
| --- | --- | --- |
|
||||
@@ -604,7 +604,7 @@ Returns a [full user profile](#full-user-profile-object) object for one target a
|
||||
|
||||
### Limitations
|
||||
|
||||
- A caller reading another account needs one of four relationships with the target: friendship, a pending friend request in either direction, a shared guild, or a shared group direct message channel.
|
||||
- A caller reading another account needs one of these relationships with the target: friendship, a pending friend request in either direction, a shared guild, or a shared group direct message channel.
|
||||
- Reading a bot target is always permitted.
|
||||
- Reading the caller's own profile bypasses both the access check and profile privacy.
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import RouteHeader from '@/components/RouteHeader.astro';
|
||||
|
||||
Recent mentions and saved messages are private lists that only the account owning them can read. Two further routes delete the messages the caller authored, one filtered and immediate, the other unfiltered and delayed by a day. Data harvests live on [Data harvests](/http-api/users/data-harvest/).
|
||||
|
||||
Every route here is user-only. Fluxer rejects a bot or OAuth2 bearer credential with 403 `ACCESS_DENIED`, and an account that has an outstanding required action with 403 `ACCOUNT_SUSPICIOUS_ACTIVITY`.
|
||||
These routes are user-only. Fluxer rejects a bot or OAuth2 bearer credential with 403 `ACCESS_DENIED`, and an account that has an outstanding required action with 403 `ACCOUNT_SUSPICIOUS_ACTIVITY`.
|
||||
|
||||
Every route except [Delete current user's messages](#delete-current-users-messages) reaches the main Gateway, and a Gateway failure returns 502 `BAD_GATEWAY`, 503 `SERVICE_UNAVAILABLE`, or 504 `GATEWAY_TIMEOUT`. [Save message](#save-message) resolves the channel before it writes, so a failure there stores nothing. The request still returns 204 when a [Saved Message Create](/gateway/events/#saved-message-create), [Saved Message Delete](/gateway/events/#saved-message-delete), or [Recent Mention Delete](/gateway/events/#recent-mention-delete) Dispatch fails to publish after the write.
|
||||
|
||||
@@ -20,7 +20,7 @@ Every route except [Delete current user's messages](#delete-current-users-messag
|
||||
|
||||
A saved entry is a private bookmark pairing one message with the channel it was saved from. Saving a message notifies nobody, including its author.
|
||||
|
||||
An entry stores no copy of the message. Fluxer resolves the message against current permissions on every read, so one entry can have a message on one read and none on the next.
|
||||
The entry stores no copy of the message. Fluxer resolves the message against current permissions on every read, so one entry can have a message on one read and none on the next.
|
||||
|
||||
### Structure
|
||||
|
||||
@@ -294,7 +294,7 @@ Every message the filter selects is deleted from its channel for every recipient
|
||||
|
||||
### JSON body
|
||||
|
||||
The caller proves sudo mode either with the five sudo fields below or with an existing proof in the request. An existing proof travels in the `X-Fluxer-Sudo-Mode-JWT` request header.
|
||||
The caller proves sudo mode either with the sudo fields below or with an existing proof in the `X-Fluxer-Sudo-Mode-JWT` request header.
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
@@ -322,7 +322,7 @@ The caller proves sudo mode either with the five sudo fields below or with an ex
|
||||
|
||||
<sup>4</sup> Supplying both bounds requires `start_date` strictly earlier than `end_date`, and an equal pair fails validation on `end_date`
|
||||
|
||||
<sup>5</sup> The five sudo fields are the [sudo verification object](/http-api/users/mfa/#sudo-verification-object). Which combination is accepted depends on the account's configured authenticators
|
||||
<sup>5</sup> These fields are the [sudo verification object](/http-api/users/mfa/#sudo-verification-object). Which combination is accepted depends on the account's configured authenticators
|
||||
|
||||
The `selected` scope requires at least one of the four context toggles to be true, and a request that disables all four fails validation on `include_dms`. The `inaccessible_only` scope ignores the toggles and the guild filter.
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ Every route except [Get current user](#get-current-user) is user-only. Those rou
|
||||
|
||||
## Sudo verification
|
||||
|
||||
Sudo verification asks the account to prove itself again inside the request. Security-sensitive fields and destructive lifecycle operations require it. Proof comes in one of three forms.
|
||||
Sudo verification asks the account to prove itself again inside the request. Security-sensitive fields and destructive lifecycle operations require it. The accepted proof depends on the account state.
|
||||
|
||||
| Account state | Accepted proof |
|
||||
| --- | --- |
|
||||
@@ -20,13 +20,13 @@ Sudo verification asks the account to prove itself again inside the request. Sec
|
||||
| An authenticator configured | `mfa_method` of `totp` with `mfa_code`, or `mfa_method` of `webauthn` with `webauthn_response` and `webauthn_challenge` |
|
||||
| No password credential and no authenticator | None, the requirement is already satisfied |
|
||||
|
||||
An already valid sudo token replaces any of those forms. Once an account configures an authenticator its `password` stops working here.
|
||||
Once an account configures an authenticator, its `password` stops working here. An already valid sudo token replaces any of those forms.
|
||||
|
||||
A sudo token is an HS256 JSON Web Token valid for five minutes. It has the account ID as its subject and is rejected for any other account. A successful verification returns the token in the `X-Fluxer-Sudo-Mode-JWT` response header. Fluxer mints a fresh token only when an authenticator satisfied the requirement. A request presenting an already valid token gets that same token back without an extended lifetime.
|
||||
|
||||
A request that does not satisfy the requirement returns 403 [`SUDO_MODE_REQUIRED`](/http-api/errors/), whose body has `has_mfa` and `methods` as top-level members. An invalid authenticator code, backup code, or WebAuthn assertion returns [`INVALID_MFA_CODE`](/http-api/errors/) on the `mfa_code` path. An incorrect password returns [`INVALID_PASSWORD`](/http-api/errors/) on the `password` path.
|
||||
A request that does not satisfy the requirement returns 403 [`SUDO_MODE_REQUIRED`](/http-api/errors/). The body has `has_mfa` and `methods` as top-level members. An invalid authenticator code, backup code, or WebAuthn assertion returns [`INVALID_MFA_CODE`](/http-api/errors/) on the `mfa_code` path. An incorrect password returns [`INVALID_PASSWORD`](/http-api/errors/) on the `password` path.
|
||||
|
||||
A request presents an existing token in the `X-Fluxer-Sudo-Mode-JWT` request header. Retain the response header value and send it back on each later operation in the same sudo window.
|
||||
A client sends an existing token in the `X-Fluxer-Sudo-Mode-JWT` request header. Retain the response header value and send it back on each later operation in the same sudo window.
|
||||
|
||||
## Get current user
|
||||
|
||||
@@ -127,9 +127,9 @@ Modifies the current account and returns the resulting [user](/http-api/users/#u
|
||||
|
||||
To skip the body proof fields, send the sudo token in the `X-Fluxer-Sudo-Mode-JWT` request header.
|
||||
|
||||
A body that has no `email_token` and nothing beyond `mfa_method`, `mfa_code`, `webauthn_response`, and `webauthn_challenge` returns the current account unchanged. That check runs before the required action barrier, so it succeeds even while an action is outstanding. A body that has only `password` is an update with nothing to change, and it still emits [User Update](/gateway/events/#user-update).
|
||||
A body that has no `email_token` and nothing beyond `mfa_method`, `mfa_code`, `webauthn_response`, and `webauthn_challenge` returns the current account unchanged. That check runs before the required action barrier, so it succeeds even while an action is outstanding. Supplying `password` alone is an update with nothing to change, and it still emits [User Update](/gateway/events/#user-update).
|
||||
|
||||
Fluxer rejects an account with an outstanding required action, with one exemption. A body that has `email_token` and nothing beyond the sudo verification fields is accepted. Any other defined field removes the exemption.
|
||||
Fluxer rejects an account with an outstanding required action. The one exemption is a body that has `email_token` and nothing beyond the sudo verification fields. Any other defined field removes the exemption.
|
||||
|
||||
A claimed account needs a verified email before changing the username, discriminator, display name, avatar, banner, biography, pronouns, accent colour, timezone, timezone privacy, or any premium badge field. A request without one returns 403 [`PROFILE_EMAIL_VERIFICATION_REQUIRED`](/http-api/errors/).
|
||||
|
||||
@@ -286,7 +286,7 @@ Records acceptance of the current terms of service and privacy policy. Returns t
|
||||
|
||||
### JSON body
|
||||
|
||||
The body is an empty object. An absent or empty body is read as an empty object, and any supplied property is discarded. A body that is not valid JSON returns 400 `INVALID_FORM_BODY` with `INVALID_FORMAT` at the `body` path. A body that parses as valid JSON but is not an object is rejected with 400.
|
||||
The body is an empty object. An absent or empty body is read as an empty object, and any supplied property is discarded. Invalid JSON returns 400 `INVALID_FORM_BODY` with `INVALID_FORMAT` at the `body` path. Valid JSON that is not an object is rejected with 400.
|
||||
|
||||
### Response
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ The status is derived from the recorded timestamps in a fixed order, testing `fa
|
||||
| completed | The archive was written and is ready for download |
|
||||
| failed | The latest attempt recorded a failure |
|
||||
|
||||
Fluxer retries the underlying job. A retry clears `failed_at` and `error_message`, sets `started_at` again, resets the reported progress to 0, and reports `processing` while it runs. A retry that succeeds records `completed_at`, `file_size`, and a download deadline, and reports `completed`.
|
||||
Fluxer retries the underlying job. A retry clears `failed_at` and `error_message`, sets `started_at` again, resets the reported progress to 0, and reports `processing` while it runs. On success it records `completed_at`, `file_size`, and a download deadline, and reports `completed`.
|
||||
|
||||
## Harvest creation object
|
||||
|
||||
@@ -144,7 +144,11 @@ The filter chooses which messages a harvest collects. Every non-message section
|
||||
|
||||
<sup>4</sup> Compared against the creation time embedded in the message [snowflake](/snowflakes/), so editing an old message does not move it into a later window
|
||||
|
||||
Under the `selected` scope a one-to-one direct message is collected when the conversation is still open and `include_dms` is true, or when it has been closed and `include_dms_closed` is true. A group DM is collected only while the caller is still a recipient and `include_group_dms` is true. A guild channel is collected only while the caller is still a member, `include_guilds` is true, and the guild is admitted by the configured list mode.
|
||||
Under the `selected` scope, each context has its own condition:
|
||||
|
||||
- A one-to-one direct message is collected when the conversation is still open and `include_dms` is true, or when it has been closed and `include_dms_closed` is true.
|
||||
- A group DM is collected only while the caller is still a recipient and `include_group_dms` is true.
|
||||
- A guild channel is collected only while the caller is still a member, `include_guilds` is true, and the guild is admitted by the configured list mode.
|
||||
|
||||
The `inaccessible_only` scope selects messages in guilds the caller has left or been removed from and in group DMs the caller has left. It never selects one-to-one direct messages, and it ignores the four toggles, the guild filter mode, and both guild lists.
|
||||
|
||||
@@ -176,9 +180,18 @@ The operation takes no request body and always returns before the archive exists
|
||||
|
||||
A harvest record is created with a new [snowflake](/snowflakes/), the request time as `created_at`, progress 0, the step `Queued`, and every other timestamp null. Creating a harvest removes no earlier record and enforces no ceiling on how many an account holds, so the route bucket is the only bound.
|
||||
|
||||
Fluxer prepares the archive asynchronously. It contains `user.json` with the account document, one `channels/{channel_id}/messages.json` file for each channel that contributed a message, `payments/payment_history.json`, `integrations/oauth.json` listing the [applications](/http-api/applications/) the account owns rather than the ones it authorised, and `account/security.json`. The account avatar and banner are added under `assets/user/` when present. Messages inside each channel file are ordered oldest first. Attachment metadata appears with its message and has the attachment ID, filename, size, content type, CDN URL, and pixel dimensions. The attachment files themselves are never included.
|
||||
Fluxer prepares the archive asynchronously. It contains:
|
||||
|
||||
At most 100,000 authored messages are collected. The collector skips a message it cannot read, and the harvest still succeeds. When the archive completes, Fluxer sends one email containing a download URL if the account has an email address and the instance has email delivery enabled. That URL expires seven days after it was minted.
|
||||
- `user.json`, the account document
|
||||
- one `channels/{channel_id}/messages.json` file for each channel that contributed a message, with its messages ordered oldest first
|
||||
- `payments/payment_history.json`
|
||||
- `integrations/oauth.json`, the [applications](/http-api/applications/) the account owns, with no entry for an application it only authorised
|
||||
- `account/security.json`
|
||||
- the account avatar and banner under `assets/user/` when present
|
||||
|
||||
Attachment metadata appears with its message and has the attachment ID, filename, size, content type, CDN URL, and pixel dimensions. The attachment files themselves are never included.
|
||||
|
||||
At most 100,000 authored messages are collected. Fluxer skips a message it cannot read, and the harvest still succeeds. When the archive completes, Fluxer sends one email containing a download URL if the account has an email address and the instance has email delivery enabled. That URL expires seven days after it was minted.
|
||||
|
||||
### Rate limit
|
||||
|
||||
@@ -303,12 +316,10 @@ The URL takes one of two forms. When the instance has presigned harvest download
|
||||
|
||||
Streams the archive of one completed harvest.
|
||||
|
||||
The signed `token` query parameter issued by [Get data harvest download URL](#get-data-harvest-download-url) is the whole authorisation, so the link stays usable from the harvest completion email and from a plain browser.
|
||||
The signed `token` query parameter issued by [Get data harvest download URL](#get-data-harvest-download-url) is the whole authorisation, so the link stays usable from the harvest completion email and from a plain browser. It binds the account, the harvest ID, the stored object key, and an expiry.
|
||||
|
||||
The operation is active only while the instance has presigned harvest downloads disabled. That setting is enabled by default, so a default deployment answers every request here as 404 without inspecting the token.
|
||||
|
||||
The token binds the account, the harvest ID, the stored object key, and an expiry.
|
||||
|
||||
:::note[Every rejection looks identical]
|
||||
A missing, malformed, forged, or expired token, a token for another harvest, a harvest that never completed, a harvest that recorded a failure, a harvest past its deadline, and a stale object key all return 404 with the plain text body `Not Found`.
|
||||
:::
|
||||
|
||||
@@ -34,7 +34,14 @@ Each of these values can advance a security-sensitive account change. Keep them
|
||||
Every ticket, code, address, proof, token, and password failure named on this page arrives as HTTP 400 whose top-level code is `INVALID_FORM_BODY`. The named value is the `code` of one [validation error](/http-api/#validation-error-object) entry, and that entry's `path` is the field it belongs to.
|
||||
:::
|
||||
|
||||
A ticket failure uses the path `ticket`, a code failure `code`, and an address failure `new_email` or `email`. A proof failure uses `original_proof` or `verification_proof`, an email token failure uses `email_token`, and a password failure uses `password` or `new_password`. The account-level codes `ACCESS_DENIED`, `ACCOUNT_SUSPICIOUS_ACTIVITY`, `SUDO_MODE_REQUIRED`, and `RATE_LIMITED` arrive as the top-level `code`.
|
||||
- A ticket failure uses the path `ticket`.
|
||||
- A code failure uses the path `code`.
|
||||
- An address failure uses the path `new_email` or `email`.
|
||||
- A proof failure uses the path `original_proof` or `verification_proof`.
|
||||
- An email token failure uses the path `email_token`.
|
||||
- A password failure uses the path `password` or `new_password`.
|
||||
|
||||
The account-level codes `ACCESS_DENIED`, `ACCOUNT_SUSPICIOUS_ACTIVITY`, `SUDO_MODE_REQUIRED`, and `RATE_LIMITED` arrive as the top-level `code`.
|
||||
|
||||
## Email send controls
|
||||
|
||||
@@ -49,7 +56,7 @@ Each send-bearing operation passes through its own route bucket and, separately,
|
||||
|
||||
## Email change ticket states
|
||||
|
||||
The email change ticket moves through three states. A step legal in one state is rejected in every other with the transition code named below.
|
||||
The email change ticket is pending-original, pending-new, or completed. A step legal in one state is rejected in every other with the transition code named below.
|
||||
|
||||
| Current state | Operation | Next state |
|
||||
| --- | --- | --- |
|
||||
@@ -61,7 +68,7 @@ The email change ticket moves through three states. A step legal in one state is
|
||||
|
||||
A ticket created without original email verification starts in the pending-new state and receives its `original_proof` directly from [Start email change](#start-email-change). Requesting or verifying a new address while the ticket is still pending original fails with `ORIGINAL_EMAIL_MUST_BE_VERIFIED_FIRST`.
|
||||
|
||||
Verifying the original stage on a ticket that never required it fails with `ORIGINAL_VERIFICATION_NOT_REQUIRED`. Resending the original code fails with `ORIGINAL_EMAIL_ALREADY_VERIFIED` on a cleared ticket and on a ticket that never required the stage, so the response does not tell the two apart. Verifying the original address again on a cleared ticket returns the same `original_proof` unchanged.
|
||||
Verifying the original stage on a ticket that never required it fails with `ORIGINAL_VERIFICATION_NOT_REQUIRED`. Resending the original code fails with `ORIGINAL_EMAIL_ALREADY_VERIFIED` on a cleared ticket and on a ticket that never required the stage, so the response does not tell the two apart. On a cleared ticket, verifying the original address again returns the same `original_proof`.
|
||||
|
||||
Resending the new address code before an address has been requested fails with `NO_NEW_EMAIL_REQUESTED`. A pending-original ticket returns the same code.
|
||||
|
||||
@@ -280,7 +287,14 @@ Binds a new address to an active ticket and sends it a verification code. Return
|
||||
|
||||
A ticket that has not cleared original address verification fails with `ORIGINAL_EMAIL_MUST_BE_VERIFIED_FIRST`. An `original_proof` that does not match the ticket fails with `INVALID_PROOF_TOKEN`.
|
||||
|
||||
Fluxer trims the address and then checks it in order. An empty address fails with `EMAIL_IS_REQUIRED`, an address equal to the ticket's recorded original with `NEW_EMAIL_MUST_BE_DIFFERENT`, a domain that fails DNS eligibility with `INVALID_EMAIL_ADDRESS`, and an address another account already owns with `EMAIL_ALREADY_IN_USE`. Ownership is checked again when the resulting email token is applied, so passing here reserves nothing. Calling the operation again with a different address replaces the pending address on the same ticket.
|
||||
Fluxer trims the address and then checks it in order.
|
||||
|
||||
- An empty address fails with `EMAIL_IS_REQUIRED`.
|
||||
- An address equal to the ticket's recorded original fails with `NEW_EMAIL_MUST_BE_DIFFERENT`.
|
||||
- A domain without DNS eligibility fails with `INVALID_EMAIL_ADDRESS`.
|
||||
- An address another account already owns fails with `EMAIL_ALREADY_IN_USE`.
|
||||
|
||||
Ownership is checked again when the resulting email token is applied, so passing here reserves nothing. Calling the operation again with a different address replaces the pending address on the same ticket.
|
||||
|
||||
The send consumes the new address control, and the ticket enforces its own 30-second cooldown.
|
||||
|
||||
@@ -385,17 +399,15 @@ The ticket moves to the completed state and a 30-minute email token is issued fo
|
||||
|
||||
Consumes an email token and writes its address to the account. Returns the updated [user](/http-api/users/#user-object) object on success. Emits a [User Update](/gateway/events/#user-update) Gateway event.
|
||||
|
||||
A claimed account proves [sudo mode](/http-api/users/mfa/#sudo-mode) before the route examines the token. The route accepts suspicious account state.
|
||||
A claimed account proves [sudo mode](/http-api/users/mfa/#sudo-mode) before the route examines the token. An unclaimed account is exempt and applies the change with its session alone. The route accepts suspicious account state.
|
||||
|
||||
A token that belongs to another account fails with `INVALID_EMAIL_TOKEN`, and one past its 30-minute lifetime fails with `EMAIL_TOKEN_EXPIRED`. An expired token is deleted when rejected. Fluxer checks the address against existing account ownership again here, and an address another account has taken since fails with `EMAIL_ALREADY_IN_USE` without consuming the token.
|
||||
|
||||
An unclaimed account is exempt from sudo mode and applies the change with its session alone.
|
||||
|
||||
[Modify current user](/http-api/users/current-user/#modify-current-user) accepts the same token alongside an unrelated profile edit. The token is single use, so a retry after success fails with `INVALID_EMAIL_TOKEN`.
|
||||
|
||||
### JSON body
|
||||
|
||||
The body extends the [sudo verification object](/http-api/users/mfa/#sudo-verification-object) with `email_token`, and the five sudo fields it merges in are repeated below. An existing sudo proof travels in the `X-Fluxer-Sudo-Mode-JWT` request header.
|
||||
The body extends the [sudo verification object](/http-api/users/mfa/#sudo-verification-object) with `email_token`, and the sudo fields it merges in are repeated below. An existing sudo proof travels in the `X-Fluxer-Sudo-Mode-JWT` request header.
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
@@ -447,7 +459,7 @@ For an ordinary change from an existing address, Fluxer sends a revert email to
|
||||
|
||||
## Bounced email recovery
|
||||
|
||||
An account whose stored address the mail provider rejected has the bounced marker, reported as `email_bounced` on the [user object](/http-api/users/#user-object). The bounce also clears the verified email state and imposes a required action. The three operations below bind a replacement address and finish without the sudo-gated apply step.
|
||||
An account whose stored address the mail provider rejected has the bounced marker, reported as `email_bounced` on the [user object](/http-api/users/#user-object). The bounce also clears the verified email state and imposes a required action. The operations below bind a replacement address and finish without the sudo-gated apply step.
|
||||
|
||||
Every one of them requires the bounced marker. An account without it is rejected with 403 `ACCESS_DENIED`, and an account with no address at all is rejected with `MUST_HAVE_EMAIL_TO_CHANGE_IT`.
|
||||
|
||||
@@ -484,7 +496,7 @@ When the account does hold a verified address the request fails with `ORIGINAL_E
|
||||
|
||||
### Side effects
|
||||
|
||||
A ticket is created pending-new with the replacement address bound to it, and one verification email goes to that address. No account field changes. Retain the returned `ticket`, because the two remaining steps take no other identifier.
|
||||
A ticket is created pending-new with the replacement address bound to it, and one verification email goes to that address. No account field changes. Retain the returned `ticket`, because the remaining steps take no other identifier.
|
||||
|
||||
### Rate limit
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ A note is short private text the caller stores about another account.
|
||||
Every route here requires a user session. A bot or OAuth2 bearer credential is refused with 403 `ACCESS_DENIED`. An account with an outstanding required action is refused with 403 `ACCOUNT_SUSPICIOUS_ACTIVITY`.
|
||||
|
||||
:::note[A note is private to its author]
|
||||
The target account never receives it in an HTTP response or [Gateway Dispatch](/gateway/events/). A note has no relationship requirement, so the caller can hold one for an account that is neither a friend nor blocked.
|
||||
The target account never receives the note in an HTTP response or [Gateway Dispatch](/gateway/events/). A note has no relationship requirement, so the caller can hold one for an account that is neither a friend nor blocked.
|
||||
:::
|
||||
|
||||
## User note object
|
||||
@@ -103,10 +103,10 @@ Creates, replaces, or deletes the caller's note for one target account. Returns
|
||||
The supplied text becomes the complete stored note. An unresolved target ID returns 404 `UNKNOWN_USER`, even when the request would only delete a note. The caller can store a note for any account that exists, including a bot account, its own account, and an account it holds no relationship with.
|
||||
|
||||
:::caution[An omitted, null, or empty note deletes it]
|
||||
An empty string is [read as `null`](/http-api/#input-normalisation), so the three forms are indistinguishable. A value that normalises to the empty string, such as one made only of whitespace, fails validation with `STRING_LENGTH_INVALID`. There is no separate delete route.
|
||||
An empty string is [read as `null`](/http-api/#input-normalisation), so the three forms are indistinguishable. Any value that normalises to the empty string, such as one made only of whitespace, fails validation with `STRING_LENGTH_INVALID`. There is no separate delete route.
|
||||
:::
|
||||
|
||||
A `note` of at least 3 characters matching the instance phrase blocklist, or with a URL matching the URL blocklist, is rejected with 403 `CONTENT_BLOCKED`. That screen runs ahead of the route's own credential and body checks, so a blocked value is refused with `CONTENT_BLOCKED` even when the request would also fail one of those checks.
|
||||
A `note` of at least 3 characters matching the instance phrase blocklist, or with a URL matching the URL blocklist, is rejected with 403 `CONTENT_BLOCKED`. The blocklist check runs before the route's own credential and body checks, so a blocked value is refused with `CONTENT_BLOCKED` even when the request would also fail one of those checks.
|
||||
|
||||
### Path parameters
|
||||
|
||||
@@ -136,7 +136,7 @@ Fluxer reads an omitted body as an empty object and deletes the stored note.
|
||||
|
||||
### Side effects
|
||||
|
||||
Supplying text creates or replaces the note, and a request that supplies none deletes it. [User Note Update](/gateway/events/#user-note-update) reaches the caller's own sessions for every accepted request, including one that supplies the current value and one that deletes an absent note. Its payload has the target ID in `id` and the note text in `note`. A deletion has the empty string.
|
||||
Supplying text creates or replaces the note, and a request that supplies none deletes it. [User Note Update](/gateway/events/#user-note-update) reaches the caller's own sessions for every accepted request, including one that supplies the current value and one that deletes an absent note. Its payload has the target ID in `id` and the note text in `note`. On a deletion the note text is the empty string.
|
||||
|
||||
### Rate limit
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ Every route here needs a non-bot user session, and each one admits a session wit
|
||||
|
||||
## Eligibility
|
||||
|
||||
[Send phone verification](#send-phone-verification) and [Verify phone code](#verify-phone-code) admit only an account that satisfies at least one of four conditions.
|
||||
[Send phone verification](#send-phone-verification) and [Verify phone code](#verify-phone-code) admit only an account that satisfies at least one of these conditions.
|
||||
|
||||
- The account already holds a verified phone.
|
||||
- A TOTP [authenticator](/http-api/users/#authenticator-types) is enrolled on the account.
|
||||
@@ -29,11 +29,11 @@ The [required actions](/http-api/users/#required-actions) array is filtered: emp
|
||||
|
||||
## Deferred phone requirement
|
||||
|
||||
An instance can defer a new account's phone requirement instead of imposing it at registration. The [deferred phone gate](/admin-api/instance/#deferred-phone-gate-object) policy sets the window after registration in which a qualifying join makes the requirement due, and the member count above which a guild is a qualifying one. A guild with the `DISCOVERABLE` [feature](/http-api/guilds/#guild-features) qualifies at any member count.
|
||||
At registration, an instance can defer a new account's phone requirement. The [deferred phone gate](/admin-api/instance/#deferred-phone-gate-object) policy sets the window after registration in which a qualifying join makes the requirement due, and the member count above which a guild is a qualifying one. A guild with the `DISCOVERABLE` [feature](/http-api/guilds/#guild-features) qualifies at any member count.
|
||||
|
||||
A deferred requirement is absent from the [required actions](/http-api/users/#required-actions) array and restricts nothing. An attempt to join a qualifying guild inside the window makes it due, and [Accept invite](/http-api/invites/#accept-invite) refuses that attempt with 403 `ACCOUNT_SUSPICIOUS_ACTIVITY`. The account then owes `REQUIRE_VERIFIED_PHONE` or `REQUIRE_REVERIFIED_PHONE`, and every ordinary user route refuses it with the same code.
|
||||
|
||||
[Get phone gate escape preview](#get-phone-gate-escape-preview) and [Run phone gate escape](#run-phone-gate-escape) set that requirement aside again in place of verifying a number. Both admit an account the rest of the API refuses. The escape is available only while all six of these hold.
|
||||
[Get phone gate escape preview](#get-phone-gate-escape-preview) and [Run phone gate escape](#run-phone-gate-escape) set that requirement aside again without verifying a number. Both admit an account the rest of the API refuses. The escape is available only while all of these hold.
|
||||
|
||||
- The instance has the deferred phone gate enabled.
|
||||
- The account holds no verified phone.
|
||||
@@ -205,13 +205,17 @@ What [Run phone gate escape](#run-phone-gate-escape) would do for the current ac
|
||||
|
||||
Starts verification of the submitted number. Requires an account satisfying [eligibility](#eligibility). Returns an [SMS delivery](#sms-delivery-object) object or an [inbound challenge](#inbound-challenge-object) object on success.
|
||||
|
||||
Several independent policies can force the inbound channel, and each one overrides a requested `sms`. Fluxer routes the attempt inbound before it contacts the lookup and SMS providers in three cases. The caller asks for `inbound_challenge`. The account has the flag that forces inbound verification. The configured inbound-required prefix policy matches the number while the account holds no verified phone.
|
||||
Several independent policies can force the inbound channel, and each one overrides a requested `sms`. Fluxer routes the attempt inbound before it contacts the lookup and SMS providers when any of these holds.
|
||||
|
||||
- The caller asks for `inbound_challenge`.
|
||||
- The account has the flag that forces inbound verification.
|
||||
- The configured inbound-required prefix policy matches the number while the account holds no verified phone.
|
||||
|
||||
An attempt that survives that step passes through phone attempt risk controls keyed by account, by client IP address, and by /24 network. A hard block returns 429 `PHONE_RATE_LIMIT_EXCEEDED` with a `Retry-After` of 86400. A captcha decision returns 400 `CAPTCHA_REQUIRED`, and this route reads no [CAPTCHA](/topics/captcha/) solution header, so the attempt cannot be retried with a solution. An inbound decision returns a challenge with the `behavioural_risk` reason.
|
||||
|
||||
An earlier attempt against the same account or number can record a provider cooldown, and a later request under that cooldown returns 429 `PHONE_RATE_LIMIT_EXCEEDED`. The destination is then resolved through [provider lookup](#provider-lookup-verdicts). A number that has already completed verification twice is refused with 400 `PHONE_ALREADY_USED`.
|
||||
|
||||
Two further controls bound outbound delivery: 3 sends per 6 hours for each account, and 3 sends per 5 days for each number. Either denial returns 429 `PHONE_RATE_LIMIT_EXCEEDED`. The provider can also refuse the send: an invalid destination returns 400 `INVALID_PHONE_NUMBER`, a throttle returns 429 `PHONE_RATE_LIMIT_EXCEEDED`, and every other refusal, an unreachable provider included, returns 400 `SMS_VERIFICATION_UNAVAILABLE`. An attempt routed inbound on an instance where the inbound flow or its receiving number is unconfigured returns the same code.
|
||||
Further controls bound outbound delivery to 3 sends per 6 hours for each account and 3 sends per 5 days for each number. Either denial returns 429 `PHONE_RATE_LIMIT_EXCEEDED`. The provider can also refuse the send. An invalid destination returns 400 `INVALID_PHONE_NUMBER`, and a throttle returns 429 `PHONE_RATE_LIMIT_EXCEEDED`. Every other refusal, an unreachable provider included, returns 400 `SMS_VERIFICATION_UNAVAILABLE`. On an instance where the inbound flow or its receiving number is unconfigured, an attempt routed inbound returns the same code.
|
||||
|
||||
### JSON body
|
||||
|
||||
@@ -276,7 +280,7 @@ Verifies an outbound SMS code. Requires an account satisfying [eligibility](#eli
|
||||
|
||||
The number is resolved through [provider lookup](#provider-lookup-verdicts) again before the code is checked, so a number that has become ineligible since the code was sent is refused.
|
||||
|
||||
The configured SMS provider checks the number and the code together, so the provider decides the code lifetime and the attempt allowance. A rejected code returns 400 `INVALID_PHONE_VERIFICATION_CODE`. A provider throttle returns 429 `PHONE_RATE_LIMIT_EXCEEDED` and records a cooldown that also denies [Send phone verification](#send-phone-verification) until it expires. An unreachable provider, or one answering with a server error, returns 400 `SMS_VERIFICATION_UNAVAILABLE`. An account record that has been deleted, or can no longer be read, fails with 400 `PHONE_VERIFICATION_REQUIRED` after the code has been accepted.
|
||||
The configured SMS provider checks the number and the code together, so the provider decides the code lifetime and the attempt allowance. A rejected code returns 400 `INVALID_PHONE_VERIFICATION_CODE`. A provider throttle returns 429 `PHONE_RATE_LIMIT_EXCEEDED` and records a cooldown that also denies [Send phone verification](#send-phone-verification) until it expires. An unreachable provider, or one answering with a server error, returns 400 `SMS_VERIFICATION_UNAVAILABLE`. Once the code has been accepted, an account record that has been deleted or can no longer be read fails with 400 `PHONE_VERIFICATION_REQUIRED`.
|
||||
|
||||
### JSON body
|
||||
|
||||
@@ -343,7 +347,7 @@ Leaves the qualifying guilds the account does not own and defers the phone requi
|
||||
|
||||
### JSON body
|
||||
|
||||
The body is an empty object. An absent or empty body is read as an empty object, and any supplied property is discarded. A body that is not valid JSON returns 400 `INVALID_FORM_BODY` with `INVALID_FORMAT` at the `body` path. A body that parses as valid JSON but is not an object is rejected with 400.
|
||||
The body is an empty object. Fluxer reads an absent or empty body as an empty object and discards any supplied property. A body that is not valid JSON returns 400 `INVALID_FORM_BODY` with `INVALID_FORMAT` at the `body` path. Valid JSON that is not an object is rejected with 400.
|
||||
|
||||
### Response
|
||||
|
||||
@@ -356,7 +360,7 @@ The body is an empty object. An absent or empty body is read as an empty object,
|
||||
|
||||
Fluxer leaves each qualifying guild the account does not own, at most 25 in one request, with the same effects as [Leave guild](/http-api/guilds/#leave-guild). A qualifying guild the account owns is kept and does not block the escape.
|
||||
|
||||
A request that leaves the whole set then defers the requirement again and dispatches [User Update](/gateway/events/#user-update). A request that stopped at the limit writes no account field, dispatches nothing, and returns the account with the requirement still due. Repeat the request until the returned `required_actions` has no phone entry.
|
||||
A request that leaves the whole set then defers the requirement again and dispatches [User Update](/gateway/events/#user-update). One that stopped at the limit writes no account field, dispatches nothing, and returns the account with the requirement still due. Repeat the request until the returned `required_actions` has no phone entry.
|
||||
|
||||
### Rate limit
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ A private channel is a conversation outside any guild. It is either a direct mes
|
||||
The routes here take a user session or a bot token. An OAuth2 bearer credential receives 403 `ACCESS_DENIED`, and an account with an outstanding required action receives 403 `ACCOUNT_SUSPICIOUS_ACTIVITY`.
|
||||
|
||||
:::note[Open state is per participant]
|
||||
A direct message channel exists once, and each participant holds its own open state. Closing one leaves the channel and its messages in place, so reopening the pair resolves the stored channel.
|
||||
A direct message channel exists once, and each participant holds its own open state. Closing one side leaves the channel and its messages in place, so reopening the pair resolves the stored channel.
|
||||
:::
|
||||
|
||||
## Preloaded messages object
|
||||
@@ -56,11 +56,11 @@ One recipient that could not be added to a group DM, and the reason. [Create pri
|
||||
|
||||
Returns the caller's open direct message and group DM [channel](/http-api/channels/#channel-object) objects.
|
||||
|
||||
The route returns only the channels the caller currently has open, so a closed direct message is absent even though the channel and its history still exist. The response has no ordering guarantee and is unpaginated. The caller's personal notes channel is never returned.
|
||||
A closed direct message is absent even though the channel and its history still exist. The response has no ordering guarantee and is unpaginated. The caller's personal notes channel is never returned.
|
||||
|
||||
Each returned channel has its recipients with the caller removed, so a direct message holds exactly one recipient and a group DM one fewer than its participant count. A group DM whose only participant is the caller omits the `recipients` key entirely.
|
||||
|
||||
An open entry whose channel no longer lists the caller among its recipients is omitted, so leaving a group DM needs no separate cleanup call.
|
||||
Fluxer omits an open entry whose channel no longer lists the caller among its recipients, so leaving a group DM needs no separate cleanup call.
|
||||
|
||||
### Response
|
||||
|
||||
@@ -215,9 +215,9 @@ Adds a direct message or group DM to the caller's pinned order. Returns 204 with
|
||||
|
||||
Fluxer resolves the channel with the caller's ordinary channel authorisation first. A private channel the caller is not a recipient of returns 404 `UNKNOWN_CHANNEL`, and a channel that does not exist returns the same. The channel then has to be a direct message or a group DM, so the caller's personal notes channel and a visible guild channel are rejected with 400 `CHANNEL_MUST_BE_DM_OR_GROUP_DM`, a field code on `channel_id` under `INVALID_FORM_BODY`.
|
||||
|
||||
A guild channel the caller cannot see fails resolution first and returns 403 `MISSING_PERMISSIONS`. A channel row that outlived its guild returns 404 `UNKNOWN_GUILD`. A guild record that still exists while the caller's membership state cannot be resolved returns 403 `ACCESS_DENIED`.
|
||||
A guild channel the caller cannot see fails resolution first and returns 403 `MISSING_PERMISSIONS`. Fluxer returns 404 `UNKNOWN_GUILD` for a channel row that outlived its guild. Where the guild record still exists and the caller's membership state cannot be resolved, the request returns 403 `ACCESS_DENIED`.
|
||||
|
||||
A newly pinned channel is appended after every channel already pinned. A channel later closed, left, or deleted stays in the pinned set, and only [Unpin private channel](#unpin-private-channel) or deletion of the caller's account removes an entry.
|
||||
Fluxer appends a newly pinned channel after every channel already pinned. A channel later closed, left, or deleted stays in the pinned set, and only [Unpin private channel](#unpin-private-channel) or deletion of the caller's account removes an entry.
|
||||
|
||||
:::caution[Unpinning needs the channel to resolve]
|
||||
Unpinning resolves the channel first, so an entry whose channel the caller can no longer resolve stays in the pinned set.
|
||||
|
||||
@@ -32,7 +32,7 @@ The [global HTTP limit](/topics/rate-limits/) applies to every management route.
|
||||
|
||||
## Origin refusal
|
||||
|
||||
Fluxer refuses a call from the official web client on four routes. A request whose `Origin` header is exactly `https://web.fluxer.app` or `https://web.canary.fluxer.app` returns 403 `INVALID_API_ORIGIN` on [Execute webhook](#execute-webhook), [Get webhook message](#get-webhook-message), [Edit webhook message](#edit-webhook-message), and [Delete webhook message](#delete-webhook-message).
|
||||
Fluxer refuses a call from the official web client on [Execute webhook](#execute-webhook), [Get webhook message](#get-webhook-message), [Edit webhook message](#edit-webhook-message), and [Delete webhook message](#delete-webhook-message). A request whose `Origin` header is exactly `https://web.fluxer.app` or `https://web.canary.fluxer.app` returns 403 `INVALID_API_ORIGIN`.
|
||||
|
||||
A request that sends no `Origin`, or any other `Origin` value, passes the check. It runs after the route rate limit and before path validation.
|
||||
|
||||
@@ -169,13 +169,13 @@ Every entry supplied in a JSON body is dropped, and it counts towards neither th
|
||||
|
||||
## Message references
|
||||
|
||||
A webhook execution accepts the [message reference input](/http-api/messages/#message-reference-input-object) object defined by the Messages resource. Three constraints are specific to webhooks.
|
||||
A webhook execution accepts the [message reference input](/http-api/messages/#message-reference-input-object) object defined by the Messages resource. The constraints below are specific to webhooks.
|
||||
|
||||
The `channel_id` field is required for a forward reference and must equal the webhook's own channel. A value naming any other channel returns 404 `UNKNOWN_MESSAGE`. A reply reference always resolves in the webhook's own channel and ignores this field.
|
||||
|
||||
The referenced message must exist in the webhook's channel, and a missing message returns 404 `UNKNOWN_MESSAGE`. A reply reference additionally requires the referenced message to be an ordinary or reply message. Any other type returns 400 `INVALID_FORM_BODY` with the validation code `CANNOT_REPLY_TO_SYSTEM_MESSAGE`.
|
||||
|
||||
A forward reference must not accompany content, embeds, or attachments, and one that does returns 400 `INVALID_FORM_BODY` with the validation code `FORWARD_MESSAGES_CANNOT_CONTAIN_CONTENT`. A forward reference that omits `channel_id` or `message_id` returns the same status with `FORWARD_REFERENCE_REQUIRES_CHANNEL_AND_MESSAGE`.
|
||||
A forward reference must not accompany content, embeds, or attachments, and one that does returns 400 `INVALID_FORM_BODY` with the validation code `FORWARD_MESSAGES_CANNOT_CONTAIN_CONTENT`. A missing `channel_id` or `message_id` on a forward reference returns the same status with `FORWARD_REFERENCE_REQUIRES_CHANNEL_AND_MESSAGE`.
|
||||
|
||||
## Webhook message edit body
|
||||
|
||||
@@ -264,7 +264,7 @@ No per-embed title, description, footer, or field maximum applies to a Slack val
|
||||
|
||||
The Slack schema bounds only `username`, which must be 1 through 80 characters and rejects a longer value with `WEBHOOK_NAME_LENGTH_INVALID`. Fluxer applies no bound to any other Slack string or to either array.
|
||||
|
||||
Three checks do apply. A conversion yielding neither content nor an embed returns 400 `CANNOT_SEND_EMPTY_MESSAGE`. Content longer than the effective maximum returns 400 `INVALID_FORM_BODY` with the validation code `CONTENT_EXCEEDS_MAX_LENGTH`, and more converted embeds than the resolved ceiling returns the same status with `TOO_MANY_EMBEDS`. Fluxer discards an attachment that produces no embed property.
|
||||
A conversion yielding neither content nor an embed returns 400 `CANNOT_SEND_EMPTY_MESSAGE`. Content longer than the effective maximum returns 400 `INVALID_FORM_BODY` with the validation code `CONTENT_EXCEEDS_MAX_LENGTH`, and more converted embeds than the resolved ceiling returns the same status with `TOO_MANY_EMBEDS`. Fluxer discards an attachment that produces no embed property.
|
||||
|
||||
## GitHub callback objects
|
||||
|
||||
@@ -1301,11 +1301,9 @@ Any other type returns 400 `CANNOT_MODIFY_SYSTEM_WEBHOOK`, and a forward, which
|
||||
| Target message type cannot be edited | 400 `CANNOT_MODIFY_SYSTEM_WEBHOOK` |
|
||||
| Stored channel no longer resolves to a guild channel | 400 `CANNOT_EXECUTE_ON_DM` |
|
||||
| Body has no visible content, no non-empty embeds, and no `flags` | 400 `CANNOT_SEND_EMPTY_MESSAGE` |
|
||||
| Content longer than the effective maximum | 400 `CONTENT_EXCEEDS_MAX_LENGTH` |
|
||||
| Content longer than the effective maximum | 400 `INVALID_FORM_BODY` with the field code `CONTENT_EXCEEDS_MAX_LENGTH` |
|
||||
| Anything else | 400 `INVALID_FORM_BODY` |
|
||||
|
||||
`CONTENT_EXCEEDS_MAX_LENGTH` is a validation code inside a 400 `INVALID_FORM_BODY` body.
|
||||
|
||||
### Side effects
|
||||
|
||||
The operation replaces the supplied fields, and a content change marks the message edited. An edit does not re-extract mentions. The stored mention lists are kept, and `allowed_mentions` is accepted and not read. Supplying embeds replaces the complete embed collection and revalidates every attachment reference the embeds make. The operation emits [Message Update](/gateway/events/#message-update) to sessions that can read the channel.
|
||||
@@ -1475,7 +1473,7 @@ A rendered callback emits a [Message Create](/gateway/events/#message-create).
|
||||
The body is an [Instatus callback](#instatus-callback-object) object. A body the schema rejects returns 400 `INVALID_FORM_BODY`.
|
||||
|
||||
:::note[Deduplication is per webhook and callback]
|
||||
Only the first callback for a webhook and a given incident update, maintenance update, or component update is processed within a 24-hour window. A repeated callback is acknowledged and creates no second message. A callback with no such identifier is processed every time.
|
||||
Only the first callback for a webhook and a given incident update, maintenance update, or component update is processed within a 24-hour window. A repeated callback is acknowledged and creates no second message. Fluxer processes every callback that has no such identifier.
|
||||
:::
|
||||
|
||||
A callback that renders nothing, and one whose message creation fails, both leave the identifier free for a later retry.
|
||||
|
||||
Reference in new issue
Block a user