Skip to content

SDK API Reference v0.5.0

Complete public API of the @dicolytics/discord.js package.

createDicolytics

Factory function that constructs a Dicolytics instance and attaches it to the discord.js client. This is the recommended way to initialize the SDK.

ts
function createDicolytics(client: Client, options: DicolyticsOptions): Dicolytics
python
def create_dicolytics(bot: discord.Bot, **options) -> Dicolytics

Equivalent to new Dicolytics(client, options).attach().

ParameterTypeDescription
clientClientA discord.js v14 Client instance
optionsDicolyticsOptionsSDK configuration. See Configuration.

Returns: A Dicolytics instance with all listeners attached and timers running.

Throws: Synchronously if apiKey is missing or empty, or if a Dicolytics instance is already attached to the same client.

Example

ts
import { Client, GatewayIntentBits } from 'discord.js';
import { createDicolytics } from '@dicolytics/discord.js';

const client = new Client({
  intents: [GatewayIntentBits.Guilds],
});

const analytics = createDicolytics(client, {
  apiKey: process.env.DICOLYTICS_KEY!,
});

client.login(process.env.DISCORD_TOKEN);
python
import os
import discord
from dicolytics import create_dicolytics

bot = discord.Bot()

analytics = create_dicolytics(bot, api_key=os.environ["DICOLYTICS_KEY"])

bot.run(os.environ["DISCORD_TOKEN"])

Note

The SDK throws an error if you call createDicolytics on a client that already has an active instance. Call shutdown() first to detach, then re-attach.

ts
await analytics.shutdown();
const newAnalytics = createDicolytics(client, { apiKey: '...' });

track

Records a custom event. The event is queued and delivered asynchronously.

ts
analytics.track(name: string, props?: Record<string, unknown>): void
python
analytics.track(name: str, props: dict[str, Any] | None = None) -> None
ParameterTypeRequiredDescription
namestringYesEvent name (1--256 characters). Trimmed and truncated if necessary.
propsRecord<string, unknown> / dictNoArbitrary properties (max 50 keys, 8 KiB total serialized, 4 KiB per string value).

Returns: void

Never throws. If the name is empty or not a string, the event is dropped with a debug log. SDK errors are silently ignored.

Examples

ts
// Simple event
analytics.track('daily_reward_claimed');

// Event with properties
analytics.track('purchase', {
  sku: 'pro',
  amount: 42,
  currency: 'USD',
});

// Event with context
analytics.track('ticket_opened', {
  category: 'billing',
  priority: 'high',
  guildId: interaction.guildId,
});
python
# Simple event
analytics.track("daily_reward_claimed")

# Event with properties
analytics.track("purchase", {
    "sku": "pro",
    "amount": 42,
    "currency": "USD",
})

# Event with context
analytics.track("ticket_opened", {
    "category": "billing",
    "priority": "high",
    "guild_id": str(interaction.guild_id),
})

Props best practices

  • Use string, number, and boolean values for best filterability in Explore.
  • Keep property keys consistent across events for meaningful cross-event analysis.
  • Avoid deeply nested objects -- they are serialized but not directly filterable.

captureError

Explicitly reports an error event. Use this for errors you catch yourself, as opposed to auto-captured command_error events.

ts
analytics.captureError(
  error: unknown,
  context?: Record<string, unknown>
): void
python
analytics.capture_error(
    exc: BaseException,
    context: dict[str, Any] | None = None,
) -> None
ParameterTypeRequiredDescription
error / excunknown / BaseExceptionYesThe error to report. Accepts Error instances, strings, or any value.
contextRecord<string, unknown> / dictNoAdditional context. Special keys: guildId, channelId, userId are extracted as top-level dimensions.

Returns: void

Never throws. Error fields are size-capped: name (200 chars), message (2 KiB), stack (8 KiB). The source field is automatically set to captureError.

Examples

ts
// Basic error capture
try {
  await riskyOperation();
} catch (err) {
  analytics.captureError(err);
}

// With context dimensions
analytics.captureError(err, {
  userId: interaction.user.id,
  guildId: interaction.guildId,
  channelId: interaction.channelId,
  operation: 'database_query',
  table: 'users',
});
python
# Basic error capture
try:
    await risky_operation()
except Exception as exc:
    analytics.capture_error(exc)

