Skip to content

Discord Agent API

API reference for the self-hosted Discord Agent.

Overview

The Discord Agent API provides endpoints for automated Discord role delivery. Your self-hosted Discord Agent polls these endpoints to:

  1. Fetch pending role operations
  2. Claim operations (prevent duplicate processing)
  3. Confirm successful completion
  4. Report failures

Authentication

All Discord Agent endpoints require an API token with the discord:agent scope.

bash
curl -X GET "https://yourstore.pixlpay.net/api/v1/discord-agent/pending" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json"

Create a token in your dashboard: Settings > API Tokens with the discord:agent scope selected.

Base URL

https://yourstore.pixlpay.net/api/v1/discord-agent/

Replace yourstore with your store's subdomain.

Endpoints

Get Pending Operations

Fetch all pending role operations for your store.

http
GET /api/v1/discord-agent/pending

Response

json
{
  "success": true,
  "data": [
    {
      "id": 1,
      "operation": "assign",
      "guild_id": "123456789012345678",
      "discord_user_id": "987654321098765432",
      "role_id": "111222333444555666",
      "role_name": "VIP",
      "order_id": 42,
      "order_number": "ORD-2025-042",
      "created_at": "2025-01-20T14:30:00Z"
    },
    {
      "id": 2,
      "operation": "remove",
      "guild_id": "123456789012345678",
      "discord_user_id": "555666777888999000",
      "role_id": "111222333444555666",
      "role_name": "VIP",
      "subscription_id": 15,
      "created_at": "2025-01-20T14:35:00Z"
    }
  ]
}

Operation Fields

FieldTypeDescription
idintegerUnique operation ID
operationstringEither assign or remove
guild_idstringDiscord server (guild) ID
discord_user_idstringDiscord user ID
role_idstringDiscord role ID to assign/remove
role_namestringHuman-readable role name
order_idintegerAssociated order ID (if applicable)
order_numberstringOrder number (if applicable)
subscription_idintegerAssociated subscription ID (if applicable)
created_atstringISO 8601 timestamp

Claim Operations

Claim one or more operations to prevent duplicate processing by other agents.

http
POST /api/v1/discord-agent/claim

Request Body

json
{
  "ids": [1, 2],
  "agent_id": "my-server-agent"
}
FieldTypeRequiredDescription
idsarrayYesArray of operation IDs to claim
agent_idstringNoIdentifier for this agent instance

Response

json
{
  "success": true,
  "data": {
    "claimed": [1, 2],
    "already_claimed": [],
    "not_found": []
  },
  "message": "Operations claimed successfully"
}

Response Fields

FieldTypeDescription
claimedarrayIDs successfully claimed
already_claimedarrayIDs already claimed by another agent
not_foundarrayIDs that don't exist

Confirm Operation

Mark an operation as successfully completed.

http
POST /api/v1/discord-agent/confirm/{id}

Path Parameters

ParameterTypeDescription
idintegerOperation ID

Response

json
{
  "success": true,
  "message": "Operation confirmed successfully"
}

Report Failure

Report that an operation failed.

http
POST /api/v1/discord-agent/fail/{id}

Request Body

json
{
  "error": "Member not found in guild. They may have left the server."
}
FieldTypeRequiredDescription
errorstringYesError message describing the failure

Response

json
{
  "success": true,
  "message": "Failure recorded"
}

Failed operations are retried automatically after a delay, with exponential backoff.

Get Operation Status

Check the status of a specific operation.

http
GET /api/v1/discord-agent/status/{id}

Response

json
{
  "success": true,
  "data": {
    "id": 1,
    "status": "completed",
    "operation": "assign",
    "guild_id": "123456789012345678",
    "discord_user_id": "987654321098765432",
    "role_id": "111222333444555666",
    "claimed_at": "2025-01-20T14:30:05Z",
    "completed_at": "2025-01-20T14:30:07Z",
    "agent_id": "my-server-agent"
  }
}

Status Values

