Introduction

Welcome to the TONE3000 API. This RESTful API provides programmatic access to TONE3000 accounts, tones and models. To use the API, you or your users will need to authenticate via their TONE3000 account.

Note: This is version 1 of our API. Endpoints and data structures may change as we improve the platform.

Integration Options

All integration options use OAuth 2.0 with PKCE and issue the same access token, giving you full access to tone data, model files, and user libraries. Choose the flow that fits your product:

Select

Your users browse and pick a tone directly within TONE3000's interface. Zero auth UI or tone browser to build. Ideal for plugins, DAW integrations, and native apps.

Load Tone

Your app specifies a tone_id; TONE3000 authenticates the user and verifies access. If the tone is unavailable, the user can browse for a replacement. Ideal for applications where the TONE3000 tone ID is known in advance.

Full API Access

Authenticate via OAuth and use the access token to build any experience: custom tone browsers, library sync, in-app model management.

Explore all integration options in the example app repository.

Rate Limit

100 requests per minute by default. For production applications please email [email protected].

Support & Feedback

Questions, issues, or feedback? Contact [email protected] - we'd love to hear from you.

Authentication

API Keys

Generate your keys in settings. Your account has two types of keys:

Publishable Key

client_id

Identifies your application in OAuth flows. Safe to include in client-side code, mobile apps, and browser environments. Used as the client_id parameter on every authorization and token request.

OAuth Authorization Flow

Use the OAuth flow when your integration is user-facing. Your app redirects the user to TONE3000, they authorize and complete the flow, and TONE3000 redirects back with an authorization code. The prompt parameter controls which flow runs:

  • prompt omitted — standard authorization only
  • prompt=select_tone — user browses and selects a tone (see Select)
  • prompt=load_tone — verifies access to a specific tone_id (see Load Tone)

1. Redirect the user to TONE3000

Generate a PKCE code_verifier and derive the code_challenge (SHA-256, base64url-encoded). Store the code_verifier and state — you'll need them to complete the token exchange.

GET https://www.tone3000.com/api/v1/oauth/authorize

TONE3000 responds with a redirect — there is no response body to read. If the user isn't signed in, they'll be prompted to log in first. Once authenticated, TONE3000 completes the flow and redirects back to your redirect_uri.

ParameterRequiredDescription
client_idYesYour publishable key
redirect_uriYesWhere to return the user after the flow. If you've registered redirect URIs in settings, only those will be accepted
response_typeYesMust be code
code_challengeYesBase64url-encoded SHA-256 hash of your code_verifier
code_challenge_methodYesMust be S256
stateYesA random value you generate; returned in the callback to verify the response is legitimate
promptNoControls the flow type. See values above. Omit for standard authorization.
tone_idConditionalRequired when prompt=load_tone
gearsNoRestrict catalog by gear type. Applies to select_tone and load_tone flows. Separate multiple values with _ (e.g. amp_full-rig). See Gear
platformNoRestrict catalog to a specific model format. Applies to select_tone and load_tone flows. See Platform
const codeVerifier = crypto.randomUUID().replace(/-/g, '') + crypto.randomUUID().replace(/-/g, '');
const hash = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(codeVerifier));
const codeChallenge = btoa(String.fromCharCode(...new Uint8Array(hash)))
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
const state = crypto.randomUUID();
sessionStorage.setItem('t3k_code_verifier', codeVerifier);
sessionStorage.setItem('t3k_state', state);
const params = new URLSearchParams({
client_id: 'YOUR_PUBLISHABLE_KEY',
redirect_uri: 'https://your-app.com/callback',
response_type: 'code',
code_challenge: codeChallenge,
code_challenge_method: 'S256',
state,
// prompt: 'select_tone' | 'load_tone'
});
window.location.href = `https://www.tone3000.com/api/v1/oauth/authorize?${params}`;

2. Handle the callback

After the user completes the flow, TONE3000 redirects to your redirect_uri. Always verify state before proceeding — this protects against CSRF attacks.

Callback parameters

