Loading page
escF1F2F3F4F5F6F7F8F9F10F11F12
~`!1@2#3$4%5^6&7*8(9)0_-+=delete
tabQWERTYUIOP{[}]|\
caps lockASDFGHJKL:;"'return
shiftZXCVBNM<,>.?/shift
fn⌃control⌥option⌘command⌘command⌥option◀▲▼▶
“Bounce it, sleep on it, listen again tomorrow.”
Meet @adenspace/sdk: a type-safe TypeScript client for the Aden API. Learn how teams can integrate their music catalog into custom apps, and how we use it to power our own mobile and desktop clients.
Aden is a collaborative workspace for artists and music teams. We handle tracks, versions, albums, collaborators, splits, feedback, releases. Everything that used to live across Drive folders, WhatsApp threads, and spreadsheets. Today, we're opening that same data layer to developers with @adenspace/sdk.
As Aden grew, we kept hitting the same problem internally: our web app, mobile app, and desktop client all needed to talk to the same API, and each one was writing its own fetch wrappers, auth logic, and type definitions. Every time we added an endpoint, we had to update three codebases.
We also started hearing from teams who wanted to build on top of Aden. Custom dashboards for managers, automated release pipelines, integrations with DAWs, bots that notify collaborators. They needed a clean, documented API. We needed one too.
So we built @adenspace/sdk: and we use it ourselves.
The SDK is a single TypeScript package that gives you type-safe access to the entire Aden API. No code generation, no build step, no schema syncing. You install it, create a client, and start making calls with full autocomplete.
Under the hood, it uses Elysia Eden Treaty to derive types directly from our API definition. When we add a field to a track response, your editor knows about it the moment you update the package. When we add a query parameter, it shows up in autocomplete. When you pass the wrong type, it fails at compile time, not at runtime.
bun add @adenspace/sdk
# or
npm install @adenspace/sdk
There are two ways to authenticate: API keys for server-to-server integrations, and Supabase tokens for apps where users sign in.
Generate a team API key from your team settings in the Aden dashboard. Keys are prefixed with aden_live_ and scoped to a single team.
import { createAdenClient } from '@adenspace/sdk'
const Aden = createAdenClient({
apiKey: 'aden_live_xxx',
})
// List tracks for your team
const { data } = await aden.api.v1.tracks.get({
query: { teamId: '123' },
})
// Get a specific track with audio URL and cover art
const { data: track } = await aden.api.v1.tracks({ id: 1 }).get()
If you're building an app where users log in with their Aden account, pass a dynamic token provider instead:
import { createClient } from '@supabase/supabase-js'
import { createAdenClient } from '@adenspace/sdk'
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY)
const Aden = createAdenClient({
getToken: async () => {
const { data } = await supabase.auth.getSession()
return data.session?.access_token ?? null
},
})
// Now all calls are authenticated as the signed-in user
const { data: me } = await aden.api.v1.auth.me.get()
const { data: teams } = await aden.api.v1.teams.get()
const { data: tracks } = await aden.api.v1.tracks.get({
query: { teamId: '123', limit: '10' },
})
The getToken function is called on every request, so token refresh is handled automatically by Supabase.
The API covers the core Aden data model. Here's what's available today:
The central resource. A track goes through statuses: idea → in_progress → ready_to_be_released → released. Each track can have multiple versions, collaborators with split percentages, and cover art.
// List all tracks for a team
const { data } = await aden.api.v1.tracks.get({
query: { teamId: '42', limit: '20', offset: '0' },
})
// Each track includes:
// - audio_url (presigned, ready to stream)
// - cover_url / cover_url_medium (optimized sizes)
// - status, description, metadata
Group tracks into albums with ordering, cover art, and release metadata like UPC codes.
// List albums
const { data } = await aden.api.v1.albums.get({
query: { teamId: '42' },
})
// Get a single album with its full tracklist and audio URLs
const { data: album } = await aden.api.v1.albums({ id: 5 }).get()
Every track can have multiple versions. Rough demo, mix V2, final master. The SDK lets you list versions, get waveform data for visualization, and manage which version is the "final" cut.
// List all versions for a track
const { data } = await aden.api.v1.tracks({ id: 1 }).versions.get()
// Each version includes:
// - audio_url (presigned)
// - waveform (normalized float array for visualization)
// - version_number, lyrics, final flag
// Get timestamped comments on a specific version
const { data: comments } = await aden.api.v1
.tracks({ id: 1 })
.versions({ versionId: 7 })
.comments.get()
Create secure, expiring share links for tracks and albums. Control whether recipients can download files, leave comments, or see full metadata.
// Create a share link for a track
const { data } = await aden.api.v1.shares.tracks({ trackId: 1 }).post({
allowDownloads: true,
allowComments: true,
expire: '2026-03-01',
email: 'label@example.com',
title: 'New single - rough mix',
})
console.log(data.shareUrl)
// → https://aden.space/@yourteam/shares/tracks/1?token=...
This is not an SDK we built for other people and forgot about. It powers our own apps.
Our React Native mobile app imports @adenspace/sdk directly. The entire API layer is 13 lines:
// apps/mobile/src/lib/aden.ts
import { createAdenClient } from '@adenspace/sdk'
import { supabase } from './supabase'
import { API_BASE_URL } from './constants'
export const Aden = createAdenClient({
baseUrl: API_BASE_URL,
getToken: async () => {
const {
data: { session },
} = await supabase.auth.getSession()
return session?.access_token ?? null
},
})
That's it. No custom fetch wrapper, no separate type definitions, no API client to maintain. When we add a new endpoint to the API, the mobile app gets type-safe access to it by updating one package.
We built an internal playground app to test endpoints interactively. Plug in an API key or Supabase token, click a button, see the response. It's the fastest way to verify an endpoint works before writing any integration code.
Our Next.js web app calls the same API through server actions and cached queries. The API definition is the single source of truth. The SDK simply exposes it to any JavaScript or TypeScript environment.
Every request made with an API key is automatically tracked. We log the method, path, status code, and timestamp to give teams visibility into how their integrations are being used. Requests made with Supabase auth tokens (i.e., your own logged-in users) are not tracked.
This means you can generate API keys for different integrations and see exactly which ones are active, how often they're called, and when they were last used.
For those curious about the internals: the API is built with Elysia, a Bun-native framework. Elysia uses TypeBox schemas for runtime validation, and those same schemas drive the TypeScript types that Eden Treaty extracts.
The flow looks like this:
App type is exported from the API packagetreaty<App>()No codegen. No OpenAPI-to-TypeScript pipeline. Just TypeScript doing what TypeScript does.
We also serve an interactive OpenAPI spec at /api/v1/swagger for teams that prefer REST clients like Postman or Insomnia.
This is the first public release of the SDK. We're actively expanding the API surface:
Install the SDK, grab an API key from your team settings, and start building.
bun add @adenspace/sdk
import { createAdenClient } from '@adenspace/sdk'
const Aden = createAdenClient({ apiKey: 'aden_live_xxx' })
const { data } = await aden.api.v1.tracks.get({ query: { teamId: '123' } })
console.log(data)
The full API reference is in the SDK README and the OpenAPI spec is live at aden.space/api/v1/swagger. If you run into anything, open an issue. We're building this in the open.