Discord REST API v10 client for Elixir (https://discord.com/api/v10).
Req-based, bot-token auth, bounded rate-limit-aware retry, lazy pagination streams, embed builders with limit validation, GFM -> Discord markdown conversion with 2000-character chunking, webhooks, and Ed25519 interaction signature verification.
REST-only. There is no gateway/websocket client: this library sends requests but does not receive gateway events (messages, presence, voice). Like the other house SDKs it stays stateless and process-free. Receive slash commands and component clicks through HTTP interactions (
Noizu.Discord.Interactions).
Installation
def deps do
[
{:noizu_discord, "~> 0.1.0"}
]
endRuntime dependencies: Req and Jason. No supervision tree.
Configuration
Token precedence: Client.new(token: ...) -> app env -> DISCORD_BOT_TOKEN.
# config/runtime.exs (optional)
config :noizu_discord, :client,
token: System.get_env("DISCORD_BOT_TOKEN")| Environment variable | Used for |
|---|---|
DISCORD_BOT_TOKEN | Bot token (sent as Authorization: Bot <token>); fallback when neither opts nor app env set one |
| Client option | Default |
|---|---|
:token | see precedence above |
:base_url | https://discord.com/api/v10 |
:user_agent | DiscordBot (https://github.com/noizu-labs-sdks/elixir-discord, 0.1.0) |
:req_options | [] (merged into every Req.request/1, e.g. plug: for tests) |
:retry | max_attempts: 3, base_backoff_ms: 500, max_delay_ms: 30_000 |
Usage
client = Noizu.Discord.client()
{:ok, me} = Noizu.Discord.Users.me(client)
{:ok, guilds} = Noizu.Discord.Guilds.me(client)
alias Noizu.Discord.{Channels, Embeds, Markdown}
embed =
Embeds.new()
|> Embeds.title("Deploy finished")
|> Embeds.description(Markdown.from_markdown(notes, target: :embed))
|> Embeds.color("#2ecc71")
|> Embeds.field("Env", "prod", true)
{:ok, [embed]} = Embeds.validate(embed, on_overflow: :truncate)
{:ok, msg} = Channels.create_message(client, channel_id, %{content: "Done", embeds: [embed]})
{:ok, _} = Noizu.Discord.Reactions.create(client, channel_id, msg["id"], "✅")
# Long markdown in message content: convert, then split at 2000 chars
for part <- report |> Markdown.from_markdown() |> Markdown.chunk() do
Channels.create_message(client, channel_id, %{content: part})
end
# Lazy history (newest first, `before` paging)
client |> Channels.messages_stream(channel_id) |> Enum.take(500)
# Webhooks need no bot token
Noizu.Discord.Webhooks.execute(webhook_url, %{content: "hi", username: "ci"})Results, errors, rate limits
Every call returns {:ok, body} (nil for 204) or
{:error, %Noizu.Discord.Error{}} — never raises. Discord's {code, message, errors} envelope becomes tag: :api, reason: <code>; see
Noizu.Discord.Error for all tags.
429s are retried (up to 3 attempts, also for POST — Discord does not apply
rate-limited requests) honouring retry_after / X-RateLimit-Reset-After;
5xx are retried for idempotent methods only. Pass meta: true to get
{:ok, body, %{status, attempts, rate_limit: %{bucket, limit, remaining, reset, reset_after, scope, global}}}.
Interactions
if Noizu.Discord.Interactions.verify_signature(public_key_hex, sig_hex, timestamp, raw_body) do
Noizu.Discord.Interactions.respond(interaction["id"], interaction["token"], {:message, %{content: "pong"}})
endNot covered in 0.1.0: file uploads (multipart), application command registration, gateway.
Development
mix deps.get
mix format --check-formatted
mix compile --warnings-as-errors
mix test # Req.Test plug stubs only — no network
mix docs
License
MIT — see LICENSE.