Organizer API Connect your organizer bot to Hatenna to manage reserves, change supported server settings, and create ready-up checklists across the servers you run. Base URL: https://res.hatenna.xyz · Version: v1 · Access: approved integrations with an API key. Before you start Ask the Hatenna team for an organizer API key bound to your bot's Discord user ID and the scopes your integration needs. This is a Hatenna API key, not your Discord bot token. One organizer key works across multiple servers. For every request, both Hatenna and your organizer bot must be in the target server. Your organizer bot must have Administrator, Manage Server, or a role authorized through h!roles there. Reading or changing settings additionally requires Manage Server or Administrator. Hatenna checks membership and permissions against Discord for each request. Removing the bot or its permissions stops access to that server. A key never grants permissions the bot does not already have. Some integrations may instead receive a single-server key; that key accepts only its assigned server ID. Confirm your key type, expiry and scopes with its issuer. Choose the server for each request Every authenticated endpoint includes the target server ID: https://res.hatenna.xyz/v1/servers/{server_id}/{resource} When processing a Discord command, use ctx.guild.id; for an interaction, use interaction.guild_id in discord.py. Scheduled jobs should store the intended server ID with the job. Reject commands in DMs, where no server ID exists. Use the same organizer key for requests in server A and server B. Set the URL's server ID to A or B as appropriate. Hatenna looks up your key's bot in that server and checks its permissions there. It does not guess the server from an IP address, a channel name, or the key alone. Sending an unrelated server ID grants no access. Send all Discord IDs in JSON as decimal strings, including user and channel IDs. This avoids JavaScript rounding large Discord IDs. Authenticate securely Authorization: Bearer YOUR_HATENNA_API_KEY Content-Type: application/json Store the key in your backend's secret environment, for example HATENNA_CONTROL_KEY. Use HTTPS. Never put it in a URL, client-side website code, source control, public Discord messages, or logs. No Discord token is sent to this API. Browsers are not supported through CORS; call from your bot/backend. Keys expire and can be revoked. If a key is lost or exposed, ask the Hatenna team to revoke it and issue a replacement. To rotate without downtime, switch to a replacement before revoking the old key. Keep the public key ID for support; never include the full secret in a support message. An already-running request may finish after revocation. Endpoints and scopes All paths below follow /v1/servers/{server_id}. A valid key must include the endpoint's scope as well as passing the server permission checks. Method | Path | Required scope | Purpose GET | /reserves | reserves:read | Read the server's reserve assignments POST | /reserves | reserves:write | Add or remove specified reserve assignments GET | /settings | settings:read | Read supported server defaults PATCH | /settings | settings:write | Update supported server defaults POST | /readyup | readyup:write | Send an interactive ready-up checklist Read reserves GET /v1/servers/123456789012345678/reserves Example response: { "reserves": [ {"pokemon": "pikachu", "user_ids": ["234567890123456789"]} ] } Names are canonical lowercase names. An empty server returns {"reserves":[]}. Assignments belong to this server; profile-linked alts are not automatically added to the public reserve list. Add or remove reserves POST /v1/servers/123456789012345678/reserves { "action": "add", "pokemon": "Pikachu", "user_ids": ["234567890123456789"] } Example response: {"ok": true, "pokemon_count": 1, "user_count": 1} Use "action":"remove" with the same payload shape to remove assignments. Repeating an identical add or remove is safe. Counts describe the requested expanded Pokémon and distinct users, rather than newly changed assignments. • Supply 1–25 user IDs and at most 100 expanded Pokémon per request. • The pokemon string accepts comma-separated names, existing server categories, and the normal reserve form syntax, such as Pikachu, all Flabebe. • Unknown names reject the request before writes. Use exact names for predictable integrations; a category can change as the server edits it. • All users must be current server members when adding. Removal also accepts IDs of departed users so old assignments can be cleaned up. • Changes feed Hatenna's existing reserve pings. Copy, transfer, exchange and server-wide clear endpoints are not available in v1. Read and change settings Read current supported defaults with: GET /v1/servers/123456789012345678/settings The response wraps the supported fields in a settings object. To change only specific fields: PATCH /v1/servers/123456789012345678/settings {"reserve_ping": true, "naming_mode": "assisted", "assisted_embed": true} The response returns the supported settings after the update. Unspecified fields retain their values. Repeating the same PATCH is safe. Use JSON booleans true and false, not the strings "true" or "false". Field | Accepted values | Controls naming | boolean | Spawn naming assisted_embed | boolean | Embed output in Assisted naming mode only_pings | boolean | Only-pings behavior best_name | boolean | Best-name behavior rare_ping | boolean | Rare pings regional_ping | boolean | Regional pings shinyhunt_ping | boolean | Shiny-hunt pings collection_ping | boolean | Collection pings reserve_ping | boolean | Reserve pings reserve_steal | boolean | Reserve-steal logging switch quest_ping | boolean | Quest pings naming_mode | default, assisted | Naming output mode starboard_mode | default, 3d, gen5 | Starboard visual mode These are server defaults. Existing channel overrides still apply. Unknown fields or invalid values reject the whole payload before any field is changed. Roles, log-channel selection, modules, appearance, channel overrides and deletion of server data are not exposed through this API version. Configure those through Hatenna's Discord commands. Create a ready-up checklist POST /v1/servers/123456789012345678/readyup { "channel_id": "345678901234567890", "user_ids": ["234567890123456789", "456789012345678901"] } Example response: { "message_id": "567890123456789012", "url": "https://discord.com/channels/123456789012345678/345678901234567890/567890123456789012" } • Supply 1–25 members of the requested server. • channel_id must identify a text channel in that server. Your organizer bot must have View Channel and Send Messages there. Hatenna needs permissions to send the message and embeds. • Hatenna sends the normal mentions and interactive checklist. Each user marks themselves ready using Discord buttons; the API cannot mark users ready. • The checklist expires after one hour. There is one active checklist per organizer bot across all servers, matching the existing Discord flow. • The normal completion DM goes to the organizer principal. API bots should not depend on receiving a DM; this version has no completion webhook/status API. • Checklists are held in memory and stop working after a bot restart. • A conflict returns 409. The existing flow can also send explanatory feedback into the requested Discord channel when it cannot create a checklist. Do not automatically retry ready-up POSTs after a timeout. The message may already exist even if your HTTP client did not receive the response. Inspect the channel first. This version does not support idempotency keys. Python integration example Use an asynchronous client inside your Discord bot so API requests do not block its event loop. This example uses aiohttp and takes the server ID from context: import os import aiohttp async def set_reserve_pings(ctx, enabled: bool): if ctx.guild is None: raise ValueError("Run this command inside a server") url = f"https://res.hatenna.xyz/v1/servers/{ctx.guild.id}/settings" headers = { "Authorization": "Bearer " + os.environ["HATENNA_CONTROL_KEY"] } timeout = aiohttp.ClientTimeout(total=120) async with aiohttp.ClientSession(timeout=timeout) as session: async with session.patch( url, headers=headers, json={"reserve_ping": enabled} ) as response: result = await response.json() if response.status != 200: raise RuntimeError( f"Hatenna API {response.status}: {result.get('error', 'Request failed')}" ) return result["settings"] Your bot must also authorize its own users before making API calls. The key represents your bot's server permissions, not those of whichever user invoked your command. Do not let an unrestricted command delegate your bot's management access to everyone. For a production integration, reuse a client session and handle timeouts and rate limits without logging the key. Limits and errors The request body limit is 16 KiB. Each key allows 60 authenticated requests per fixed UTC minute, shared across all its servers. A 429 response includes Retry-After: 60 from the application; edge limits can also return 429. Respect Retry-After when supplied and otherwise wait at least 60 seconds. Do not create retry bursts. Discord delivery can queue behind Hatenna's shared outbound limiter. Application errors use {"error":"description"}. The reverse proxy may return its own text/HTML error response, so robust clients also handle non-JSON errors. Status | Meaning | What to do 200 | Request succeeded | Read the JSON response 400 | Invalid payload, name, ID or target | Correct the request 401 | Missing, invalid, expired or revoked key | Check the key or contact its issuer 403 | Missing scope, membership or server/channel permission | Check the bound bot's permissions in that server 404 | Hatenna absent, or unknown endpoint/method | Check server ID and path; single-server key mismatches return 403 409 | Ready-up was not created | Check the channel and existing checklist 413 | Request exceeds body limit | Send a smaller request 429 | Rate limit | Wait, then retry with backoff 503 | Reconnecting or operation failed | Read state before retrying a write Reads and identical reserve/settings writes can be retried with bounded backoff. Multi-Pokémon writes and multi-field setting changes are not transactions: a storage failure can leave partial changes. Read the current state, repeat the idempotent write if needed, and verify. Ready-up POSTs need manual inspection after ambiguous failures. API audit entries retain operation metadata for about 90 days, without keys or request bodies. API reserve mutations do not emit the Discord add/exchange command logs. Help and downloads Contact the Hatenna support server (https://discord.gg/5XurcUaW83) for key access, scope changes, expiry, revocation, or integration issues. Include your public key ID, server ID, endpoint, timestamp and HTTP status. Never send the key secret. Download the Markdown guide (https://hatenna.xyz/api-guide.md), plain-text guide (https://hatenna.xyz/api-guide.txt), or Discord message pack (https://hatenna.xyz/api-discord.txt). Each message in the Discord pack is under 2,000 characters and can be pasted separately in order.