Automate Discord event reminders by creating scheduled events with POST /guilds/{guild.id}/scheduled-events, then polling GET /guilds/{guild.id}/scheduled-events on a cron schedule to compare event start times against the current time and fire reminder messages via POST /channels/{channel.id}/messages at your configured intervals (e.g., 1 hour and 15 minutes before start). Discord has no built-in reminder callback — the polling and comparison logic is entirely your responsibility.
API Quick Reference
Bot Token
50 requests/second (global)
JSON
Available
Understanding the Discord API
Discord's Scheduled Events API (part of REST API v10 at https://discord.com/api/v10) allows bots to create, list, modify, and delete server events. Events can be in-venue (in a voice channel), at an external location, or at an unspecified location. Each event has a scheduled_start_time (ISO 8601), optional scheduled_end_time, entity_type, and status (SCHEDULED, ACTIVE, COMPLETED, CANCELED).
The key architectural limitation is that there is no push notification or webhook callback when an event is about to start. You must implement your own polling loop: fetch all scheduled events, compare their scheduled_start_time to the current UTC time, and fire reminder messages to your designated announcement channel at each configured threshold (e.g., 60 minutes before, 15 minutes before). Track which reminders have already been sent in a persistent store to avoid duplicate notifications.
Event creation requires the MANAGE_EVENTS permission for bot token requests. Reading events is accessible to any bot with access to the guild. Official docs: https://discord.com/developers/docs/resources/guild-scheduled-event
https://discord.com/api/v10Setting Up Discord API Authentication
Bot tokens are static credentials that do not expire until reset. For event management, the bot needs Manage Events to create or delete events, and Send Messages in the announcement channel where reminders are posted. No privileged intents are required — scheduled events are accessible via REST without a Gateway connection.
- 1Go to https://discord.com/developers/applications and click New Application.
- 2Open the Bot tab. Click Reset Token — copy it immediately.
- 3No privileged intents needed for REST-based event reminders.
- 4Open OAuth2 > URL Generator. Select scopes: bot and applications.commands.
- 5Under Bot Permissions, check: Manage Events, Send Messages, Embed Links, View Channels.
- 6Copy the generated URL and invite the bot to your server.
- 7Identify the announcement channel where reminders will be posted and copy its ID.
- 8Store DISCORD_BOT_TOKEN, DISCORD_GUILD_ID, and DISCORD_ANNOUNCEMENT_CHANNEL_ID in environment variables.
1import os2import requests34BOT_TOKEN = os.environ["DISCORD_BOT_TOKEN"]5BASE = "https://discord.com/api/v10"6HEADERS = {"Authorization": f"Bot {BOT_TOKEN}", "Content-Type": "application/json"}78# Verify authentication9resp = requests.get(f"{BASE}/users/@me", headers=HEADERS)10resp.raise_for_status()11print(f"Authenticated as: {resp.json()['username']}")Security notes
- •Store DISCORD_BOT_TOKEN in environment variables — never hardcode it.
- •Grant only Manage Events and Send Messages — not Administrator.
- •Store your sent-reminders state in a database rather than in memory to survive bot restarts without sending duplicate reminders.
- •If the token is exposed, reset it immediately in the Developer Portal.
- •Log all reminder sends with event IDs and timestamps for audit purposes.
Key endpoints
/guilds/{guild.id}/scheduled-eventsCreates a new scheduled event in the guild. The bot needs Manage Events permission. Returns the full event object with the new event ID.
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | required | Event name — 1 to 100 characters |
entity_type | number | required | 1 = Stage Instance, 2 = Voice, 3 = External (requires entity_metadata and scheduled_end_time) |
scheduled_start_time | string | required | ISO 8601 datetime string in UTC — must be in the future |
privacy_level | number | required | Always 2 (GUILD_ONLY) — the only supported value |
Request
1{"channel_id":"voice_channel_id","name":"Weekly AMA","privacy_level":2,"scheduled_start_time":"2026-05-15T18:00:00+00:00","scheduled_end_time":"2026-05-15T19:00:00+00:00","description":"Ask me anything about our product roadmap","entity_type":2}Response
1{"id":"998877665544","guild_id":"555555555","channel_id":"voice_channel_id","name":"Weekly AMA","description":"Ask me anything","scheduled_start_time":"2026-05-15T18:00:00+00:00","scheduled_end_time":"2026-05-15T19:00:00+00:00","privacy_level":2,"status":1,"entity_type":2,"user_count":0}/guilds/{guild.id}/scheduled-eventsReturns all scheduled events for the guild with status SCHEDULED or ACTIVE. Use with_user_count=true to include the number of interested users.
| Parameter | Type | Required | Description |
|---|---|---|---|
with_user_count | boolean | optional | Set to true to include the user_count of interested members |
Response
1[{"id":"998877665544","guild_id":"555555555","name":"Weekly AMA","scheduled_start_time":"2026-05-15T18:00:00+00:00","status":1,"entity_type":2,"user_count":24}]/channels/{channel.id}/messagesPosts the reminder message to the announcement channel. Use embeds for formatted reminders with event details, time remaining, and a link to the event.
| Parameter | Type | Required | Description |
|---|---|---|---|
content | string | optional | Text content — use @here or @everyone for broadcast reminders (requires permission) |
embeds | array | optional | Rich embed with event details — use Discord timestamp format <t:UNIX_EPOCH:F> for automatic timezone conversion |
Request
1{"content":"@here Event reminder!","embeds":[{"title":"Weekly AMA starts in 1 hour!","description":"Get your questions ready.","color":16776960,"fields":[{"name":"When","value":"<t:1747335600:F>","inline":true},{"name":"Interested","value":"24 members","inline":true}]}]}Response
1{"id":"112233445566","channel_id":"111222333444","content":"@here Event reminder!","embeds":[{"title":"Weekly AMA starts in 1 hour!"}],"timestamp":"2026-05-15T17:00:00.000Z"}Step-by-step automation
Create a Scheduled Event via the API
Why: Creating events via the API lets you automate event creation from external sources like calendar apps or slash commands, not just the Discord UI.
POST to /guilds/{guild.id}/scheduled-events with the event name, start time (ISO 8601 UTC), entity_type, and privacy_level (always 2). For voice events, include the channel_id. For external events, include entity_metadata with location and a scheduled_end_time. The response contains the event ID you'll need for the reminder tracker.
1curl -X POST \2 -H "Authorization: Bot $DISCORD_BOT_TOKEN" \3 -H "Content-Type: application/json" \4 -d '{5 "name": "Weekly AMA",6 "entity_type": 3,7 "scheduled_start_time": "2026-05-15T18:00:00+00:00",8 "scheduled_end_time": "2026-05-15T19:00:00+00:00",9 "privacy_level": 2,10 "entity_metadata": {"location": "https://discord.com/invite/your-server"}11 }' \12 "https://discord.com/api/v10/guilds/$GUILD_ID/scheduled-events"Pro tip: Store the event ID in a database with a 'reminders_sent' array so your poller can track which reminder intervals have already fired for each event.
Expected result: HTTP 200 with the event object including id, name, scheduled_start_time, and status: 1 (SCHEDULED). The event appears in Discord's Events tab immediately.
Poll Scheduled Events and Compare Times
Why: Discord has no push callback for 'event starting soon' — you must poll and compare timestamps yourself.
On a cron schedule (e.g., every 5 minutes), fetch all SCHEDULED events with GET /guilds/{guild.id}/scheduled-events. For each event, calculate the time until start in minutes. Check if the time falls within your reminder windows (e.g., between 55-65 minutes = 1-hour reminder, between 10-20 minutes = 15-minute reminder). Look up each event in your database to confirm the reminder hasn't been sent already.
1curl -s -H "Authorization: Bot $DISCORD_BOT_TOKEN" \2 "https://discord.com/api/v10/guilds/$GUILD_ID/scheduled-events?with_user_count=true" \3 | python3 -m json.toolPro tip: Use a window approach (55-65 minutes) rather than exact equality (exactly 60 minutes) because your cron may not run at precisely the right second. The window size should be at least twice your poll interval.
Expected result: A list of events that fall within each reminder window. Only events with status SCHEDULED (1) are included — ACTIVE (2), COMPLETED (3), and CANCELED (4) are filtered out.
Send the Reminder Message
Why: A well-formatted reminder with Discord timestamps auto-converts to the viewer's local timezone — much better than a hardcoded time string.
POST a reminder embed to your announcement channel. Use Discord's dynamic timestamp format <t:UNIX_EPOCH:R> for 'in X minutes' countdown and <t:UNIX_EPOCH:F> for the full date/time display. Include the event name, description, and user_count (number of interested members). Mention @here or a specific role to ensure members are pinged.
1UNIX_START=$(date -d "2026-05-15T18:00:00Z" +%s)2curl -X POST \3 -H "Authorization: Bot $DISCORD_BOT_TOKEN" \4 -H "Content-Type: application/json" \5 -d "{6 \"content\": \"@here Event reminder!\",7 \"embeds\": [{8 \"title\": \"Weekly AMA starts in 1 hour!\",9 \"description\": \"Get your questions ready!\",10 \"color\": 16776960,11 \"fields\": [12 {\"name\": \"Starts\", \"value\": \"<t:$UNIX_START:F>\", \"inline\": true},13 {\"name\": \"Countdown\", \"value\": \"<t:$UNIX_START:R>\", \"inline\": true}14 ]15 }]16 }" \17 "https://discord.com/api/v10/channels/$CHANNEL_ID/messages"Pro tip: Use <t:UNIX:R> (relative time: 'in 58 minutes') in combination with <t:UNIX:F> (full time: 'Thursday, May 15, 2026 6:00 PM') for the best user experience. Discord handles timezone conversion automatically.
Expected result: A formatted embed appears in the announcement channel with a live countdown timer (updates in Discord clients automatically) and the full date/time in each viewer's local timezone.
Track Sent Reminders to Prevent Duplicates
Why: Your poller runs every 5 minutes — without deduplication, the same event will trigger the same reminder 2-3 times within the window.
Maintain a set or database table of (event_id, reminder_label) pairs for reminders that have already been sent. Before sending any reminder, check this set. After sending successfully, add the pair to the set. Persist this state to a file or database so restarts don't cause duplicates.
1# No cURL equivalent — this is application-level state management2# Verify your sent-state file exists:3ls -la sent_reminders.json 2>/dev/null || echo '{"sent": []}' > sent_reminders.jsonPro tip: Use a database (SQLite, PostgreSQL, Redis) instead of a JSON file for production — it handles concurrent writes safely and is easier to query for debugging.
Expected result: Each reminder fires exactly once per event per label. Restarting the bot does not cause duplicate reminders because state is persisted to disk.
Complete working code
A complete event reminder script that polls all scheduled events every 5 minutes, checks reminder windows, avoids duplicates via persistent state, and posts formatted embeds with Discord timestamps to the announcement channel. Run via cron (every 5 minutes) or as a long-running process with asyncio.sleep.
1import os, time, json, logging, requests2from datetime import datetime, timezone3from pathlib import Path45logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")67BOT_TOKEN = os.environ["DISCORD_BOT_TOKEN"]8GUILD_ID = os.environ["DISCORD_GUILD_ID"]9ANN_CHANNEL = os.environ["DISCORD_ANNOUNCEMENT_CHANNEL_ID"]10BASE = "https://discord.com/api/v10"11HEADERS = {"Authorization": f"Bot {BOT_TOKEN}", "Content-Type": "application/json"}12STATE_FILE = Path("sent_reminders.json")13REMINDER_WINDOWS = {"1h": (55, 65), "15m": (10, 20)}14POLL_INTERVAL = 300 # 5 minutes1516def load_sent():17 if STATE_FILE.exists():18 return set(tuple(x) for x in json.loads(STATE_FILE.read_text()).get("sent", []))19 return set()2021def save_sent(sent):22 STATE_FILE.write_text(json.dumps({"sent": [list(x) for x in sent]}))2324def get_events():25 resp = requests.get(f"{BASE}/guilds/{GUILD_ID}/scheduled-events",26 params={"with_user_count": "true"}, headers=HEADERS)27 resp.raise_for_status()28 return [e for e in resp.json() if e["status"] == 1]2930def minutes_until(event):31 start = datetime.fromisoformat(event["scheduled_start_time"].replace("Z", "+00:00"))32 return (start - datetime.now(timezone.utc)).total_seconds() / 603334def send_reminder(event, label):35 start = datetime.fromisoformat(event["scheduled_start_time"].replace("Z", "+00:00"))36 ts = int(start.timestamp())37 colors = {"1h": 16776960, "15m": 16711680}38 labels = {"1h": "starts in 1 hour", "15m": "starts in 15 minutes"}39 payload = {40 "content": "@here",41 "embeds": [{42 "title": f"{event['name']} {labels.get(label, 'is starting soon')}!",43 "description": event.get("description", ""),44 "color": colors.get(label, 5814783),45 "fields": [46 {"name": "Starts", "value": f"<t:{ts}:F>", "inline": True},47 {"name": "Countdown", "value": f"<t:{ts}:R>", "inline": True},48 {"name": "Interested", "value": str(event.get("user_count", 0)), "inline": True}49 ]50 }]51 }52 requests.post(f"{BASE}/channels/{ANN_CHANNEL}/messages",53 headers=HEADERS, json=payload).raise_for_status()54 logging.info(f"Sent {label} reminder for '{event['name']}'")5556def run():57 sent = load_sent()58 while True:59 try:60 events = get_events()61 active_ids = {e["id"] for e in events}62 sent = {(eid, lbl) for eid, lbl in sent if eid in active_ids} # Prune stale63 for event in events:64 mins = minutes_until(event)65 for label, (lo, hi) in REMINDER_WINDOWS.items():66 if lo <= mins <= hi and (event["id"], label) not in sent:67 send_reminder(event, label)68 sent.add((event["id"], label))69 save_sent(sent)70 except Exception as e:71 logging.error(f"Poll error: {e}")72 time.sleep(POLL_INTERVAL)7374if __name__ == "__main__":75 run()76Error handling
{"code": 50013, "message": "Missing Permissions"}The bot lacks Manage Events permission for event creation, or Send Messages / Embed Links for the announcement channel.
Re-invite the bot with the correct permissions. For channel-specific issues, check that the announcement channel doesn't have a permission override denying the bot's role.
Do not retry — fix permissions.
{"code": 50035, "message": "Invalid Form Body", "errors": {"scheduled_start_time": {"_errors": [{"code": "DATE_TIME_IN_PAST"}]}}}The scheduled_start_time is in the past. Events must be created with a future start time.
Always validate that the start time is at least 1 minute in the future before calling the creation endpoint. Add server-side validation before accepting event creation requests from users.
Do not retry — fix the timestamp.
{"message": "You are being rate limited.", "retry_after": 1.0}The polling loop hit the per-route rate limit on the scheduled-events endpoint, or the announcement channel hit 5 messages/5 seconds during simultaneous reminders.
Increase the poll interval to 5 minutes minimum. Read X-RateLimit-Remaining on each poll response and back off when it approaches 0. For channels, use the queue pattern from the welcome messages guide.
Sleep for retry_after seconds, then retry. Increase poll interval if 429s are frequent.
{"code": 10070, "message": "Unknown Guild Scheduled Event"}An event was deleted or cancelled between your poll and a subsequent GET for details.
Handle 404 on individual event fetches gracefully — remove the event from your sent-reminders state and continue. The main list endpoint only returns SCHEDULED and ACTIVE events, so this mainly affects targeted single-event fetches.
Do not retry — remove from state and continue.
Rate Limits for Discord API
| Scope | Limit | Window |
|---|---|---|
| Global (per bot) | 50 requests | per second |
| Scheduled events endpoint (per route) | Read X-RateLimit-Remaining header | Read X-RateLimit-Reset-After header |
| Per channel (messages) | 5 messages | per 5 seconds |
1import time, random23def poll_with_backoff(url, headers, max_retries=5):4 import requests5 for i in range(max_retries):6 resp = requests.get(url, headers=headers)7 remaining = int(resp.headers.get('X-RateLimit-Remaining', 1))8 if resp.status_code == 429:9 wait = resp.json().get('retry_after', 1) + random.uniform(0, 0.5)10 time.sleep(wait)11 continue12 if remaining <= 2:13 time.sleep(float(resp.headers.get('X-RateLimit-Reset-After', 1)))14 return resp15 raise RuntimeError('Max retries exceeded')- Poll on a 5-minute interval — not faster. One poll per 5 minutes uses 1 API call; even with 20 events, reminder sends use at most 2 extra calls per poll.
- Read X-RateLimit-Remaining on every scheduled-events poll response and skip the next poll if it's 0.
- Store sent-reminder state in a database — JSON file is fine for small deployments but SQLite or Redis handle concurrent access and restarts better.
- Don't poll if you know no events are scheduled in the next 24 hours — check your database and skip polling until events exist.
- For servers with many events, filter SCHEDULED events (status=1) at the API level — the endpoint returns only non-expired events by default, but filtering on status reduces unnecessary processing.
Security checklist
- Store DISCORD_BOT_TOKEN in environment variables — never commit to source control.
- Grant only Manage Events and Send Messages — not Administrator.
- Validate the scheduled_start_time before event creation to prevent past-dated events that would immediately error.
- Sanitize event names and descriptions from user input before embedding them in reminder messages.
- Persist the sent-reminders state securely — if using a database, use parameterized queries to prevent injection.
- Monitor for unexpected event creation or deletion events if your bot also receives Gateway events — log anomalies.
- Test the reminder system in a private test server before deploying to production communities.
Automation use cases
Multi-Channel Reminders
intermediateSend different reminder messages to different channels — a #general announcement for 1-hour reminders and a #voice-chat ping for 5-minute reminders.
Role-Specific Reminders
intermediatePing specific roles based on the event type — ping @GameNight-interested for gaming events and @AMA-interested for Q&A events.
Calendar Sync
advancedAutomatically create Discord scheduled events from a Google Calendar or Notion calendar, keeping event creation fully automated.
RSVP Tracking
advancedSubscribe to GUILD_SCHEDULED_EVENT_USER_ADD Gateway events to track RSVPs and post a live attendee count update before the event starts.
No-code alternatives
Don't want to write code? These platforms can automate the same workflows visually.
Zapier
Free tier availableZapier can send Slack or Discord messages on a schedule but cannot read Discord's scheduled events API to check times — not suitable for event-aware reminders.
- + Simple cron-based message sending
- + No code required
- - No Discord Scheduled Events API integration
- - Cannot read event data dynamically
- - Fixed schedule, not event-relative timing
Make (formerly Integromat)
Free tier available (1,000 operations/month)Make's Discord module combined with HTTP Request nodes can poll the scheduled events endpoint and post reminders based on time comparisons in the scenario logic.
- + Can use HTTP nodes to query Discord API
- + Visual logic for time comparisons
- + More flexible than Zapier for this use case
- - Complex scenario setup required
- - Execution time limits may affect polling intervals
- - State management between executions is tricky
n8n
Free self-hosted; Cloud from €20/monthn8n's Cron trigger plus HTTP Request node gives full access to the Discord API, making event polling and conditional reminder sending straightforward to implement visually.
- + Full Discord API access
- + Built-in Cron trigger
- + Persistent state via database nodes
- - Requires self-hosting for full control
- - State deduplication requires custom logic
- - No Discord-specific UI guidance
Best practices
- Use ISO 8601 timestamps with explicit UTC offset (+00:00) when creating events — never pass naive (timezone-unaware) datetimes.
- Store reminder state persistently (database or file) — in-memory state is lost on bot restart and causes duplicate reminders.
- Use Discord timestamp tags (<t:UNIX:F> and <t:UNIX:R>) in reminder embeds — they display in each viewer's local timezone automatically without any server-side conversion.
- Filter events to status=1 (SCHEDULED) only — ACTIVE (2), COMPLETED (3), and CANCELED (4) events should not trigger reminders.
- Set your polling window slightly larger than the poll interval — for a 5-minute poll, use a 10-minute wide reminder window (e.g., 55-65 minutes for the 1-hour reminder).
- Prune stale entries from your sent-reminders state after each poll — remove entries for events that are no longer in the SCHEDULED list to keep state small.
- Implement a startup check: on bot start, verify the announcement channel exists and the bot has Send Messages permission before starting the poll loop.
Ask AI to help
Copy one of these prompts to get a personalized, working implementation.
I'm building a Discord event reminder bot using the REST API v10. The bot polls GET /guilds/{guild.id}/scheduled-events every 5 minutes, checks if any events are within 1 hour or 15 minutes of their start time, and posts a reminder embed to an announcement channel. Help me: 1) parse scheduled_start_time ISO strings and calculate time-until-start in Python, 2) build the reminder embed with Discord timestamp tags (<t:UNIX:F> and <t:UNIX:R>), 3) track sent reminders in a JSON file to prevent duplicates on bot restarts, and 4) prune stale state for completed events. Use Python with the requests library.
Build a web dashboard for managing Discord event reminders. Features: event calendar view showing all upcoming Discord scheduled events fetched from the API; reminder configuration panel where you can set multiple reminder intervals (e.g., 24h, 1h, 15m) and target channels per interval; sent reminders log showing which reminders fired and when; a manual trigger button to test-send a reminder without affecting the sent-state; and analytics showing event attendance trends from user_count over time.
Frequently asked questions
Is the Discord Scheduled Events API free?
Yes. Creating, reading, and deleting scheduled events via the API has no cost. The Discord API is free with no per-request pricing. Manage Events permission is required for creation, but no paid tier is needed.
What happens when I hit the rate limit while polling?
You receive HTTP 429 with retry_after in seconds. Read X-RateLimit-Remaining on each poll response — if it's 0, skip the next poll until X-RateLimit-Reset-After has elapsed. Polling at 5-minute intervals for a server with under 50 events uses minimal API budget and rarely hits rate limits.
Why doesn't Discord send a push notification when an event is about to start?
The Scheduled Events API has no built-in reminder webhook or callback. Discord's client shows notifications to members who clicked 'Interested' on the event, but bots receive no automatic notification. You must implement your own polling loop and time comparison logic.
Can I create recurring events automatically?
Discord has no native recurring event support. To create weekly events, use a cron job that calls POST /guilds/{id}/scheduled-events each week to create the next occurrence. When the previous event completes (status changes to COMPLETED), create the next one.
Can I use Gateway events instead of REST polling for event monitoring?
Yes. The Gateway fires GUILD_SCHEDULED_EVENT_CREATE, GUILD_SCHEDULED_EVENT_UPDATE, and GUILD_SCHEDULED_EVENT_USER_ADD events. However, these notify you when events are created/modified — not when they're about to start. You still need time comparison logic to fire pre-event reminders. The Gateway approach avoids repeated polling but still requires your bot to be online.
Do Discord timestamps (<t:UNIX:F>) handle timezones automatically?
Yes. Discord renders <t:UNIX_EPOCH:F> and <t:UNIX:R> in each user's local timezone based on their device settings. You provide a UTC Unix timestamp; Discord handles the conversion. This is far better than hardcoding a timezone in your reminder text.
Can RapidDev help build a Discord event management system?
Yes. RapidDev has built 600+ apps including Discord bots with event scheduling, cross-platform reminders, and calendar integrations. Visit rapidevelopers.com for a free consultation.
Need this automated?
Our team has built 600+ apps with API automations. We can build this for you.
Book a free consultation