# Hatenna Organizer API · 1/7 Use your organizer bot to manage reserves, update supported Hatenna settings, and create ready-up checklists. **API:** https://res.hatenna.xyz **Full guide:** https://hatenna.xyz/api.html **Access:** an API key granted by the Hatenna team. **One organizer key works across multiple servers.** It is bound to your bot's Discord user ID. Each request explicitly selects a server: ```text /v1/servers/SERVER_ID/RESOURCE ``` Get SERVER_ID from the Discord event you're processing: `ctx.guild.id` or `interaction.guild_id`. Scheduled jobs should store the server ID. Reject DMs. Both Hatenna and your organizer bot must be in that server. Your bot needs Administrator, Manage Server, or an `h!roles` authorized role there. Settings also require Manage Server/Administrator. Hatenna checks permissions live for every request. Your API key never grants server permissions by itself. Removing your bot or its role stops access in that server. A single-server key, if issued instead, only works in its assigned server. ===== COPY EACH MESSAGE SEPARATELY (DO NOT INCLUDE THIS LINE) ===== # Authentication & scopes · 2/7 Send these headers from your backend: ```http Authorization: Bearer YOUR_HATENNA_API_KEY Content-Type: application/json ``` This is a Hatenna API key, **not your Discord token**. Store it in a secret environment variable. Never share it in a channel, URL, frontend, repository, or log. Use HTTPS; browser CORS access is not supported. Request only the scopes you need: • `reserves:read` — GET reserves • `reserves:write` — add/remove reserves • `settings:read` — GET settings • `settings:write` — PATCH settings • `readyup:write` — create a checklist Keys expire and can be revoked. Contact the Hatenna team to replace or rotate a key. Share only the public key ID when requesting help, never the secret. A request already executing may finish after revocation. **Send Discord IDs as strings** to avoid rounding in JavaScript: ```json {"user_ids":["234567890123456789"]} ``` Your bot must also check who can invoke its commands. API permission checks authorize your bot, not the user who triggered it. ===== COPY EACH MESSAGE SEPARATELY (DO NOT INCLUDE THIS LINE) ===== # Reserve API · 3/7 Read server reserves: ```http GET /v1/servers/SERVER_ID/reserves ``` Example response: ```json {"reserves":[{"pokemon":"pikachu","user_ids":["234567890123456789"]}]} ``` Add assignments: ```http POST /v1/servers/SERVER_ID/reserves ``` ```json {"action":"add","pokemon":"Pikachu","user_ids":["234567890123456789"]} ``` Use `"action":"remove"` to remove those assignments. ```json {"ok":true,"pokemon_count":1,"user_count":1} ``` • 1–25 user IDs; up to 100 expanded Pokémon per request. • `pokemon` supports comma-separated names, server categories, and reserve form syntax such as `all Flabebe`. • Unknown names reject the payload before writes. Adds require current members; removals can clean up departed users. • Repeating the same add/remove is safe. Counts describe requested Pokémon/users, not newly changed assignments. • Changes affect Hatenna's reserve pings. Alt accounts are not automatically added. • Copy, transfer, exchange and server-wide clear are not API endpoints in v1. ===== COPY EACH MESSAGE SEPARATELY (DO NOT INCLUDE THIS LINE) ===== # Settings API · 4/7 Read supported server defaults: ```http GET /v1/servers/SERVER_ID/settings ``` Change selected fields: ```http PATCH /v1/servers/SERVER_ID/settings ``` ```json {"reserve_ping":true,"naming_mode":"assisted","assisted_embed":true} ``` The response wraps the supported fields in `{"settings":{...}}`. Omitted fields retain their values. Repeating the same PATCH is safe. Supported boolean fields: ```text naming assisted_embed only_pings best_name rare_ping regional_ping shinyhunt_ping collection_ping reserve_ping reserve_steal quest_ping ``` Use actual JSON `true`/`false`, not quoted strings. Supported modes: • `naming_mode`: `default` or `assisted` • `starboard_mode`: `default`, `3d`, or `gen5` Unknown fields or wrong types reject the payload before writes. These are server defaults; channel overrides still apply. Role/log-channel configuration, modules, appearance, channel overrides and server-data deletion remain Discord-only. Requires `settings:read` or `settings:write` as appropriate, plus Manage Server/Administrator for your organizer bot in that server. ===== COPY EACH MESSAGE SEPARATELY (DO NOT INCLUDE THIS LINE) ===== # Ready-up API · 5/7 ```http POST /v1/servers/SERVER_ID/readyup ``` ```json {"channel_id":"345678901234567890","user_ids":["234567890123456789","456789012345678901"]} ``` Example response: ```json {"message_id":"567890123456789012","url":"https://discord.com/channels/..."} ``` • Track 1–25 members of that server. • The channel must be a text channel in that server. Your bot needs View Channel and Send Messages; Hatenna needs permission to send/embed. • Hatenna sends mentions and its normal interactive checklist. Users press their own ready buttons; the API cannot mark them ready. • Lists expire after one hour. **One active list per organizer bot across all servers.** • The normal completion DM goes to the organizer principal; API bots should not depend on receiving it. No completion webhook/status endpoint exists in v1. • Checklists stop working after a Hatenna restart. • A conflict returns 409; the existing flow can send explanatory feedback into the target channel. **Do not automatically retry this POST after a timeout.** Discord may already have received the message. Inspect the channel first. There are no idempotency keys in v1. ===== COPY EACH MESSAGE SEPARATELY (DO NOT INCLUDE THIS LINE) ===== # Python example · 6/7 Use an async HTTP client inside your bot. This selects the server from the command being processed: ```python import os import aiohttp async def set_reserve_pings(ctx, enabled): if ctx.guild is None: raise ValueError("Use inside a server") url = ( "https://res.hatenna.xyz/v1/servers/" f"{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"API error {response.status}" ) return result["settings"] ``` Authorize your own command users before calling this function. In production, reuse the HTTP session and handle timeouts, 429s and non-JSON proxy errors. Never log headers containing the key. ===== COPY EACH MESSAGE SEPARATELY (DO NOT INCLUDE THIS LINE) ===== # Limits, errors & support · 7/7 **60 authenticated requests per key per fixed UTC minute**, shared across all servers. **16 KiB** maximum request body. Discord sends may queue behind the shared delivery limiter. Application errors: `{"error":"description"}`. Edge/proxy errors may be text or HTML. • **400** — invalid payload, name, ID or target • **401** — missing, invalid, expired or revoked key • **403** — missing scope, membership, permission, or single-server key mismatch • **404** — Hatenna absent or unknown endpoint/method • **409** — ready-up not created; check existing checklist/channel • **413** — request too large • **429** — wait for `Retry-After`, or at least 60 seconds if absent • **503** — operation failed or Discord reconnecting Retry reads and identical reserve/settings writes with bounded backoff. Multi-item writes are not transactions: failures can leave partial changes. Read state and verify after retrying. Do not automatically retry ready-up creation after an ambiguous failure. API audit metadata is kept for about 90 days without secrets or bodies. API reserve mutations do not emit Discord add/exchange command logs. **Help:** https://discord.gg/5XurcUaW83 Include the public key ID, server ID, endpoint, timestamp and HTTP status. **Never send the secret.** **Full reference:** https://hatenna.xyz/api.html