Reference

REST API

A small, predictable JSON API bound to one server per key. Read your verified members, start pulls and backups, poll jobs and read the audit log from your own tools.

Authentication

Create a key under Dashboard → Developer → API keys (Premium and Plus). Keys start with kc_, are shown once, stored hashed, and are sent as a Bearer token. Each key belongs to one server and carries a set of scopes.

Base URL and header
GET /v1/guild HTTP/1.1
Host: api.keepcord.app
Authorization: Bearer kc_live_3f9c...
Accept: application/json

Keep keys server-side

Never ship a key in a browser or a mobile app. Revoke a leaked key from the dashboard: revocation is immediate.

Scopes

ScopeGrants
members:readGET /v1/guild, GET /v1/members, GET /v1/members/:userId, GET /v1/jobs/:id
members:writeDELETE /v1/members/:userId, POST /v1/members/:userId/blacklist
pullPOST /v1/pull
backups:readGET /v1/backups
backups:writePOST /v1/backups
logs:readGET /v1/logs

Conventions

  • All requests and responses are JSON (Content-Type: application/json). Ids are Discord snowflakes as strings. Dates are ISO 8601 in UTC.
  • List endpoints paginate with page (1-based) and limit (default 100, max 500) and return total and page.
  • Rate limit: 300 requests per minute per IP. Exceeding it returns 429 with a retry-after header.
  • Long operations (pull, backup) return a job; poll GET /v1/jobs/:id or subscribe to the matching webhook events instead of polling.
  • Keys are bound to a server, so there is no guildId parameter anywhere.

Errors

Errors are JSON with a machine-readable error code and a human message. The HTTP status matches.

{ "error": "forbidden", "message": "API key lacks scope members:write" }
StatuserrorWhen
400validationBody or query failed validation; the message names the field.
401unauthorizedMissing or invalid key, or the key was revoked.
402plan_requiredThe server's plan does not include the developer API (key created on a plan that later expired).
403forbiddenKey lacks the required scope.
404not_foundMember, backup or job does not exist for this server.
409conflictA pull or backup is already running.
429rate_limitedToo many requests.

Guild

GET/v1/guildscope members:read

Server summary

Name, plan, member count, bot status and how many members are currently verified.

Response

{
  "guild": {
    "id": "987654321098765432",
    "name": "Keepcord HQ",
    "plan": "premium",
    "memberCount": 1284,
    "status": "active"
  },
  "verified": 1130
}

Request

curl -X GET "https://api.keepcord.app/v1/guild" \
  -H "Authorization: Bearer $KEEPCORD_KEY"

Members

GET/v1/membersscope members:read

List verified members

Paginated, newest verification first.

Parameters

pagequeryinteger
1-based page number. Default 1.
limitqueryinteger
Items per page, 1–500. Default 100.
statusquerystring
Filter: active, quarantined, flagged, blocked, revoked, blacklisted.

Response

{
  "total": 1130,
  "page": 1,
  "members": [
    {
      "userId": "240000000000000001",
      "username": "nova",
      "globalName": "Nova",
      "avatar": "https://cdn.discordapp.com/avatars/240000000000000001/a_1f2e.png",
      "status": "active",
      "verifiedAt": "2026-10-18T14:02:11.000Z",
      "accountCreatedAt": "2019-03-02T09:12:45.000Z",
      "country": "IT",
      "flags": [],
      "connections": [{ "type": "steam", "id": "7656119…", "name": "nova", "verified": true }],
      "roles": ["1210000000000000001"]
    }
  ]
}

Request

curl -X GET "https://api.keepcord.app/v1/members?status=active&limit=50" \
  -H "Authorization: Bearer $KEEPCORD_KEY"
GET/v1/members/:userIdscope members:read

Get one member

Full record including e-mail (if the scope is enabled), VPN flag and nickname.

Parameters

userIdpathsnowflakerequired
Discord user id.

Response

{
  "member": {
    "userId": "240000000000000001",
    "username": "nova",
    "globalName": "Nova",
    "email": "[email protected]",
    "status": "active",
    "verifiedAt": "2026-10-18T14:02:11.000Z",
    "accountCreatedAt": "2019-03-02T09:12:45.000Z",
    "country": "IT",
    "isVpn": false,
    "flags": [],
    "connections": [],
    "roles": ["1210000000000000001"],
    "nickname": "Nova ✦"
  }
}

Request

curl -X GET "https://api.keepcord.app/v1/members/240000000000000001" \
  -H "Authorization: Bearer $KEEPCORD_KEY"
DELETE/v1/members/:userIdscope members:write

Delete a member's data

Removes the authorization (token, IP, device, connections). The member keeps their roles in Discord but can no longer be pulled.

Parameters

userIdpathsnowflakerequired
Discord user id.

Response

{ "deleted": 1 }

Request

curl -X DELETE "https://api.keepcord.app/v1/members/240000000000000001" \
  -H "Authorization: Bearer $KEEPCORD_KEY"
POST/v1/members/:userId/blacklistscope members:write

Blacklist a member

Adds a user blacklist entry and sets the authorization status to blacklisted. Future verifications are refused.

Parameters

userIdpathsnowflakerequired
Discord user id.
reasonbodystring
Optional, up to 300 characters. Shown in the dashboard and in /info.

Response

{ "ok": true }

Request

curl -X POST "https://api.keepcord.app/v1/members/240000000000000001/blacklist" \
  -H "Authorization: Bearer $KEEPCORD_KEY" \
  -H "Content-Type: application/json" \
  -d '{"reason":"Spam in #general"}'

