REST API v1 · Developers · API reference
Applications API reference
All 4 Applications 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}/applications/forms | applications:read | List application forms |
GET/guilds/{guildId}/applications/submissions | applications:read | List applications |
GET/guilds/{guildId}/applications/submissions/{number} | applications:read | Get an application |
POST/guilds/{guildId}/applications/submissions/{number}/decision | applications:write | Accept or deny an application |
Besides its own errors below, every endpoint can return these common ones:
List application forms
GET/guilds/{guildId}/applications/forms
In the console’s order, with their questions and submission counts.
Needs this feature enabled on the server: Applications
| 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 | Yes | |
items[].title | string | Yes | |
items[].description | string | Yes | |
items[].open | boolean | Yes | The open toggle (the open window is in opensAt / closesAt) |
items[].opensAt | string | null | Yes | ISO 8601 date-time |
items[].closesAt | string | null | Yes | ISO 8601 date-time |
items[].questions | array of object | Yes | |
items[].questions[].id | string | Yes | |
items[].questions[].type | string | Yes | |
items[].questions[].label | string | Yes | |
items[].questions[].required | boolean | Yes | |
items[].submissions | object | Yes | |
items[].submissions.total | integer | Yes | |
items[].submissions.open | integer | Yes |
{
"items": [
{
"id": 98765,
"title": "example",
"description": "example",
"open": true,
"opensAt": "2026-10-07T12:00:00.000Z",
"closesAt": "2026-10-07T12:00:00.000Z",
"questions": [
{
"id": "example",
"type": "example",
"label": "Example",
"required": true
}
],
"submissions": {
"total": 10,
"open": 10
}
}
]
}Examples
curl -s "https://console.botgg.app/api/v1/guilds/123456789012345678/applications/forms" \
-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/applications/forms', {
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/applications/forms",
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 applications
GET/guilds/{guildId}/applications/submissions
Newest first, without answers. status=open = pending + interview + hold, decided = accepted + denied. Pass before=<nextCursor> for the next page.
Needs this feature enabled on the server: Applications
| Name | In | Type | Required | Description |
|---|---|---|---|---|
guildId | path | string | Yes | Your server’s ID · Discord ID (string) |
status | query | string | No | One of pending, interview, hold, accepted, denied, withdrawn, open, decided |
formId | query | integer | No | at least 1 |
userId | query | string | No | Only this applicant Discord ID (string) |
limit | query | integer | No | 1–100 · Default 25 |
before | query | integer | No | 1–999,999,999 |
Response
| Field | Type | Required | Description |
|---|---|---|---|
items | array of object | Yes | |
items[].number | integer | Yes | |
items[].formId | integer | null | Yes | null when the form was deleted |
items[].formTitle | string | null | Yes | |
items[].userId | string | Yes | |
items[].status | string | Yes | One of pending, interview, hold, accepted, denied, withdrawn |
items[].votes | object | Yes | |
items[].votes.up | integer | Yes | |
items[].votes.down | integer | Yes | |
items[].submittedAt | string | null | Yes | ISO 8601 date-time |
items[].decidedAt | string | null | Yes | ISO 8601 date-time |
items[].decidedBy | string | null | Yes | null = system / API |
items[].reason | string | null | Yes | |
nextCursor | string | null | Yes |
{
"items": [
{
"number": 7,
"formId": 98765,
"formTitle": "example",
"userId": "876543210987654321",
"status": "pending",
"votes": {
"up": 10,
"down": 10
},
"submittedAt": "2026-10-07T12:00:00.000Z",
"decidedAt": "2026-10-07T12:00:00.000Z",
"decidedBy": "example",
"reason": "Tournament prize"
}
],
"nextCursor": "98700"
}Examples
curl -s "https://console.botgg.app/api/v1/guilds/123456789012345678/applications/submissions?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/applications/submissions?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/applications/submissions",
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 an application
GET/guilds/{guildId}/applications/submissions/{number}
With the answers and the applicant snapshot taken at submit time (account age, join date, roles, level).
Needs this feature enabled on the server: Applications
| Name | In | Type | Required | Description |
|---|---|---|---|---|
guildId | path | string | Yes | Your server’s ID · Discord ID (string) |
number | path | string | Yes |
Response
| Field | Type | Required | Description |
|---|---|---|---|
number | integer | Yes | |
formId | integer | null | Yes | null when the form was deleted |
formTitle | string | null | Yes | |
userId | string | Yes | |
status | string | Yes | One of pending, interview, hold, accepted, denied, withdrawn |
votes | object | Yes | |
votes.up | integer | Yes | |
votes.down | integer | Yes | |
submittedAt | string | null | Yes | ISO 8601 date-time |
decidedAt | string | null | Yes | ISO 8601 date-time |
decidedBy | string | null | Yes | null = system / API |
reason | string | null | Yes | |
answers | array of object | Yes | |
answers[].id | string | Yes | |
answers[].type | string | Yes | |
answers[].label | string | Yes | |
answers[].text | string | Yes | |
applicant | object | Yes | |
applicant.accountCreatedAt | string | null | Yes | ISO 8601 date-time |
applicant.joinedAt | string | null | Yes | ISO 8601 date-time |
applicant.roleIds | array of string | Yes | |
applicant.level | integer | null | Yes | |
applying | boolean | Yes | A console/API decision is still being applied in Discord |
failedActions | array of object | Yes | |
failedActions[].type | string | Yes | |
failedActions[].error | string | null | Yes |
{
"number": 7,
"formId": 98765,
"formTitle": "example",
"userId": "876543210987654321",
"status": "pending",
"votes": {
"up": 10,
"down": 10
},
"submittedAt": "2026-10-07T12:00:00.000Z",
"decidedAt": "2026-10-07T12:00:00.000Z",
"decidedBy": "example",
"reason": "Tournament prize",
"answers": [
{
"id": "example",
"type": "example",
"label": "Example",
"text": "Example answer"
}
],
"applicant": {
"accountCreatedAt": "2026-10-07T12:00:00.000Z",
"joinedAt": "2026-10-07T12:00:00.000Z",
"roleIds": [
"234567890123456789"
],
"level": 5
},
"applying": true,
"failedActions": [
{
"type": "example",
"error": "example"
}
]
}Examples
curl -s "https://console.botgg.app/api/v1/guilds/123456789012345678/applications/submissions/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/applications/submissions/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/applications/submissions/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 | APPLICATION_NOT_FOUND |
| 409 | FEATURE_DISABLED |
| 503 | SERVICE_UNAVAILABLE |
Accept or deny an application
POST/guilds/{guildId}/applications/submissions/{number}/decision
Same as Accept / Deny in the console: decided now (status, reason, reapply cooldown after a denial), then the bot gives / removes the form’s roles, DMs the applicant, announces and updates the review within a few seconds (applying). Written to the audit log.
Needs this feature enabled on the server: Applications
| Name | In | Type | Required | Description |
|---|---|---|---|---|
guildId | path | string | Yes | Your server’s ID · Discord ID (string) |
number | 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 |
|---|---|---|---|
decision | string | Yes | One of accepted, denied |
reason | string | null | No | Sent to the applicant (DM) and shown in the review up to 1,000 characters |
Response
| Field | Type | Required | Description |
|---|---|---|---|
number | integer | Yes | |
formId | integer | null | Yes | null when the form was deleted |
formTitle | string | null | Yes | |
userId | string | Yes | |
status | string | Yes | One of pending, interview, hold, accepted, denied, withdrawn |
votes | object | Yes | |
votes.up | integer | Yes | |
votes.down | integer | Yes | |
submittedAt | string | null | Yes | ISO 8601 date-time |
decidedAt | string | null | Yes | ISO 8601 date-time |
decidedBy | string | null | Yes | null = system / API |
reason | string | null | Yes | |
answers | array of object | Yes | |
answers[].id | string | Yes | |
answers[].type | string | Yes | |
answers[].label | string | Yes | |
answers[].text | string | Yes | |
applicant | object | Yes | |
applicant.accountCreatedAt | string | null | Yes | ISO 8601 date-time |
applicant.joinedAt | string | null | Yes | ISO 8601 date-time |
applicant.roleIds | array of string | Yes | |
applicant.level | integer | null | Yes | |
applying | boolean | Yes | A console/API decision is still being applied in Discord |
failedActions | array of object | Yes | |
failedActions[].type | string | Yes | |
failedActions[].error | string | null | Yes |
{
"number": 7,
"formId": 98765,
"formTitle": "example",
"userId": "876543210987654321",
"status": "pending",
"votes": {
"up": 10,
"down": 10
},
"submittedAt": "2026-10-07T12:00:00.000Z",
"decidedAt": "2026-10-07T12:00:00.000Z",
"decidedBy": "example",
"reason": "Tournament prize",
"answers": [
{
"id": "example",
"type": "example",
"label": "Example",
"text": "Example answer"
}
],
"applicant": {
"accountCreatedAt": "2026-10-07T12:00:00.000Z",
"joinedAt": "2026-10-07T12:00:00.000Z",
"roleIds": [
"234567890123456789"
],
"level": 5
},
"applying": true,
"failedActions": [
{
"type": "example",
"error": "example"
}
]
}Examples
curl -s -X POST "https://console.botgg.app/api/v1/guilds/123456789012345678/applications/submissions/234567890123456789/decision" \
-H "Authorization: Bearer $BOTGG_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"decision":"accepted","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/applications/submissions/234567890123456789/decision', {
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({
decision: 'accepted',
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/applications/submissions/234567890123456789/decision",
headers={
"Authorization": f"Bearer {os.environ['BOTGG_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()), # reuse it when you retry this request
},
json={
"decision": "accepted",
"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
| HTTP | Codes |
|---|---|
| 400 | INVALID_JSON |
| 403 | SELF_DECISION |
| 404 | APPLICATION_NOT_FOUND |
| 409 | FEATURE_DISABLED, ALREADY_DECIDED |
| 413 | PAYLOAD_TOO_LARGE |
| 415 | UNSUPPORTED_MEDIA_TYPE |
| 422 | IDEMPOTENCY_KEY_REUSED |
| 503 | SERVICE_UNAVAILABLE |