REST API for your JoinOurServer community. JSON in / JSON out. Bearer authentication via API key.
https://api.joinourserver.com/v1
No hard limits in dev mode. In production: 1,000 requests/hour per API key.
Every request must include your API key as a Bearer token in the Authorization header:
Authorization: Bearer pk_live_xxxxxxxxxxxxxxxx
You'll find your API key in the partner dashboard under Settings β API & Webhooks. You can also regenerate the key there.
Each subscription optionally receives a unique license key
(format: PH-XXXX-XXXX-XXXX-XXXX) if the associated plan has the
βGenerate serial number" option enabled (default: on).
Your own software (e.g. a Minecraft plugin, trading tool, desktop appβ¦) can verify this key against the API to check whether the user has a valid, paid subscription β and read additional per-subscription data along the way.
/licenses/verify
Checks a license key against your community. The call is scoped to your
partner account β keys belonging to other partners get a not_found
response.
{
"license_key": "PH-ABCD-EF01-2345-6789"
}
curl -X POST https://api.joinourserver.com/v1/licenses/verify \
-H "Authorization: Bearer pk_live_xxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{"license_key":"PH-ABCD-EF01-2345-6789"}'
{
"valid": true,
"status": "active",
"license_key": "PH-ABCD-EF01-2345-6789",
"custom_data": "user_42/level_gold",
"subscription": {
"id": "sub_xxx",
"status": "active",
"billing_type": "recurring",
"created_at": "2026-04-14 11:30:00",
"next_payment_date": "2026-05-14",
"access_expires_at": null,
"canceled_at": null
},
"plan": {
"id": "plan_premium",
"name": "Premium",
"price": 29.99,
"interval": "1 month",
"billing_type": "recurring"
},
"member": {
"id": "mem_xxx",
"discord_id": "322101351830126594",
"discord_username": "Chacky",
"discord_email": "user@example.com",
"discord_avatar": "a1b2c3d4e5f60718293a4b5c6d7e8f90",
"discord_avatar_url": "https://cdn.discordapp.com/avatars/322101351830126594/a1b2c3d4e5f60718293a4b5c6d7e8f90.png",
"name": "Chacky",
"status": "active"
}
}
discord_avatar is the raw avatar hash Discord assigned to the user.
discord_avatar_url is a ready-to-use CDN URL you can drop into an <img src="β¦">
— animated avatars (hash starting with a_) are served as .gif, everything
else as .png. If the user hasn't set a custom avatar, the URL resolves to one of Discord's
6 default avatars.
{
"valid": false,
"status": "canceled", // or "expired" / "past_due" / "refunded"
"license_key": "PH-ABCD-EF01-2345-6789",
... (same sub/plan/member structure)
}
HTTP/1.1 404 Not Found
{"valid": false, "error": "not_found"}
valid === true in your app. The
status gives additional context as to why a key may be invalid
(expired, canceled, past_due, refunded).
/licenses/{license_key}
Identical response to POST /licenses/verify β handy when you need
a GET-based check (e.g. browser extension).
curl https://api.joinourserver.com/v1/licenses/PH-ABCD-EF01-2345-6789 \
-H "Authorization: Bearer pk_live_xxxxxxxxxxxxxxxx"
/subscriptions/{subscription_id}
For each subscription you can set a free-form text field which is returned with every license verify call. Perfect for user-specific data such as level, permissions, feature flags.
Limit: max 100 characters per subscription. Values longer than 100 characters are rejected with HTTP 400.
{
"custom_data": "tier=gold;seats=5;feature_ai=1"
}
{
"id": "sub_xxx",
"custom_data": "tier=gold;seats=5;feature_ai=1",
"status": "active",
"license_key": "PH-ABCD-EF01-2345-6789",
...
}
/members
| Parameter | Type | Description |
|---|---|---|
page | integer | Page (default 1) |
limit | integer | Per page (default 20, max 100) |
status | string | active, trial, waitlisted, canceled, expired, past_due, pending, rejected |
plan_id | string | Filter by plan |
curl "https://api.joinourserver.com/v1/members?status=active&limit=50" \
-H "Authorization: Bearer pk_live_xxxxxxxxxxxxxxxx"
{
"members": [
{
"id": "mem_1234567890abcdef",
"discord_id": "322101351830126594",
"discord_username": "Chacky",
"discord_email": "user@example.com",
"discord_avatar": "a1b2c3d4e5f60718293a4b5c6d7e8f90",
"discord_avatar_url": "https://cdn.discordapp.com/avatars/322101351830126594/a1b2c3d4e5f60718293a4b5c6d7e8f90.png",
"name": "Chacky",
"status": "active",
"plan_id": "plan_premium",
"access_expires_at": null,
"created_at": "2026-04-14 11:30:00",
"metadata": {}
}
],
"pagination": {
"total": 156,
"page": 1,
"per_page": 50,
"total_pages": 4
}
}
discord_access_token and
discord_refresh_token are never returned in responses.
/members/{id}
curl https://api.joinourserver.com/v1/members/mem_xxx \
-H "Authorization: Bearer pk_live_xxxxxxxxxxxxxxxx"
/members/{id}
Allowed fields: name, status, email, metadata.
{
"status": "active",
"metadata": {"notes": "VIP customer"}
}
A member can have multiple subscriptions in parallel (e.g. different plans or the same plan multiple times). Each subscription has its own license key, status and access expiry.
/subscriptions
status | string | active, trial, past_due, canceled, expired |
member_id | string | All subs of a member |
plan_id | string | All subs of a plan |
limit | integer | Default 50, max 100 |
offset | integer | Pagination |
curl "https://api.joinourserver.com/v1/subscriptions?status=active" \
-H "Authorization: Bearer pk_live_xxxxxxxxxxxxxxxx"
/subscriptions/{id}
/subscriptions/{id}
Allowed fields:
custom_data (string, max 100 chars) — free-form text field, returned on license verify/payments
curl https://api.joinourserver.com/v1/payments \
-H "Authorization: Bearer pk_live_xxxxxxxxxxxxxxxx"
Note: Amounts are returned in cents. 2999 = β¬29.99.
/plans
curl https://api.joinourserver.com/v1/plans \
-H "Authorization: Bearer pk_live_xxxxxxxxxxxxxxxx"
/stats
{
"monthly_revenue_cents": 125000,
"monthly_revenue": 1250.00,
"active_members": 42,
"platform_fee": 31.25,
"platform_fee_percentage": 2.5,
"conversion_rate": 68.5
}
JoinOurServer POSTs events to the webhook URL you configure
(Settings β API). Every payload is signed via HMAC-SHA256 using your API key
(header X-JoinOurServer-Signature: sha256=<hex>).
member.created | New member created |
member.updated | Member data updated |
member.deleted | Member deactivated |
subscription.canceled | Subscription cancelled |
payment.refunded | Payment refunded |
function verifyWebhookSignature(payload, signature, apiKey) {
const expected = 'sha256=' + crypto
.createHmac('sha256', apiKey)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
HMAC key = the API key with which the originating action was triggered
(e.g. if your webhook backend makes a POST /members call, we sign the
resulting member.created webhook with the same key). For system-internal
events the signature falls back to the partner's oldest non-revoked key.
{
"error": {
"type": "validation_error",
"code": "invalid_api_key",
"message": "Invalid or missing API key"
}
}
200 | OK |
201 | Created |
204 | No Content (e.g. on DELETE) |
400 | Bad Request β validation failed |
401 | Unauthorized β API key missing or invalid |
404 | Not found |
405 | Method not allowed |
500 | Internal server error |
We use cookies and similar technologies to operate the platform and to analyse its usage. "Necessary" cookies are technically required (login, session, CSRF). Statistics and marketing cookies are only activated if you consent. More info: privacy policy.
Session, login, CSRF protection. These cookies are required to operate the site and cannot be disabled.
Anonymised usage data (page views, browser, approximate location) for improving the platform. ID: G-K1B9Z8G1ZC.
Tag Manager for dynamic loading of advertising pixels and conversion tracking. ID: GTM-54M9WF9Z.