ParameterDescription
codeShort-lived authorization code to exchange for tokens
stateThe value you sent in Step 1 — verify this matches before proceeding
tone_idPresent on success for prompt=select_tone and prompt=load_tone flows
errorPresent on failure
const params = new URLSearchParams(window.location.search);
const code = params.get('code');
const state = params.get('state');
const error = params.get('error');
if (state !== sessionStorage.getItem('t3k_state')) {
throw new Error('State mismatch. Possible CSRF attack.');
}
if (error) {
// Handle error — e.g. error === 'access_denied'
return;
}

Token Exchange

Exchange the authorization code for an access token. Include the access token as a Bearer token on every subsequent API request.

POST https://www.tone3000.com/api/v1/oauth/token

Content-Type: application/x-www-form-urlencoded

Request body

FieldDescription
grant_typeMust be authorization_code
codeThe authorization code from the callback
code_verifierThe PKCE verifier you generated in Step 1
redirect_uriMust match the redirect_uri used in Step 1
client_idYour publishable key
const response = await fetch('https://www.tone3000.com/api/v1/oauth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'authorization_code',
code,
code_verifier: sessionStorage.getItem('t3k_code_verifier')!,
redirect_uri: 'https://your-app.com/callback',
client_id: 'YOUR_PUBLISHABLE_KEY',
}),
});
const { access_token, refresh_token, expires_in } = await response.json();

Response

FieldTypeDescription
access_tokenstringBearer token for API requests
refresh_tokenstringUse to get a new access token without re-authorization
token_typestringAlways bearer
expires_innumberSeconds until the access token expires
scopestringThe scope granted, as passed in the authorization request

Session Management

1. Store tokens

Store the access token, refresh token, and expiration time securely.

sessionStorage.setItem('t3k_access_token', access_token);
sessionStorage.setItem('t3k_refresh_token', refresh_token);
sessionStorage.setItem('t3k_expires_at', String(Date.now() + expires_in * 1000));

2. Make authenticated requests

Include the access token as a Bearer token in the Authorization header. Check expiration before each request and refresh proactively.

const expiresAt = parseInt(sessionStorage.getItem('t3k_expires_at') || '0');
if (Date.now() > expiresAt) await refreshTokens();
const response = await fetch('https://www.tone3000.com/api/v1/user', {
headers: { Authorization: `Bearer ${sessionStorage.getItem('t3k_access_token')}` },
});

3. Refresh the access token

When the access token expires, POST to the same token endpoint with grant_type=refresh_token.

Request body

FieldDescription
grant_typeMust be refresh_token
refresh_tokenThe refresh token from the previous token response
client_idYour publishable key
const response = await fetch('https://www.tone3000.com/api/v1/oauth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: sessionStorage.getItem('t3k_refresh_token')!,
client_id: 'YOUR_PUBLISHABLE_KEY',
}),
});
const { access_token, refresh_token, expires_in } = await response.json();
sessionStorage.setItem('t3k_access_token', access_token);
sessionStorage.setItem('t3k_refresh_token', refresh_token);
sessionStorage.setItem('t3k_expires_at', String(Date.now() + expires_in * 1000));

The response shape is the same as the initial token exchange.

4. Handle refresh failure

A 400 response with error: invalid_grant means the refresh token has expired. Clear stored tokens and restart the authorization flow. Refresh tokens are long-lived, but handle this gracefully so users aren't left in a broken state.

sessionStorage.removeItem('t3k_access_token');
sessionStorage.removeItem('t3k_refresh_token');
sessionStorage.removeItem('t3k_expires_at');
startAuthorization(); // restart from Step 1

Select

Select lets your users browse the TONE3000 catalog and pick a tone without you building any auth UI or tone browser. The user is redirected to TONE3000, signs in, browses, and selects a tone — then lands back in your app with an access token and the selected tone_id ready to use.

Step 1: Send the User to TONE3000

Build the authorization URL with prompt=select_tone. You can optionally restrict the catalog by gear type or platform so users only see tones relevant to your product. See Authentication for PKCE generation details.