StatusDescription
pendingAwaiting processing
claimedClaimed by an agent
completedSuccessfully completed
failedFailed (will retry)
cancelledCancelled (won't retry)

Error Responses

401 Unauthorized

json
{
  "success": false,
  "error": "Unauthorized",
  "message": "Invalid or missing API token"
}

403 Forbidden

json
{
  "success": false,
  "error": "Forbidden",
  "message": "Token does not have discord:agent scope"
}

404 Not Found

json
{
  "success": false,
  "error": "Not Found",
  "message": "Operation not found"
}

409 Conflict

json
{
  "success": false,
  "error": "Conflict",
  "message": "Operation already claimed by another agent"
}

429 Too Many Requests

json
{
  "success": false,
  "error": "Too Many Requests",
  "message": "Rate limit exceeded",
  "retry_after": 60
}

Rate Limits

Discord Agent endpoints have relaxed rate limits compared to standard API endpoints:

EndpointLimit
GET /pending120 requests/minute
POST /claim60 requests/minute
POST /confirm/{id}120 requests/minute
POST /fail/{id}60 requests/minute

Polling Best Practices

Poll every 30 seconds for a balance between responsiveness and efficiency:

javascript
const POLL_INTERVAL = 30 * 1000; // 30 seconds

setInterval(async () => {
  const pending = await fetchPending();
  if (pending.length > 0) {
    await processOperations(pending);
  }
}, POLL_INTERVAL);

Prevent Duplicate Processing

Always claim operations before processing:

javascript
async function processOperations(operations) {
  // 1. Claim first
  const ids = operations.map(op => op.id);
  const claimResult = await claim(ids);

  // 2. Only process claimed operations
  for (const id of claimResult.claimed) {
    const op = operations.find(o => o.id === id);
    await processOperation(op);
  }
}

Handle Errors Gracefully

Report failures so Pixlpay can retry later:

javascript
async function processOperation(operation) {
  try {
    await assignRole(operation);
    await confirm(operation.id);
  } catch (error) {
    await reportFailure(operation.id, error.message);
  }
}

Example Implementation

Here's a minimal implementation in Node.js:

javascript
const axios = require('axios');

const api = axios.create({
  baseURL: 'https://yourstore.pixlpay.net/api/v1/discord-agent',
  headers: {
    'Authorization': `Bearer ${process.env.PIXLPAY_API_KEY}`,
    'Content-Type': 'application/json',
  },
});

async function pollForOperations() {
  try {
    // 1. Fetch pending operations
    const { data } = await api.get('/pending');

    if (!data.data || data.data.length === 0) {
      return; // Nothing to process
    }

    console.log(`Found ${data.data.length} pending operations`);

    // 2. Claim them
    const ids = data.data.map(op => op.id);
    await api.post('/claim', { ids });

    // 3. Process each operation
    for (const operation of data.data) {
      await processOperation(operation);
    }

  } catch (error) {
    console.error('Poll error:', error.message);
  }
}

async function processOperation(op) {
  try {
    // Your Discord role logic here
    if (op.operation === 'assign') {
      await discordClient.guilds.cache
        .get(op.guild_id)
        .members.fetch(op.discord_user_id)
        .then(member => member.roles.add(op.role_id));
    } else {
      await discordClient.guilds.cache
        .get(op.guild_id)
        .members.fetch(op.discord_user_id)
        .then(member => member.roles.remove(op.role_id));
    }

    // Confirm success
    await api.post(`/confirm/${op.id}`);
    console.log(`Completed operation ${op.id}`);

  } catch (error) {
    // Report failure
    await api.post(`/fail/${op.id}`, { error: error.message });
    console.error(`Failed operation ${op.id}:`, error.message);
  }
}

// Poll every 30 seconds
setInterval(pollForOperations, 30000);
pollForOperations(); // Initial poll

Webhook Alternative

If you prefer webhooks over polling, you can configure a webhook endpoint to receive role operations in real-time:

  1. Go to Settings > Webhooks
  2. Create a webhook with these events:
    • discord.role.assign
    • discord.role.remove

See Webhook Events for payload formats.

However, the polling approach is recommended because:

  • More reliable (no missed webhooks)
  • Works behind firewalls
  • Handles rate limits gracefully
  • Built-in retry logic

Built for game developers, by game developers.