Pull

POST/v1/pullscope pull

Start a member pull

Queues a pull job for the server bound to the key. Returns 409 if one is already running. Plan cooldowns and member caps apply.

Parameters

restoreRolesbodyboolean
Give members their previous roles. Default true.
restoreNicknamesbodyboolean
Default true.
extraRoleIdsbodysnowflake[]
Up to 10 roles added to every pulled member.
userIdsbodysnowflake[]
Restrict the pull to these users. Empty = everyone active.
skipBlacklistedbodyboolean
Default true.
skipFlaggedbodyboolean
Default false.
minTrustScorebodyinteger
0–100. Default 0.

Response

{ "job": { "id": "ckx9…", "status": "queued" } }
sourceGuildId (cross-server pull) is dashboard-only and ignored here.

Request

curl -X POST "https://api.keepcord.app/v1/pull" \
  -H "Authorization: Bearer $KEEPCORD_KEY" \
  -H "Content-Type: application/json" \
  -d '{"restoreRoles":true,"extraRoleIds":["1210000000000000009"],"minTrustScore":20}'

Backups

GET/v1/backupsscope backups:read

List backups

Newest version first, up to 100.

Response

{
  "backups": [
    {
      "id": "ckx8…",
      "version": 12,
      "kind": "scheduled",
      "status": "completed",
      "name": null,
      "sizeBytes": 1834021,
      "stats": { "roles": 24, "channels": 61, "emojis": 48, "bans": 3, "messages": 6100, "attachments": 212 },
      "createdAt": "2026-10-19T03:00:00.000Z"
    }
  ]
}

Request

curl -X GET "https://api.keepcord.app/v1/backups" \
  -H "Authorization: Bearer $KEEPCORD_KEY"
POST/v1/backupsscope backups:write

Create a backup

Queues a manual backup with the server's current message and media settings. Returns 409 if a backup is already running.

Parameters

namebodystring
Optional label.

Response

{ "job": { "id": "ckx9…", "status": "queued" } }

Request

curl -X POST "https://api.keepcord.app/v1/backups" \
  -H "Authorization: Bearer $KEEPCORD_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"before-event"}'

Jobs

GET/v1/jobs/:idscope members:read

Get a job

Status, progress and result of any job of this server (pull, backup, restore, massrole, …).

Parameters

idpathstringrequired
Job id returned by a pull or backup request.

Response

{
  "job": {
    "id": "ckx9abc",
    "type": "pull",
    "status": "running",
    "progress": { "total": 1130, "done": 412, "failed": 3, "skipped": 10, "phase": "adding", "etaSeconds": 380 },
    "result": null,
    "error": null,
    "createdAt": "2026-10-20T10:00:00.000Z",
    "finishedAt": null
  }
}

Request

curl -X GET "https://api.keepcord.app/v1/jobs/ckx9abc" \
  -H "Authorization: Bearer $KEEPCORD_KEY"

Logs

GET/v1/logsscope logs:read

Audit log

Every event Keepcord recorded for the server, newest first. The same events are pushed to webhooks.

Parameters

pagequeryinteger
Default 1.
limitqueryinteger
Default 100, max 500.
typequerystring
Exact event type, e.g. verify.blocked. See the event list.

Response

{
  "page": 1,
  "logs": [
    {
      "id": "ckxa…",
      "type": "verify.blocked",
      "level": "warn",
      "actorId": null,
      "targetId": "240000000000000077",
      "message": "Blocked: VPN detected (NordVPN)",
      "data": { "reason": "vpn", "vpnProvider": "NordVPN", "country": "NL" },
      "createdAt": "2026-10-20T09:41:12.000Z"
    }
  ]
}

Request

curl -X GET "https://api.keepcord.app/v1/logs?type=verify.blocked&limit=20" \
  -H "Authorization: Bearer $KEEPCORD_KEY"

Client snippets

Minimal wrappers you can copy into your project.

// keepcord.js
const BASE = "https://api.keepcord.app";

export class Keepcord {
  constructor(key) { this.key = key; }

  async request(method, path, body) {
    const res = await fetch(BASE + path, {
      method,
      headers: { Authorization: `Bearer ${this.key}`, ...(body ? { "Content-Type": "application/json" } : {}) },
      body: body ? JSON.stringify(body) : undefined,
    });
    const data = await res.json();
    if (!res.ok) throw new Error(`${data.error}: ${data.message}`);
    return data;
  }

  guild() { return this.request("GET", "/v1/guild"); }
  members(params = {}) { return this.request("GET", "/v1/members?" + new URLSearchParams(params)); }
  pull(options = {}) { return this.request("POST", "/v1/pull", options); }
  backup(name) { return this.request("POST", "/v1/backups", { name }); }
  job(id) { return this.request("GET", `/v1/jobs/${id}`); }

  async waitForJob(id, everyMs = 3000) {
    for (;;) {
      const { job } = await this.job(id);
      if (["completed", "failed", "cancelled"].includes(job.status)) return job;
      await new Promise((r) => setTimeout(r, everyMs));
    }
  }
}

// usage
const kc = new Keepcord(process.env.KEEPCORD_KEY);
const { job } = await kc.backup("nightly");
console.log(await kc.waitForJob(job.id));

GET endpoints are safe to retry. POST pull and backup are idempotent while a job is running thanks to the 409 guard.