Skip to content

Repository files navigation

@noctaly/sdk

Official Node.js SDK for the Noctaly public API.

📖 API Reference · npm · GitHub

Installation

npm install @noctaly/sdk
# or
pnpm add @noctaly/sdk
# or
yarn add @noctaly/sdk

Requires Node.js ≥ 18

Quick Start

import { NoctalyClient } from "@noctaly/sdk";

const noctaly = new NoctalyClient({ apiKey: "your-api-key" });

const user = noctaly.guilds("GUILD_ID").users("USER_ID");

// Get leveling profile
const { data: profile } = await user.getLevelProfile();
console.log(`Level ${profile.level} - ${profile.totalXp} total XP`);

// Give 500 XP (with server multipliers applied)
const { data: xpResult } = await user.updateXP({ xp: 500, multiply: true });
console.log(`XP given: ${xpResult.xp}`);

API Reference

new NoctalyClient(options)

Option Type Default Description
apiKey string - Your Noctaly API key.
baseURL string https://noctaly.com/api/v1 Override the base URL. Useful for staging environments.
retry boolean true Automatically retry requests that receive a 429 rate-limit response.
maxRetries number 3 Maximum number of automatic retries before the error is thrown.
timeout number 30000 Request timeout in milliseconds. Set to 0 to disable.

When retry is true, the SDK waits the retryAfter duration from the API response before each retry, capped at 60 seconds.


client.guilds(guildID)GuildResource

Returns a GuildResource scoped to the given Discord guild.

guild.users(userID)UserResource

Returns a UserResource scoped to the given guild member.

All methods return Promise<NoctalyResponse<T>>:

interface NoctalyResponse<T> {
  data: T;
  rateLimit?: RateLimitInfo;
}

UserResource

user.getLevelProfile()

GET /guilds/{guildID}/users/{userID}/level-profile
const { data } = await user.getLevelProfile();
// data: LevelProfile
Field Type Description
level number Current level.
xp number XP accumulated in the current level.
neededXp number Remaining XP needed to reach next level.
totalXp number Total XP across all levels.
messages number Total messages sent.
voiceMinutes number Total minutes spent in voice channels.
reactions number Total reactions added.

user.getEcoProfile(options?)

GET /guilds/{guildID}/users/{userID}/eco-profile

Only money is returned by default. Pass options to include optional relations. The return type is narrowed automatically based on the options you pass.

// Basic
const { data } = await user.getEcoProfile();
// data: { money: number }

// With all relations
const { data } = await user.getEcoProfile({ items: true, chests: true, dailyStreak: true });
// data: { money: number, items: UserItem[], chests: UserChest[], dailyStreak: DailyStreak | null }
Option Type Description
items boolean Include the member's item inventory.
chests boolean Include the member's chest inventory.
dailyStreak boolean Include the member's daily streak.

user.updateXP(body)

POST /guilds/{guildID}/users/{userID}/xp
const { data } = await user.updateXP({ xp: 1000, multiply: true });
// data: { xp: number }  ← actual XP given after multipliers
Field Type Required Description
xp number XP to give (positive) or remove (negative). Range: ±100 M.
multiply boolean Apply server and booster-role multipliers. Default: false.

user.updateLevel(body)

POST /guilds/{guildID}/users/{userID}/level
const { data } = await user.updateLevel({ level: 5 });
// data: { level: number }
Field Type Required Description
level number Levels to give (positive) or remove (negative). ±1 000.

user.updateMoney(body)

POST /guilds/{guildID}/users/{userID}/money
const { data } = await user.updateMoney({ money: 5000, multiply: true });
// data: { money: number }
Field Type Required Description
money number Money to give (positive) or remove (negative). Range: ±1 000 M.
multiply boolean Apply server and booster-role multipliers. Default: false.

UserItemsResource - user.items

user.items.add(body)

POST /guilds/{guildID}/users/{userID}/items
const { data } = await user.items.add({ itemID: "uuid", quantity: 3 });
// data: { quantity: number }  ← new total quantity in inventory

user.items.remove(body)

DELETE /guilds/{guildID}/users/{userID}/items
const { data } = await user.items.remove({ itemID: "uuid", quantity: 1 });
// data: { quantity: number }  ← remaining quantity (0 if fully removed)

user.items.set(body)

PUT /guilds/{guildID}/users/{userID}/items
const { data } = await user.items.set({ itemID: "uuid", quantity: 10 });
// Passing 0 removes the item entirely.
// data: { quantity: number }

user.items.use(itemID, body?)

POST /guilds/{guildID}/users/{userID}/items/{itemID}/use

Triggers the item's configured actions. Only works for CUSTOM items with at least one action. The item is consumed unless it has the KEEP_AFTER_USE flag.

