App Store Connect API

RSS for tag

The App Store Connect API helps you automate tasks usually done on the Apple Developer website and App Store Connect.

App Store Connect API Documentation

Posts under App Store Connect API subtopic

Post

Replies

Boosts

Views

Activity

ONE_TIME_CHARGE notification type in Production
Hello, We've integrated App Store Server Notifications V2 in our system. We are heavily relying on the ONE_TIME_CHARGE notification type to handle Consumable purchases, but this notification type is available only for Sandbox. And this is for a while now - starting with June 10th 2024 ( https://developer.apple.com/documentation/appstoreservernotifications/app-store-server-notifications-changelog#June-10-2024 ). Can you please provide a timeline for when the ONE_TIME_CHARGE notification type will be available in Production ? Thank you!
1
1
672
May ’25
App Store Notification
Hello, I am developing App Store Server to Server Notifications. (The app has already been deployed and is in operation.) Test notifications in both the Sandbox and Production environments have been working correctly. Additionally, I tested in-app purchases using a Sandbox account and confirmed that the server notifications are received. However, when an actual purchase is made in the live app, the server notifications are not received. Please provide the possible causes and solutions for this issue.
2
0
879
May ’25
I can't verify App Store Notification
I'm setting up App Store Notifications for my app. Having trouble verifying even the TEST notification, through. I'm generating JWT-token and sending it via Postman. I get a successful notification UUID as a response. But my Node.JS endpoint says it can't verify it. Here's the endpoint: const fs = require('fs'); const path = require('path'); const { SignedDataVerifier, Environment } = require('@apple/app-store-server-library'); module.exports = function (sqlexec) { function loadRootCAs() { // const gPath = path.resolve(__dirname, "AppleIncRootCertificate.cer"); const g3Path = path.resolve(__dirname, "AppleRootCA-G3.cer"); // const g2Path = path.resolve(__dirname, "AppleRootCA-G2.cer"); const loadedCerts = []; try { // loadedCerts.push(fs.readFileSync(gPath)); loadedCerts.push(fs.readFileSync(g3Path)); // loadedCerts.push(fs.readFileSync(g2Path)); if (loadedCerts.length === 0) { throw new Error("No Apple Root CA certificates were loaded."); } console.log("[APPLE NOTIFICATIONS2] Apple Root CA certificates loaded successfully."); return loadedCerts; } catch (error) { console.error("❌ CRITICAL: Error loading Apple Root CA certificate(s):", error.message); console.error("Ensure 'AppleRootCA-G3.cer' (and others if specified) are present at the expected path and readable."); throw new Error("Failed to load essential Apple Root CA certificates. Notification processing will fail."); } } const appleRootCAs = loadRootCAs(); const enableOnlineChecks = true; const environment = Environment.SANDBOX; const bundleId = "SomeBundleID"; const appAppleId = undefined; // Set if you're in PRODUCTION const verifier = new SignedDataVerifier( appleRootCAs, enableOnlineChecks, environment, bundleId, appAppleId ); router.post('/notifications_refund', async function (req, res) { const signedPayload = req.body.signedPayload; try { const notificationVerificationResult = await verifier.verifyAndDecodeNotification(signedPayload); if (!notificationVerificationResult.isValid) { console.error(`[APPLE NOTIFICATIONS2] Failed to verify notification. Status: ${notificationVerificationResult.verificationStatus}, Error: ${notificationVerificationResult.errorMessage || 'N/A'}`); return res.status(400).json({ status: "error", message: "Invalid notification signature or payload." }); } const decodedNotification = notificationVerificationResult.payload; const notificationType = decodedNotification.notificationType; const subtype = decodedNotification.subtype; if (notificationType === 'TEST') { console.log(`[APPLE NOTIFICATIONS2] Received TEST notification. Subtype: ${subtype}`); // The TEST notification's data.signedTransactionInfo is a JWS representing a sample transaction. } else if (notificationType === 'REFUND') { console.log(`[APPLE NOTIFICATIONS2] Received REFUND notification. Subtype: ${subtype}`); } else { console.log(`[APPLE NOTIFICATIONS2] Received notificationType: ${notificationType}, Subtype: ${subtype}. Skipping non-refund/test type for this endpoint.`); return res.status(200).json({ status: "success", message: "Notification received, but not a type processed by this refund endpoint." }); } // Ensure data and signedTransactionInfo exist if (!decodedNotification.data || !decodedNotification.data.signedTransactionInfo) { console.error("[APPLE NOTIFICATIONS2] Notification payload is missing data or signedTransactionInfo."); return res.status(400).json({ status: "error", message: "Notification missing transaction info." }); } const transactionInfoJWS = decodedNotification.data.signedTransactionInfo; const transactionVerificationResult = await verifier.verifyAndDecodeTransaction(transactionInfoJWS); if (!transactionVerificationResult.isValid) { console.error(`[APPLE NOTIFICATIONS2] Failed to verify signedTransactionInfo. Status: ${transactionVerificationResult.verificationStatus}, Error: ${transactionVerificationResult.errorMessage || 'N/A'}`); return res.status(400).json({ status: "error", message: "Invalid signedTransactionInfo in notification." }); } const verifiedTransactionPayload = transactionVerificationResult.payload; const transactionId = verifiedTransactionPayload.originalTransactionId || verifiedTransactionPayload.transactionId; console.log(`[APPLE NOTIFICATIONS2] Successfully decoded Transaction ID: ${transactionId} for notification type ${notificationType}`); // This is where my refund logic starts in case the notif is refund, but fow now I'm just trying to verify a TEST notif return res.status(200).json({ status: "success", message: "Refund (or TEST) notification processed successfully and validated." }); } catch (error) { console.error("[APPLE NOTIFICATIONS2] Critical error processing notification:", error); // Check if the error is from the verifier or elsewhere if (error.name === 'SignedDataVerificationError') { // Example, check actual error type from library return res.status(400).json({status: "error", message: `Notification verification failed: ${error.message}`}); } return res.status(500).json({ status: "error", message: "Failed to process notification due to an internal server error." }); } }); return router; }; I tried different root certs, only G3 works, other two give errors. Also tried adding G3 intermediate (WWDRCAG3), but it doesn't seem to help. What am I doing wrong?
0
0
88
May ’25
App Store Analytics API Reports
Hi everyone, I’m new here. I’m working with the App Store Connect (ASC) Analytics API and I have some questions about how to retrieve historical data for downloads and in-app purchases. I’ve created two types of reports: 1. ONGOING: Retrieves current and recent data. 2. ONE_TIME_SNAPSHOT: Supposedly retrieves historical data. I am specifically interested in the Detailed versions: • App Store Downloads Detailed • App Store Purchases Detailed What's my problem? 1. The ONGOING report brings instances with data from the last few days, which seems correct for App Downloads. But not with Purchases, it brings just a few random days and I'm not sure about the granularity. 2. The ONE_TIME_SNAPSHOT report, however, retrieves instances with data from specific days, but the selection of these days appears to be random. I can’t figure out the criteria behind it. Does anyone know what the selection criteria are for the days that appear in the ONE_TIME_SNAPSHOT report? Is it possible to configure the date range for this type of report? Why are some historical dates available while others are not? Is there a way to ensure that the ONE_TIME_SNAPSHOT report brings data from all days within a specified range? Any advice or insights would be greatly appreciated! Thanks in advance!
0
0
276
May ’25
App Store Connect Metrics via REST API
I hope this message finds you well. I’m reaching out to ask whether specific App Store Connect metrics available in the App Analytics dashboard can also be accessed via the App Store Connect REST API. I have reviewed the official API documentation, but I couldn’t find confirmation regarding the metrics listed below. Could you kindly clarify if the following metrics are available through the REST API? And if so, could you point me to the relevant endpoints or documentation? From the "Usage" group: Installations (Opt-in only) Active Devices Deletions (Uninstalls) From the "App Store" group: Impressions (Unique Devices) Product Page Views (Unique Devices) If these metrics are not available via the REST API, is there an alternative method to programmatically access or export them? Thank you very much in advance for your help and guidance.
2
0
286
May ’25
Retrieving each user’s “last login” timestamp via the App Store Connect API – is it possible?
Hello everyone, I’m building a custom tool that uses the App Store Connect API (v1) to manage my team’s users. I can successfully list all users with: GET https://api.appstoreconnect.apple.com/v1/users …but the JSON response only includes fields like firstName, lastName, email, allAppsVisible, provisioningAllowed, and roles. There is no lastLogin or timestamp field anywhere in the User object: { "data": [ { "id": "USER_ID", "type": "users", "attributes": { "firstName": "mohit", "lastName": "tiwari", "email": "", "allAppsVisible": false, "provisioningAllowed": false }, "relationships": { … } }, … ] } My main question is: How can I retrieve each user’s “last login” timestamp via the App Store Connect API? Is this even possible with the current endpoints? If it isn’t exposed, has Apple any plans to add this? Or are there any recommended workarounds—perhaps via audit logs or another API—to track when each user last accessed App Store Connect? Thanks in advance for your guidance and any code/endpoint examples you can share!
0
0
159
May ’25
Recommended way to detect when a user has joined your team via App Store Connect API?
I'm using App Store Connect API to automate onboarding/off-boarding user invitations for my team members when they start working on my app. Once devs join the team, I'd like to invite them to the specific app's beta test group. This requires some additional work after the user has joined the team because unlike visibleApps, there's no way to initially indicate the beta test groups that a user should have access to. One challenge I'm finding is that people don't immediately join the team on time so polling feels a bit excessive/wasteful. Is there some recommended mechanism for adding users to the internal test flight group, or is there a way to trigger a webhook when the list of users is updated. I noticed that there's a new /v1/marketplaceWebhooks API so I imagine this is the recommended format for registering callbacks. Any assistance here would be greatly appreciated.
1
16
588
Jun ’25
App Store Connect API: 'UNIVERSAL' is not a valid value for the attribute 'platform'
Hello, We are encountering an issue when using the App Store Connect API to create a bundle ID via the endpoint: POST https://api.appstoreconnect.apple.com/v1/bundleIds In our request, we specify the platform value "UNIVERSAL", which according to the official documentation is a valid value: BundleIdPlatform documentation However, the API now returns the following error response: { "errors": [ { "code": "ENTITY_ERROR.ATTRIBUTE.TYPE", "detail": "'UNIVERSAL' is not a valid value for the attribute 'platform'. Expected one of: 'IOS', 'MAC_OS'", "status": "409", "title": "An attribute in the provided entity has the wrong type" } ] } According to the documentation, the platform attribute accepts the following values: IOS, MAC_OS and UNIVERSAL. It appears that UNIVERSAL is no longer accepted even though it is still listed as a valid option. Has support for UNIVERSAL been deprecated or changed recently? If so, what is the current recommended way to create bundle IDs that are intended for multiple platforms? Any clarification would be greatly appreciated. Thank you!
0
0
177
Jun ’25
App Store Connect API
As of June 9, 2025 we are no longer able to automate the creation of our offline provisioning profiles that we used to do on a weekly basis for testing of our internal products offline. I am not sure if the isOfflineProfile was an undocumented attribute that we were using, or if it was deprecated or if the removal of that capability was an oversight. The release notes for 4.0 of the API don't mention a deprecation. https://developer.apple.com/documentation/appstoreconnectapi/app-store-connect-api-4-0-release-notes When making the request to the v1/profiles endpoint to create the provisioning profile we now receive the following response: { "status": "409", "code": "ENTITY_ERROR.ATTRIBUTE.UNKNOWN", "title": "The provided entity includes an unknown attribute", "detail": "'isOfflineProfile' is not an attribute on the resource 'profiles'", "source": { "pointer": "/data/attributes/isOfflineProfile" } }
3
1
377
Jun ’25
DEVELOPER_ID_APPLICATION_G2 is not recognized by ASC API
Hi, ASC API call rejects DEVELOPER_ID_APPLICATION_G2 and does not recognize it, even though it is listed as a valid certificateType in the docs: https://developer.apple.com/documentation/appstoreconnectapi/get-v1-certificates. If it was removed from the certificate list, then why Apple forces to choose the G2 type when creating a DEVELOPER_ID_APPLICATION certificate manually in the Apple Developer account. Can someone from Apple support please check it as it is a contradictory behavior and is a blocker.
2
0
167
Jun ’25
Recent change in the AppStoreConnect API broke fastlane
Hi there, Recently, a change was made to the App Store Connect API, which removed the unofficially supported templateName parameter when creating provisioning profiles. This broke fastlane as it was using the templateName parameter. Could that change be reverted or official support added for templateName? I believe this change was first rolled out around March 18th and then reverted shortly after, before being rolled out again around May 6th. The fastlane issue can be seen here: https://github.com/fastlane/fastlane/issues/29498 Official AppStoreConnect API docs for the endpoint are here: https://developer.apple.com/documentation/appstoreconnectapi/profilecreaterequest/data-data.dictionary/attributes-data.dictionary
2
10
712
Jun ’25
Customer review forbidden error message despite successful auth
Hi, I am seeking assistance and feedback on the below post on feedback assistant. FB18169176 (Customer review API forbidden error) I am calling this endpoint in a python script: https://api.appstoreconnect.apple.com/v1/apps/6450458286/customerReviews?limit=200&sort=-createdDate I can verify that I am getting a valid JWT token. An example is on the feedback link. Yet I am getting this error: { "errors" : [ { "id" : "eda3b456-9aa9-47bd-8736-439db0c73545", "status" : "403", "code" : "FORBIDDEN_ERROR", "title" : "This request is forbidden for security reasons", "detail" : "The API key in use does not allow this request" } ] } Please advise why this is the case, and please assist as this information is needed urgently. Thank you.
0
0
73
Jun ’25
External Purchase: status 401
Good morning, I am configuring in backend the sending of reports regarding purchases made in app with external platform (Stripe) as per documentation. To be clear I am talking about ExternalPurchase. However, when I make the call it returns "Apple responded with status 401". I verified the token on jwt.io as per documentation and it is working. I don't understand where I am going wrong. Below I share the code with you: const express = require("express"); const bodyParser = require("body-parser"); const jwt = require("jsonwebtoken"); const fs = require("fs"); const app = express(); const https = require("https"); const APPLE_KEY_ID = "XXXXX"; const APPLE_ISSUER_ID = "xxx-xxx-xxx-xx-xxxxxx"; const APPLE_PRIVATE_KEY = fs.readFileSync("AuthKey_xxxxx.p8", "utf8"); const APPLE_AUDIENCE = "appstoreconnect-v1"; function generateAppleJwt() { const now = Math.floor(Date.now() / 1000); const payload = { iss: APPLE_ISSUER_ID, iat: now, exp: now + (5 * 60), aud: APPLE_AUDIENCE }; return jwt.sign(payload, APPLE_PRIVATE_KEY, { algorithm: "ES256", header: { alg: "ES256", kid: APPLE_KEY_ID, typ: "JWT" } }); } app.post('/webhook', bodyParser.json({ type: 'application/json' }), async (req, res) => { let eventType = req.body.type; const relevantEvents = [ "invoice.paid" ]; if (relevantEvents.includes(eventType)) { try { const data= req.body.data; const platform = data.object.subscription_details.metadata.platform; if (platform === "IOS") { const token = generateAppleJwt(); const applePayload = { appAppleId: "xxxx", bundleId: 'com.xxx.xxx.test', externalPurchaseId: data.object.id, purchaseTime: new Date(data.object.created * 1000).toISOString(), purchaseAmount: { amount: (data.object.total / 100).toFixed(2), currencyCode: data.object.currency.toUpperCase() }, purchaseLocation: { isoCountryCode: "IT" } }; const jsonString = JSON.stringify(applePayload); const agent = new https.Agent({ keepAlive: false }); const response = await fetch( "https://api.storekit-sandbox.apple.com/externalPurchase/v1/reports", { method: "PUT", headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", "Accept-Encoding": "identity", }, body: JSON.stringify(applePayload), } ); if (!response.ok) { const errorText = await response.text(); throw new Error( `Apple responded with status ${response.status}: ${errorText}` ); } console.log("✅ Notifica inviata ad Apple con successo"); } else { if(!canSendNotification){ console.log("Non è una Sub. Nessuna notifica inviata."); }else{ console.log("Customer non iOS. Nessuna notifica inviata."); } } } catch (err) { console.error("Errore durante l’invio ad Apple:"); if (err.response) { console.error("Status:", err.response.status); console.error("Headers:", err.response.headers); console.error("Data:", err.response.data); } else { console.error("Message:", err.message); } } } res.status(200).send("OK"); }); exports.checkSubStripe = functions.https.onRequest(app);
0
0
155
Jun ’25
External Purchase: Error 401
Good morning, I am configuring in backend the sending of reports regarding purchases made in app with external platform (Stripe) as per documentation. To be clear I am talking about ExternalPurchase. However, when I make the call it returns "Apple responded with status 401". I verified the token on jwt.io as per documentation and it is working. I don't understand where I am going wrong. Below is the code: const express = require("express"); const bodyParser = require("body-parser"); const jwt = require("jsonwebtoken"); const fs = require("fs"); const app = express(); const https = require("https"); const APPLE_KEY_ID = "xxx"; const APPLE_ISSUER_ID = "xxx-xxx-xxx-xxx-xxx"; const APPLE_PRIVATE_KEY = fs.readFileSync("AuthKey_xxx.p8", "utf8"); const APPLE_AUDIENCE = "appstoreconnect-v1"; function generateAppleJwt() { const now = Math.floor(Date.now() / 1000); const payload = { iss: APPLE_ISSUER_ID, iat: now, exp: now + (5 * 60), aud: APPLE_AUDIENCE }; return jwt.sign(payload, APPLE_PRIVATE_KEY, { algorithm: "ES256", header: { alg: "ES256", kid: APPLE_KEY_ID, typ: "JWT" } }); } app.post('/webhook', bodyParser.json({ type: 'application/json' }), async (req, res) => { let eventType = req.body.type; const relevantEvents = [ "invoice.paid" ]; if (relevantEvents.includes(eventType)) { try { const data= req.body.data; const platform = data.object.subscription_details.metadata.platform; if (platform === "IOS") { const token = generateAppleJwt(); const applePayload = { appAppleId: "xxx", bundleId: 'com.xxx.xxx.test', externalPurchaseId: data.object.id, purchaseTime: new Date(data.object.created * 1000).toISOString(), purchaseAmount: { amount: (data.object.total / 100).toFixed(2), currencyCode: data.object.currency.toUpperCase() }, purchaseLocation: { isoCountryCode: "IT" } }; const jsonString = JSON.stringify(applePayload); const agent = new https.Agent({ keepAlive: false }); const response = await fetch( "https://api.storekit-sandbox.apple.com/externalPurchase/v1/reports", { method: "PUT", headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", "Accept-Encoding": "identity", }, body: JSON.stringify(applePayload), } ); if (!response.ok) { const errorText = await response.text(); throw new Error( `Apple responded with status ${response.status}: ${errorText}` ); } console.log("✅ Notifica inviata ad Apple con successo"); } else { if(!canSendNotification){ console.log("Non è una Sub. Nessuna notifica inviata."); }else{ console.log("Customer non iOS. Nessuna notifica inviata."); } } } catch (err) { console.error("Errore durante l’invio ad Apple:"); if (err.response) { console.error("Status:", err.response.status); console.error("Headers:", err.response.headers); console.error("Data:", err.response.data); } else { console.error("Message:", err.message); } } } res.status(200).send("OK"); }); exports.checkSubStripe = functions.https.onRequest(app);
0
0
125
Jun ’25
Failing to create leaderboard via the API
I'm getting the following error when attempting to create a leaderboard via the documented POST url. Here is the full error response: { "errors" : [ { "id" : "xxxxxx", "status" : "400", "code" : "ENTITY_INVALID", "title" : "The request entity is not valid json", "detail" : "The request entity data could not be processed. Please ensure you are sending valid json.", "meta" : { "position" : { "row" : 1, "column" : 1 } } } ] } Except what I'm sending IS valid JSON, emitted directly from JsonUtility and verified via JSONLint: {"data":{"type":"gameCenterLeaderboards","attributes":{"defaultFormatter":"INTEGER","referenceName":"TEST_AUTO","vendorIdentifier":"TEST_AUTO","submissionType":"MOST_RECENT_SCORE","sortScoreType":"DESC","scoreRangeStart":"0","scoreRangeEnd":"20000","recurrenceStartDate":"2025-06-25T13:00:00Z","recurrenceDuration":"PT30M","recurrenceRule":"FREQ=HOURLY;INTERVAL=1"},"relationships":{"gameCenterDetail":{"data":{"type":"gameCenterDetails","id":"xxxxxx"}}}}} This follows EXACTLY the pattern in the App Store Connect documentation. I've even tried sending the example JSON they give (swapping out the id and fixing the quotation marks) and the server rejects THAT as not being valid JSON. What am I doing wrong? Thanks!
0
0
93
Jun ’25
Fails to Find AuthKey .p8 in Unity Cloud Build Post-Build Script
Hi everyone!I got an error trying to automatically upload my .ipa to TestFlight through Post-build script (Unity Cloud Build (Build Automation)): [error] ErrorDomain=ITunesConnectionAuthenticationErrorDomain Code=-26000 “Failed to generate JWT token: ErrorDomain=NSCocoaErrorDomain Code=-43 ‘Failed to load AuthKey file.’ The file ‘AuthKey_<YOUR_API_KEY>.p8’ could not be found in: • /BUILD_PATH/.../private_keys • ~/private_keys • ~/.private_keys • ~/.appstoreconnect/private_keys” In post-build.bash I tried the following two options but both don't work: 1 option: KEY_WITH_NEWLINES=$(echo $CONNECT_API_KEY | jq '.private_key |= sub(" (?!PRIVATE|KEY)"; "\n"; "g")' -c -j) echo $KEY_WITH_NEWLINES > ~/.appstoreconnect/private_keys/AuthKey_${API_KEY_ID}.p8 2 option: mkdir -p ~/.appstoreconnect/private_keys echo "$CONNECT_API_KEY" | jq -r '.private_key' > ~/.appstoreconnect/private_keys/AuthKey_${API_KEY_ID}.p8 In Unity Cloud Build (Build Automation) → Advanced Options → Environment Variables → Variable value for CONNECT_API_KEY I indicate in the following format: {"private_key":"-----BEGIN PRIVATE KEY-----\nMIIEv...\n-----END PRIVATE KEY-----"} Please tell me how to fix the error? Is it wrong in my code in post-build.bash or the input format Variable value for CONNECT_API_KEY?
2
0
208
Jun ’25
Failed to create profile via the api
When I use the Enterprise Program API to create a profile via https://developer.apple.com/documentation/enterpriseprogramapi/create-a-profile, I got an error: org.springframework.web.client.HttpClientErrorException$MethodNotAllowed: 405 Not Allowed: "{?"errors": [{??"status": "405",??"code": "METHOD_NOT_ALLOWED",??"title": "The request method is not valid for the resource path.",??"detail": "The request method used for this request is not valid for the resource path. Please consult the documentation."?}]}". I tried the empty body to request, but still got the same error. So I doubt the sever is not allow the POST method. Please help me to resolve this problem, thx!
1
0
82
Jul ’25
Unable to recreate a ONE_TIME_SNAPSHOT analytics report
Previously created a one time snapshot report that was later deleted. Trying to recreate it but keeping getting a 409. Been already a week since it was deleted. Error message { "errors" : [ { "id" : "b0d8d738-1744-463d-bdcf-a48fbfa5d715", "status" : "409", "code" : "STATE_ERROR", "title" : "The request cannot be fulfilled because of the state of another resource.", "detail" : "You already have such an entity" } ] } Reports request only returns the ONGOING reports link no ONE_TIME_SNAPSHOT . Wonder if it takes time although unlikely or might it be a cache or similar causing the problem.
4
1
1.5k
Jul ’25
ONE_TIME_CHARGE notification type in Production
Hello, We've integrated App Store Server Notifications V2 in our system. We are heavily relying on the ONE_TIME_CHARGE notification type to handle Consumable purchases, but this notification type is available only for Sandbox. And this is for a while now - starting with June 10th 2024 ( https://developer.apple.com/documentation/appstoreservernotifications/app-store-server-notifications-changelog#June-10-2024 ). Can you please provide a timeline for when the ONE_TIME_CHARGE notification type will be available in Production ? Thank you!
Replies
1
Boosts
1
Views
672
Activity
May ’25
App Store Notification
Hello, I am developing App Store Server to Server Notifications. (The app has already been deployed and is in operation.) Test notifications in both the Sandbox and Production environments have been working correctly. Additionally, I tested in-app purchases using a Sandbox account and confirmed that the server notifications are received. However, when an actual purchase is made in the live app, the server notifications are not received. Please provide the possible causes and solutions for this issue.
Replies
2
Boosts
0
Views
879
Activity
May ’25
When will ONE_TIME_CHARGE notification type become available for production?
Sever notifications v1 is already deprecated and we are supposed to use the new v2 server notifications and yet arguably the most important notification type is not available for production.
Replies
7
Boosts
8
Views
1.6k
Activity
May ’25
I can't verify App Store Notification
I'm setting up App Store Notifications for my app. Having trouble verifying even the TEST notification, through. I'm generating JWT-token and sending it via Postman. I get a successful notification UUID as a response. But my Node.JS endpoint says it can't verify it. Here's the endpoint: const fs = require('fs'); const path = require('path'); const { SignedDataVerifier, Environment } = require('@apple/app-store-server-library'); module.exports = function (sqlexec) { function loadRootCAs() { // const gPath = path.resolve(__dirname, "AppleIncRootCertificate.cer"); const g3Path = path.resolve(__dirname, "AppleRootCA-G3.cer"); // const g2Path = path.resolve(__dirname, "AppleRootCA-G2.cer"); const loadedCerts = []; try { // loadedCerts.push(fs.readFileSync(gPath)); loadedCerts.push(fs.readFileSync(g3Path)); // loadedCerts.push(fs.readFileSync(g2Path)); if (loadedCerts.length === 0) { throw new Error("No Apple Root CA certificates were loaded."); } console.log("[APPLE NOTIFICATIONS2] Apple Root CA certificates loaded successfully."); return loadedCerts; } catch (error) { console.error("❌ CRITICAL: Error loading Apple Root CA certificate(s):", error.message); console.error("Ensure 'AppleRootCA-G3.cer' (and others if specified) are present at the expected path and readable."); throw new Error("Failed to load essential Apple Root CA certificates. Notification processing will fail."); } } const appleRootCAs = loadRootCAs(); const enableOnlineChecks = true; const environment = Environment.SANDBOX; const bundleId = "SomeBundleID"; const appAppleId = undefined; // Set if you're in PRODUCTION const verifier = new SignedDataVerifier( appleRootCAs, enableOnlineChecks, environment, bundleId, appAppleId ); router.post('/notifications_refund', async function (req, res) { const signedPayload = req.body.signedPayload; try { const notificationVerificationResult = await verifier.verifyAndDecodeNotification(signedPayload); if (!notificationVerificationResult.isValid) { console.error(`[APPLE NOTIFICATIONS2] Failed to verify notification. Status: ${notificationVerificationResult.verificationStatus}, Error: ${notificationVerificationResult.errorMessage || 'N/A'}`); return res.status(400).json({ status: "error", message: "Invalid notification signature or payload." }); } const decodedNotification = notificationVerificationResult.payload; const notificationType = decodedNotification.notificationType; const subtype = decodedNotification.subtype; if (notificationType === 'TEST') { console.log(`[APPLE NOTIFICATIONS2] Received TEST notification. Subtype: ${subtype}`); // The TEST notification's data.signedTransactionInfo is a JWS representing a sample transaction. } else if (notificationType === 'REFUND') { console.log(`[APPLE NOTIFICATIONS2] Received REFUND notification. Subtype: ${subtype}`); } else { console.log(`[APPLE NOTIFICATIONS2] Received notificationType: ${notificationType}, Subtype: ${subtype}. Skipping non-refund/test type for this endpoint.`); return res.status(200).json({ status: "success", message: "Notification received, but not a type processed by this refund endpoint." }); } // Ensure data and signedTransactionInfo exist if (!decodedNotification.data || !decodedNotification.data.signedTransactionInfo) { console.error("[APPLE NOTIFICATIONS2] Notification payload is missing data or signedTransactionInfo."); return res.status(400).json({ status: "error", message: "Notification missing transaction info." }); } const transactionInfoJWS = decodedNotification.data.signedTransactionInfo; const transactionVerificationResult = await verifier.verifyAndDecodeTransaction(transactionInfoJWS); if (!transactionVerificationResult.isValid) { console.error(`[APPLE NOTIFICATIONS2] Failed to verify signedTransactionInfo. Status: ${transactionVerificationResult.verificationStatus}, Error: ${transactionVerificationResult.errorMessage || 'N/A'}`); return res.status(400).json({ status: "error", message: "Invalid signedTransactionInfo in notification." }); } const verifiedTransactionPayload = transactionVerificationResult.payload; const transactionId = verifiedTransactionPayload.originalTransactionId || verifiedTransactionPayload.transactionId; console.log(`[APPLE NOTIFICATIONS2] Successfully decoded Transaction ID: ${transactionId} for notification type ${notificationType}`); // This is where my refund logic starts in case the notif is refund, but fow now I'm just trying to verify a TEST notif return res.status(200).json({ status: "success", message: "Refund (or TEST) notification processed successfully and validated." }); } catch (error) { console.error("[APPLE NOTIFICATIONS2] Critical error processing notification:", error); // Check if the error is from the verifier or elsewhere if (error.name === 'SignedDataVerificationError') { // Example, check actual error type from library return res.status(400).json({status: "error", message: `Notification verification failed: ${error.message}`}); } return res.status(500).json({ status: "error", message: "Failed to process notification due to an internal server error." }); } }); return router; }; I tried different root certs, only G3 works, other two give errors. Also tried adding G3 intermediate (WWDRCAG3), but it doesn't seem to help. What am I doing wrong?
Replies
0
Boosts
0
Views
88
Activity
May ’25
App Store Analytics API Reports
Hi everyone, I’m new here. I’m working with the App Store Connect (ASC) Analytics API and I have some questions about how to retrieve historical data for downloads and in-app purchases. I’ve created two types of reports: 1. ONGOING: Retrieves current and recent data. 2. ONE_TIME_SNAPSHOT: Supposedly retrieves historical data. I am specifically interested in the Detailed versions: • App Store Downloads Detailed • App Store Purchases Detailed What's my problem? 1. The ONGOING report brings instances with data from the last few days, which seems correct for App Downloads. But not with Purchases, it brings just a few random days and I'm not sure about the granularity. 2. The ONE_TIME_SNAPSHOT report, however, retrieves instances with data from specific days, but the selection of these days appears to be random. I can’t figure out the criteria behind it. Does anyone know what the selection criteria are for the days that appear in the ONE_TIME_SNAPSHOT report? Is it possible to configure the date range for this type of report? Why are some historical dates available while others are not? Is there a way to ensure that the ONE_TIME_SNAPSHOT report brings data from all days within a specified range? Any advice or insights would be greatly appreciated! Thanks in advance!
Replies
0
Boosts
0
Views
276
Activity
May ’25
App Store Connect Metrics via REST API
I hope this message finds you well. I’m reaching out to ask whether specific App Store Connect metrics available in the App Analytics dashboard can also be accessed via the App Store Connect REST API. I have reviewed the official API documentation, but I couldn’t find confirmation regarding the metrics listed below. Could you kindly clarify if the following metrics are available through the REST API? And if so, could you point me to the relevant endpoints or documentation? From the "Usage" group: Installations (Opt-in only) Active Devices Deletions (Uninstalls) From the "App Store" group: Impressions (Unique Devices) Product Page Views (Unique Devices) If these metrics are not available via the REST API, is there an alternative method to programmatically access or export them? Thank you very much in advance for your help and guidance.
Replies
2
Boosts
0
Views
286
Activity
May ’25
Retrieving each user’s “last login” timestamp via the App Store Connect API – is it possible?
Hello everyone, I’m building a custom tool that uses the App Store Connect API (v1) to manage my team’s users. I can successfully list all users with: GET https://api.appstoreconnect.apple.com/v1/users …but the JSON response only includes fields like firstName, lastName, email, allAppsVisible, provisioningAllowed, and roles. There is no lastLogin or timestamp field anywhere in the User object: { "data": [ { "id": "USER_ID", "type": "users", "attributes": { "firstName": "mohit", "lastName": "tiwari", "email": "", "allAppsVisible": false, "provisioningAllowed": false }, "relationships": { … } }, … ] } My main question is: How can I retrieve each user’s “last login” timestamp via the App Store Connect API? Is this even possible with the current endpoints? If it isn’t exposed, has Apple any plans to add this? Or are there any recommended workarounds—perhaps via audit logs or another API—to track when each user last accessed App Store Connect? Thanks in advance for your guidance and any code/endpoint examples you can share!
Replies
0
Boosts
0
Views
159
Activity
May ’25
Recommended way to detect when a user has joined your team via App Store Connect API?
I'm using App Store Connect API to automate onboarding/off-boarding user invitations for my team members when they start working on my app. Once devs join the team, I'd like to invite them to the specific app's beta test group. This requires some additional work after the user has joined the team because unlike visibleApps, there's no way to initially indicate the beta test groups that a user should have access to. One challenge I'm finding is that people don't immediately join the team on time so polling feels a bit excessive/wasteful. Is there some recommended mechanism for adding users to the internal test flight group, or is there a way to trigger a webhook when the list of users is updated. I noticed that there's a new /v1/marketplaceWebhooks API so I imagine this is the recommended format for registering callbacks. Any assistance here would be greatly appreciated.
Replies
1
Boosts
16
Views
588
Activity
Jun ’25
API permison
Hello, I am thinking of developing a parental control app but I do not know if I need to request the api for using "Time Screen API". In positive case, where it should be? Is there anyway to use this for educational centers? Not just for families? Kind regards.
Replies
1
Boosts
0
Views
127
Activity
Jun ’25
App Store Connect API: 'UNIVERSAL' is not a valid value for the attribute 'platform'
Hello, We are encountering an issue when using the App Store Connect API to create a bundle ID via the endpoint: POST https://api.appstoreconnect.apple.com/v1/bundleIds In our request, we specify the platform value "UNIVERSAL", which according to the official documentation is a valid value: BundleIdPlatform documentation However, the API now returns the following error response: { "errors": [ { "code": "ENTITY_ERROR.ATTRIBUTE.TYPE", "detail": "'UNIVERSAL' is not a valid value for the attribute 'platform'. Expected one of: 'IOS', 'MAC_OS'", "status": "409", "title": "An attribute in the provided entity has the wrong type" } ] } According to the documentation, the platform attribute accepts the following values: IOS, MAC_OS and UNIVERSAL. It appears that UNIVERSAL is no longer accepted even though it is still listed as a valid option. Has support for UNIVERSAL been deprecated or changed recently? If so, what is the current recommended way to create bundle IDs that are intended for multiple platforms? Any clarification would be greatly appreciated. Thank you!
Replies
0
Boosts
0
Views
177
Activity
Jun ’25
App Store Connect API
As of June 9, 2025 we are no longer able to automate the creation of our offline provisioning profiles that we used to do on a weekly basis for testing of our internal products offline. I am not sure if the isOfflineProfile was an undocumented attribute that we were using, or if it was deprecated or if the removal of that capability was an oversight. The release notes for 4.0 of the API don't mention a deprecation. https://developer.apple.com/documentation/appstoreconnectapi/app-store-connect-api-4-0-release-notes When making the request to the v1/profiles endpoint to create the provisioning profile we now receive the following response: { "status": "409", "code": "ENTITY_ERROR.ATTRIBUTE.UNKNOWN", "title": "The provided entity includes an unknown attribute", "detail": "'isOfflineProfile' is not an attribute on the resource 'profiles'", "source": { "pointer": "/data/attributes/isOfflineProfile" } }
Replies
3
Boosts
1
Views
377
Activity
Jun ’25
DEVELOPER_ID_APPLICATION_G2 is not recognized by ASC API
Hi, ASC API call rejects DEVELOPER_ID_APPLICATION_G2 and does not recognize it, even though it is listed as a valid certificateType in the docs: https://developer.apple.com/documentation/appstoreconnectapi/get-v1-certificates. If it was removed from the certificate list, then why Apple forces to choose the G2 type when creating a DEVELOPER_ID_APPLICATION certificate manually in the Apple Developer account. Can someone from Apple support please check it as it is a contradictory behavior and is a blocker.
Replies
2
Boosts
0
Views
167
Activity
Jun ’25
Recent change in the AppStoreConnect API broke fastlane
Hi there, Recently, a change was made to the App Store Connect API, which removed the unofficially supported templateName parameter when creating provisioning profiles. This broke fastlane as it was using the templateName parameter. Could that change be reverted or official support added for templateName? I believe this change was first rolled out around March 18th and then reverted shortly after, before being rolled out again around May 6th. The fastlane issue can be seen here: https://github.com/fastlane/fastlane/issues/29498 Official AppStoreConnect API docs for the endpoint are here: https://developer.apple.com/documentation/appstoreconnectapi/profilecreaterequest/data-data.dictionary/attributes-data.dictionary
Replies
2
Boosts
10
Views
712
Activity
Jun ’25
Customer review forbidden error message despite successful auth
Hi, I am seeking assistance and feedback on the below post on feedback assistant. FB18169176 (Customer review API forbidden error) I am calling this endpoint in a python script: https://api.appstoreconnect.apple.com/v1/apps/6450458286/customerReviews?limit=200&sort=-createdDate I can verify that I am getting a valid JWT token. An example is on the feedback link. Yet I am getting this error: { "errors" : [ { "id" : "eda3b456-9aa9-47bd-8736-439db0c73545", "status" : "403", "code" : "FORBIDDEN_ERROR", "title" : "This request is forbidden for security reasons", "detail" : "The API key in use does not allow this request" } ] } Please advise why this is the case, and please assist as this information is needed urgently. Thank you.
Replies
0
Boosts
0
Views
73
Activity
Jun ’25
External Purchase: status 401
Good morning, I am configuring in backend the sending of reports regarding purchases made in app with external platform (Stripe) as per documentation. To be clear I am talking about ExternalPurchase. However, when I make the call it returns "Apple responded with status 401". I verified the token on jwt.io as per documentation and it is working. I don't understand where I am going wrong. Below I share the code with you: const express = require("express"); const bodyParser = require("body-parser"); const jwt = require("jsonwebtoken"); const fs = require("fs"); const app = express(); const https = require("https"); const APPLE_KEY_ID = "XXXXX"; const APPLE_ISSUER_ID = "xxx-xxx-xxx-xx-xxxxxx"; const APPLE_PRIVATE_KEY = fs.readFileSync("AuthKey_xxxxx.p8", "utf8"); const APPLE_AUDIENCE = "appstoreconnect-v1"; function generateAppleJwt() { const now = Math.floor(Date.now() / 1000); const payload = { iss: APPLE_ISSUER_ID, iat: now, exp: now + (5 * 60), aud: APPLE_AUDIENCE }; return jwt.sign(payload, APPLE_PRIVATE_KEY, { algorithm: "ES256", header: { alg: "ES256", kid: APPLE_KEY_ID, typ: "JWT" } }); } app.post('/webhook', bodyParser.json({ type: 'application/json' }), async (req, res) => { let eventType = req.body.type; const relevantEvents = [ "invoice.paid" ]; if (relevantEvents.includes(eventType)) { try { const data= req.body.data; const platform = data.object.subscription_details.metadata.platform; if (platform === "IOS") { const token = generateAppleJwt(); const applePayload = { appAppleId: "xxxx", bundleId: 'com.xxx.xxx.test', externalPurchaseId: data.object.id, purchaseTime: new Date(data.object.created * 1000).toISOString(), purchaseAmount: { amount: (data.object.total / 100).toFixed(2), currencyCode: data.object.currency.toUpperCase() }, purchaseLocation: { isoCountryCode: "IT" } }; const jsonString = JSON.stringify(applePayload); const agent = new https.Agent({ keepAlive: false }); const response = await fetch( "https://api.storekit-sandbox.apple.com/externalPurchase/v1/reports", { method: "PUT", headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", "Accept-Encoding": "identity", }, body: JSON.stringify(applePayload), } ); if (!response.ok) { const errorText = await response.text(); throw new Error( `Apple responded with status ${response.status}: ${errorText}` ); } console.log("✅ Notifica inviata ad Apple con successo"); } else { if(!canSendNotification){ console.log("Non è una Sub. Nessuna notifica inviata."); }else{ console.log("Customer non iOS. Nessuna notifica inviata."); } } } catch (err) { console.error("Errore durante l’invio ad Apple:"); if (err.response) { console.error("Status:", err.response.status); console.error("Headers:", err.response.headers); console.error("Data:", err.response.data); } else { console.error("Message:", err.message); } } } res.status(200).send("OK"); }); exports.checkSubStripe = functions.https.onRequest(app);
Replies
0
Boosts
0
Views
155
Activity
Jun ’25
External Purchase: Error 401
Good morning, I am configuring in backend the sending of reports regarding purchases made in app with external platform (Stripe) as per documentation. To be clear I am talking about ExternalPurchase. However, when I make the call it returns "Apple responded with status 401". I verified the token on jwt.io as per documentation and it is working. I don't understand where I am going wrong. Below is the code: const express = require("express"); const bodyParser = require("body-parser"); const jwt = require("jsonwebtoken"); const fs = require("fs"); const app = express(); const https = require("https"); const APPLE_KEY_ID = "xxx"; const APPLE_ISSUER_ID = "xxx-xxx-xxx-xxx-xxx"; const APPLE_PRIVATE_KEY = fs.readFileSync("AuthKey_xxx.p8", "utf8"); const APPLE_AUDIENCE = "appstoreconnect-v1"; function generateAppleJwt() { const now = Math.floor(Date.now() / 1000); const payload = { iss: APPLE_ISSUER_ID, iat: now, exp: now + (5 * 60), aud: APPLE_AUDIENCE }; return jwt.sign(payload, APPLE_PRIVATE_KEY, { algorithm: "ES256", header: { alg: "ES256", kid: APPLE_KEY_ID, typ: "JWT" } }); } app.post('/webhook', bodyParser.json({ type: 'application/json' }), async (req, res) => { let eventType = req.body.type; const relevantEvents = [ "invoice.paid" ]; if (relevantEvents.includes(eventType)) { try { const data= req.body.data; const platform = data.object.subscription_details.metadata.platform; if (platform === "IOS") { const token = generateAppleJwt(); const applePayload = { appAppleId: "xxx", bundleId: 'com.xxx.xxx.test', externalPurchaseId: data.object.id, purchaseTime: new Date(data.object.created * 1000).toISOString(), purchaseAmount: { amount: (data.object.total / 100).toFixed(2), currencyCode: data.object.currency.toUpperCase() }, purchaseLocation: { isoCountryCode: "IT" } }; const jsonString = JSON.stringify(applePayload); const agent = new https.Agent({ keepAlive: false }); const response = await fetch( "https://api.storekit-sandbox.apple.com/externalPurchase/v1/reports", { method: "PUT", headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", "Accept-Encoding": "identity", }, body: JSON.stringify(applePayload), } ); if (!response.ok) { const errorText = await response.text(); throw new Error( `Apple responded with status ${response.status}: ${errorText}` ); } console.log("✅ Notifica inviata ad Apple con successo"); } else { if(!canSendNotification){ console.log("Non è una Sub. Nessuna notifica inviata."); }else{ console.log("Customer non iOS. Nessuna notifica inviata."); } } } catch (err) { console.error("Errore durante l’invio ad Apple:"); if (err.response) { console.error("Status:", err.response.status); console.error("Headers:", err.response.headers); console.error("Data:", err.response.data); } else { console.error("Message:", err.message); } } } res.status(200).send("OK"); }); exports.checkSubStripe = functions.https.onRequest(app);
Replies
0
Boosts
0
Views
125
Activity
Jun ’25
Failing to create leaderboard via the API
I'm getting the following error when attempting to create a leaderboard via the documented POST url. Here is the full error response: { "errors" : [ { "id" : "xxxxxx", "status" : "400", "code" : "ENTITY_INVALID", "title" : "The request entity is not valid json", "detail" : "The request entity data could not be processed. Please ensure you are sending valid json.", "meta" : { "position" : { "row" : 1, "column" : 1 } } } ] } Except what I'm sending IS valid JSON, emitted directly from JsonUtility and verified via JSONLint: {"data":{"type":"gameCenterLeaderboards","attributes":{"defaultFormatter":"INTEGER","referenceName":"TEST_AUTO","vendorIdentifier":"TEST_AUTO","submissionType":"MOST_RECENT_SCORE","sortScoreType":"DESC","scoreRangeStart":"0","scoreRangeEnd":"20000","recurrenceStartDate":"2025-06-25T13:00:00Z","recurrenceDuration":"PT30M","recurrenceRule":"FREQ=HOURLY;INTERVAL=1"},"relationships":{"gameCenterDetail":{"data":{"type":"gameCenterDetails","id":"xxxxxx"}}}}} This follows EXACTLY the pattern in the App Store Connect documentation. I've even tried sending the example JSON they give (swapping out the id and fixing the quotation marks) and the server rejects THAT as not being valid JSON. What am I doing wrong? Thanks!
Replies
0
Boosts
0
Views
93
Activity
Jun ’25
Fails to Find AuthKey .p8 in Unity Cloud Build Post-Build Script
Hi everyone!I got an error trying to automatically upload my .ipa to TestFlight through Post-build script (Unity Cloud Build (Build Automation)): [error] ErrorDomain=ITunesConnectionAuthenticationErrorDomain Code=-26000 “Failed to generate JWT token: ErrorDomain=NSCocoaErrorDomain Code=-43 ‘Failed to load AuthKey file.’ The file ‘AuthKey_<YOUR_API_KEY>.p8’ could not be found in: • /BUILD_PATH/.../private_keys • ~/private_keys • ~/.private_keys • ~/.appstoreconnect/private_keys” In post-build.bash I tried the following two options but both don't work: 1 option: KEY_WITH_NEWLINES=$(echo $CONNECT_API_KEY | jq '.private_key |= sub(" (?!PRIVATE|KEY)"; "\n"; "g")' -c -j) echo $KEY_WITH_NEWLINES > ~/.appstoreconnect/private_keys/AuthKey_${API_KEY_ID}.p8 2 option: mkdir -p ~/.appstoreconnect/private_keys echo "$CONNECT_API_KEY" | jq -r '.private_key' > ~/.appstoreconnect/private_keys/AuthKey_${API_KEY_ID}.p8 In Unity Cloud Build (Build Automation) → Advanced Options → Environment Variables → Variable value for CONNECT_API_KEY I indicate in the following format: {"private_key":"-----BEGIN PRIVATE KEY-----\nMIIEv...\n-----END PRIVATE KEY-----"} Please tell me how to fix the error? Is it wrong in my code in post-build.bash or the input format Variable value for CONNECT_API_KEY?
Replies
2
Boosts
0
Views
208
Activity
Jun ’25
Failed to create profile via the api
When I use the Enterprise Program API to create a profile via https://developer.apple.com/documentation/enterpriseprogramapi/create-a-profile, I got an error: org.springframework.web.client.HttpClientErrorException$MethodNotAllowed: 405 Not Allowed: "{?"errors": [{??"status": "405",??"code": "METHOD_NOT_ALLOWED",??"title": "The request method is not valid for the resource path.",??"detail": "The request method used for this request is not valid for the resource path. Please consult the documentation."?}]}". I tried the empty body to request, but still got the same error. So I doubt the sever is not allow the POST method. Please help me to resolve this problem, thx!
Replies
1
Boosts
0
Views
82
Activity
Jul ’25
Unable to recreate a ONE_TIME_SNAPSHOT analytics report
Previously created a one time snapshot report that was later deleted. Trying to recreate it but keeping getting a 409. Been already a week since it was deleted. Error message { "errors" : [ { "id" : "b0d8d738-1744-463d-bdcf-a48fbfa5d715", "status" : "409", "code" : "STATE_ERROR", "title" : "The request cannot be fulfilled because of the state of another resource.", "detail" : "You already have such an entity" } ] } Reports request only returns the ONGOING reports link no ONE_TIME_SNAPSHOT . Wonder if it takes time although unlikely or might it be a cache or similar causing the problem.
Replies
4
Boosts
1
Views
1.5k
Activity
Jul ’25