Approved integrations

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.

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"
}

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 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, plain-text guide, or Discord message pack. Each message in the Discord pack is under 2,000 characters and can be pasted separately in order.