Developers

Developer API

Read and manage your server's tickets and giveaways from your own code, and get a signed webhook when something happens.

Overview

The greed API lets your own website, bot or service work with your server's tickets and giveaways. You can:

  • read tickets, their messages and transcripts, and reply to, claim or close them;
  • start, end and reroll giveaways, read entries and claims, and set a private award;
  • receive a webhook the moment a ticket opens or closes, or a giveaway picks a winner.

Everything is managed from the dashboard under Developer API. You need Manage Server.

The API is available to every server. Server Premium raises the limits, see Limits.


API keys

Create a key on the Keys tab of the Developer API page. Give it a name and pick its scopes:

ScopeAllows
tickets:readListing tickets, reading messages and transcripts, listing panels
tickets:writeReplying to, claiming and closing tickets
giveaways:readListing giveaways, reading entries and claims
giveaways:writeStarting, ending and rerolling giveaways, setting awards

The key is shown once, when you create it. Store it somewhere safe, such as an environment variable. If you lose it, rotate it.

  • Rotate creates a new key with the same name and scopes. The old key keeps working for 24 hours, so you can switch over without downtime.
  • Revoke stops a key immediately.

A key belongs to one server. Every change made with a key shows up in the server's audit log with the key's name.

Treat a key like a password. Never put it in a website, a public repository or a Discord message.


Making requests

Send the key as a Bearer token. All requests and responses are JSON.

curl https://x.greed.best/v1/tickets?status=open \
  -H "Authorization: Bearer gk_live_..."

Successful responses look like { "data": ... }. Errors look like { "error": "...", "code": "TICKET_NOT_FOUND" } with a matching HTTP status. All ids are strings.

GET /v1/me returns the key's server, name and scopes, which is a quick way to check a key works.


Tickets

A ticket id is the id returned by the list endpoint or carried by a ticket webhook.


Giveaways

A giveaway's id is the id of its message.


Webhooks

Add an endpoint on the Webhooks tab, pick the events it should receive, and greed sends a POST to it whenever one of them happens. The endpoint must be a public https:// address.

Events

EventWhen
ticket.openedA ticket was opened
ticket.claimedA staff member claimed a ticket
ticket.closedA ticket was closed, by a person or automatically
ticket.reopenedA closed ticket was reopened
ticket.deletedA ticket was deleted
ticket.transcriptA transcript was saved, with its link
giveaway.startedA giveaway was posted
giveaway.endedA giveaway ended, with its winners
giveaway.winner_selectedWinners were picked, after an end, a reroll or a missed claim
giveaway.claimedA winner claimed their prize
giveaway.claim_expiredA winner did not claim in time
giveaway.rerolledSomeone rerolled a giveaway

Every request has the same shape:

{
  "id": "evt_6f1c9e3a2b8d4f0e9a7c5b3d1e2f4a6b",
  "type": "giveaway.winner_selected",
  "createdAt": "2026-10-28T18:04:11.512Z",
  "guildId": "123456789012345678",
  "data": {
    "id": "1300000000000000000",
    "prize": "Discord Nitro",
    "winners": ["987654321098765432"],
    "claimDeadline": "2026-10-28T19:04:11.000Z",
    "reason": "ended"
  }
}

Ticket events carry the same ticket object as GET /v1/tickets/{id}. An event can occasionally arrive twice, so use id to ignore one you have already handled.

The Send test button sends a ping event so you can check your endpoint.

Verifying the signature

Each endpoint has a signing secret (whsec_...), shown once when you add the endpoint or rotate it. Every request carries a greed-signature header:

greed-signature: t=1761674651,v1=5f2b...e91c

v1 is an HMAC-SHA256 of {t}.{raw request body} using your secret. Always check it, and refuse requests older than 5 minutes so an old request cannot be replayed.

import crypto from "node:crypto";

export function verifyGreed(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const age = Math.abs(Date.now() / 1000 - Number(parts.t));
  if (!parts.t || !parts.v1 || age > 300) return false;
  const expected = crypto.createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest("hex");
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}

Verify against the raw body exactly as received. Parsing the JSON and re-serializing it changes the bytes and the signature will not match.

Responding and retries

Answer with any 2xx status within 10 seconds. If your endpoint times out, can't be reached, or answers 408, 429 or 5xx, greed tries again after 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours. Other 4xx answers are not retried.

The Deliveries list on each endpoint shows the last 50 deliveries with their status and the HTTP status your endpoint answered with, and lets you send one again. greed never stores the body of your response. After 50 failed delivery attempts in a row the endpoint is turned off; turn it back on from the dashboard once it is fixed.


Limits

FreeServer Premium
API keys525
Webhook endpoints310
Webhook deliveries per day1,00050,000
Requests per minute, per key60300

Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset. Over the limit you get 429 with a Retry-After header. Deliveries past the daily limit are skipped and shown as skipped in the deliveries list; the count resets at midnight UTC.

Delivery history is kept for 7 days.