> ## Documentation Index
> Fetch the complete documentation index at: https://docs.discordbotlist.lol/llms.txt
> Use this file to discover all available pages before exploring further.

# Vote rewards

> Gate a perk behind a vote, with the two endpoints that exist for it.

This is the reason to list a bot on a directory at all: a reader votes, your bot
gives them something, and both of you come back tomorrow. Two endpoints support
it and they answer different questions.

| You want                                  | Use                                  | Why                                                     |
| ----------------------------------------- | ------------------------------------ | ------------------------------------------------------- |
| "Can this user claim the perk right now?" | `GET /check`                         | One indexed lookup against the 12-hour window. 600/min. |
| "Who voted while I was offline?"          | `GET /votes`                         | The append-only log, newest first.                      |
| "Tell me the moment it happens"           | [Vote webhook](/guides/vote-webhook) | No polling, and it carries the user id.                 |

## The cooldown

A vote lasts **12 hours**. `/check` answers from that window directly, so you
never have to compute it:

```json theme={null}
{
  "voted": 1,
  "hasVoted": true,
  "votedAt": "2026-09-12T08:15:00.000Z",
  "nextVoteAt": "2026-09-12T20:15:00.000Z"
}
```

`nextVoteAt` is there so your bot can say *when* to come back rather than "you
have already voted", which is the difference between a useful message and a
dead end.

## In a command

```js theme={null}
const VOTE_URL = `https://discordbotlist.lol/bot/${process.env.BOT_ID}`;

async function hasVoted(userId) {
  const response = await fetch(
    `https://discordbotlist.lol/api/v1/bots/${process.env.BOT_ID}/check?userId=${userId}`,
    { headers: { Authorization: process.env.DBL_TOKEN } }
  );

  // Fail open. A directory being unreachable should not break your command --
  // the cost of a wrongly granted perk is smaller than the cost of a bot that
  // stops working when somebody else's site is down.
  if (!response.ok) return true;

  return response.json();
}

export async function daily(interaction) {
  const vote = await hasVoted(interaction.user.id);

  if (vote !== true && !vote.hasVoted) {
    const when = Math.floor(Date.now() / 1000);
    return interaction.reply({
      content: `This one is for voters. [Vote here](${VOTE_URL}) — it takes ten seconds and unlocks it for 12 hours.`,
      ephemeral: true,
    });
  }

  await grantDaily(interaction.user.id);
  const until = vote === true ? null : Math.floor(Date.parse(vote.nextVoteAt) / 1000);
  await interaction.reply(
    until ? `Claimed. You can vote again <t:${until}:R>.` : 'Claimed.'
  );
}
```

<Tip>
  Cache the answer for a minute or two per user. A busy bot calls this on every
  invocation, and the state cannot change more than twice a day — the 600/min
  limit is headroom, not an invitation.
</Tip>

## Catching up after downtime

```js theme={null}
const response = await fetch(
  `https://discordbotlist.lol/api/v1/bots/${botId}/votes?limit=100`,
  { headers: { Authorization: process.env.DBL_TOKEN } }
);
const { votes } = await response.json();

for (const vote of votes) {
  if (Date.parse(vote.voted_at) <= lastSeen) break; // newest first
  await grantReward(vote.user_id);
}
```

The log returns ids and timestamps only. Names and avatars are deliberately not
in it: you already share a server with these people, and a vote lookup that
returned profiles would be a scraping endpoint with extra steps.

## What not to build

<Warning>
  Do not offer anything that makes voting *feel* mandatory to use your bot at
  all, and never reward a user for voting from more than one account. The first
  makes your listing worse to read; the second is what gets a bot delisted.
</Warning>
