Skip to main content
Glama
api-endpoints.ts7.63 kB
/** * App Store Connect API Endpoints * * Centralized API endpoint management for App Store Connect */ import { APP_STORE_API_BASE_URL, APP_STORE_PLATFORM, DEFAULT_APP_LIST_LIMIT, DEFAULT_VERSIONS_FETCH_LIMIT, } from "./constants"; import type { ApiResponse, AppInfo, AppInfoLocalization, AppInfoLocalizationUpdateAttributes, AppStoreApp, AppStoreLocalization, AppStoreScreenshot, AppStoreScreenshotSet, AppStoreVersion, AppStoreVersionLocalizationUpdateAttributes, } from "./types"; export class AppStoreApiEndpoints { constructor( private generateToken: () => Promise<string>, private issuerId: string, private keyId: string ) {} normalizeNextLink(nextLink?: string | null): string | null { if (!nextLink) return null; return nextLink.replace(APP_STORE_API_BASE_URL, ""); } private async request<T>( endpoint: string, options: RequestInit = {} ): Promise<T> { const token = await this.generateToken(); const url = endpoint.startsWith("http") ? endpoint : `${APP_STORE_API_BASE_URL}${endpoint}`; const response = await fetch(url, { ...options, headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", ...options.headers, }, }); if (!response.ok) { const error = await response.json().catch(() => ({ errors: [] })); if (response.status === 401) { throw new Error( `App Store Connect API authentication failed (401 Unauthorized)\n` + `Issuer ID: ${this.issuerId}\n` + `Key ID: ${this.keyId}\n` + `Error: ${JSON.stringify(error, null, 2)}` ); } if (response.status === 409) { const errorDetails = JSON.stringify(error, null, 2); if (errorDetails.includes("STATE_ERROR")) { throw new Error( `App Store Connect API error: 409 Conflict (STATE_ERROR)\n` + `Metadata cannot be modified in current state. Please check app status.\n` + `Error: ${errorDetails}` ); } } throw new Error( `App Store Connect API error: ${response.status} ${ response.statusText }\n${JSON.stringify(error, null, 2)}` ); } return response.json(); } async listApps( nextUrl = `/apps?limit=${DEFAULT_APP_LIST_LIMIT}` ): Promise<ApiResponse<AppStoreApp[]>> { return this.request<ApiResponse<AppStoreApp[]>>(nextUrl); } async findAppByBundleId( bundleId: string ): Promise<ApiResponse<AppStoreApp[]>> { return this.request<ApiResponse<AppStoreApp[]>>( `/apps?filter[bundleId]=${bundleId}` ); } async getApp(appId: string): Promise<ApiResponse<AppStoreApp>> { return this.request<ApiResponse<AppStoreApp>>(`/apps/${appId}`); } async listAppInfos(appId: string): Promise<ApiResponse<AppInfo[]>> { return this.request<ApiResponse<AppInfo[]>>( `/apps/${appId}/appInfos?limit=1` ); } async listAppInfoLocalizations( appInfoId: string, locale?: string ): Promise<ApiResponse<AppInfoLocalization[]>> { const filter = locale ? `?filter[locale]=${locale}` : ""; return this.request<ApiResponse<AppInfoLocalization[]>>( `/appInfos/${appInfoId}/appInfoLocalizations${filter}` ); } async updateAppInfoLocalization( localizationId: string, attributes: AppInfoLocalizationUpdateAttributes ): Promise<void> { await this.request(`/appInfoLocalizations/${localizationId}`, { method: "PATCH", body: JSON.stringify({ data: { type: "appInfoLocalizations", id: localizationId, attributes, }, }), }); } async createAppInfoLocalization( appInfoId: string, locale: string, attributes: AppInfoLocalizationUpdateAttributes ): Promise<ApiResponse<AppInfoLocalization>> { return this.request<ApiResponse<AppInfoLocalization>>( `/appInfoLocalizations`, { method: "POST", body: JSON.stringify({ data: { type: "appInfoLocalizations", attributes: { locale, ...attributes }, relationships: { appInfo: { data: { type: "appInfos", id: appInfoId } }, }, }, }), } ); } async listAppStoreVersions( appId: string, options: { platform?: string; state?: string; limit?: number } = {} ): Promise<ApiResponse<AppStoreVersion[]>> { const { platform = APP_STORE_PLATFORM, state, limit = DEFAULT_VERSIONS_FETCH_LIMIT, } = options; const queryParts = [`filter[platform]=${platform}`, `limit=${limit}`]; if (state) queryParts.push(`filter[appStoreState]=${state}`); const query = queryParts.join("&"); return this.request<ApiResponse<AppStoreVersion[]>>( `/apps/${appId}/appStoreVersions?${query}` ); } async createAppStoreVersion( appId: string, versionString: string, platform = APP_STORE_PLATFORM ): Promise<ApiResponse<AppStoreVersion>> { return this.request<ApiResponse<AppStoreVersion>>(`/appStoreVersions`, { method: "POST", body: JSON.stringify({ data: { type: "appStoreVersions", attributes: { platform, versionString }, relationships: { app: { data: { type: "apps", id: appId } } }, }, }), }); } async listAppStoreVersionLocalizations( versionId: string, locale?: string ): Promise<ApiResponse<AppStoreLocalization[]>> { const filter = locale ? `?filter[locale]=${locale}` : ""; return this.request<ApiResponse<AppStoreLocalization[]>>( `/appStoreVersions/${versionId}/appStoreVersionLocalizations${filter}` ); } async getAppStoreVersionLocalization( localizationId: string ): Promise<ApiResponse<AppStoreLocalization>> { return this.request<ApiResponse<AppStoreLocalization>>( `/appStoreVersionLocalizations/${localizationId}` ); } async updateAppStoreVersionLocalization( localizationId: string, attributes: AppStoreVersionLocalizationUpdateAttributes ): Promise<void> { await this.request(`/appStoreVersionLocalizations/${localizationId}`, { method: "PATCH", body: JSON.stringify({ data: { type: "appStoreVersionLocalizations", id: localizationId, attributes, }, }), }); } async createAppStoreVersionLocalization( versionId: string, locale: string, attributes: AppStoreVersionLocalizationUpdateAttributes ): Promise<ApiResponse<AppStoreLocalization>> { return this.request<ApiResponse<AppStoreLocalization>>( `/appStoreVersionLocalizations`, { method: "POST", body: JSON.stringify({ data: { type: "appStoreVersionLocalizations", attributes: { locale, ...attributes }, relationships: { appStoreVersion: { data: { type: "appStoreVersions", id: versionId }, }, }, }, }), } ); } async listScreenshotSets( localizationId: string ): Promise<ApiResponse<AppStoreScreenshotSet[]>> { return this.request<ApiResponse<AppStoreScreenshotSet[]>>( `/appStoreVersionLocalizations/${localizationId}/appScreenshotSets` ); } async listScreenshots( screenshotSetId: string ): Promise<ApiResponse<AppStoreScreenshot[]>> { return this.request<ApiResponse<AppStoreScreenshot[]>>( `/appScreenshotSets/${screenshotSetId}/appScreenshots` ); } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/quartz-labs-dev/pabal-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server