Skip to content

REST Requests ​

Overview ​

Client.Request(ctx, method, path, body, target) is the low-level JSON request boundary. It combines the client's BaseURL, authorization mode, JSON serialization, audit-log reason, rate limiter, invalid-request guard, response decoding, and error conversion. RequestNoAuth performs the same work without an Authorization header.

Architecture ​

The path is appended to BaseURL, and its query string is removed when the route bucket key is selected. A non-nil body is marshaled and sends Content-Type: application/json. WithReason(ctx, reason) attaches a value that becomes the URL-escaped X-Audit-Log-Reason header; ReasonFromContext reads it back.

After Limiter.Wait, the request is sent. Headers update the limiter. A 429 uses the JSON retry_after, then the Retry-After header, then reset timing, and retries while the context remains active. Responses at 400 or above become *APIError, except CAPTCHA-shaped bodies which become *CaptchaError. Both carry HTTP status, API code/message, and structured fields where available. The invalid-request guard records 401, 403, and 429 responses and can return ErrInvalidRequestLimitExceeded before another request is sent.

Quick Start ​

go
package main

import (
	"context"
	"fmt"
	"io"
	"net/http"
	"strings"

	"github.com/discord-go/discord.go/rest"
)

type localHTTP struct{}

func (localHTTP) Do(req *http.Request) (*http.Response, error) {
	return &http.Response{StatusCode: http.StatusOK, Header: make(http.Header), Body: io.NopCloser(strings.NewReader(`{"ok":true}`))}, nil
}
func (localHTTP) Get(string) (*http.Response, error) { return nil, nil }
func (localHTTP) Post(string, string, io.Reader) (*http.Response, error) { return nil, nil }

func main() {
	c := rest.New("token", nil, localHTTP{})
	var result struct{ OK bool `json:"ok"` }
	ctx := rest.WithReason(context.Background(), "maintenance")
	if err := c.Request(ctx, http.MethodGet, "/example", nil, &result); err != nil {
		panic(err)
	}
	reason, _ := rest.ReasonFromContext(ctx)
	fmt.Println(result.OK, reason)
}

Using Authentication ​

Request uses AuthBot or AuthBearer when the client token is non-empty. RequestNoAuth omits it regardless of the client's mode and is intended for interaction and webhook URLs whose token is already in the path. Neither method signs arbitrary external URLs; path remains relative to BaseURL.

Common Patterns ​

Pass nil for body on GET and DELETE requests. Pass nil for target when the endpoint returns no useful body. Use a typed response target for JSON and let model custom unmarshalers handle components and snowflakes. Use errors.As(err, &apiErr) to inspect Code, Message, Errors, and HTTPStatus. Endpoints that return a non-standard error body (for example invite target-user CSV validation, which answers with {"target_users_file": [...]}) keep that body in APIError.Message instead of surfacing an empty message.

Best Practices ​

Do not reuse a canceled context while REST is sleeping on a 429. Keep route paths stable so the limiter can map returned bucket hashes. Make writes repeatable before enabling application-level retries around a call that already retries 429s.

Common Mistakes ​

The request body is JSON, not form data; use multipart helpers for files. The reason is URL-escaped by the client, so do not pre-escape it. A nil response target does not mean the server returned no bytes. Do not use RequestNoAuth to avoid authenticating ordinary bot operations.

API Walkthrough ​

The exported request API is Client.Request, Client.RequestNoAuth, WithReason, ReasonFromContext, APIError.Error, CaptchaError.Error, and ErrInvalidRequestLimitExceeded. Client construction and authentication are covered in README.md; rate-limit behavior is in ratelimits.md.

Examples ​

The Quick Start program is complete and runnable using an in-memory HTTP implementation. It demonstrates JSON decoding and reason propagation without contacting Discord.

Released under the Apache License 2.0.