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
});
| Option | Type | Required | Description |
|---|---|---|---|
baseUrl | string | Yes | The API base URL |
apiKey | string | Yes | Your ASCND API key |
interceptors | Interceptor[] | No | Custom 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:
| Field | Type | Required | Description |
|---|---|---|---|
leaderboardId | string | Yes | Target leaderboard ID |
playerId | string | Yes | Unique player identifier |
score | bigint | Yes | The score value |
metadata | Uint8Array | No | JSON data as bytes |
idempotencyKey | string | No | Prevents 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:
| Field | Type | Required | Description |
|---|---|---|---|
leaderboardId | string | Yes | Target leaderboard ID |
limit | number | No | Max entries (default: 10, max: 100) |
offset | number | No | Skip entries for pagination |
period | string | No | "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:
| Field | Type | Required | Description |
|---|---|---|---|
leaderboardId | string | Yes | Target leaderboard ID |
playerId | string | Yes | Player to look up |
period | string | No | "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';