Rest is an asynchronous HTTP client built on top of reqwest. Used directly via client.rest or through struct methods (guild.fetch_roles, channel.send, etc.).
Rest is based on Arc - cloning is cheap, pass it into closures via .clone().
RestOptions
Passed intoRest::new(options) or configured via ClientOptions.rest.
| Field | Type | Default | Description |
|---|---|---|---|
api_url | String | "https://api.fluxer.app/v1" | Base API URL. Change when working with a self-hosted instance. |
user_agent | String | "FluxerBot (Rust, 0.1)" | Value of the User-Agent header. |
timeout | Duration | 15 seconds | Timeout per HTTP request. |
max_retries | u32 | 3 | Maximum number of retry attempts on rate limit (429). |
Example: connecting to a self-hosted instance
Example: connecting to a self-hosted instance
Rest
Rest::new
set_token before the first request. When used via Client, the token is set automatically during login.
set_token
"Bot " or "Bearer " - the prefix "Bot " is added automatically.
Request Methods
All methods take a route relative toapi_url (e.g. /channels/123/messages) and return Result<T, RestError>.
get
GET request. Deserializes the response into T.
post
POST request with an optional JSON body.
patch
PATCH request with an optional JSON body.
put
PUT request with an optional JSON body.
put_empty
PUT request without a body and expects no content in the response. Used for operations like assigning a role (PUT /guilds/{id}/members/{user_id}/roles/{role_id}).
delete_route
DELETE request. Returns no response body.
post_multipart
POST request with multipart/form-data. Used for sending files. The Content-Type header is set automatically with the boundary.
Example: direct REST call without structs
Example: direct REST call without structs
Example: sending a file via post_multipart
Example: sending a file via post_multipart
Routes
Routes is a set of static methods for building API routes. All methods return String or &'static str.
Channels
| Method | Route |
|---|---|
Routes::channel(id) | /channels/{id} |
Routes::channel_messages(id) | /channels/{id}/messages |
Routes::channel_message(channel_id, message_id) | /channels/{channel_id}/messages/{message_id} |
Routes::channel_bulk_delete(id) | /channels/{id}/messages/bulk-delete |
Routes::channel_pins(id) | /channels/{id}/messages/pins |
Routes::channel_pin(channel_id, message_id) | /channels/{channel_id}/messages/pins/{message_id} |
Routes::channel_pin_message(channel_id, message_id) | /channels/{channel_id}/pins/{message_id} |
Routes::channel_message_reactions(channel_id, message_id) | /channels/{channel_id}/messages/{message_id}/reactions |
Routes::channel_message_reaction(channel_id, message_id, emoji) | /channels/{channel_id}/messages/{message_id}/reactions/{emoji} |
Routes::channel_webhooks(id) | /channels/{id}/webhooks |
Routes::channel_typing(id) | /channels/{id}/typing |
Routes::channel_invites(id) | /channels/{id}/invites |
Routes::channel_permission(channel_id, overwrite_id) | /channels/{channel_id}/permissions/{overwrite_id} |
Routes::channel_recipient(channel_id, user_id) | /channels/{channel_id}/recipients/{user_id} |
Routes::channel_message_attachment(channel_id, message_id, attachment_id) | /channels/{channel_id}/messages/{message_id}/attachments/{attachment_id} |
Guilds
| Method | Route |
|---|---|
Routes::guilds() | /guilds |
Routes::guild(id) | /guilds/{id} |
Routes::guild_delete(id) | /guilds/{id}/delete |
Routes::guild_channels(id) | /guilds/{id}/channels |
Routes::guild_members(id) | /guilds/{id}/members |
Routes::guild_member(guild_id, user_id) | /guilds/{guild_id}/members/{user_id} |
Routes::guild_member_role(guild_id, user_id, role_id) | /guilds/{guild_id}/members/{user_id}/roles/{role_id} |
Routes::guild_roles(id) | /guilds/{id}/roles |
Routes::guild_role(guild_id, role_id) | /guilds/{guild_id}/roles/{role_id} |
Routes::guild_bans(id) | /guilds/{id}/bans |
Routes::guild_ban(guild_id, user_id) | /guilds/{guild_id}/bans/{user_id} |
Routes::guild_invites(id) | /guilds/{id}/invites |
Routes::guild_audit_logs(id) | /guilds/{id}/audit-logs |
Routes::guild_emojis(id) | /guilds/{id}/emojis |
Routes::guild_emoji(guild_id, emoji_id) | /guilds/{guild_id}/emojis/{emoji_id} |
Routes::guild_stickers(id) | /guilds/{id}/stickers |
Routes::guild_sticker(guild_id, sticker_id) | /guilds/{guild_id}/stickers/{sticker_id} |
Routes::guild_webhooks(id) | /guilds/{id}/webhooks |
Routes::guild_vanity_url(id) | /guilds/{id}/vanity-url |
Routes::guild_transfer_ownership(id) | /guilds/{id}/transfer-ownership |
Users
| Method | Route |
|---|---|
Routes::user(id) | /users/{id} |
Routes::current_user() | /users/@me |
Routes::current_user_guilds() | /users/@me/guilds |
Routes::leave_guild(guild_id) | /users/@me/guilds/{guild_id} |
Routes::user_me_channels() | /users/@me/channels |
Routes::user_profile(id, guild_id) | /users/{id}/profile or /users/{id}/profile?guild_id={gid} |
Other
| Method | Route |
|---|---|
Routes::invite(code) | /invites/{code} |
Routes::webhook(id) | /webhooks/{id} |
Routes::webhook_execute(id, token) | /webhooks/{id}/{token} |
Routes::application_commands(app_id) | /applications/{app_id}/commands |
Routes::application_command(app_id, cmd_id) | /applications/{app_id}/commands/{cmd_id} |
Routes::interaction_callback(interaction_id, token) | /interactions/{interaction_id}/{token}/callback |
Routes::gateway_bot() | /gateway/bot |
Routes::instance() | /instance |
The
Routes::channel_message_reaction method automatically URL-encodes the emoji parameter. Pass the emoji as-is: a Unicode character ("👍") or "name:id" for custom ones.Rate Limiting
RateLimitManager manages rate limits automatically. No manual intervention is required.
Behavior
- Before each request, the global rate limit and per-route bucket are checked.
- If
remaining == 0for a bucket - the request waits untilreset_at. - When a
429response is received, the client waitsretry_afterseconds and retries the request. - The number of retries is limited by
RestOptions.max_retries(default: 3). - After all attempts are exhausted,
RestError::RateLimit(RateLimitError)is returned.
Bucket Keys
Routes are normalized before being stored in a bucket: numeric IDs are replaced with:id. This allows grouping requests to the same resource into one bucket.
Normalization example:
| Original Route | Bucket Key |
|---|---|
/channels/123/messages | /channels/:id/messages |
/guilds/456/members/789 | /guilds/:id/members/:id |
Global Rate Limit
If the response contains the headerx-ratelimit-global: true - a global lock is set for the duration of retry_after. All subsequent requests wait for the lock to be released.
Error Handling
RestError
Returned by allRest methods. Covers all possible errors in the HTTP layer.
| Variant | Type | Description |
|---|---|---|
RestError::Api(FluxerApiError) | - | The API returned a structured error with a code and message. |
RestError::Http(HttpError) | - | HTTP error without a recognized response body. |
RestError::RateLimit(RateLimitError) | - | Rate limit exhausted after all retry attempts. |
RestError::Reqwest(reqwest::Error) | - | Network error (timeout, DNS, TLS). |
RestError::Json(serde_json::Error) | - | Response deserialization error. |
FluxerApiError
Occurs when the server returns a JSON error withcode and message fields.
| Field | Type | Description |
|---|---|---|
code | String | Machine-readable error code. |
message | String | Human-readable message. |
status_code | u16 | HTTP response status. |
errors | Vec<FieldError> | List of errors for specific request fields. Empty if there are no field errors. |
FieldError
| Field | Type | Description |
|---|---|---|
path | String | JSON path to the field with the error (e.g. "name", "color"). |
message | String | Error description for this field. |
HttpError
Occurs when the status is >= 400, but the response body is not a recognized JSON error.| Field | Type | Description |
|---|---|---|
status_code | u16 | HTTP response status. |
body | String | Raw response body. |
RateLimitError
Occurs after all retry attempts are exhausted with a 429 status.| Field | Type | Description |
|---|---|---|
retry_after | f64 | Recommended wait time in seconds. |
global | bool | true if this is a global rate limit. |
message | String | Message from the response body. |
Examples
API error handling
API error handling
Direct request to an arbitrary route
Direct request to an arbitrary route
Assign a role directly via REST
Assign a role directly via REST
Send an interaction response
Send an interaction response