Native apps: Use a deep link (e.g. yourapp://callback) as your redirect_uri and open the authorization URL in an in-app browser (SFSafariViewController on iOS, Chrome Custom Tabs on Android).

// PKCE generation: see Authentication section
const params = new URLSearchParams({
client_id: 'YOUR_CLIENT_ID',
redirect_uri: 'https://your-app.com/callback',
response_type: 'code',
code_challenge: codeChallenge,
code_challenge_method: 'S256',
state,
prompt: 'select_tone',
// gears: 'full-rig', // optional: amp, full-rig, pedal, outboard, ir
// gears: 'amp_full-rig', // optional: multiple values separated by _
// platform: 'nam', // optional: nam, aida-x, aa-snapshot, proteus, ir
// menubar: 'true', // optional: show navigation bar with back/forward/refresh/close
});
window.location.href = `https://www.tone3000.com/api/v1/oauth/authorize?${params}`;
ParameterDescription
promptMust be select_tone (required)
gearsRestrict the catalog to one or more gear types. Separate multiple values with _ (e.g. amp_full-rig). The gear filter will be locked to your selection (optional, see Gear)
platformRestrict the catalog to a specific model format. Only compatible tones will be shown (optional, see Platform)
menubarSet to true to show a navigation bar at the top of the TONE3000 experience with back, forward, refresh, and close buttons. Recommended for in-app browsers and popup windows (optional)

Step 2: The User Browses and Selects

TONE3000 handles sign-in, browsing, and selection. The user sees the full public catalog and their own private tones, filtered by any gear or platform constraints you specified. Once they tap a tone, TONE3000 redirects them back to your app.

Step 3: Handle the Callback

TONE3000 redirects to your redirect_uri with a code, state, and the tone_id the user selected. Verify state before proceeding.

If the menubar is enabled and the user clicks the close button, the redirect will include canceled=true instead of a tone_id. If the user had already signed in, a code is still included and can be exchanged for tokens. If the user closed before signing in, no code is present.

const params = new URLSearchParams(window.location.search);
const code = params.get('code');
const state = params.get('state');
const toneId = params.get('tone_id');
const canceled = params.get('canceled') === 'true';
if (state !== sessionStorage.getItem('t3k_state')) {
throw new Error('State mismatch. Possible CSRF attack.');
}
if (canceled) {
// User exited without selecting a tone.
// If code is present, you can still exchange it for tokens.
// If code is absent, the user closed before signing in.
return;
}

Step 4: Exchange the Code and Fetch the Tone

Exchange the authorization code for an access token (see Token Exchange), then use the tone_id from the callback to fetch tone metadata and model download URLs.

// Token exchange: see Authentication section
const { access_token } = await exchangeCode(code);
// Fetch tone metadata
const tone = await fetch(`https://www.tone3000.com/api/v1/tones/${toneId}`, {
headers: { Authorization: `Bearer ${access_token}` },
}).then(r => r.json());
// Fetch models — each has a model_url for downloading
const { data: models } = await fetch(
`https://www.tone3000.com/api/v1/models?tone_id=${toneId}`,
{ headers: { Authorization: `Bearer ${access_token}` } },
).then(r => r.json());

Load Tone

Load Tone is for apps that already know which tone they want. Your app passes a tone_id and TONE3000 handles authentication and access verification. If the tone is accessible, the user is redirected straight back to your app. If it's private or deleted, the user can browse for a replacement — your app receives the result either way. You can optionally pass gears and platform filters to scope the replacement browse view.

Step 1: Send the User to TONE3000

Build the authorization URL with prompt=load_tone and the tone_id you want to load. Optionally pass gears and platform to filter the replacement browse view if the tone is inaccessible. See Authentication for PKCE generation details.

// PKCE generation: see Authentication section
const params = new URLSearchParams({
client_id: 'YOUR_CLIENT_ID',
redirect_uri: 'https://your-app.com/callback',
response_type: 'code',
code_challenge: codeChallenge,
code_challenge_method: 'S256',
state,
prompt: 'load_tone',
tone_id: '42',
// gears: 'amp', // optional: filter replacement browse by gear type
// platform: 'nam', // optional: filter replacement browse by platform
// menubar: 'true', // optional: show navigation bar with back/forward/refresh/close
});
window.location.href = `https://www.tone3000.com/api/v1/oauth/authorize?${params}`;
ParameterDescription
promptMust be load_tone (required)
tone_idThe ID of the tone to load (required)
gearsRestrict the replacement browse view to one or more gear types. Separate multiple values with _ (e.g. amp_full-rig). Only applied when the user needs to browse for a replacement (optional, see Gear)
platformRestrict the replacement browse view to a specific model format. Only applied when the user needs to browse for a replacement (optional, see Platform)
menubarSet to true to show a navigation bar at the top of the TONE3000 experience with back, forward, refresh, and close buttons. Recommended for in-app browsers and popup windows (optional)

Step 2: TONE3000 Verifies Access

After sign-in, TONE3000 checks whether the user can access the requested tone: public tones, tones they own, and tones they've favorited all proceed immediately. If the tone is private or deleted, TONE3000 shows a friendly error page with the option to browse the catalog and pick a replacement. Any gears or platform filters you passed are applied to that replacement browse view.

When a user selects a replacement tone, the callback is the same as a successful load. The tone_id in the callback will be the newly selected tone, not the one you originally requested.

Step 3: Handle the Callback

TONE3000 redirects to your redirect_uri with a code, state, and the resolved tone_id. Always verify state before proceeding. Note that tone_id may differ from your original request if the user selected a replacement.

If the menubar is enabled and the user clicks the close button, the redirect will include canceled=true instead of a tone_id. If the user had already signed in, a code is still included and can be exchanged for tokens. If the user closed before signing in, no code is present.

const params = new URLSearchParams(window.location.search);
const code = params.get('code');
const state = params.get('state');
const toneId = params.get('tone_id'); // may differ from your original request
const canceled = params.get('canceled') === 'true';
if (state !== sessionStorage.getItem('t3k_state')) {
throw new Error('State mismatch. Possible CSRF attack.');
}
if (canceled) {
// User exited without loading a tone.
// If code is present, you can still exchange it for tokens.
// If code is absent, the user closed before signing in.
return;
}

Step 4: Exchange the Code and Fetch the Tone

Exchange the authorization code for an access token (see Token Exchange), then use the tone_id from the callback to fetch tone metadata and model download URLs.

// Token exchange: see Authentication section
const { access_token } = await exchangeCode(code);
// Fetch tone metadata
const tone = await fetch(`https://www.tone3000.com/api/v1/tones/${toneId}`, {
headers: { Authorization: `Bearer ${access_token}` },
}).then(r => r.json());
// Fetch models — each has a model_url for downloading
const { data: models } = await fetch(
`https://www.tone3000.com/api/v1/models?tone_id=${toneId}`,
{ headers: { Authorization: `Bearer ${access_token}` } },
).then(r => r.json());

User

Get information about the currently authenticated user.

const response = await fetch('https://www.tone3000.com/api/v1/user', {
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
}
});
const user = await response.json();

