From 252a1b2746fa7f52b50ac2f86e8461f290d59e79 Mon Sep 17 00:00:00 2001 From: Raymond Jacobson Date: Tue, 31 Mar 2026 14:34:14 -0700 Subject: [PATCH 1/4] Attach api_key and app_name to SDK relay calls Relay calls from EntityManagerClient didn't include any API identification, causing them to fall back to IP-based rate limiting at 5 RPS. Batch uploads (e.g. 22 tracks) easily exceed this limit. Thread apiKey/appName through to EntityManagerClient and append them as query params on /relay requests. Co-Authored-By: Claude Opus 4.6 --- packages/sdk/src/sdk/createSdkWithServices.ts | 4 +++- .../sdk/services/EntityManager/EntityManagerClient.ts | 10 +++++++++- packages/sdk/src/sdk/services/EntityManager/types.ts | 8 ++++++++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/packages/sdk/src/sdk/createSdkWithServices.ts b/packages/sdk/src/sdk/createSdkWithServices.ts index 0a6bfcb6c99..0fda5a0b94e 100644 --- a/packages/sdk/src/sdk/createSdkWithServices.ts +++ b/packages/sdk/src/sdk/createSdkWithServices.ts @@ -179,7 +179,9 @@ const initializeServices = ({ new EntityManagerClient({ ...getDefaultEntityManagerConfig(servicesConfig), audiusWalletClient, - logger + logger, + apiKey, + appName: 'appName' in config ? config.appName : undefined }) const storage = diff --git a/packages/sdk/src/sdk/services/EntityManager/EntityManagerClient.ts b/packages/sdk/src/sdk/services/EntityManager/EntityManagerClient.ts index 4477b2616e2..dd43f5d2a4a 100644 --- a/packages/sdk/src/sdk/services/EntityManager/EntityManagerClient.ts +++ b/packages/sdk/src/sdk/services/EntityManager/EntityManagerClient.ts @@ -40,6 +40,8 @@ export class EntityManagerClient implements EntityManagerService { private readonly chainId: number private readonly contractAddress: string private readonly endpoint: string + private readonly apiKey?: string + private readonly appName?: string constructor(config_: EntityManagerConfig) { const config = mergeConfigWithDefaults( @@ -51,6 +53,8 @@ export class EntityManagerClient implements EntityManagerService { this.contractAddress = config.contractAddress this.logger = config.logger.createPrefixedLogger('[entity-manager]') this.endpoint = config.endpoint + this.apiKey = config.apiKey + this.appName = config.appName } /** @@ -87,7 +91,11 @@ export class EntityManagerClient implements EntityManagerService { const [senderAddress] = await this.audiusWalletClient.getAddresses() const signature = await this.audiusWalletClient.signTypedData(typedData) - const url = `${this.endpoint}/relay` + const params = new URLSearchParams() + if (this.apiKey) params.set('api_key', this.apiKey) + if (this.appName) params.set('app_name', this.appName) + const qs = params.toString() + const url = `${this.endpoint}/relay${qs ? `?${qs}` : ''}` this.logger.info(`Making relay request to ${url}`) const response = await fetch(url, { method: 'POST', diff --git a/packages/sdk/src/sdk/services/EntityManager/types.ts b/packages/sdk/src/sdk/services/EntityManager/types.ts index 54decce02c9..003afaf52e3 100644 --- a/packages/sdk/src/sdk/services/EntityManager/types.ts +++ b/packages/sdk/src/sdk/services/EntityManager/types.ts @@ -26,6 +26,14 @@ export type EntityManagerConfigInternal = { * The endpoint to use for relays */ endpoint: string + /** + * API key for rate limit identification on relay calls + */ + apiKey?: string + /** + * App name for rate limit identification on relay calls + */ + appName?: string } export type EntityManagerConfig = Partial & { audiusWalletClient: AudiusWalletClient From a1cb92a2880046995d9a216e6436b0c8bff7a0b6 Mon Sep 17 00:00:00 2001 From: Raymond Jacobson Date: Tue, 31 Mar 2026 16:04:30 -0700 Subject: [PATCH 2/4] Add Authorization header (Bearer/Basic) to SDK relay calls Relay calls now send an Authorization header so the server's rate limit middleware can identify the app via getApiSigner. Prefers Bearer token (OAuth/PKCE), falls back to Basic auth (apiKey:apiSecret), with api_key/app_name query params as a final fallback. Co-Authored-By: Claude Opus 4.6 --- packages/sdk/src/sdk/createSdkWithServices.ts | 9 ++++++-- .../EntityManager/EntityManagerClient.ts | 21 ++++++++++++++++--- .../src/sdk/services/EntityManager/types.ts | 12 +++++------ 3 files changed, 31 insertions(+), 11 deletions(-) diff --git a/packages/sdk/src/sdk/createSdkWithServices.ts b/packages/sdk/src/sdk/createSdkWithServices.ts index 0fda5a0b94e..13672a7c6e5 100644 --- a/packages/sdk/src/sdk/createSdkWithServices.ts +++ b/packages/sdk/src/sdk/createSdkWithServices.ts @@ -95,6 +95,7 @@ export const createSdkWithServices = (config: SdkConfig) => { let apiKey = 'apiKey' in config ? config.apiKey : undefined const appName = 'appName' in config ? config.appName : undefined const apiSecret = 'apiSecret' in config ? config.apiSecret : undefined + const bearerToken = 'bearerToken' in config ? config.bearerToken : undefined // Default apiKey to derived key from apiSecret if not provided if (apiSecret && !apiKey) { @@ -102,7 +103,7 @@ export const createSdkWithServices = (config: SdkConfig) => { } // Initialize services - const services = initializeServices({ config, apiKey, apiSecret }) + const services = initializeServices({ config, apiKey, apiSecret, bearerToken }) // Warn if using private key in the browser if (apiSecret && isBrowser) { @@ -145,11 +146,13 @@ export const createSdkWithServices = (config: SdkConfig) => { const initializeServices = ({ config, apiKey, - apiSecret + apiSecret, + bearerToken }: { config: SdkConfig apiKey?: string apiSecret?: string + bearerToken?: string }) => { const servicesConfig = config.environment === 'development' ? developmentConfig : productionConfig @@ -181,6 +184,8 @@ const initializeServices = ({ audiusWalletClient, logger, apiKey, + apiSecret, + bearerToken, appName: 'appName' in config ? config.appName : undefined }) diff --git a/packages/sdk/src/sdk/services/EntityManager/EntityManagerClient.ts b/packages/sdk/src/sdk/services/EntityManager/EntityManagerClient.ts index dd43f5d2a4a..efadb679b6b 100644 --- a/packages/sdk/src/sdk/services/EntityManager/EntityManagerClient.ts +++ b/packages/sdk/src/sdk/services/EntityManager/EntityManagerClient.ts @@ -41,6 +41,8 @@ export class EntityManagerClient implements EntityManagerService { private readonly contractAddress: string private readonly endpoint: string private readonly apiKey?: string + private readonly apiSecret?: string + private readonly bearerToken?: string private readonly appName?: string constructor(config_: EntityManagerConfig) { @@ -54,6 +56,8 @@ export class EntityManagerClient implements EntityManagerService { this.logger = config.logger.createPrefixedLogger('[entity-manager]') this.endpoint = config.endpoint this.apiKey = config.apiKey + this.apiSecret = config.apiSecret + this.bearerToken = config.bearerToken this.appName = config.appName } @@ -97,11 +101,22 @@ export class EntityManagerClient implements EntityManagerService { const qs = params.toString() const url = `${this.endpoint}/relay${qs ? `?${qs}` : ''}` this.logger.info(`Making relay request to ${url}`) + + const headers = new Headers({ + 'Content-Type': 'application/json' + }) + + // Add auth header for API identification and rate limiting + if (this.bearerToken) { + headers.set('Authorization', `Bearer ${this.bearerToken}`) + } else if (this.apiKey && this.apiSecret) { + const credentials = btoa(`${this.apiKey}:${this.apiSecret}`) + headers.set('Authorization', `Basic ${credentials}`) + } + const response = await fetch(url, { method: 'POST', - headers: new Headers({ - 'Content-Type': 'application/json' - }), + headers, body: JSON.stringify({ contractAddress: this.contractAddress, contractRegistryKey: 'EntityManager', diff --git a/packages/sdk/src/sdk/services/EntityManager/types.ts b/packages/sdk/src/sdk/services/EntityManager/types.ts index 003afaf52e3..4a12cffecb8 100644 --- a/packages/sdk/src/sdk/services/EntityManager/types.ts +++ b/packages/sdk/src/sdk/services/EntityManager/types.ts @@ -26,13 +26,13 @@ export type EntityManagerConfigInternal = { * The endpoint to use for relays */ endpoint: string - /** - * API key for rate limit identification on relay calls - */ + /** API key for relay calls */ apiKey?: string - /** - * App name for rate limit identification on relay calls - */ + /** API secret for relay calls */ + apiSecret?: string + /** Bearer token for relay calls */ + bearerToken?: string + /** App name for relay calls */ appName?: string } export type EntityManagerConfig = Partial & { From 0caa4c4eaa83cb15d14ada752f18bd00c36e6b0a Mon Sep 17 00:00:00 2001 From: Raymond Jacobson Date: Tue, 31 Mar 2026 16:25:17 -0700 Subject: [PATCH 3/4] Fix prettier formatting Co-Authored-By: Claude Opus 4.6 --- packages/sdk/src/sdk/createSdkWithServices.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/sdk/src/sdk/createSdkWithServices.ts b/packages/sdk/src/sdk/createSdkWithServices.ts index 13672a7c6e5..dc6b9797f4a 100644 --- a/packages/sdk/src/sdk/createSdkWithServices.ts +++ b/packages/sdk/src/sdk/createSdkWithServices.ts @@ -103,7 +103,12 @@ export const createSdkWithServices = (config: SdkConfig) => { } // Initialize services - const services = initializeServices({ config, apiKey, apiSecret, bearerToken }) + const services = initializeServices({ + config, + apiKey, + apiSecret, + bearerToken + }) // Warn if using private key in the browser if (apiSecret && isBrowser) { From 33a97abd59a9706bcaa195519f124b1a00e57ebb Mon Sep 17 00:00:00 2001 From: Raymond Jacobson Date: Tue, 31 Mar 2026 16:27:06 -0700 Subject: [PATCH 4/4] Add changeset for SDK patch Co-Authored-By: Claude Opus 4.6 --- .changeset/relay-rate-limit-auth.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/relay-rate-limit-auth.md diff --git a/.changeset/relay-rate-limit-auth.md b/.changeset/relay-rate-limit-auth.md new file mode 100644 index 00000000000..d8cdc0cf5ba --- /dev/null +++ b/.changeset/relay-rate-limit-auth.md @@ -0,0 +1,5 @@ +--- +'@audius/sdk': patch +--- + +Add Authorization header and API identification to relay calls to support proper rate limiting