A guild-scoped REST API for reading and managing your applications, members, roles and exams
programmatically. All endpoints are versioned under /api/v1 and
return JSON.
Base URL
https://guildbase.gg/api/v1
Auth
Bearer API key (Pro)
Rate limit
120 requests / minute / key
Create an API key in Guild Settings → API Keys (requires a Pro subscription). Pass it as a Bearer token on every request. Keys are shown only once at creation — store them securely and never expose them in client-side code.
curl https://guildbase.gg/api/v1/me \
-H "Authorization: Bearer gb_live_your_key_here" \
-H "Accept: application/json"
A missing or invalid key returns 401:
{ "success": false, "error": "missing_api_key", "message": "API key is required. Use Authorization: Bearer <api_key>" }
Each key is granted a set of scopes when it is created. A request to an endpoint whose scope the key
lacks returns 403 insufficient_scope. The scope required by each
endpoint is listed in the reference below.
guild:read
Read guild profile
applications:read
Read applications & templates
applications:write
Approve, reject, move & delete applications
members:read
Read guild members
members:write
Add, update & remove members
roles:read
Read guild roles
roles:write
Create, update & delete roles
exams:read
Read exams, attempts & certificates
exams:write
Revoke certificates
Content-Type: application/json (form-encoded bodies also work).1/0 or true/false.
List endpoints are paginated. Use ?page= and
?per_page= (1–100, default 25). Every list response carries a
meta object:
"meta": {
"current_page": 1,
"per_page": 25,
"total": 128,
"last_page": 6,
"has_more": true
}
Responses include X-RateLimit-Limit and
X-RateLimit-Remaining. When throttled you receive
429 with a Retry-After header.
Every response is wrapped in a consistent envelope.
Single object
{
"success": true,
"data": { "id": 1, "name": "..." }
}
Paginated list
{
"success": true,
"data": [ { "id": 1, ... }, { "id": 2, ... } ],
"meta": { "current_page": 1, "per_page": 25,
"total": 42, "last_page": 2, "has_more": true }
}
Fields marked "omitted" in the object reference are only present when the related data is loaded for that
endpoint (for example, answers appears only on
GET /applications/{id}).
Errors use the same envelope with success: false, a stable
error code, and a human-readable message.
Integrate against the code, not the message text.
| Status | Error code | When it happens |
|---|---|---|
| 401 | missing_api_key |
No Authorization header, or it is not a Bearer token. |
| 401 | invalid_api_key_format |
The token does not begin with gb_live_. |
| 401 | invalid_api_key |
The token does not match any key. |
| 401 | api_key_invalid |
The key is inactive or has expired. |
| 403 | pro_required |
The guild that owns the key does not have an active Pro subscription. |
| 403 | insufficient_scope |
The key is valid but lacks the scope the endpoint requires. |
| 403 | template_not_published |
POST /sessions — the template is a draft, scheduled, closed or full. The current state is returned as template_status. |
| 403 | template_locked |
POST /sessions — the template is disabled by the guild's plan limit (e.g. after a downgrade). |
| 404 | not_found |
The resource does not exist, or belongs to a different guild. |
| 404 | template_not_found |
POST /sessions — no template in this guild matches the template_slug / template_id sent. List your templates with GET /api/v1/templates and copy the exact slug. |
| 404 | invalid_session |
POST /sessions/check — the session token does not exist. |
| 410 | session_invalid |
POST /sessions/check — the session has expired or has already been used. |
| 405 | method_not_allowed |
The HTTP verb is not supported for this path. |
| 409 | already_final |
Conflict — e.g. approving an application that is already accepted/rejected. |
| 422 | validation_failed |
The request body failed validation. Includes an errors object. |
| 429 | rate_limited |
Rate limit exceeded (120 requests / minute / key). |
| 500 | server_error |
An unexpected error occurred on our side. |
Insufficient scope (403)
{
"success": false,
"error": "insufficient_scope",
"message": "This API key is missing the required scope: members:write.",
"required_scopes": ["members:write"]
}
Validation failed (422)
{
"success": false,
"error": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"stage_id": ["The stage id field is required."]
}
}
In-game scripts (FiveM, launchers, kiosks) let a player apply without signing in through Discord. Your
script creates a session for the player's Discord id, then opens the returned
embed_url in a browser or NUI frame. Sessions are
single-use and expire after 1 hour.
Do this once, not on every request. Template slugs are generated with a random suffix
(staff-application-a1b2c3) unless you set a custom slug in
Template Settings — guessing a friendly name such as
staff-app is the most common cause of
404 template_not_found.
curl https://guildbase.gg/api/v1/templates \
-H "Authorization: Bearer gb_live_your_key_here" \
-H "Accept: application/json"
curl -X POST https://guildbase.gg/api/v1/sessions \
-H "Authorization: Bearer gb_live_your_key_here" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"template_slug":"staff-application-a1b2c3","discord_id":"584378000000000000"}'
{
"success": true,
"session_token": "...",
"embed_url": "https://guildbase.gg/embed/apply/your-guild/staff-application-a1b2c3?s=...",
"expires_at": "2026-01-01T12:00:00+00:00",
"template": { "id": 12, "name": "Staff Application", "slug": "staff-application-a1b2c3" }
}
Open embed_url verbatim — the ?s=
token is what authenticates the player, so building the URL yourself will not work. Once the application is
submitted the session is consumed; create a new one for the next player.
Troubleshooting a failed session request
404 template_not_found — the slug or id does not exist in the guild that owns the API key. Re-check step 1; the response repeats the value it received as template_identifier.403 template_not_published — the template exists but is not live. The response includes template_status (draft, scheduled, closed, full).401 — the key is missing, malformed, inactive or expired.403 pro_required — the guild's Pro subscription is not active.422 validation_failed — usually discord_id; it must be the numeric id, not a username.
Log the response body, not just the status code — every error carries a message explaining the cause.
/api/v1/me
Details about the current API key: guild, scopes and usage.
Returns: Key & guild metadata
/api/v1/sessions
Create a short-lived session so a player can fill in an application from inside your game/server (FiveM NUI, launcher, kiosk) without a Discord OAuth login. Returns an embed_url to open in the browser/NUI.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
template_slug |
body | string | conditional | Slug of the template to apply for. Required unless template_id is given. Copy it from GET /api/v1/templates — generated slugs end in a random suffix (e.g. staff-application-a1b2c3). |
template_id |
body | integer | conditional | Template id. Required unless template_slug is given. |
discord_id |
body | string | required | Discord user id of the player (digits only, max 20). The submitted application is attributed to this Discord account. |
Returns: { session_token, embed_url, expires_at, template }
/api/v1/sessions/check
Check whether a session token is still valid (not expired, not yet used).
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
session_token |
body | string | required | The session_token returned by POST /api/v1/sessions. |
Returns: { session: { discord_id, template_id, expires_at } }
/api/v1/templates
applications:read
List application templates for the guild.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
is_active |
query | boolean | optional | Filter to active/inactive templates only. |
page |
query | integer | optional | Page number (default 1). |
per_page |
query | integer | optional | Items per page, 1–100 (default 25). |
/api/v1/templates/{id}
applications:read
Retrieve a single template.
Returns: An ApplicationTemplate object
/api/v1/applications
applications:read
List applications for the guild.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
status |
query | string | optional | Filter by status (pending, in_review, accepted, rejected, withdrawn). |
template_id |
query | integer | optional | Filter by application template id. |
risk_level |
query | string | optional | Filter by risk level (low, medium, high). |
claimed |
query | boolean | optional | Only claimed (true) or unclaimed (false) applications. |
sort |
query | string | optional | submitted_at (default) or created_at. |
direction |
query | string | optional | asc or desc (default). |
page |
query | integer | optional | Page number. |
per_page |
query | integer | optional | Items per page, 1–100. |
Returns: Paginated list of Application objects (without answers)
/api/v1/applications/{id}
applications:read
Retrieve one application, including its formatted answers.
Returns: An Application object (with answers)
/api/v1/applications/{id}/approve
applications:write
Approve the application — moves it to the workflow's accepted final stage (or sets status to accepted) and fires the usual Discord notification.
Returns: The updated Application object
/api/v1/applications/{id}/reject
applications:write
Reject the application.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
reason |
body | string | optional | Optional rejection reason (max 1000 chars), included in the notification. |
Returns: The updated Application object
/api/v1/applications/{id}/move
applications:write
Move the application to a specific workflow stage.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
stage_id |
body | integer | required | Target workflow stage id. Must belong to the application's workflow. |
notes |
body | string | optional | Optional note (max 1000 chars). |
Returns: The updated Application object
/api/v1/applications/{id}
applications:write
Permanently delete the application and its uploaded files.
Returns: { "deleted": true }
/api/v1/members
members:read
List guild members.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
role_id |
query | integer | optional | Filter by role id. |
page |
query | integer | optional | Page number. |
per_page |
query | integer | optional | Items per page, 1–100. |
/api/v1/members
members:write
Add an existing Guildbase user to the guild.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
user_id |
body | integer | conditional | Guildbase user id. Required unless discord_id is given. |
discord_id |
body | string | conditional | Discord user id. Required unless user_id is given. |
role_id |
body | integer | optional | Role to assign. Must belong to the guild. |
Returns: The created GuildMember object (201)
/api/v1/members/{id}
members:write
Change a member's role.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
role_id |
body | integer|null | required | New role id, or null to clear the role. Field must be present. |
Returns: The updated GuildMember object
/api/v1/members/{id}
members:write
Remove a member from the guild.
Returns: { "deleted": true }
/api/v1/roles
roles:read
List guild roles with their permissions.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
page |
query | integer | optional | Page number. |
per_page |
query | integer | optional | Items per page, 1–100. |
Returns: Paginated list of GuildRole objects
/api/v1/roles
roles:write
Create a custom role.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
name |
body | string | required | Role name (max 50). |
color |
body | string | required | Hex colour, e.g. #8b5cf6. |
permissions |
body | string[] | optional | Array of permission keys (validated against the guild permission list). |
discord_role_id |
body | string | optional | Discord role id to map (must be unique within the guild). |
Returns: The created GuildRole object (201)
/api/v1/roles/{id}
roles:write
Update a role. Default roles (e.g. Owner) are locked and return 422.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
name |
body | string | optional | Role name (max 50). |
color |
body | string | optional | Hex colour. |
permissions |
body | string[] | optional | Replaces the role's permission set. |
discord_role_id |
body | string | optional | Discord role mapping. |
Returns: The updated GuildRole object
/api/v1/roles/{id}
roles:write
Delete a role. Default roles are locked and return 422.
Returns: { "deleted": true }
/api/v1/exams
exams:read
List exam templates.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
page |
query | integer | optional | Page number. |
per_page |
query | integer | optional | Items per page, 1–100. |
/api/v1/exams/{id}/attempts
exams:read
List attempts for an exam.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
status |
query | string | optional | Filter by attempt status (in_progress, submitted, graded, ...). |
page |
query | integer | optional | Page number. |
per_page |
query | integer | optional | Items per page, 1–100. |
/api/v1/certificates
exams:read
List issued certificates.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
exam_template_id |
query | integer | optional | Filter by exam template. |
user_id |
query | integer | optional | Filter by recipient user id. |
valid |
query | boolean | optional | true = not revoked, false = revoked. |
page |
query | integer | optional | Page number. |
per_page |
query | integer | optional | Items per page, 1–100. |
/api/v1/certificates/{id}/revoke
exams:write
Revoke a certificate.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
reason |
body | string | optional | Optional revocation reason (max 500). |
Returns: The updated ExamCertificate object
The shape of every resource returned by the API. Nested objects (like user
or current_stage) reference the types below.
The guild a key is scoped to.
| Field | Type | Description |
|---|---|---|
id |
integer | Guild id. |
name |
string | Display name. |
slug |
string | URL slug. |
description |
string|null | Guild description. |
is_pro |
boolean | Whether the guild has Pro. |
member_count |
integer | Member count (present on GET /guild). |
created_at |
string (ISO 8601) | Creation timestamp. |
{
"success": true,
"data": {
"id": 42,
"name": "Nightfall Syndicate",
"slug": "nightfall",
"description": "Competitive roleplay community.",
"is_pro": true,
"member_count": 318,
"created_at": "2026-01-04T18:22:10+00:00"
}
}
A form definition applicants submit against.
| Field | Type | Description |
|---|---|---|
id |
integer | Template id. |
name |
string | Template name. |
slug |
string | URL slug. |
description |
string|null | Description shown to applicants. |
status |
string | Computed: draft, live, closed, scheduled or full. |
is_active |
boolean | Whether the template is enabled. |
is_public |
boolean | Whether it is listed publicly. |
access_mode |
string | Who may apply (e.g. everyone, roles). |
allow_multiple_submissions |
boolean | Whether a user may submit more than once. |
opens_at |
string|null | ISO 8601 open time, or null. |
closes_at |
string|null | ISO 8601 close time, or null. |
applications_count |
integer | Number of applications (on list/show). |
workflow_id |
integer|null | Workflow this template feeds. |
created_at |
string (ISO 8601) | Creation timestamp. |
{
"success": true,
"data": {
"id": 7,
"name": "Police Whitelist",
"slug": "police-whitelist",
"description": "Apply to join the PD.",
"status": "live",
"is_active": true,
"is_public": true,
"access_mode": "everyone",
"allow_multiple_submissions": false,
"opens_at": null,
"closes_at": null,
"applications_count": 128,
"workflow_id": 12,
"created_at": "2026-03-11T09:00:00+00:00"
}
}
A submitted application. The answers array is only included on the single-application endpoint.
| Field | Type | Description |
|---|---|---|
id |
string (uuid) | Application id. |
guild_id |
integer | Owning guild id. |
status |
string | pending, in_review, accepted, rejected or withdrawn. |
is_final |
boolean | Whether the application is in a terminal state. |
risk_level |
string|null | Automated risk assessment (low/medium/high). |
flags |
string[] | Automated flags raised on the application. |
revision_count |
integer | How many times it has been resubmitted. |
time_spent_seconds |
integer|null | Time the applicant spent completing the form. |
submitted_at |
string|null | ISO 8601 submission time. |
created_at |
string (ISO 8601) | Creation timestamp. |
updated_at |
string (ISO 8601) | Last update timestamp. |
template |
ApplicationTemplate|omitted | Included when loaded (see obj-template). |
user |
UserSummary|omitted | The applicant (see obj-user). |
current_stage |
WorkflowStage|omitted | Current workflow stage (see obj-stage). |
workflow_id |
integer|null | Workflow id. |
is_claimed |
boolean | Whether a reviewer has claimed it. |
claimed_by |
UserSummary|omitted | Reviewer who claimed it. |
claimed_at |
string|null | ISO 8601 claim time. |
answers |
Section[]|omitted | Formatted answers. Only on GET /applications/{id}. |
{
"success": true,
"data": {
"id": "9b1deae4-2f6a-4c1e-9a0b-2e5f7c3a11d2",
"guild_id": 42,
"status": "in_review",
"is_final": false,
"risk_level": "low",
"flags": [],
"revision_count": 0,
"time_spent_seconds": 214,
"submitted_at": "2026-07-20T14:05:00+00:00",
"created_at": "2026-07-20T14:01:26+00:00",
"updated_at": "2026-07-20T15:10:03+00:00",
"template": { "id": 7, "name": "Police Whitelist", "slug": "police-whitelist", "status": "live" },
"user": {
"id": 8, "username": "ecliptor", "display_name": "Ecliptor",
"avatar_url": "https://cdn.discordapp.com/avatars/12.../a.png", "discord_id": "123456789012345678"
},
"current_stage": { "id": 3, "name": "Interview", "color": "#8b5cf6", "position": 2, "is_final": false, "final_status": null },
"workflow_id": 12,
"is_claimed": true,
"claimed_by": { "id": 2, "username": "hr_lead", "display_name": "HR Lead", "avatar_url": null, "discord_id": "98765..." },
"claimed_at": "2026-07-20T14:40:00+00:00",
"answers": [
{
"title": "About you",
"fields": [
{ "label": "Age", "type": "number", "value": "24", "formatted_value": "24" },
{ "label": "Why join?", "type": "textarea", "value": "I love RP.", "formatted_value": "I love RP." }
]
}
]
}
}
A user's membership within a guild.
| Field | Type | Description |
|---|---|---|
id |
integer | Membership id (used in member endpoints). |
guild_id |
integer | Guild id. |
role |
GuildRole|null|omitted | Assigned role (see obj-role). |
user |
UserSummary|omitted | The user (see obj-user). |
joined_at |
string|null | ISO 8601 join time. |
{
"success": true,
"data": {
"id": 501,
"guild_id": 42,
"role": { "id": 4, "name": "Moderator", "color": "#22c55e", "position": 5, "is_default": false },
"user": { "id": 8, "username": "ecliptor", "display_name": "Ecliptor", "avatar_url": null, "discord_id": "1234..." },
"joined_at": "2026-05-02T11:30:00+00:00"
}
}
A permission role within a guild.
| Field | Type | Description |
|---|---|---|
id |
integer | Role id. |
name |
string | Role name. |
color |
string | Hex colour. |
position |
integer | Sort position. |
is_default |
boolean | Default roles (e.g. Owner) are locked from edit/delete. |
discord_role_id |
string|null | Mapped Discord role id. |
permissions |
string[]|omitted | Permission keys (present on role endpoints). |
members_count |
integer | Members with this role (on list/show). |
created_at |
string (ISO 8601) | Creation timestamp. |
{
"success": true,
"data": {
"id": 4,
"name": "Moderator",
"color": "#22c55e",
"position": 5,
"is_default": false,
"discord_role_id": "1102938475610293847",
"permissions": ["applications.view", "applications.review", "tickets.view"],
"members_count": 12,
"created_at": "2026-02-18T20:15:00+00:00"
}
}
An exam definition.
| Field | Type | Description |
|---|---|---|
id |
integer | Exam id. |
name |
string | Exam name. |
slug |
string | URL slug. |
description |
string|null | Description. |
is_active |
boolean | Whether the exam is enabled. |
access_mode |
string | Who may take it. |
time_limit_minutes |
integer|null | Time limit, or null for none. |
max_attempts |
integer|null | Attempt cap per user. |
pass_mark |
number|null | Pass threshold (percent). |
certificate_enabled |
boolean | Whether passing issues a certificate. |
published_at |
string|null | ISO 8601 publish time. |
opens_at |
string|null | ISO 8601 open time. |
closes_at |
string|null | ISO 8601 close time. |
sections_count |
integer | Section count (on list/show). |
questions_count |
integer | Question count (on list/show). |
attempts_count |
integer | Attempt count (on list/show). |
created_at |
string (ISO 8601) | Creation timestamp. |
{
"success": true,
"data": {
"id": 15,
"name": "Driving Theory",
"slug": "driving-theory",
"description": "Rules of the road.",
"is_active": true,
"access_mode": "roles",
"time_limit_minutes": 30,
"max_attempts": 2,
"pass_mark": 80,
"certificate_enabled": true,
"published_at": "2026-06-01T00:00:00+00:00",
"opens_at": null,
"closes_at": null,
"sections_count": 3,
"questions_count": 40,
"attempts_count": 210,
"created_at": "2026-05-20T12:00:00+00:00"
}
}
A single user's attempt at an exam.
| Field | Type | Description |
|---|---|---|
id |
integer | Attempt id. |
guild_id |
integer | Guild id. |
exam_template_id |
integer | Exam id. |
attempt_number |
integer | Nth attempt for this user. |
status |
string | in_progress, submitted, graded, expired, etc. |
score |
number|null | Raw score. |
max_score |
number|null | Maximum possible score. |
percent |
number|null | Score as a percentage. |
grade_label |
string|null | Grade band label. |
requires_manual_grading |
boolean | Whether manual grading is pending. |
violation_count |
integer | Anti-cheat violations recorded. |
risk_level |
string|null | Risk assessment. |
started_at |
string|null | ISO 8601 start time. |
submitted_at |
string|null | ISO 8601 submit time. |
released_at |
string|null | ISO 8601 results-release time. |
time_spent_seconds |
integer|null | Time spent. |
exam |
ExamTemplate|omitted | Included on the single-attempt endpoint. |
user |
UserSummary|omitted | The candidate. |
{
"success": true,
"data": {
"id": 9032,
"guild_id": 42,
"exam_template_id": 15,
"attempt_number": 1,
"status": "graded",
"score": 34,
"max_score": 40,
"percent": 85,
"grade_label": "Pass",
"requires_manual_grading": false,
"violation_count": 0,
"risk_level": "low",
"started_at": "2026-07-19T10:00:00+00:00",
"submitted_at": "2026-07-19T10:24:00+00:00",
"released_at": "2026-07-19T10:24:05+00:00",
"time_spent_seconds": 1440,
"user": { "id": 8, "username": "ecliptor", "display_name": "Ecliptor", "avatar_url": null, "discord_id": "1234..." }
}
}
A certificate issued for a passing attempt.
| Field | Type | Description |
|---|---|---|
id |
integer | Certificate id. |
code |
string | Public verification code. |
guild_id |
integer | Guild id. |
exam_template_id |
integer | Exam id. |
exam_attempt_id |
integer | Source attempt id. |
recipient_name |
string | Name printed on the certificate. |
exam_name |
string | Exam name snapshot. |
score_percent |
number|null | Score achieved. |
grade_label |
string|null | Grade band. |
is_valid |
boolean | Not expired and not revoked. |
is_revoked |
boolean | Whether it has been revoked. |
issued_at |
string|null | ISO 8601 issue time. |
expires_at |
string|null | ISO 8601 expiry, or null. |
revoked_at |
string|null | ISO 8601 revocation time. |
revoked_reason |
string|null | Reason supplied at revocation. |
user |
UserSummary|omitted | The recipient. |
{
"success": true,
"data": {
"id": 771,
"code": "GB-CERT-4F9A2C",
"guild_id": 42,
"exam_template_id": 15,
"exam_attempt_id": 9032,
"recipient_name": "Ecliptor",
"exam_name": "Driving Theory",
"score_percent": 85,
"grade_label": "Pass",
"is_valid": true,
"is_revoked": false,
"issued_at": "2026-07-19T10:24:05+00:00",
"expires_at": "2027-07-19T10:24:05+00:00",
"revoked_at": null,
"revoked_reason": null,
"user": { "id": 8, "username": "ecliptor", "display_name": "Ecliptor", "avatar_url": null, "discord_id": "1234..." }
}
}
A minimal, PII-safe view of a user embedded in other objects.
| Field | Type | Description |
|---|---|---|
id |
integer | User id. |
username |
string | Guildbase username. |
display_name |
string|null | Display name. |
avatar_url |
string|null | Avatar URL. |
discord_id |
string|null | Linked Discord user id. |
{
"id": 8,
"username": "ecliptor",
"display_name": "Ecliptor",
"avatar_url": "https://cdn.discordapp.com/avatars/12.../a.png",
"discord_id": "123456789012345678"
}
A stage within an application workflow.
| Field | Type | Description |
|---|---|---|
id |
integer | Stage id. |
name |
string | Stage name. |
color |
string|null | Hex colour. |
position |
integer | Order within the workflow. |
is_final |
boolean | Whether this is a terminal stage. |
final_status |
string|null | accepted or rejected when is_final is true. |
{
"id": 3,
"name": "Interview",
"color": "#8b5cf6",
"position": 2,
"is_final": false,
"final_status": null
}