Users

Get a list of users with public content, sorted by various metrics.

// Get top users by tones count
const response = await fetch('https://www.tone3000.com/api/v1/users?sort=tones&page=1&page_size=10', {
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
}
});
const result = await response.json();
// Search for users with username containing "john"
const searchResponse = await fetch('https://www.tone3000.com/api/v1/users?query=john', {
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
}
});
const searchResult = await searchResponse.json();
  • Response type: PaginatedResponse<PublicUser[]>
  • Query parameters:
    NameTypeDescription
    sortUsersSortSort users by most stat (default: 'tones', optional)
    pagenumberPage number for pagination (default: 1, optional)
    page_sizenumberNumber of users per page (default: 10, max: 10, optional)
    querystringText search query to filter users by username (optional)

Tones

Created Tones

Get a list of tones created by the currently authenticated user.

const response = await fetch(`https://www.tone3000.com/api/v1/tones/created?page=${page}&page_size=${pageSize}`, {
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
}
});
const tones = await response.json();
  • Response type: PaginatedResponse<Tones[]>
  • Query parameters:
    NameTypeDescription
    pagenumberPage number for pagination (default: 1, optional)
    page_sizenumberNumber of items per page (default: 10, max: 100, optional)

Favorited Tones

Get a list of tones favorited by the currently authenticated user.

