Drekord Discord API Wrapper
A lightweight, async Discord REST API wrapper for Python.
Drekord is NOT a bot framework (like
discord.py). It is a pure REST API client designed to be integrated into any async application that needs to interact with the Discord API, without maintaining a persistent WebSocket connection.
Features
- Pure REST: No WebSocket gateway; just API calls x)
- Fully async: Built on
aiohttpfor cleanasync/awaitusage - Object-Oriented: Call methods directly on channels, messages, users, guilds, etc.
- discord.py-inspired: Familiar API if you've used discord.py before
- Rate-limit awareness: Automatic retry on rate limits and transient errors
Installation
pip install drekord
Or from source:
git clone https://github.com/drek124/drekord.git
cd drekord
pip install -e .
Quick Start
import asyncio
import drekord
async def main():
async with drekord.Client(token="YOUR_BOT_TOKEN") as client:
# Get the current bot user
me = await client.me()
print(f"Logged in as {me.username}")
# Fetch a channel and send a message
channel = await client.fetch_channel(CHANNEL_ID)
msg = await channel.send("Hello from Drekord! 🎉")
print(f"Sent message {msg.id}")
# Read the last 10 messages
messages = await channel.fetch_messages(limit=10)
for m in messages:
print(f"{m.author.username}: {m.content}")
asyncio.run(main())
API Overview
Client Methods
Top-level fetch_* methods on the client:
await client.me() # Current bot user
await client.fetch_user(user_id) # Any user
await client.fetch_channel(channel_id) # Any channel
await client.fetch_message(channel_id, msg_id) # A specific message
await client.fetch_guilds() # List bot's guilds
await client.fetch_guild(guild_id) # A specific guild
await client.fetch_member(guild_id, user_id) # A guild member
await client.fetch_webhook(webhook_id) # A webhook
await client.fetch_webhooks(channel_id) # Webhooks in a channel
Channels
channel = await client.fetch_channel(channel_id)
# Send messages
msg = await channel.send("Hello!", tts=False)
msg = await channel.send(embeds=[embed])
msg = await channel.send(view=layout_view) # Components V2
# Read messages
messages = await channel.fetch_messages(limit=50)
message = await channel.fetch_message(message_id)
# Edit the channel
channel = await channel.edit(name="renamed", topic="New topic")
# Delete the channel
await channel.delete()
# Typing indicator
await channel.typing()
# Invites
invites = await channel.fetch_invites()
Messages
message = await channel.fetch_message(msg_id)
# Edit
await message.edit(content="Edited!")
# Delete
await message.delete(reason="Cleanup requested")
# Pin/Unpin
await message.pin()
await message.unpin()
# Reactions
await message.add_reaction("👍")
await message.remove_reaction("👍")
# Crosspost (announcements)
await message.crosspost()
Users
user = await client.fetch_user(user_id)
# Properties
print(user.username)
print(user.display_name) # global_name or username
print(user.avatar_url())
# Send a DM (auto-creates DM channel)
await user.send("Hello!")
await user.send("Check this out!", embeds=[embed])
# Or create the DM channel explicitly
dm_channel = await user.create_dm()
# Re-fetch
user = await user.fetch()
Guilds
guild = await client.fetch_guild(guild_id)
# Properties
print(guild.name)
# Fetch sub-resources
channels = await guild.fetch_channels()
members = await guild.fetch_members(limit=100)
roles = await guild.fetch_roles()
emojis = await guild.fetch_emojis()
bans = await guild.fetch_bans()
audit_logs = await guild.fetch_audit_logs(limit=50)
# Create
new_channel = await guild.create_channel({"name": "new-channel", "type": 0})
new_role = await guild.create_role({"name": "Moderator", "color": 0xFF0000})
# Edit
guild = await guild.edit({"name": "New Name"})
# Leave / Delete
await guild.leave()
await guild.delete()
Members
member = await client.fetch_member(guild_id, user_id)
# Properties
print(member.display_name)
print(member.roles)
print(member.joined_at)
# Actions
await member.kick(reason="Rule violation")
await member.ban(reason="Spam", delete_message_days=7)
await member.edit(nick="New Nick", roles=[role_id1, role_id2])
Webhooks
webhook = await client.fetch_webhook(webhook_id)
# Execute (send)
await webhook.execute("Hello!", username="Bot")
await webhook.execute(embeds=[embed], wait=True)
# Edit
await webhook.edit({"name": "New Name"})
# Delete
await webhook.delete()
Embeds
embed = drekord.Embed(
title="Hello",
description="World",
color=0x5865F2,
)
embed.set_thumbnail(url="https://example.com/thumb.png")
embed.add_field(name="Field 1", value="Value 1", inline=True)
embed.set_footer(text="Footer text")
await channel.send(embeds=[embed])
Components V2
from drekord.ui import (
LayoutView, Container, TextDisplay, Separator,
ActionRow, Button, Section, Thumbnail,
)
view = LayoutView()
view.add_item(Container(
TextDisplay("## Hello World"),
Separator(),
ActionRow(
Button(label="Click Me", style=1, custom_id="btn_1"),
),
accent_color="#5865F2",
))
await channel.send(view=view)
Raw Requests (Escape Hatch)
For endpoints not yet covered:
data = await client.request("GET", "/gateway")
data = await client.request("POST", "/channels/123/threads", json={"name": "Thread"})
Error Handling
Drekord raises typed exceptions for all error cases:
import drekord
try:
msg = await channel.send("Hello!")
except drekord.ForbiddenError:
print("I don't have permission to send messages here!")
except drekord.NotFoundError:
print("Channel not found!")
except drekord.RateLimitedError as e:
print(f"Rate limited! Retry after {e.retry_after}s")
except drekord.HTTPError as e:
print(f"HTTP error {e.status_code}: {e}")
Exception Hierarchy
DrekordError
└── HTTPError
├── BadRequestError (400)
├── UnauthorizedError (401)
├── ForbiddenError (403)
├── NotFoundError (404)
├── RateLimitedError (429)
└── DiscordServerError (5xx)
Models
All API responses are returned as model objects with typed properties:
user = await client.fetch_user(user_id)
print(user.id) # int
print(user.username) # str
print(user.display_name) # str (global_name or username)
print(user.bot) # bool
print(user.avatar_url()) # str | None
channel = await client.fetch_channel(channel_id)
print(channel.id) # int
print(channel.name) # str | None
print(channel.type_name) # str ("text", "voice", etc.)
message = await channel.fetch_message(msg_id)
print(message.content) # str
print(message.author) # User | None
print(message.embeds) # list[Embed]
License
MIT
Metadata
Release files for drekord 2.0.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| drekord-2.0.3.tar.gz | 27.9 kB | Without provenance | Details for drekord-2.0.3.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| drekord-2.0.3-py3-none-any.whl | Python 3 | none | any | Details for drekord-2.0.3-py3-none-any.whl |
Total release size: 57.8 kB
Release files / drekord-2.0.3.tar.gz
| Download URL | drekord-2.0.3.tar.gz |
|---|---|
| Size | 27.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
43b025eb1d7f12b1ef50a9033d5bd349402639bd61cac50976b142c3b8b344de
|
|
BLAKE2b-256 checksum How to use checksums |
8f903d7f802e75c968a010a143ec4d5138c200531c4ad4b71a409296e19c2cc3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.4
|
Release files / drekord-2.0.3-py3-none-any.whl
| Download URL | drekord-2.0.3-py3-none-any.whl |
|---|---|
| Size | 29.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f1cedbc078b88ad988d3d3a56c184ea7408ba98a8223626422780e92bc108df2
|
|
BLAKE2b-256 checksum How to use checksums |
e0f89fce614e2756a5f1a9660d3f19da2ada8204635ac07741a3ee575c8cc90b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.4
|
Security for drekord 2.0.3
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.