Skip to main content

TypeScript SDK

The official ASCND TypeScript client provides type-safe access to the leaderboard API.

Installation

npm install @ascnd/client

Or with other package managers:

yarn add @ascnd/client
pnpm add @ascnd/client

Quick Start

import { createAscndClient } from '@ascnd/client';

const client = createAscndClient({
baseUrl: 'https://api.ascnd.gg',
apiKey: process.env.ASCND_API_KEY!,
});

// Submit a score
const result = await client.submitScore({
leaderboardId: 'lb_xxx',
playerId: 'player_123',
score: 50000n,
});

console.log(`Rank: #${result.rank}`);

API Reference

createAscndClient

Creates a new client instance.

import { createAscndClient } from '@ascnd/client';

const client = createAscndClient({
baseUrl: 'https://api.ascnd.gg',
apiKey: 'your_api_key',
interceptors: [], // Optional custom interceptors
});
OptionTypeRequiredDescription
baseUrlstringYesThe API base URL
apiKeystringYesYour ASCND API key
interceptorsInterceptor[]NoCustom Connect interceptors

client.submitScore

Records a player's score on a leaderboard.

const result = await client.submitScore({
leaderboardId: 'lb_xxx',
playerId: 'player_123',
score: 50000n,
metadata: new TextEncoder().encode(JSON.stringify({ level: 5 })),
idempotencyKey: 'session_abc_final',
});

Parameters:

FieldTypeRequiredDescription
leaderboardIdstringYesTarget leaderboard ID
playerIdstringYesUnique player identifier
scorebigintYesThe score value
metadataUint8ArrayNoJSON data as bytes
idempotencyKeystringNoPrevents duplicate submissions

Returns:

{
scoreId: string; // Unique score ID
rank: number; // Player's new rank
isNewBest: boolean; // Beat previous best?
wasDeduplicated: boolean; // Was this a duplicate?
}

client.getLeaderboard

Retrieves the top scores for a leaderboard.

const leaderboard = await client.getLeaderboard({
leaderboardId: 'lb_xxx',
limit: 10,
offset: 0,
period: 'current',
});

for (const entry of leaderboard.entries) {
console.log(`#${entry.rank} - ${entry.playerId}: ${entry.score}`);
}

Parameters:

FieldTypeRequiredDescription
leaderboardIdstringYesTarget leaderboard ID
limitnumberNoMax entries (default: 10, max: 100)
offsetnumberNoSkip entries for pagination
periodstringNo"current", "previous", or ISO timestamp

Returns:

{
entries: Array<{
rank: number;
playerId: string;
score: bigint;
submittedAt: string;
metadata?: Uint8Array;
}>;
totalEntries: number;
hasMore: boolean;
periodStart: string;
periodEnd?: string;
}

client.getPlayerRank

Gets a specific player's rank information.

const playerRank = await client.getPlayerRank({
leaderboardId: 'lb_xxx',
playerId: 'player_123',
});

if (playerRank.rank) {
console.log(`Rank: #${playerRank.rank} (${playerRank.percentile})`);
}

Parameters:

FieldTypeRequiredDescription
leaderboardIdstringYesTarget leaderboard ID
playerIdstringYesPlayer to look up
periodstringNo"current", "previous", or ISO timestamp

Returns:

{
rank?: number; // Player's rank (undefined if not on board)
score?: bigint; // Current score
bestScore?: bigint; // Best score this period
totalEntries: number; // Total players on leaderboard
percentile?: string; // e.g., "top 5%"
}

Working with BigInt

Scores use JavaScript's bigint type to handle large numbers precisely:

// Use the 'n' suffix for bigint literals
const score = 50000n;

// Or convert from number
const scoreFromNumber = BigInt(playerScore);

// Convert back to number (if within safe range)
const displayScore = Number(result.score);

Working with Metadata

Store additional data with scores using the metadata field:

// Encoding metadata
const metadata = new TextEncoder().encode(JSON.stringify({
level: 5,
character: 'warrior',
time: 120.5,
}));

await client.submitScore({
leaderboardId: 'lb_xxx',
playerId: 'player_123',
score: 50000n,
metadata,
});

// Decoding metadata from leaderboard entries
const leaderboard = await client.getLeaderboard({ leaderboardId: 'lb_xxx' });

for (const entry of leaderboard.entries) {
if (entry.metadata) {
const data = JSON.parse(new TextDecoder().decode(entry.metadata));
console.log(`Level: ${data.level}`);
}
}

Error Handling

The client throws ConnectError for API errors:

import { ConnectError } from '@connectrpc/connect';

try {
await client.submitScore({
leaderboardId: 'invalid_id',
playerId: 'player_123',
score: 50000n,
});
} catch (error) {
if (error instanceof ConnectError) {
console.error(`API Error: ${error.message}`);
console.error(`Code: ${error.code}`);
}
}

Environment Variables

We recommend storing your API key in environment variables:

// .env
ASCND_API_KEY=rkd_live_xxxxxxxxxxxxx

// Usage
const client = createAscndClient({
baseUrl: 'https://api.ascnd.gg',
apiKey: process.env.ASCND_API_KEY!,
});

Server-Side Usage

The SDK works in Node.js, Deno, Bun, and edge runtimes:

// Next.js API Route
import { createAscndClient } from '@ascnd/client';

const client = createAscndClient({
baseUrl: 'https://api.ascnd.gg',
apiKey: process.env.ASCND_API_KEY!,
});

export async function POST(request: Request) {
const { playerId, score } = await request.json();

const result = await client.submitScore({
leaderboardId: process.env.LEADERBOARD_ID!,
playerId,
score: BigInt(score),
});

return Response.json({ rank: result.rank });
}

Type Exports

The SDK exports all TypeScript types for full type safety:

import {
createAscndClient,
type AscndClient,
type AscndClientOptions,
type SubmitScoreRequest,
type SubmitScoreResponse,
type GetLeaderboardRequest,
type GetLeaderboardResponse,
type GetPlayerRankRequest,
type GetPlayerRankResponse,
type LeaderboardEntry,
} from '@ascnd/client';