const { data } = await user.items.use("item-uuid", { quantity: 2 });
// data: UseItemResult

UseItemResult fields:

Field Type Description
xp number Total XP awarded (negative if removed).
money number Total money awarded (negative if removed).
addedRoles string[] Role IDs added to the member.
removedRoles string[] Role IDs removed from the member.
addedItems Record<string, number> Map of item UUID → quantity added.
itemsRemoved Record<string, number> Map of item UUID → quantity removed.
addedChests Record<string, number> Map of chest UUID → quantity added.
chestsRemoved Record<string, number> Map of chest UUID → quantity removed.
rolesDuration Record<string, number> Map of role ID → Unix timestamp when the temporary role expires.

UserChestsResource - user.chests

user.chests.add(body)

POST /guilds/{guildID}/users/{userID}/chests
const { data } = await user.chests.add({ chestID: "uuid", quantity: 2 });
// data: { quantity: number }

user.chests.remove(body)

DELETE /guilds/{guildID}/users/{userID}/chests
const { data } = await user.chests.remove({ chestID: "uuid", quantity: 1 });
// data: { quantity: number }

user.chests.set(body)

PUT /guilds/{guildID}/users/{userID}/chests
const { data } = await user.chests.set({ chestID: "uuid", quantity: 0 });
// Passing 0 removes the chest entirely.
// data: { quantity: number }

GuildItemsResource - guild.items

guild.items.list()

GET /guilds/{guildID}/items
const { data } = await guild.items.list();
// data: { items: Item[] }

Item fields:

Field Type Description
id string UUID of the item.
name string Display name.
description string | null Optional description.
type string Item type (e.g. "CUSTOM").
flags string[] Item flags (e.g. ["KEEP_AFTER_USE"]).
emoji string | null Emoji string.
emojiType "UNICODE" | "CUSTOM" | null Emoji type.
iconURL string | null Custom icon URL.
buyPrice number Buy price.
sellPrice number Sell price.
cooldown number Cooldown between uses in seconds (0 = none).
durability number | null Full durability per item (0 = no durability).
quantity number | null Quantity in the shop (null = unlimited).
quantityLimit number | null Per-member quantity limit (null = unlimited).

GuildChestsResource - guild.chests

guild.chests.list()

GET /guilds/{guildID}/chests
const { data } = await guild.chests.list();
// data: { chests: Chest[] }

Chest fields:

Field Type Description
id string UUID of the chest.
name string Display name.
primaryColor string Primary hex color (without #).
secondaryColor string Secondary hex color (without #).
iconURL string | null Custom icon URL.
flags string[] Chest flags (e.g. ["ANIMATED"]).
buyPrice number Buy price.
sellPrice number Sell price.
itemDrawCount number Number of items drawn when the chest is opened.
quantity number | null Quantity in the shop (null = unlimited).
quantityLimit number | null Per-member quantity limit (null = unlimited).

Error Handling

All methods throw a NoctalyError on non-2xx responses.

import { NoctalyError } from "@noctaly/sdk";

try {
  await user.updateXP({ xp: 500 });
} catch (err) {
  if (err instanceof NoctalyError) {
    console.error(err.code);       // "rate_limited" | "not_found" | …
    console.error(err.status);     // 429 | 404 | …
    console.error(err.message);    // Human-readable message
    console.error(err.retryAfter); // seconds (rate_limited only)
    console.error(err.global);     // boolean (rate_limited only)
    console.error(err.details);    // ValidationIssue[] (validation_error only)
  }
}
Code Status Description
unauthenticated 401 No API key provided.
unauthorized 403 API key doesn't have access to this guild.
rate_limited 429 Rate limit exceeded. Check retryAfter and global.
not_found 404 The requested member or resource was not found.
module_disabled 400 The relevant module is disabled in this guild.
validation_error 400 Invalid request body. Check details for field errors.
unexpected_error 500 Server-side error.

Rate Limits

Every successful response exposes the endpoint's rate-limit info:

const { data, rateLimit } = await user.getLevelProfile();

if (rateLimit) {
  console.log(`${rateLimit.remaining}/${rateLimit.limit} remaining`);
  console.log(`Resets at ${new Date(rateLimit.reset * 1000).toISOString()}`);
  console.log(`Resets in ${rateLimit.resetAfter}s`);
}
Field Type Description
limit number Maximum requests allowed in the window.
remaining number Requests remaining in the current window.
reset number Unix timestamp (seconds) when the window resets.
resetAfter number Seconds until the window resets.

The global rate limit is 50 req/s across all routes. Exceeding it results in a NoctalyError with code: "rate_limited" and global: true.

When retry: true (the default), the SDK automatically handles 429 responses by waiting retryAfter seconds and retrying up to maxRetries times before throwing.

About

Official Node.js SDK for the Noctaly public API.

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages