REST API v1 · Developers · API reference
Shop API reference
All 10 Shop endpoints of the BotGG REST API v1: scopes, parameters, fields, errors and ready-to-run curl, JavaScript and Python examples.
Developer docs
On this page
Endpoints
Paths are relative to https://console.botgg.app/api/v1. Send your API key as Authorization: Bearer botgg_…; IDs are strings.
| Endpoint | Scope | Description |
|---|---|---|
GET/guilds/{guildId}/shop/stores | shop:read | List stores |
GET/guilds/{guildId}/shop/items | shop:read | List items |
GET/guilds/{guildId}/shop/items/{itemId} | shop:read | Get an item |
PATCH/guilds/{guildId}/shop/items/{itemId} | shop:write | Restock or turn an item on/off |
GET/guilds/{guildId}/shop/members/{userId}/inventory | shop:read | A member’s inventory |
GET/guilds/{guildId}/shop/purchases | shop:read | List purchases |
GET/guilds/{guildId}/shop/purchases/{purchaseId} | shop:read | Get a purchase |
POST/guilds/{guildId}/shop/members/{userId}/purchases | shop:write | Buy an item for a member |
POST/guilds/{guildId}/shop/members/{userId}/grants | shop:write | Give an item to a member for free |
POST/guilds/{guildId}/shop/purchases/{purchaseId}/refund | shop:write | Refund a purchase |
Besides its own errors below, every endpoint can return these common ones:
List stores
GET/guilds/{guildId}/shop/stores
The built-in Main store (id: null, items without a store) first, then the stores in their order.
Needs this feature enabled on the server: Shop
| Name | In | Type | Required | Description |
|---|---|---|---|---|
guildId | path | string | Yes | Your server’s ID · Discord ID (string) |
Response
| Field | Type | Required | Description |
|---|---|---|---|
items | array of object | Yes | |
items[].id | integer | null | Yes | null = the built-in Main store |
items[].main | boolean | Yes | |
items[].name | string | null | Yes | null for the Main store |
items[].emoji | string | null | Yes | |
items[].description | string | null | Yes | |
items[].color | string | null | Yes | |
items[].enabled | boolean | Yes | |
items[].channelIds | array of string | Yes | |
items[].itemCount | integer | Yes |
{
"items": [
{
"id": 98765,
"main": true,
"name": "Example",
"emoji": "🪙",
"description": "example",
"color": "#18d7ff",
"enabled": true,
"channelIds": [
"234567890123456789"
],
"itemCount": 10
}
]
}Examples
curl -s "https://console.botgg.app/api/v1/guilds/123456789012345678/shop/stores" \
-H "Authorization: Bearer $BOTGG_API_KEY"// Node.js 18+ (built-in fetch) — on your server, never in a browser
const res = await fetch('https://console.botgg.app/api/v1/guilds/123456789012345678/shop/stores', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.BOTGG_API_KEY}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(`${res.status} ${data.error}: ${data.message}`);
console.log(data);import os
import requests
res = requests.get(
"https://console.botgg.app/api/v1/guilds/123456789012345678/shop/stores",
headers={
"Authorization": f"Bearer {os.environ['BOTGG_API_KEY']}",
},
timeout=10,
)
data = res.json()
if not res.ok:
raise RuntimeError(f"{res.status_code} {data['error']}: {data['message']}")
print(data)Errors
| HTTP | Codes |
|---|---|
| 409 | FEATURE_DISABLED |
| 503 | SERVICE_UNAVAILABLE |
List items
GET/guilds/{guildId}/shop/items
In sort order. Only enabled items unless includeDisabled=true; hidden items are included (hidden: true).
Needs this feature enabled on the server: Shop
| Name | In | Type | Required | Description |
|---|---|---|---|---|
guildId | path | string | Yes | Your server’s ID · Discord ID (string) |
storeId | query | "main" | No | |
category | query | string | No | 1–32 characters |
includeDisabled | query | string | No | One of 1, 0, true, false |
Response
| Field | Type | Required | Description |
|---|---|---|---|
items | array of object | Yes | |
items[].id | integer | Yes | |
items[].storeId | integer | null | Yes | null = Main store |
items[].name | string | Yes | |
items[].description | string | null | Yes | |
items[].emoji | string | null | Yes | |
items[].price | integer | Yes | |
items[].type | string | Yes | One of role, color, item |
items[].category | string | null | Yes | |
items[].roleId | string | null | Yes | |
items[].durationSec | integer | Yes | Timed role length; 0 = permanent |
items[].consumable | boolean | Yes | |
items[].fulfillment | boolean | Yes | Staff deliver it (fulfilment request) |
items[].stock | integer | null | Yes | null = unlimited |
items[].perUserLimit | integer | null | Yes | |
items[].requiredRoleIds | array of string | Yes | |
items[].minLevel | integer | Yes | |
items[].enabled | boolean | Yes | |
items[].hidden | boolean | Yes | |
items[].sortOrder | integer | Yes | |
currency | object | Yes | |
currency.name | string | Yes | |
currency.plural | string | Yes | |
currency.emoji | string | null | Yes |
{
"items": [
{
"id": 98765,
"storeId": 98765,
"name": "Example",
"description": "example",
"emoji": "🪙",
"price": 250,
"type": "role",
"category": "example",
"roleId": "234567890123456789",
"durationSec": 10,
"consumable": true,
"fulfillment": true,
"stock": 10,
"perUserLimit": 10,
"requiredRoleIds": [
"234567890123456789"
],
"minLevel": 10,
"enabled": true,
"hidden": true,
"sortOrder": 10
}
],
"currency": {
"name": "Example",
"plural": "example",
"emoji": "🪙"
}
}Examples
curl -s "https://console.botgg.app/api/v1/guilds/123456789012345678/shop/items" \
-H "Authorization: Bearer $BOTGG_API_KEY"// Node.js 18+ (built-in fetch) — on your server, never in a browser
const res = await fetch('https://console.botgg.app/api/v1/guilds/123456789012345678/shop/items', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.BOTGG_API_KEY}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(`${res.status} ${data.error}: ${data.message}`);
console.log(data);import os
import requests
res = requests.get(
"https://console.botgg.app/api/v1/guilds/123456789012345678/shop/items",
headers={
"Authorization": f"Bearer {os.environ['BOTGG_API_KEY']}",
},
timeout=10,
)
data = res.json()
if not res.ok:
raise RuntimeError(f"{res.status_code} {data['error']}: {data['message']}")
print(data)Errors
| HTTP | Codes |
|---|---|
| 409 | FEATURE_DISABLED |
| 503 | SERVICE_UNAVAILABLE |
Get an item
GET/guilds/{guildId}/shop/items/{itemId}
Needs this feature enabled on the server: Shop
| Name | In | Type | Required | Description |
|---|---|---|---|---|
guildId | path | string | Yes | Your server’s ID · Discord ID (string) |
itemId | path | string | Yes |
Response
| Field | Type | Required | Description |
|---|---|---|---|
id | integer | Yes | |
storeId | integer | null | Yes | null = Main store |
name | string | Yes | |
description | string | null | Yes | |
emoji | string | null | Yes | |
price | integer | Yes | |
type | string | Yes | One of role, color, item |
category | string | null | Yes | |
roleId | string | null | Yes | |
durationSec | integer | Yes | Timed role length; 0 = permanent |
consumable | boolean | Yes | |
fulfillment | boolean | Yes | Staff deliver it (fulfilment request) |
stock | integer | null | Yes | null = unlimited |
perUserLimit | integer | null | Yes | |
requiredRoleIds | array of string | Yes | |
minLevel | integer | Yes | |
enabled | boolean | Yes | |
hidden | boolean | Yes | |
sortOrder | integer | Yes |
{
"id": 98765,
"storeId": 98765,
"name": "Example",
"description": "example",
"emoji": "🪙",
"price": 250,
"type": "role",
"category": "example",
"roleId": "234567890123456789",
"durationSec": 10,
"consumable": true,
"fulfillment": true,
"stock": 10,
"perUserLimit": 10,
"requiredRoleIds": [
"234567890123456789"
],
"minLevel": 10,
"enabled": true,
"hidden": true,
"sortOrder": 10
}Examples
curl -s "https://console.botgg.app/api/v1/guilds/123456789012345678/shop/items/234567890123456789" \
-H "Authorization: Bearer $BOTGG_API_KEY"// Node.js 18+ (built-in fetch) — on your server, never in a browser
const res = await fetch('https://console.botgg.app/api/v1/guilds/123456789012345678/shop/items/234567890123456789', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.BOTGG_API_KEY}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(`${res.status} ${data.error}: ${data.message}`);
console.log(data);import os
import requests
res = requests.get(
"https://console.botgg.app/api/v1/guilds/123456789012345678/shop/items/234567890123456789",
headers={
"Authorization": f"Bearer {os.environ['BOTGG_API_KEY']}",
},
timeout=10,
)
data = res.json()
if not res.ok:
raise RuntimeError(f"{res.status_code} {data['error']}: {data['message']}")
print(data)Errors
| HTTP | Codes |
|---|---|
| 404 | SHOP_ITEM_NOT_FOUND |
| 409 | FEATURE_DISABLED |
| 503 | SERVICE_UNAVAILABLE |
Restock or turn an item on/off
PATCH/guilds/{guildId}/shop/items/{itemId}
Like /shopadmin restock and toggle. Send stock (null = unlimited) and/or enabled.
Needs this feature enabled on the server: Shop
| Name | In | Type | Required | Description |
|---|---|---|---|---|
guildId | path | string | Yes | Your server’s ID · Discord ID (string) |
itemId | path | string | Yes |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
enabled | boolean | No | Off = not listed, not buyable |
stock | integer | null | No | New stock; null = unlimited 0–1,000,000 |
Response
| Field | Type | Required | Description |
|---|---|---|---|
id | integer | Yes | |
storeId | integer | null | Yes | null = Main store |
name | string | Yes | |
description | string | null | Yes | |
emoji | string | null | Yes | |
price | integer | Yes | |
type | string | Yes | One of role, color, item |
category | string | null | Yes | |
roleId | string | null | Yes | |
durationSec | integer | Yes | Timed role length; 0 = permanent |
consumable | boolean | Yes | |
fulfillment | boolean | Yes | Staff deliver it (fulfilment request) |
stock | integer | null | Yes | null = unlimited |
perUserLimit | integer | null | Yes | |
requiredRoleIds | array of string | Yes | |
minLevel | integer | Yes | |
enabled | boolean | Yes | |
hidden | boolean | Yes | |
sortOrder | integer | Yes |
{
"id": 98765,
"storeId": 98765,
"name": "Example",
"description": "example",
"emoji": "🪙",
"price": 250,
"type": "role",
"category": "example",
"roleId": "234567890123456789",
"durationSec": 10,
"consumable": true,
"fulfillment": true,
"stock": 10,
"perUserLimit": 10,
"requiredRoleIds": [
"234567890123456789"
],
"minLevel": 10,
"enabled": true,
"hidden": true,
"sortOrder": 10
}Examples
curl -s -X PATCH "https://console.botgg.app/api/v1/guilds/123456789012345678/shop/items/234567890123456789" \
-H "Authorization: Bearer $BOTGG_API_KEY" \
-H "Content-Type: application/json" \
-d '{"enabled":true,"stock":10}'// Node.js 18+ (built-in fetch) — on your server, never in a browser
const res = await fetch('https://console.botgg.app/api/v1/guilds/123456789012345678/shop/items/234567890123456789', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.BOTGG_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
enabled: true,
stock: 10,
}),
});
const data = await res.json();
if (!res.ok) throw new Error(`${res.status} ${data.error}: ${data.message}`);
console.log(data);import os
import requests
res = requests.patch(
"https://console.botgg.app/api/v1/guilds/123456789012345678/shop/items/234567890123456789",
headers={
"Authorization": f"Bearer {os.environ['BOTGG_API_KEY']}",
},
json={
"enabled": True,
"stock": 10,
},
timeout=10,
)
data = res.json()
if not res.ok:
raise RuntimeError(f"{res.status_code} {data['error']}: {data['message']}")
print(data)Errors
| HTTP | Codes |
|---|---|
| 400 | INVALID_JSON |
| 404 | SHOP_ITEM_NOT_FOUND |
| 409 | FEATURE_DISABLED |
| 413 | PAYLOAD_TOO_LARGE |
| 415 | UNSUPPORTED_MEDIA_TYPE |
| 503 | SERVICE_UNAVAILABLE |
A member’s inventory
GET/guilds/{guildId}/shop/members/{userId}/inventory
Non-refunded purchases (owned and used), newest first; roleActive = a granted role that hasn’t expired or been removed.
Needs this feature enabled on the server: Shop
| Name | In | Type | Required | Description |
|---|---|---|---|---|
guildId | path | string | Yes | Your server’s ID · Discord ID (string) |
userId | path | string | Yes | The member’s Discord user ID · Discord ID (string) |
Response
| Field | Type | Required | Description |
|---|---|---|---|
items | array of object | Yes | |
items[].id | integer | Yes | |
items[].itemId | integer | null | Yes | null when the item was deleted (the snapshot stays) |
items[].storeId | integer | null | Yes | |
items[].storeName | string | null | Yes | |
items[].itemName | string | Yes | |
items[].itemEmoji | string | null | Yes | |
items[].itemType | string | Yes | |
items[].userId | string | Yes | |
items[].price | integer | Yes | 0 for free grants |
items[].status | string | Yes | One of owned, used, refunded |
items[].roleId | string | null | Yes | |
items[].expiresAt | string | null | Yes | |
items[].roleRemovedAt | string | null | Yes | |
items[].fulfillment | string | Yes | One of none, pending, done |
items[].bankTransactionId | integer | null | Yes | |
items[].refundTransactionId | integer | null | Yes | |
items[].usedAt | string | null | Yes | |
items[].refundedAt | string | null | Yes | |
items[].createdAt | string | Yes | |
items[].roleActive | boolean | Yes |
{
"items": [
{
"id": 98765,
"itemId": 98765,
"storeId": 98765,
"storeName": "example",
"itemName": "example",
"itemEmoji": "🪙",
"itemType": "example",
"userId": "876543210987654321",
"price": 250,
"status": "owned",
"roleId": "234567890123456789",
"expiresAt": "2026-10-07T12:00:00.000Z",
"roleRemovedAt": "2026-10-07T12:00:00.000Z",
"fulfillment": "none",
"bankTransactionId": 98765,
"refundTransactionId": 98765,
"usedAt": "2026-10-07T12:00:00.000Z",
"refundedAt": "2026-10-07T12:00:00.000Z",
"createdAt": "2026-10-07T12:00:00.000Z",
"roleActive": true
}
]
}Examples
curl -s "https://console.botgg.app/api/v1/guilds/123456789012345678/shop/members/876543210987654321/inventory" \
-H "Authorization: Bearer $BOTGG_API_KEY"// Node.js 18+ (built-in fetch) — on your server, never in a browser
const res = await fetch('https://console.botgg.app/api/v1/guilds/123456789012345678/shop/members/876543210987654321/inventory', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.BOTGG_API_KEY}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(`${res.status} ${data.error}: ${data.message}`);
console.log(data);import os
import requests
res = requests.get(
"https://console.botgg.app/api/v1/guilds/123456789012345678/shop/members/876543210987654321/inventory",
headers={
"Authorization": f"Bearer {os.environ['BOTGG_API_KEY']}",
},
timeout=10,
)
data = res.json()
if not res.ok:
raise RuntimeError(f"{res.status_code} {data['error']}: {data['message']}")
print(data)Errors
| HTTP | Codes |
|---|---|
| 409 | FEATURE_DISABLED |
| 503 | SERVICE_UNAVAILABLE |
List purchases
GET/guilds/{guildId}/shop/purchases
The purchase ledger, newest first. Pass before=<nextCursor> for the next page.
Needs this feature enabled on the server: Shop
| Name | In | Type | Required | Description |
|---|---|---|---|---|
guildId | path | string | Yes | Your server’s ID · Discord ID (string) |
limit | query | integer | No | 1–100 · Default 25 |
before | query | integer | No | at least 1 |
userId | query | string | No | Discord ID (string) |
itemId | query | integer | No | at least 1 |
storeId | query | "main" | No | |
status | query | string | No | One of owned, used, refunded |
Response
| Field | Type | Required | Description |
|---|---|---|---|
items | array of object | Yes | |
items[].id | integer | Yes | |
items[].itemId | integer | null | Yes | null when the item was deleted (the snapshot stays) |
items[].storeId | integer | null | Yes | |
items[].storeName | string | null | Yes | |
items[].itemName | string | Yes | |
items[].itemEmoji | string | null | Yes | |
items[].itemType | string | Yes | |
items[].userId | string | Yes | |
items[].price | integer | Yes | 0 for free grants |
items[].status | string | Yes | One of owned, used, refunded |
items[].roleId | string | null | Yes | |
items[].expiresAt | string | null | Yes | |
items[].roleRemovedAt | string | null | Yes | |
items[].fulfillment | string | Yes | One of none, pending, done |
items[].bankTransactionId | integer | null | Yes | |
items[].refundTransactionId | integer | null | Yes | |
items[].usedAt | string | null | Yes | |
items[].refundedAt | string | null | Yes | |
items[].createdAt | string | Yes | |
nextCursor | string | null | Yes |
{
"items": [
{
"id": 98765,
"itemId": 98765,
"storeId": 98765,
"storeName": "example",
"itemName": "example",
"itemEmoji": "🪙",
"itemType": "example",
"userId": "876543210987654321",
"price": 250,
"status": "owned",
"roleId": "234567890123456789",
"expiresAt": "2026-10-07T12:00:00.000Z",
"roleRemovedAt": "2026-10-07T12:00:00.000Z",
"fulfillment": "none",
"bankTransactionId": 98765,
"refundTransactionId": 98765,
"usedAt": "2026-10-07T12:00:00.000Z",
"refundedAt": "2026-10-07T12:00:00.000Z",
"createdAt": "2026-10-07T12:00:00.000Z"
}
],
"nextCursor": "98700"
}Examples
curl -s "https://console.botgg.app/api/v1/guilds/123456789012345678/shop/purchases?limit=25" \
-H "Authorization: Bearer $BOTGG_API_KEY"// Node.js 18+ (built-in fetch) — on your server, never in a browser
const res = await fetch('https://console.botgg.app/api/v1/guilds/123456789012345678/shop/purchases?limit=25', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.BOTGG_API_KEY}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(`${res.status} ${data.error}: ${data.message}`);
console.log(data);import os
import requests
res = requests.get(
"https://console.botgg.app/api/v1/guilds/123456789012345678/shop/purchases",
headers={
"Authorization": f"Bearer {os.environ['BOTGG_API_KEY']}",
},
params={
"limit": 25,
},
timeout=10,
)
data = res.json()
if not res.ok:
raise RuntimeError(f"{res.status_code} {data['error']}: {data['message']}")
print(data)Errors
| HTTP | Codes |
|---|---|
| 409 | FEATURE_DISABLED |
| 503 | SERVICE_UNAVAILABLE |
Get a purchase
GET/guilds/{guildId}/shop/purchases/{purchaseId}
Needs this feature enabled on the server: Shop
| Name | In | Type | Required | Description |
|---|---|---|---|---|
guildId | path | string | Yes | Your server’s ID · Discord ID (string) |
purchaseId | path | string | Yes |
Response
| Field | Type | Required | Description |
|---|---|---|---|
id | integer | Yes | |
itemId | integer | null | Yes | null when the item was deleted (the snapshot stays) |
storeId | integer | null | Yes | |
storeName | string | null | Yes | |
itemName | string | Yes | |
itemEmoji | string | null | Yes | |
itemType | string | Yes | |
userId | string | Yes | |
price | integer | Yes | 0 for free grants |
status | string | Yes | One of owned, used, refunded |
roleId | string | null | Yes | |
expiresAt | string | null | Yes | |
roleRemovedAt | string | null | Yes | |
fulfillment | string | Yes | One of none, pending, done |
bankTransactionId | integer | null | Yes | |
refundTransactionId | integer | null | Yes | |
usedAt | string | null | Yes | |
refundedAt | string | null | Yes | |
createdAt | string | Yes |
{
"id": 98765,
"itemId": 98765,
"storeId": 98765,
"storeName": "example",
"itemName": "example",
"itemEmoji": "🪙",
"itemType": "example",
"userId": "876543210987654321",
"price": 250,
"status": "owned",
"roleId": "234567890123456789",
"expiresAt": "2026-10-07T12:00:00.000Z",
"roleRemovedAt": "2026-10-07T12:00:00.000Z",
"fulfillment": "none",
"bankTransactionId": 98765,
"refundTransactionId": 98765,
"usedAt": "2026-10-07T12:00:00.000Z",
"refundedAt": "2026-10-07T12:00:00.000Z",
"createdAt": "2026-10-07T12:00:00.000Z"
}Examples
curl -s "https://console.botgg.app/api/v1/guilds/123456789012345678/shop/purchases/234567890123456789" \
-H "Authorization: Bearer $BOTGG_API_KEY"// Node.js 18+ (built-in fetch) — on your server, never in a browser
const res = await fetch('https://console.botgg.app/api/v1/guilds/123456789012345678/shop/purchases/234567890123456789', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.BOTGG_API_KEY}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(`${res.status} ${data.error}: ${data.message}`);
console.log(data);import os
import requests
res = requests.get(
"https://console.botgg.app/api/v1/guilds/123456789012345678/shop/purchases/234567890123456789",
headers={
"Authorization": f"Bearer {os.environ['BOTGG_API_KEY']}",
},
timeout=10,
)
data = res.json()
if not res.ok:
raise RuntimeError(f"{res.status_code} {data['error']}: {data['message']}")
print(data)Errors
| HTTP | Codes |
|---|---|
| 404 | SHOP_PURCHASE_NOT_FOUND |
| 409 | FEATURE_DISABLED |
| 503 | SERVICE_UNAVAILABLE |
Buy an item for a member
POST/guilds/{guildId}/shop/members/{userId}/purchases
Same rules as /shop: the price is charged from the member’s Bank balance in one transaction with the stock and limits; requirements (roles, level) apply. A role item’s role is given right away (if Discord refuses, nothing is charged). Fulfilment requests and the Shop log are posted like a /shop purchase.
Needs this feature enabled on the server: Shop
| Name | In | Type | Required | Description |
|---|---|---|---|---|
guildId | path | string | Yes | Your server’s ID · Discord ID (string) |
userId | path | string | Yes | The member’s Discord user ID · Discord ID (string) |
Idempotency-Key | header | string | No | Optional. Send a unique value (a UUID) and reuse it when you retry: the change happens only once. How it works |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
itemId | integer | Yes | at least 1 |
expectedPrice | integer | No | Refused with SHOP_ITEM_UNAVAILABLE (price_changed) when the price differs 0–1,000,000,000 |
Response
| Field | Type | Required | Description |
|---|---|---|---|
purchase | object | Yes | |
purchase.id | integer | Yes | |
purchase.itemId | integer | null | Yes | null when the item was deleted (the snapshot stays) |
purchase.storeId | integer | null | Yes | |
purchase.storeName | string | null | Yes | |
purchase.itemName | string | Yes | |
purchase.itemEmoji | string | null | Yes | |
purchase.itemType | string | Yes | |
purchase.userId | string | Yes | |
purchase.price | integer | Yes | 0 for free grants |
purchase.status | string | Yes | One of owned, used, refunded |
purchase.roleId | string | null | Yes | |
purchase.expiresAt | string | null | Yes | |
purchase.roleRemovedAt | string | null | Yes | |
purchase.fulfillment | string | Yes | One of none, pending, done |
purchase.bankTransactionId | integer | null | Yes | |
purchase.refundTransactionId | integer | null | Yes | |
purchase.usedAt | string | null | Yes | |
purchase.refundedAt | string | null | Yes | |
purchase.createdAt | string | Yes | |
balance | integer | null | Yes | The member’s balance afterwards (null when unchanged) |
{
"purchase": {
"id": 98765,
"itemId": 98765,
"storeId": 98765,
"storeName": "example",
"itemName": "example",
"itemEmoji": "🪙",
"itemType": "example",
"userId": "876543210987654321",
"price": 250,
"status": "owned",
"roleId": "234567890123456789",
"expiresAt": "2026-10-07T12:00:00.000Z",
"roleRemovedAt": "2026-10-07T12:00:00.000Z",
"fulfillment": "none",
"bankTransactionId": 98765,
"refundTransactionId": 98765,
"usedAt": "2026-10-07T12:00:00.000Z",
"refundedAt": "2026-10-07T12:00:00.000Z",
"createdAt": "2026-10-07T12:00:00.000Z"
},
"balance": 1350
}Examples
curl -s -X POST "https://console.botgg.app/api/v1/guilds/123456789012345678/shop/members/876543210987654321/purchases" \
-H "Authorization: Bearer $BOTGG_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"itemId":98765,"expectedPrice":10}'// Node.js 18+ (built-in fetch) — on your server, never in a browser
const res = await fetch('https://console.botgg.app/api/v1/guilds/123456789012345678/shop/members/876543210987654321/purchases', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.BOTGG_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(), // reuse it when you retry this request
},
body: JSON.stringify({
itemId: 98765,
expectedPrice: 10,
}),
});
const data = await res.json();
if (!res.ok) throw new Error(`${res.status} ${data.error}: ${data.message}`);
console.log(data);import os, uuid
import requests
res = requests.post(
"https://console.botgg.app/api/v1/guilds/123456789012345678/shop/members/876543210987654321/purchases",
headers={
"Authorization": f"Bearer {os.environ['BOTGG_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()), # reuse it when you retry this request
},
json={
"itemId": 98765,
"expectedPrice": 10,
},
timeout=10,
)
data = res.json()
if not res.ok:
raise RuntimeError(f"{res.status_code} {data['error']}: {data['message']}")
print(data)Errors
Give an item to a member for free
POST/guilds/{guildId}/shop/members/{userId}/grants
A staff grant: recorded as a purchase with price 0, no Bank change. Ignores the item’s on/off switch, a closed store, requirements and the per-member limit; stock still applies. A role item’s role is given right away.
Needs this feature enabled on the server: Shop
| Name | In | Type | Required | Description |
|---|---|---|---|---|
guildId | path | string | Yes | Your server’s ID · Discord ID (string) |
userId | path | string | Yes | The member’s Discord user ID · Discord ID (string) |
Idempotency-Key | header | string | No | Optional. Send a unique value (a UUID) and reuse it when you retry: the change happens only once. How it works |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
itemId | integer | Yes | at least 1 |
reason | string | null | No | Shown in the audit log up to 200 characters |
Response
| Field | Type | Required | Description |
|---|---|---|---|
purchase | object | Yes | |
purchase.id | integer | Yes | |
purchase.itemId | integer | null | Yes | null when the item was deleted (the snapshot stays) |
purchase.storeId | integer | null | Yes | |
purchase.storeName | string | null | Yes | |
purchase.itemName | string | Yes | |
purchase.itemEmoji | string | null | Yes | |
purchase.itemType | string | Yes | |
purchase.userId | string | Yes | |
purchase.price | integer | Yes | 0 for free grants |
purchase.status | string | Yes | One of owned, used, refunded |
purchase.roleId | string | null | Yes | |
purchase.expiresAt | string | null | Yes | |
purchase.roleRemovedAt | string | null | Yes | |
purchase.fulfillment | string | Yes | One of none, pending, done |
purchase.bankTransactionId | integer | null | Yes | |
purchase.refundTransactionId | integer | null | Yes | |
purchase.usedAt | string | null | Yes | |
purchase.refundedAt | string | null | Yes | |
purchase.createdAt | string | Yes | |
balance | integer | null | Yes | The member’s balance afterwards (null when unchanged) |
{
"purchase": {
"id": 98765,
"itemId": 98765,
"storeId": 98765,
"storeName": "example",
"itemName": "example",
"itemEmoji": "🪙",
"itemType": "example",
"userId": "876543210987654321",
"price": 250,
"status": "owned",
"roleId": "234567890123456789",
"expiresAt": "2026-10-07T12:00:00.000Z",
"roleRemovedAt": "2026-10-07T12:00:00.000Z",
"fulfillment": "none",
"bankTransactionId": 98765,
"refundTransactionId": 98765,
"usedAt": "2026-10-07T12:00:00.000Z",
"refundedAt": "2026-10-07T12:00:00.000Z",
"createdAt": "2026-10-07T12:00:00.000Z"
},
"balance": 1350
}Examples
curl -s -X POST "https://console.botgg.app/api/v1/guilds/123456789012345678/shop/members/876543210987654321/grants" \
-H "Authorization: Bearer $BOTGG_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"itemId":98765,"reason":"Tournament prize"}'// Node.js 18+ (built-in fetch) — on your server, never in a browser
const res = await fetch('https://console.botgg.app/api/v1/guilds/123456789012345678/shop/members/876543210987654321/grants', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.BOTGG_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(), // reuse it when you retry this request
},
body: JSON.stringify({
itemId: 98765,
reason: 'Tournament prize',
}),
});
const data = await res.json();
if (!res.ok) throw new Error(`${res.status} ${data.error}: ${data.message}`);
console.log(data);import os, uuid
import requests
res = requests.post(
"https://console.botgg.app/api/v1/guilds/123456789012345678/shop/members/876543210987654321/grants",
headers={
"Authorization": f"Bearer {os.environ['BOTGG_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()), # reuse it when you retry this request
},
json={
"itemId": 98765,
"reason": "Tournament prize",
},
timeout=10,
)
data = res.json()
if not res.ok:
raise RuntimeError(f"{res.status_code} {data['error']}: {data['message']}")
print(data)Errors
Refund a purchase
POST/guilds/{guildId}/shop/purchases/{purchaseId}/refund
Like Refund in the console: the price goes back to the member’s balance (one transaction), the purchase becomes refunded, optionally restocked; a granted role is removed by the bot within ~30 s.
Needs this feature enabled on the server: Shop
| Name | In | Type | Required | Description |
|---|---|---|---|---|
guildId | path | string | Yes | Your server’s ID · Discord ID (string) |
purchaseId | path | string | Yes | |
Idempotency-Key | header | string | No | Optional. Send a unique value (a UUID) and reuse it when you retry: the change happens only once. How it works |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
restock | boolean | No | Put the unit back in stock (default: the Shop’s “restock on refund” setting) |
force | boolean | No | Also refund an item that was already used |
reason | string | null | No | Shown in the audit log up to 200 characters |
Response
| Field | Type | Required | Description |
|---|---|---|---|
purchase | object | Yes | |
purchase.id | integer | Yes | |
purchase.itemId | integer | null | Yes | null when the item was deleted (the snapshot stays) |
purchase.storeId | integer | null | Yes | |
purchase.storeName | string | null | Yes | |
purchase.itemName | string | Yes | |
purchase.itemEmoji | string | null | Yes | |
purchase.itemType | string | Yes | |
purchase.userId | string | Yes | |
purchase.price | integer | Yes | 0 for free grants |
purchase.status | string | Yes | One of owned, used, refunded |
purchase.roleId | string | null | Yes | |
purchase.expiresAt | string | null | Yes | |
purchase.roleRemovedAt | string | null | Yes | |
purchase.fulfillment | string | Yes | One of none, pending, done |
purchase.bankTransactionId | integer | null | Yes | |
purchase.refundTransactionId | integer | null | Yes | |
purchase.usedAt | string | null | Yes | |
purchase.refundedAt | string | null | Yes | |
purchase.createdAt | string | Yes | |
balance | integer | null | Yes | The member’s balance afterwards (null when unchanged) |
{
"purchase": {
"id": 98765,
"itemId": 98765,
"storeId": 98765,
"storeName": "example",
"itemName": "example",
"itemEmoji": "🪙",
"itemType": "example",
"userId": "876543210987654321",
"price": 250,
"status": "owned",
"roleId": "234567890123456789",
"expiresAt": "2026-10-07T12:00:00.000Z",
"roleRemovedAt": "2026-10-07T12:00:00.000Z",
"fulfillment": "none",
"bankTransactionId": 98765,
"refundTransactionId": 98765,
"usedAt": "2026-10-07T12:00:00.000Z",
"refundedAt": "2026-10-07T12:00:00.000Z",
"createdAt": "2026-10-07T12:00:00.000Z"
},
"balance": 1350
}Examples
curl -s -X POST "https://console.botgg.app/api/v1/guilds/123456789012345678/shop/purchases/234567890123456789/refund" \
-H "Authorization: Bearer $BOTGG_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"restock":true,"force":true,"reason":"Tournament prize"}'// Node.js 18+ (built-in fetch) — on your server, never in a browser
const res = await fetch('https://console.botgg.app/api/v1/guilds/123456789012345678/shop/purchases/234567890123456789/refund', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.BOTGG_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(), // reuse it when you retry this request
},
body: JSON.stringify({
restock: true,
force: true,
reason: 'Tournament prize',
}),
});
const data = await res.json();
if (!res.ok) throw new Error(`${res.status} ${data.error}: ${data.message}`);
console.log(data);import os, uuid
import requests
res = requests.post(
"https://console.botgg.app/api/v1/guilds/123456789012345678/shop/purchases/234567890123456789/refund",
headers={
"Authorization": f"Bearer {os.environ['BOTGG_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()), # reuse it when you retry this request
},
json={
"restock": True,
"force": True,
"reason": "Tournament prize",
},
timeout=10,
)
data = res.json()
if not res.ok:
raise RuntimeError(f"{res.status_code} {data['error']}: {data['message']}")
print(data)