const response = await fetch(`https://www.tone3000.com/api/v1/tones/favorited?page=${page}&page_size=${pageSize}`, {
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
}
});
const tones = await response.json();
  • Response type: PaginatedResponse<Tones[]>
  • Query parameters:
    NameTypeDescription
    pagenumberPage number for pagination (default: 1, optional)
    page_sizenumberNumber of items per page (default: 10, max: 100, optional)

Get Tone

Get a single tone by ID. Public tones are accessible to any authenticated user. Private tones are only accessible to the owner or users who have favorited the tone.

const response = await fetch(`https://www.tone3000.com/api/v1/tones/${toneId}`, {
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
}
});
const tone = await response.json();
  • Response type: Tone
  • To get download URLs for a tone's models, use the List Models endpoint.
  • Path parameters:
    NameTypeDescription
    idnumberID of the tone to retrieve

Search Tones

Search and filter tones with various filters and sorting options.

This endpoint is heavily rate-limited by default. If you plan to use it in production, please contact [email protected]. We highly recommend using the Select OAuth flow for tone browsing and search rather than this endpoint.
const response = await fetch(`https://www.tone3000.com/api/v1/tones/search?query=${query}&page=${page}&page_size=${pageSize}&sort=${sort}&gears=${gears}&sizes=${sizes}`, {
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
}
});
const tones = await response.json();
  • Response type: PaginatedResponse<Tones[]>
  • Query parameters:
    NameTypeDescription
    querystringSearch query term (optional, default: empty string)
    pagenumberPage number for pagination (default: 1, optional)
    page_sizenumberNumber of items per page (default: 10, max: 25, optional)
    sortTonesSortSort order (default: 'best-match' if query provided, 'trending' otherwise)
    gearsGear[]Filter by gear type. Underscore-separated for multiple values (e.g. amp_pedal_ir) (optional)
    sizesSize[]Filter by model sizes. Underscore-separated for multiple values (e.g. standard_lite_feather) (optional)

Models

Get Model

Get a single model by ID. Accessible if the parent tone is public, owned by the authenticated user, or favorited by the authenticated user.

const response = await fetch(`https://www.tone3000.com/api/v1/models/${modelId}`, {
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
}
});
const model = await response.json();
  • Response type: Model
  • The model_url field is a pre-built download URL. Pass your access token as a Bearer token when fetching it.
  • Path parameters:
    NameTypeDescription
    idnumberID of the model to retrieve

List Models

Get a list of models for a specific tone accessible by the current authenticated user.

const response = await fetch(`https://www.tone3000.com/api/v1/models?tone_id=${toneId}&page=${page}&page_size=${pageSize}`, {
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
}
});
const models = await response.json();
  • Response type: PaginatedResponse<Model[]>
  • Query parameters:
    NameTypeDescription
    tone_idnumberID of the tone to get models for
    pagenumberPage number for pagination (default: 1, optional)
    page_sizenumberNumber of items per page (default: 10, max: 100, optional)

The model_url field can be used to download the model. It is only valid for tones that are accessible by the current authenticated user.

// Download model in browser
const downloadModel = async (modelUrl: string) => {
const response = await fetch(modelUrl, {
headers: {
'Authorization': `Bearer ${accessToken}`
}
});
if (!response.ok) {
throw new Error('Failed to download model');
}
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = modelUrl.split('/').pop() || 'model';
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
};
// Usage
await downloadModel(model.model_url);

Enums

Gear

In the search API, use underscore-separated values for multiple gear types (e.g. amp_pedal_ir). Comma-separated is deprecated.

enum Gear {
Amp = 'amp',
FullRig = 'full-rig',
Pedal = 'pedal',
Outboard = 'outboard',
Ir = 'ir'
}

Platforms

enum Platform {
Nam = 'nam',
Ir = 'ir',
AidaX = 'aida-x',
AaSnapshot = 'aa-snapshot',
Proteus = 'proteus'
}

Licenses

