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 support@tone3000.com.
Support & Feedback
Questions, issues, or feedback? Contact support@tone3000.com - we'd love to hear from you.
Terms
Use of the TONE3000 API is governed by our API Terms of Service, Design Requirements, and Commercial Terms. Please review and follow these requirements as you explore the API.
Authentication
API Keys
Generate your keys in settings. Your account has two types of keys:
Publishable Key
client_idIdentifies 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. The publishable key is your durable application identifier; it is preserved across secret revocation and regeneration so embedded devices using it keep working.
Secret Key
t3k_cs_…Server-only Bearer credential. Treat it like a database password: never embed it in client-side code, mobile binaries, or any environment that reaches a user's device. Pass it as Authorization: Bearer t3k_cs_… on direct API calls. You can revoke or regenerate the secret in settings at any time without affecting OAuth flows or your publishable key. To fully retire an integration (taking the publishable key offline), contact support.
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, so 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.
| Parameter | Required | Description |
|---|---|---|
client_id | Yes | Your publishable key |
redirect_uri | Yes | Where to return the user after the flow. If you've registered redirect URIs in settings, only those will be accepted |
response_type | Yes | Must be code |
code_challenge | Yes | Base64url-encoded SHA-256 hash of your code_verifier |
code_challenge_method | Yes | Must be S256 |
state | Yes | A random value you generate; returned in the callback to verify the response is legitimate |
login_hint | No | Optional. Hints which email address to pre-fill on the login screen. Malformed or overlong values are ignored (the flow still continues). |
prompt | No | Controls the flow type. See values above. Omit for standard authorization. |
tone_id | Conditional | Required when prompt=load_tone |
gears | No | Restrict catalog by gear type. Applies to select_tone and load_tone flows. Separate multiple values with _ (e.g. amp_amp-cab). full-rig and ir are deprecated alias values; see Deprecated Fields. See also Gear. |
format | No | Restrict catalog to a specific model format. Applies to select_tone and load_tone flows. platform is a deprecated alias; see Deprecated Fields. See also Format. |
architecture | No | Filter by model architecture. Accepts 1 (A1), 2 (A2), or custom. Omitting falls back to A1 + Custom (legacy default; excludes A2). Applies to select_tone and load_tone flows. |
calibrated | No | Pass true to lock the catalog to tones that have at least one calibrated model. Applies to select_tone and load_tone flows. |
menubar | No | Set to true to show a navigation bar at the top of the TONE3000 experience with back, forward, refresh, and close buttons. Applies to every flow, including the sign-in page shown when prompt is omitted. Recommended for in-app browsers and popup windows. |
preview | No | Pass true to render audition players in the flow, so users can hear a tone before selecting it. Players appear on the search results, creator profiles, and the tone detail page. Playback is demo-only (no live input), and requires a browser or webview with SharedArrayBuffer — where that's unavailable the players are omitted and the rest of the flow is unaffected. Applies to the select_tone flow. |
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,// login_hint: 'user@example.com', // optional: pre-fill login form (OpenID / OAuth)// prompt: 'select_tone' | 'load_tone'// architecture: '2', // optional: 1 | 2 | 'custom'. Omit for legacy A1 + Custom. Applies to select_tone and load_tone.// calibrated: 'true', // optional: lock to tones with a calibrated model. Applies to select_tone and load_tone.// menubar: 'true', // optional: show navigation bar with back/forward/refresh/close// preview: 'true', // optional: audition players in the flow (demo-only playback). Applies to select_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
| Parameter | Description |
|---|---|
code | Short-lived authorization code to exchange for tokens |
state | The value you sent in Step 1; verify this matches before proceeding |
tone_id | Present on success for prompt=select_tone and prompt=load_tone flows |
error | Present 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
| Field | Description |
|---|---|
grant_type | Must be authorization_code |
code | The authorization code from the callback |
code_verifier | The PKCE verifier you generated in Step 1 |
redirect_uri | Must match the redirect_uri used in Step 1 |
client_id | Your 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
| Field | Type | Description |
|---|---|---|
access_token | string | Bearer token for API requests |
refresh_token | string | Use to get a new access token without re-authorization |
token_type | string | Always bearer |
expires_in | number | Seconds until the access token expires |
scope | string | The 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
| Field | Description |
|---|---|
grant_type | Must be refresh_token |
refresh_token | The refresh token from the previous token response |
client_id | Your 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 format 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 sectionconst 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: 'amp-cab', // optional: amp-cab, amp, cab, pedal, outboard, space, experimental// gears: 'amp_amp-cab', // optional: multiple values separated by _// format: 'nam', // optional: nam, aida-x, aa-snapshot, proteus, ir// architecture: '2', // optional: 1 | 2 | 'custom'. Omit for legacy A1 + Custom.// menubar: 'true', // optional: show navigation bar with back/forward/refresh/close// preview: 'true', // optional: audition players in the flow (demo-only playback)// login_hint: 'user@example.com', // optional: pre-fill sign-in email (see Authentication)});window.location.href = `https://www.tone3000.com/api/v1/oauth/authorize?${params}`;
| Parameter | Description |
|---|---|
prompt | Must be select_tone (required) |
gears | Restrict the catalog to one or more gear types. Separate multiple values with _ (e.g. amp_amp-cab). The gear filter will be locked to your selection. full-rig and ir are deprecated alias values; see Deprecated Fields (optional, see also Gear) |
format | Restrict the catalog to a specific model format. Only compatible tones will be shown. platform is a deprecated alias; see Deprecated Fields (optional, see also Format) |
architecture | Filter the catalog by model architecture. Accepts 1 (A1), 2 (A2), or custom. Only tones with at least one model in the selected architecture are shown. Omitting returns the legacy A1 + Custom set (excludes A2) so API users that pre-date A2 don't receive models they can't load. (optional) |
menubar | Set 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) |
preview | Set to true to render audition players in the flow so users can hear a tone before selecting it. Players appear on the search results, creator profiles, and the tone detail page. Playback is demo-only — there is no live-input mode, since that isn't available inside an embedded webview. Requires a browser or webview that supports SharedArrayBuffer; where it's unavailable the players are omitted and the rest of the flow works normally (optional) |
login_hint | Optional. Pre-fills the email field if the user must sign in. Same rules as Authentication; malformed or overlong values are ignored. |
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 format 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 sectionconst { access_token } = await exchangeCode(code);// Fetch tone metadataconst 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 downloadingconst { 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, and your app receives the result either way. You can optionally pass gears and format 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 format to filter the replacement browse view if the tone is inaccessible. See Authentication for PKCE generation details.
// PKCE generation: see Authentication sectionconst 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// format: 'nam', // optional: filter replacement browse by format// architecture: '2', // optional: 1 | 2 | 'custom' (replacement browse). Omit for legacy A1 + Custom.// menubar: 'true', // optional: show navigation bar with back/forward/refresh/close// login_hint: 'user@example.com', // optional: pre-fill sign-in email (see Authentication)});window.location.href = `https://www.tone3000.com/api/v1/oauth/authorize?${params}`;
| Parameter | Description |
|---|---|
prompt | Must be load_tone (required) |
tone_id | The ID of the tone to load (required) |
gears | Restrict the replacement browse view to one or more gear types. Separate multiple values with _ (e.g. amp_amp-cab). Only applied when the user needs to browse for a replacement. full-rig and ir are deprecated alias values; see Deprecated Fields (optional, see also Gear) |
format | Restrict the replacement browse view to a specific model format. Only applied when the user needs to browse for a replacement. platform is a deprecated alias; see Deprecated Fields (optional, see also Format) |
architecture | Filter the replacement browse view by model architecture. Accepts 1 (A1), 2 (A2), or custom. Omitting falls back to A1 + Custom (legacy default; excludes A2). Only applied when the user needs to browse for a replacement. (optional) |
menubar | Set 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) |
login_hint | Optional. Pre-fills the email field if the user must sign in. Same rules as Authentication; malformed or overlong values are ignored. |
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 format 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 requestconst 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 sectionconst { access_token } = await exchangeCode(code);// Fetch tone metadataconst 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 downloadingconst { 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();
- Response type: User
Users
Get a list of users with public content, sorted by various metrics.
// Get top users by tones countconst 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:
Name Type Description sortUsersSort Sort users by most stat (default: 'tones', optional) pagenumber Page number for pagination (default: 1, optional) page_sizenumber Number of users per page (default: 10, max: 10, optional) querystring Text search query to filter users by username (optional)
Tones
Created Tones
Get a list of tones created by the currently authenticated user. Optionally filter by gear type.
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:
Name Type Description pagenumber Page number for pagination (default: 1, optional) page_sizenumber Number of items per page (default: 10, max: 100, optional) gearGear Optional. Gear type to filter by, e.g. amp-cab. When omitted, returns tones of all gear types. A single value
Favorited Tones
Get a list of tones favorited by the currently authenticated user. Optionally filter by gear type.
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:
Name Type Description pagenumber Page number for pagination (default: 1, optional) page_sizenumber Number of items per page (default: 10, max: 100, optional) gearGear Optional. Gear type to filter by, e.g. amp-cab. When omitted, returns tones of all gear types. A single value
Downloaded Tones
Get a list of tones downloaded by the currently authenticated user. Duplicate downloads of the same tone are collapsed, so each tone appears once. Optionally filter by gear type.
const response = await fetch(`https://www.tone3000.com/api/v1/tones/downloaded?page=${page}&page_size=${pageSize}`, {headers: {'Authorization': `Bearer ${accessToken}`,'Content-Type': 'application/json'}});const tones = await response.json();
- Response type: PaginatedResponse<Tones[]>
- Query parameters:
Name Type Description pagenumber Page number for pagination (default: 1, optional) page_sizenumber Number of items per page (default: 10, max: 100, optional) gearGear Optional. Gear type to filter by, e.g. amp-cab. When omitted, returns tones of all gear types. A single value
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.
// architecture is optional: 1 | 2 | 'custom'. Omit for legacy A1 + Custom.const response = await fetch(`https://www.tone3000.com/api/v1/tones/${toneId}?architecture=${architecture}`, {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:
Name Type Description idnumber ID of the tone to retrieve - Query parameters:
Name Type Description architectureArchitecture Filter the tone's models_countby architecture. Accepts1(A1),2(A2), orcustom. Omitting returns A1 + Custom (legacy default; excludes A2). Per-architecture breakdown columns (a1_models_count,a2_models_count,custom_models_count) are always returned so you can compute your own visible count. Applies to NAM tones only; ignored for other formats. Use the List Models endpoint with the same parameter to fetch the matching model objects. (optional)
Favorite Tone
Favorite a tone on behalf of the authenticated user. Idempotent: returns 200 with the favorite whether it was just created or already existed. Only public tones or tones you own can be favorited.
const response = await fetch(`https://www.tone3000.com/api/v1/tones/${toneId}/favorite`, {method: 'PUT',headers: {'Authorization': `Bearer ${accessToken}`,'Content-Type': 'application/json'}});const favorite = await response.json();
- Response type: Favorite (status
200) - Path parameters:
Name Type Description idnumber ID of the tone to favorite
Unfavorite Tone
Remove a tone from the authenticated user's favorites. Idempotent: returns 204 No Content whether or not the tone was favorited.
await fetch(`https://www.tone3000.com/api/v1/tones/${toneId}/favorite`, {method: 'DELETE',headers: {'Authorization': `Bearer ${accessToken}`}});
- Returns
204 No Contentwith no response body. - Path parameters:
Name Type Description idnumber ID of the tone to unfavorite
Search Tones
Search and filter tones with various filters and sorting options.
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}&tags=${tags}&makes=${makes}&creators=${creators}&format=${format}&architecture=${architecture}`, {headers: {'Authorization': `Bearer ${accessToken}`,'Content-Type': 'application/json'}});const tones = await response.json();
- Response type: PaginatedResponse<Tones[]>
- Query parameters:
Name Type Description querystring Search query term (optional, default: empty string) pagenumber Page number for pagination (default: 1, optional) page_sizenumber Number of items per page (default: 10, max: 25, optional) sortTonesSort Sort order (default: 'best-match' if query provided, 'trending' otherwise) gearsGear[] Filter by gear type. Underscore-separated for multiple values (e.g. amp_amp-cab_pedal).full-rigandirare deprecated alias values; see Deprecated Fields (optional)sizesSize[] Filter by model sizes. Underscore-separated for multiple values (e.g. standard_lite_feather) (optional)tagsstring[] Filter by tag name, matched exactly against the values in a tone's tagsarray. Underscore-separated for multiple values (e.g.clean_high-gain). Multiple values are OR'd: a tone matches if it carries any of them (optional)makesstring[] Filter by make and model name, matched exactly against the values in a tone's makesarray. Underscore-separated for multiple values (e.g.Fender Twin Reverb_1965 Vox AC30). Multiple values are OR'd (optional)creatorsstring[] Filter by creator, matched exactly against a tone's user.username. Comma-separated for multiple values (e.g.tone3000,amalgamaudio), not underscore-separated, because usernames may themselves contain_and-. Multiple values are OR'd (optional)formatFormat Restrict results to a single tone format (e.g. nam,ir).platformis a deprecated alias; see Deprecated Fields (optional; omit for all formats)architectureArchitecture Filter results by model architecture. Accepts 1(A1),2(A2), orcustom. Omitting returns tones with A1 or Custom models (legacy default; excludes A2-only tones). (optional)calibratedboolean Restrict results to tones that have at least one calibrated model. Pass trueto enable (optional; default returns all)
Trending Tones
Get the top 10 trending tones, the same feed as the trending lanes on the TONE3000 homepage. Optionally filter by gear type. Not paginated; always returns at most 10 tones, sorted by trending score.
// Top trending across all gear typesconst response = await fetch('https://www.tone3000.com/api/v1/tones/trending', {headers: {'Authorization': `Bearer ${accessToken}`,'Content-Type': 'application/json'}});// Or filter by gear: /api/v1/tones/trending?gear=${gear}const { data: tones } = await response.json();
- Response type:
{ data:Tones[] } - Only public tones with at least one downloadable model, an image, and a description are included.
- Query parameters:
Name Type Description gearGear Optional. Gear type to filter the trending feed, e.g. amp-cab. When omitted, returns the top 10 trending tones across all gear types. A single value
Latest Tones
Get the 10 most recently published tones, the same feed as the latest section on the TONE3000 homepage. Not paginated; always returns at most 10 tones, sorted by publish date descending.
const response = await fetch('https://www.tone3000.com/api/v1/tones/latest', {headers: {'Authorization': `Bearer ${accessToken}`,'Content-Type': 'application/json'}});const { data: tones } = await response.json();
- Response type:
{ data:Tones[] } - Covers
namandirtones. Only public tones with at least one downloadable model, an image, and a description are included. - No query parameters.
Download Tone
403. For nearly all integrations, download individual models via the model_url field from List Models instead: it gives you per-model control and works for every API client. If your use case genuinely requires whole-tone zip archives, contact support@tone3000.com.Get a download URL for a zip archive containing all of a tone's models. 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}/download`, {headers: {'Authorization': `Bearer ${accessToken}`,'Content-Type': 'application/json'}});const { url, expires_at, filename } = await response.json();// url is a temporary link to the zip archive, no auth header neededconst zip = await fetch(url);
- Response fields:
Name Type Description urlstring Temporary download URL for the zip archive. Requires no authentication and expires one hour after being issued. Request it when the user initiates a download rather than storing it. expires_atstring ISO 8601 timestamp at which urlstops workingfilenamestring Suggested filename for the archive, derived from the tone title (e.g. My Tone.zip) - The archive contains every model in the tone, named after the model (deduplicated with a numeric suffix where needed). These are the same files and names as downloading the tone on tone3000.com.
- For tones with format
nam, the archive contains A2 files only. Other formats include all of the tone's models. - Archives are cached server-side and rebuilt automatically when the tone's models change, so the first request after a change may take a few seconds while subsequent requests are fast.
- Returns
400if the tone has no downloadable models. - Path parameters:
Name Type Description idnumber ID of the tone to download
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:
Name Type Description idnumber ID of the model to retrieve
List Models
Get a list of models for a specific tone accessible by the current authenticated user.
// architecture is optional: 1 | 2 | 'custom'. Omit for legacy A1 + Custom.const response = await fetch(`https://www.tone3000.com/api/v1/models?tone_id=${toneId}&page=${page}&page_size=${pageSize}&architecture=${architecture}`, {headers: {'Authorization': `Bearer ${accessToken}`,'Content-Type': 'application/json'}});const models = await response.json();
- Response type: PaginatedResponse<Model[]>
- Query parameters:
Name Type Description tone_idnumber ID of the tone to get models for pagenumber Page number for pagination (default: 1, optional) page_sizenumber Number of items per page (default: 10, max: 300, optional) architectureArchitecture Filter by model architecture. Accepts 1(A1),2(A2), orcustom. Omitting the parameter returns A1 + Custom (legacy default; excludes A2).
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 browserconst 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);};// Usageawait downloadModel(model.model_url);
Enums
Gear
In the search API, use underscore-separated values for multiple gear types (e.g. amp_amp-cab_pedal). Comma-separated is deprecated. Two value-level deprecations to be aware of (see Deprecated Fields): full-rig is accepted as an alias for amp-cab and is normalized on persist; ir as a Gear is stripped from gears on persist andformat=ir is inferred when no format was supplied (explicit format wins).
enum Gear {Amp = 'amp',AmpCab = 'amp-cab',Pedal = 'pedal',Outboard = 'outboard',Cab = 'cab',Space = 'space',Experimental = 'experimental',// Deprecated: accepted on input, normalized on persist.FullRig = 'full-rig', // alias for AmpCabIr = 'ir', // stripped from gears; use Format=ir}
Architecture
Model architecture version. Values are the strings '1' (A1), '2' (A2), or 'custom'. Omitting the query parameter falls back to A1 + Custom (the legacy default before A2 was introduced).
enum Architecture {A1 = '1',A2 = '2',Custom = 'custom'}
Format
enum Format {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'}
Deprecated Fields
These query parameters and field values are accepted permanently for backwards compatibility but are normalized on persist. Responses to deprecated requests carry the following headers so SDKs can flag them without inspecting bodies:
Deprecation: true(per RFC 8594)Link: <…>; rel="deprecation"pointing to this sectionX-Tone3000-Deprecations:comma-separated list of the specific kinds that fired (e.g.legacy_platform_key,legacy_ir_gear_value)
| Field | Replacement | Endpoints | Notes |
|---|---|---|---|
?platform=… | ?format=… | All endpoints that accept Format | Accepted permanently as a deprecated alias. The response always emits format; the platform field is no longer included in response bodies. |
?gear=… | ?gears=… | Search, Select, Load Tone | Singular ?gear= is accepted but rewritten to ?gears=. On the /search page, a 308 redirect rewrites the URL to canonical form. |
gears=ir | format=ir | Search, Select, Load Tone | The ir Gear value is deprecated. Any ir token is removed from the gears filter before the query runs (or on persist, for the OAuth Select / Load Tone flows). When no format is supplied, format=ir is inferred as the intent. An explicit format always wins. |
gears=full-rig | gears=amp-cab | Search, Select, Load Tone | Accepted as a permanent deprecated alias. The runtime treats full-rig and amp-cab as the same logical Gear: responses always emit amp-cab, and the search filter is symmetric (querying either returns rows of both during the transition window). |
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 expirestoken_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;}
Favorite
interface Favorite {id: number;tone_id: number;user_id: string;created_at: string;}
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;format: Format;license: License;sizes: Size[];makes: Make[];tags: Tag[];models_count: number;a1_models_count: number;a2_models_count: number;irs_count: number;custom_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;// Architecture for NAM models; null for non-NAM (e.g. IR).architecture_version: Architecture | null;}
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 flowawait startSelectFlow(PUBLISHABLE_KEY, REDIRECT_URI);// Handle the callbackconst result = await handleOAuthCallback(PUBLISHABLE_KEY, REDIRECT_URI);if (result.ok) {client.setTokens(result.tokens);const { toneId } = result;}// Make authenticated API requestsconst 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);
Design Requirements
Every TONE3000 integration follows these core design requirements. They keep the experience consistent for users, protect creator attribution, and make integration sign-off fast. The wireframes below show reference layouts for desktop and mobile. They are illustrative, so match them to your product's own design language and replace the placeholder content (Your Logo, Gear title, @username) with real integration content.
1Entry point
The TONE3000 entry point is associated with adding a tone to a signal block. Users reach TONE3000 from the place where they add or change a tone in their signal chain.
2Partnership splash
If the user has not authenticated, show a splash screen introducing the partnership and its value before starting the sign-in flow. Recommended copy:
“[Your brand] has partnered with TONE3000 to give you access to a massive library of Neural Amp Modeler (NAM) captures and IRs of real analog gear, created by a global community of musicians.”
[Your brand] has partnered with TONE3000 to give you access to a massive library of Neural Amp Modeler (NAM) captures and IRs of real analog gear, created by a global community of musicians.
Continue[Your brand] has partnered with TONE3000 to give you access to a massive library of NAM captures and IRs of real analog gear, created by a global community of musicians.
Continue3Authentication
After the user hits Continue on the partnership splash, they must authenticate via the TONE3000 Select flow: they enter their email and receive a magic link to sign in.
4Tone list views
After the user authenticates, a tone list view is the first view they should see. Any list or grid of TONE3000 tones includes, for each tone:
- Tone image
- Gear title
- Gear type (e.g. Amp, Amp + Cab)
- Format (NAM or IR)
- Creator (username and avatar)
- TONE3000 branding on the view
The view also shows the signed-in user's avatar and username, so it's clear they are logged in. Tabs for Favorites, Created, and Downloaded, all available via the CRUD API, let users quickly reach their favorite and own tones. You can also query and render trending and latest tones from the API to help users discover new tones.
- Where you show a partial list (e.g. suggested or recent tones), include a card or link inviting users to discover more on TONE3000.
- Always provide a clear, persistent path to browse the full TONE3000 catalog via the Select flow. Suggested and recent tones are a starting point, not a substitute: from anywhere a user sees TONE3000 tones, they can reach the complete library, e.g. the persistent “Browse TONE3000” button shown in the wireframe below.
5Loaded tones in the signal chain
A tone loaded from TONE3000 displays the tone image and the TONE3000 mark within its signal block. Where space allows, also display the tone pack title, gear type, format, and creator.
6Tone details
Tapping a tone from a list view or a signal block opens a tone details view. This view includes:
- Tone pack image
- Tone pack title
- Gear type (e.g. Amp, Amp + Cab)
- Format (NAM or IR)
- Creator (username and avatar)
- Model selector
Users should be able to easily switch between models within a tone pack from this view. Where space allows, also display the creator's description.
Creator's own description of the capture rig, character, and suggested use, shown where space allows… Read more
Creator's description of the capture rig, character, and suggested use, a few lines truncated where space is tight… Read more
7Logo usage
- Users must see the full TONE3000 logo before they see the shorter T3K mark, so they have context for what T3K means.
- Use the TONE3000 logo at entry points, anywhere users might access TONE3000, such as the partnership splash and tone list views.
- Use the T3K mark in compact placements: in buttons that open the TONE3000 library via the Select flow from a list view, or as a marker on a signal-chain block so users understand the origin of the tone in that block.
Entry points and first exposure:
partnership splash, list view headers
Compact placements: buttons,
signal-block markers
Download TONE3000 Logos for the official logo and mark assets to use in your integration.
Commercial Terms
Evaluating and developing against the API is free for everyone. Shipping a product falls into one of two tiers:
Free
You may build and publish TONE3000-enabled products for free if your product is non-commercial. This means free and open software or hardware with no paid product, or upsell. Examples include open source projects, DIY guitar pedals, research projects, and community tools. Free tier integrations may only use the OAuth prompt flows (select_tone and get_tone) and bounded list endpoints (favorited, downloaded, created, trending and latest). TONE3000 may, at its discretion, provide design, engineering, or promotional support for outstanding free integrations.
Commercial
If you charge for your product, or your product promotes or accompanies a paid product, a commercial agreement is required. Commercial products may use the full API, including all CRUD endpoints. Contact us at support@tone3000.com to learn more.
Commercial integrations must be reviewed and signed off by TONE3000 before they are publicly published or announced. Free tier integrations do not require sign-off, but access may be revoked if the integration does not follow guidelines.