Features being implemented
store External Payment Report
Implementation Language and Framework
JAVA springboot
a problem
https://developer.apple.com/documentation/externalpurchaseserverapi/send-external-purchase-report?changes=latest_major
I send jwt tokens in the header to the apple endpoint
If a 401 authentication error occurs all of a sudden while being sent successfully in the beginning, a 401 error has occurred since then
What I checked
Tokens are being re-created and sent every time a report is sent
I'm currently using aws ec2 load balancer and I get 401 error and when I shut down Tomcat and restart it, it works normally and then the above problem occurs again
Even if I send the token that I used to send since 401 happened using postman, 401 error
If my local server issues tokens again with the same content and sends the report to postman, it works fine
Considering the above problems and the confirmed contents, why 401 problems suddenly occur
I'd like to know how to solve that part.
private String keyId="******";
private String issuerId="******";
private String bundleId = "ai.******";
Instant now = Instant.now();
Date issuedAt = Date.from(now);
Date expiresAt = Date.from(now.plusSeconds(20 * 60));
public String createToken(){
try {
PrivateKey key = getPrivateKey();
return Jwts.builder()
.setHeaderParam("alg", "ES256")
.setHeaderParam("kid", keyId)
.setHeaderParam("typ", "JWT")
.setIssuer(issuerId)
.setIssuedAt(issuedAt)
.setExpiration(expiresAt)
.setAudience("appstoreconnect-v1")
.signWith(key, SignatureAlgorithm.ES256)
.claim("bid",bundleId)
.compact();
}catch (Exception e){
e.printStackTrace();
throw new RuntimeException("JWT error", e);
}
}
private static PrivateKey getPrivateKey() throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
InputStream privateKey = new ClassPathResource("certs/SubscriptionKey_***********.p8").getInputStream();
String result = new BufferedReader(new InputStreamReader(privateKey)) .lines().collect(Collectors.joining("\n"));
String key = result.replace("-----BEGIN PRIVATE KEY-----\n", "")
.replace("-----END PRIVATE KEY-----", "")
.replace("\n", "");
byte[] decoded = Base64.getDecoder().decode(key);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decoded);
KeyFactory keyFactory = KeyFactory.getInstance("EC");
return keyFactory.generatePrivate(keySpec);
}
App Store Connect API
RSS for tagThe App Store Connect API helps you automate tasks usually done on the Apple Developer website and App Store Connect.
Posts under App Store Connect API tag
165 Posts
Sort by:
Post
Replies
Boosts
Views
Activity
We are producing a function to submit an Apple external purchase report.
When I sent the report, I created a jwt token and put it in the header
There are times when you operate normally and suddenly get a 401 error.
When I checked the entity to log before sending the report, I found that the header was well contained and the token changed every time I called.
Once you get 401 error, you have to shut down your server(tomcat) once and run it again
May I know what kind of problem is causing this phenomenon?
Or can I find a way to fix the problem? The server is using aws ec2 load balancer
The back language is java spring boot
jwt token create code
private String keyId="******";
private String issuerId="******";
private String bundleId = "ai.******";
Instant now = Instant.now();
Date issuedAt = Date.from(now);
Date expiresAt = Date.from(now.plusSeconds(20 * 60));
public String createToken(){
try {
PrivateKey key = getPrivateKey();
return Jwts.builder()
.setHeaderParam("alg", "ES256")
.setHeaderParam("kid", keyId)
.setHeaderParam("typ", "JWT")
.setIssuer(issuerId)
.setIssuedAt(issuedAt)
.setExpiration(expiresAt)
.setAudience("appstoreconnect-v1")
.signWith(key, SignatureAlgorithm.ES256)
.claim("bid",bundleId)
.compact();
}catch (Exception e){
e.printStackTrace();
throw new RuntimeException("JWT error", e);
}
}
private static PrivateKey getPrivateKey() throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
InputStream privateKey = new ClassPathResource("certs/SubscriptionKey_***********.p8").getInputStream();
String result = new BufferedReader(new InputStreamReader(privateKey)) .lines().collect(Collectors.joining("\n"));
String key = result.replace("-----BEGIN PRIVATE KEY-----\n", "")
.replace("-----END PRIVATE KEY-----", "")
.replace("\n", "");
byte[] decoded = Base64.getDecoder().decode(key);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decoded);
KeyFactory keyFactory = KeyFactory.getInstance("EC");
return keyFactory.generatePrivate(keySpec);
}
Hi,
I am trying to connect and use the App Store Connect API so that I can download app reviews remotely. I have read the documentation and have used the API address as stated in the documentation:
GET https://api.appstoreconnect.apple.com/v1/apps/{id}/customerReviews.
I keep on receiving a 403 error:
"errors" : [ {
"id" : "fbe7b837-2023-4f60-8fa0-88b50368f4cc",
"status" : "403",
"code" : "FORBIDDEN_ERROR",
"title" : "This request is forbidden for security reasons",
"detail" : "The API key in use does not allow this request"
}
I am using the same keys and headers that I use to connect to obtain sales reports info, which works absolutely fine, using the below link:
https://api.appstoreconnect.apple.com/v1/salesReports
Is there anything extra that I need to set up before I can access the reviews or am I missing something.
Thanks.
Hi everyone.
I have account and an access to several accounts. When I use List Apps endpoint I get apps only from one account.
Is it possible to get all applications list from api.
Hello!
We would like to know the steps required to build an application and submit it to the Apple Store using an automated process on a server.
Here are our conditions:
We have a server running macOS Sonoma 14.6.1 on Amazon EC2.
Xcode 16.1 (Build version 16B40) is installed.
We use only console commands, as the GUI is not available.
We use Cordova to add the iOS platform to the application.
A private key, certificate, and provisioning profile have already been created and are located on the server.
Could you please clarify:
What commands are needed to configure the Keychain to use the certificate and provisioning profile?
How can we build the application using xcodebuild?
What are the steps to sign and submit the application to the App Store with minimal human interaction?
Thank you in advance for your assistance!
Is there a way to view the opt-in rate via the API? I have been unsuccessful in finding one.
My company provides an app that allows businesses to connect their Apple Apps to Salesforce so they can manage and respond to their Customer Reviews.
One of our customers has created an API Key for that purpose with a "Customer Support" Role. According to the documentation, that Role should have access to both read and respond to customer reviews:
https://developer.apple.com/help/account/manage-your-team/roles/
We can retrieve Reviews successfully with this API Key. However, whenever they attempt to respond to a Review, they get an error:
[Status=Forbidden, StatusCode=403]
We've confirmed this seems to be related to the Key Role, as they created a new key with an "Admin" role and the response works fine with that key. We don't understand why "Customer Support" doesn't work, though, since the docs seem to indicate that they should support responding to Reviews.
Is there some way to make a "Customer Support" API Key work for responses? If not, are there other roles that would enable them to both read and respond to Customer Reviews? Or is an "Admin" key truly the only Role that would enable responses to Customer Reviews via the API? The customer is understandably hesitant to use an "Admin" key since that opens up a lot of other access.
Error and bak to homepage.
I have an existing system that fetches reviews for an app from the following URL:
https://itunes.apple.com/en/rss/customerreviews/?id=[numeric app ID]
The response from this URL gives review IDs in numeric format, for example 11990822406.
I'd like to use the new API described here to fetch any review response, but the new API only accepts an opaque review ID, for example 00000037-1653-fg99-8224-821b00000000.
Is there any way to map from the numeric review ID to the opaque ID?
Creating profile via a post request keeps getting a server error 500.
It just worked yestday.
POST https://api.appstoreconnect.apple.com/v1/profiles
{
"code": "UNEXPECTED_ERROR",
"detail": "An unexpected error occurred on the server side. If this issue continues, contact us at https://developer.apple.com/contact/.",
"status": "500",
"title": "An unexpected error occurred."
}
I am using an ASO tool.
After linking my Search Ads account campaign with my App Store Connect account, it gets my email and app specific password then makes API call to search ads.
I couldn't find any documentation regarding this flow. Can someone lead me to the correct path where I can implement a similar flow?
Thank you all
Hi all,
I‘m using the certificates API in order to create a development certificate. I want to create a Jenkins job that will give employees an option to create a certificate without giving them admin rights.
I’m creating a new certificate without any issues.
When I try to create another certificate with a different CSR (for a diff user) I get an error that a certificate already exists.
Is it limited to create only one certificate per API key??
Thanks!
Hi Folks, I’m looking to pull performance metrics using https://developer.apple.com/documentation/appstoreconnectapi/get-v1-apps-_id_-perfpowermetrics from Lookback for yesterday's data. Could you guide me on the best way to filter or query this specific timeframe? I dont actually see a way pull data from history. Any advice or steps would be greatly appreciated. TIA!
I'm currently pulling device-specific data for my app, and I'm manually listing 150 models like this:
device_models = [ "iPhone1_1", "iPhone1_2", "iPhone2_1", ... "iPad16_6"]
Is there an API endpoint or an automated method to dynamically retrieve a complete list of device models?
I'm specifically looking to connect this with the performance metrics API to monitor launch times per device type. Any suggestions on how to streamline or automate this list would be greatly appreciated. Thanks!
Unable to Add for Review
The items below are required to start the review process:
An unexpected error was encountered when submitting for review. If the issue persists please contact us
Hello fellow developers! I am interested in obtaining the following information regarding one of our Home Screen widgets:
Pull the following data about the "my_app_widget" homepage widget in total and by month for the last 12 months (Sep '23 - Sep '24):
Widget install events
Unique widget installs
Device type (iPad, iOS).
Thus far, I am using AWS Lambda and have the proper credentials within a Secrets Manager and I keep getting a 409 error from this code:
const AWS = require('aws-sdk');
const axios = require('axios');
const jose = require('jose');
const crypto = require('crypto');
const s3 = new AWS.S3();
const secretsManager = new AWS.SecretsManager();
const cloudwatchlogs = new AWS.CloudWatchLogs();
const logGroupName = '/aws/lambda/my_lambda_function';
const logStreamName = '2024/11/05/[$LATEST]XXXXXXXXXXXXXX';
const getSecret = async (secretName) => {
const secret = await secretsManager.getSecretValue({ SecretId: secretName }).promise();
return JSON.parse(secret.SecretString);
};
const logError = async (message) => {
const params = {
logGroupName,
logStreamName,
logEvents: [
{
message: JSON.stringify(message),
timestamp: Date.now()
}
]
};
try {
await cloudwatchlogs.putLogEvents(params).promise();
} catch (error) {
console.error('Failed to log to CloudWatch:', error);
}
};
const getJwtToken = async (keyId, issuerId, privateKeyPem) => {
try {
const privateKey = crypto.createPrivateKey({
key: privateKeyPem,
format: 'pem',
type: 'pkcs8'
});
const payload = {
iss: issuerId,
exp: Math.floor(Date.now() / 1000) + 20 * 60,
aud: 'appstoreconnect-v1'
};
const token = await new jose.SignJWT(payload)
.setProtectedHeader({ alg: 'ES256', kid: keyId })
.sign(privateKey);
return token;
} catch (error) {
await logError({ error: 'Error generating JWT', details: error });
throw new Error('JWT generation failed');
}
};
const fetchAndUploadReport = async (url, token, s3Key, isAnalytics = false, body = null) => {
try {
const headers = {
'Authorization': `Bearer ${token}`,
'Accept': isAnalytics ? 'application/json' : 'application/a-gzip',
'Content-Type': 'application/json'
};
const response = await axios({
method: isAnalytics ? 'post' : 'get',
url: url,
headers: headers,
data: body,
responseType: isAnalytics ? 'json' : 'stream'
});
if (response.status === 200) {
let bodyContent = isAnalytics ? JSON.stringify(response.data) : response.data;
await s3.upload({
Bucket: 'my_bucket_name',
Key: s3Key,
Body: bodyContent
}).promise();
return { statusCode: 200, body: `${s3Key} fetched and uploaded successfully` };
} else {
await logError({ error: 'API response error', status: response.status, statusText: response.statusText, url });
return { statusCode: response.status, body: response.statusText };
}
} catch (error) {
await logError({ error: 'Error fetching report', url, details: error });
return { statusCode: 500, body: JSON.stringify(`Error fetching ${s3Key}`) };
}
};
exports.handler = async (event) => {
const secretName = 'AppStoreConnectPrivateKey';
try {
const secretData = await getSecret(secretName);
const { keyId, issuerId, private_key: privateKeyPem } = secretData;
const token = await getJwtToken(keyId, issuerId, privateKeyPem);
const startDate = '2023-09-01';
const endDate = '2024-09-30';
const apiEndpoints = [
{
url: `https://api.appstoreconnect.apple.com/v1/analyticsReportRequests`, // Changed to request report
s3Key: 'my_folder/my_subfolder/unique_widget_installs.json',
isAnalytics: true,
body: {
"data": {
"type": "analyticsReportRequests",
"attributes": {
"accessType": "ONGOING",
"name": "Home Screen Widget Installs",
"category": "APP_USAGE"
},
"relationships": {
"app": {
"data": {
"type": "apps",
"id": "YYYYYYYYYYYY"
}
}
}
}
}
}
];
Returns the following error :
{
"error": "Error fetching report",
"url": "https://api.appstoreconnect.apple.com/v1/analyticsReportRequests",
"details": {
"message": "Request failed with status code 409",
"name": "AxiosError",
"stack": "AxiosError: Request failed with status code 409\n at settle (/var/task/node_modules/axios/dist/node/axios.cjs:2019:12)\n at IncomingMessage.handleStreamEnd (/var/task/node_modules/axios/dist/node/axios.cjs:3135:11)\n at IncomingMessage.emit (node:events:531:35)\n at IncomingMessage.emit (node:domain:488:12)\n at endReadableNT (node:internal/streams/readable:1696:12)\n at process.processTicksAndRejections (node:internal/process/task_queues:82:21)\n at Axios.request (/var/task/node_modules/axios/dist/node/axios.cjs:4287:41)\n at process.processTicksAndRejections (node:internal/process/task_queues:95:5)\n at async fetchAndUploadReport (/var/task/index.js:68:26)\n at async Promise.all (index 0)\n at async exports.handler (/var/task/index.js:174:25)",
"config": {
"transitional": {
"silentJSONParsing": true,
"forcedJSONParsing": true,
"clarifyTimeoutError": false
},
"adapter": [
"xhr",
"http",
"fetch"
],
"transformRequest": [
null
],
"transformResponse": [
null
],
"timeout": 0,
"xsrfCookieName": "XSRF-TOKEN",
"xsrfHeaderName": "X-XSRF-TOKEN",
"maxContentLength": -1,
"maxBodyLength": -1,
"env": {},
So I am not sure if the JSON body I am passing this URL endpoint are correct or if this is the correct API to be using to attempt to obtain this information. Please let me know when you have the chance and I look forward to hearing from you.
I've been working on retrieving total app installs via the Apple Connect API using Python. However, I've noticed a discrepancy between the data obtained manually and the API results.
When I download the Sales & Trends report manually with these settings:
Sales & Trends, Monthly, By Territory, Total Installs
…the results don't match those from the API when I use the following parameters
python
report_date = '2024-09'
params = {
"filter[frequency]": "MONTHLY",
"filter[reportDate]": report_date, # Date for the report
"filter[reportSubType]": "SUMMARY",
"filter[reportType]": "SALES",
"filter[vendorNumber]": vendor_number
}
I’m seeing a clear mismatch in units between the manual download and the API output
Note: This report was generated on 2024-11-05
It's not possible to create a new version for any App Store Connect app.
To reproduce, I navigate to my app and click the (+) button to create a new version number. I type in a version number, and click "Create."
It fails with an error, "An error has occurred. Try again later."
Hi everyone,
I’m working with the App Store Connect API to analyze my app's launch time performance, but I’d like to retrieve historical data spanning multiple dates. However, I haven’t found parameters like startDate or endDate in the API documentation.
Is there any way to retrieve data for past dates, or is the API limited to recent data only? If anyone has experience with this or any workaround, I’d love to hear about it.
Thank you!
Hi Folks,
I'm currently using the App Store Connect API's perfPowerMetrics endpoint to pull launch time metrics for my app, but I want to break down the data by specific iPhone models (e.g., iPhone 13 Pro, iPhone 12, etc.). I’ve tried using filter[deviceType] set to all_iphones, which works, but it aggregates data for all iPhone models.
Does anyone know the specific identifiers for individual iPhone models, or if there's a way to retrieve metrics by specific iPhone model segments?
TIA