# With context dimensions
analytics.capture_error(exc, {
    "user_id": interaction.user.id,
    "guild_id": interaction.guild_id,
    "channel_id": interaction.channel_id,
    "operation": "database_query",
    "table": "users",
})
Context key behavior
Context KeyBehavior
guildIdExtracted as top-level event dimension (filterable by server)
channelIdExtracted as top-level event dimension (filterable by channel)
userIdExtracted as top-level event dimension (filterable by user)
All other keysStored in data.context as additional metadata

flush

Force-sends all queued events immediately. Resolves when the queue is drained.

ts
analytics.flush(): Promise<void>
python
await analytics.flush() -> None

Returns: Promise<void> -- resolves when all queued events have been sent (or dropped after exhausting retries). Never rejects.

When to Use

Normally unnecessary. The SDK auto-flushes on two triggers:

  1. Timer: every 5 seconds (configurable via flushIntervalMs)
  2. High-water mark: when 50 events are queued

Use flush() explicitly when you need to guarantee delivery before a specific point:

ts
// Ensure all events are sent before a graceful restart
analytics.track('maintenance_start', { reason: 'deploy' });
await analytics.flush();
process.exit(0);
python
# Ensure all events are sent before a graceful restart
analytics.track("maintenance_start", {"reason": "deploy"})
await analytics.flush()
sys.exit(0)

shutdown

Detaches all listeners, clears all timers, and performs a final flush with a 3-second timeout. Safe to call multiple times.

ts
analytics.shutdown(): Promise<void>
python
await analytics.shutdown() -> None

Returns: Promise<void> -- resolves when detachment and final flush are complete. Never rejects.

What It Does

  1. Closes open voice sessions (emitting final action_voice_session events)
  2. Flushes remaining gateway event counters
  3. Stops the snapshot loop
  4. Stops REST usage tracking
  5. Detaches REST method patches
  6. Removes all event listeners from the client
  7. Removes process signal hooks (SIGINT, SIGTERM, beforeExit)
  8. Stops the transport flush timer
  9. Performs a final flush (up to 3 seconds)

Automatic Signal Handling

The SDK auto-registers handlers for SIGINT, SIGTERM, and beforeExit that trigger a flush with a 3-second timeout. After flushing, the signal is re-raised so the default termination behavior proceeds.

ts
// Usually you don't need to call shutdown() manually.
// The SDK handles SIGINT/SIGTERM automatically.

// But if you need to re-attach to a new client:
await analytics.shutdown();
const newAnalytics = createDicolytics(newClient, { apiKey: '...' });

Timer cleanup

All SDK timers use unref(), so the SDK never keeps your Node.js process alive. If all other work is done, the process exits naturally without needing to call shutdown().


DicolyticsOptions

The options object passed to createDicolytics or the Dicolytics constructor. See Configuration for detailed descriptions and examples of each option.

ts
interface DicolyticsOptions {
  apiKey: string;
  endpoint?: string;
  disabledEvents?: readonly string[];
  debug?: boolean;
  snapshotIntervalMs?: number;
  flushIntervalMs?: number;
  requestTimeoutMs?: number;
  clusterId?: number;
  autoCapturePromiseRejections?: boolean;
}
python
# create_dicolytics() keyword arguments
api_key: str                          # required
endpoint: str = "https://api.dicolytics.com"
disabled_events: list[str] = []
debug: bool = False
snapshot_interval_ms: int = 60000
flush_interval_ms: int = 5000
request_timeout_ms: int = 10000
cluster_id: int | None = None
auto_capture_exceptions: bool = False

Delivery Semantics

AspectBehavior
Event IDUUIDv7, minted at enqueue time. Stable across retries for server-side deduplication.
EnvelopePOST {endpoint}/v1/events with JSON body. Max 500 events / 1 MiB per batch.
Flush triggersAt 50 queued events or every 5 seconds. One in-flight request at a time (order-preserving).
Retry5xx / network errors: full-jitter exponential backoff, max 5 attempts.
429 handlingHonors Retry-After header (default 300s quiet period). heartbeat and guild_snapshot are exempt.
BufferMax 5,000 events. Oldest dropped first on overflow.
ShutdownUp to 3-second flush on SIGINT/SIGTERM, then re-raises the signal.
TimersAll timers use unref() -- the SDK never keeps your process alive.

See Transport & Retries for the complete transport specification.

Dicolytics — Discord bot analytics