enum License {
T3k = 't3k',
CcBy = 'cc-by',
CcBySa = 'cc-by-sa',
CcByNc = 'cc-by-nc',
CcByNcSa = 'cc-by-nc-sa',
CcByNd = 'cc-by-nd',
CcByNcNd = 'cc-by-nc-nd',
Cco = 'cco'
}

Sizes

In the search API, use hyphen-separated values for multiple sizes (e.g. standard-lite-feather). Comma-separated is deprecated.

enum Size {
Standard = 'standard',
Lite = 'lite',
Feather = 'feather',
Nano = 'nano',
Custom = 'custom'
}

UsersSort

enum UsersSort {
Tones = 'tones',
Downloads = 'downloads',
Favorites = 'favorites',
Models = 'models'
}

TonesSort

enum TonesSort {
BestMatch = 'best-match',
Newest = 'newest',
Oldest = 'oldest',
Trending = 'trending',
DownloadsAllTime = 'downloads-all-time'
}

Types

Session

The response type for both session creation and refresh endpoints.

interface Session {
access_token: str;
refresh_token: str;
expires_in: number; // seconds until token expires
token_type: 'bearer';
}

EmbeddedUser

interface EmbeddedUser {
id: int;
username: str;
avatar_url: str | null;
url: str;
}

User

interface User extends EmbeddedUser {
bio: str | null;
links: str[] | null;
created_at: str;
updated_at: str;
}

PublicUser

Public user information with content counts, returned by the users endpoint.

interface PublicUser {
id: int;
username: str;
bio: str | null;
links: str[] | null;
avatar_url: str | null;
downloads_count: number;
favorites_count: number;
models_count: number;
tones_count: number;
url: str;
}

Make

interface Make {
id: number;
name: str;
}

Tag

interface Tag {
id: number;
name: str;
}

Paginated Response

interface PaginatedResponse<T> {
data: T[];
page: number;
page_size: number;
total: number;
total_pages: number;
}

Tone

interface Tone {
id: number;
user_id: int;
user: EmbeddedUser;
created_at: str;
updated_at: str;
title: str;
description: str | null;
gear: Gear;
images: str[] | null;
is_public: boolean | null;
links: str[] | null;
platform: Platform;
license: License;
sizes: Size[];
makes: Make[];
tags: Tag[];
models_count: number;
downloads_count: number;
favorites_count: number;
url: str;
}

Model

interface Model {
id: number;
created_at: str;
updated_at: str;
user_id: int;
model_url: str;
name: str;
size: Size;
tone_id: number;
}

Example App

The example repository contains self-contained demo apps — one for each integration flow — along with tone3000-client.ts, a zero-dependency helper that covers all OAuth flows and API endpoints.

Acme Inc — Select Flow

User browses the TONE3000 catalog and picks a tone. The app receives the selected tone_id and fetches tone metadata and model download URLs.

Beacon Inc — Load Tone Flow

App stores TONE3000 tone IDs in presets and loads them on demand. TONE3000 handles auth and access — if a tone is unavailable, the user can pick a replacement.

Chord Inc — Full API Integration

Reference implementation covering every documented endpoint: search, tone detail, user profile, favorites, model listings, and file downloads.

tone3000-client.ts

src/tone3000-client.ts is a zero-dependency integration helper included as inspiration for your own integration. It covers PKCE generation, OAuth flows, automatic token refresh, and authenticated requests via T3KClient.

import { startSelectFlow, handleOAuthCallback, T3KClient } from './tone3000-client';
// Start a flow
await startSelectFlow(PUBLISHABLE_KEY, REDIRECT_URI);
// Handle the callback
const result = await handleOAuthCallback(PUBLISHABLE_KEY, REDIRECT_URI);
if (result.ok) {
client.setTokens(result.tokens);
const { toneId } = result;
}
// Make authenticated API requests
const client = new T3KClient(PUBLISHABLE_KEY, () => {
startSelectFlow(PUBLISHABLE_KEY, REDIRECT_URI); // called when re-auth is needed
});
const tone = await client.getTone(toneId);
const { data: models } = await client.listModels(toneId);
await client.downloadModel(models[0].model_url, models[0].name);