App Store Connect API से स्क्रीनशॉट अपलोड करना
appScreenshotSet बनाएँ, एक appScreenshot रिज़र्व करें (Apple आपको pre-signed chunk URLs देता है), बाइट्स को PUT करें, फिर uploaded: true और फ़ाइल के MD5 के साथ रिज़र्वेशन को PATCH करें। ऑथ आपकी .p8 key से साइन किया गया 20-मिनट का JWT है। इसमें से कुछ भी मुश्किल नहीं है; सब कुछ बारीक और झंझटभरा है। यहाँ असली क्रम है — और अगर आप इसे खुद नहीं बनाना चाहते तो शॉर्टकट भी।इसके लिए Apple का डॉक्यूमेंटेशन सटीक तो है पर लगभग अपठनीय है — यह फ़्लो एक दर्जन रेफरेंस पेजों में बिखरा है, बिना किसी एक worked example के। यह पोस्ट वही एक walkthrough है, क्रम में, सटीक endpoints और field names के साथ। यह उस व्यक्ति के लिए लिखा गया है जो तय कर रहा है कि यह integration खुद बनाए या ऐसा टूल इस्तेमाल करे जिसमें यह पहले से मौजूद हो।
वह ऑब्जेक्ट मॉडल जिसमें आप अपलोड कर रहे हैं
स्क्रीनशॉट सीधे आपके ऐप से नहीं जुड़ते। वे objects की एक चेन में लटके होते हैं, और आपको इसे ऊपर से नीचे तक चलना पड़ता है:
- appStoreVersion — वह ख़ास version जिसे आप एडिट कर रहे हैं (एडिटेबल state में होना चाहिए)।
- appStoreVersionLocalization — हर App Store locale के लिए एक (en-US, de-DE, ja, वगैरह)।
- appScreenshotSet — एक localization के अंदर हर display type के लिए एक (6.7" iPhone, 13" iPad, आदि)।
- appScreenshot — इंडिविजुअल इमेज, प्रति सेट अधिकतम 10।
तो दो iPhone साइज़ और एक iPad साइज़ में 50 भाषाओं में पूरी तरह localized listing = 50 × 3 sets, हर एक में अधिकतम 10 स्क्रीनशॉट। यही गुणा वह पूरी वजह है जिसकी वजह से लोग वेब UI पर क्लिक करने के बजाय इसे automate करते हैं।
चरण 0 — 20-मिनट का JWT
बाइट अपलोड को छोड़कर हर रिक्वेस्ट को एक Authorization: Bearer header चाहिए, जिसमें आपका खुद साइन किया गया JSON Web Token हो। इसके लिए App Store Connect से तीन चीज़ें चाहिए: आपकी issuer ID, एक key ID, और .p8 प्राइवेट key फ़ाइल जो key बनाते समय एक बार डाउनलोड होती है (Apple इसे दोबारा कभी नहीं दिखाता)।
टोकन को ES256 से साइन किया जाता है — P-256 curve पर ECDSA विद SHA-256 — उसी .p8 key से। Header में kid होता है; payload में issuer iss के रूप में, audience लिटरल स्ट्रिंग appstoreconnect-v1 के रूप में, और exp जो iat से 20 मिनट से ज़्यादा दूर न हो। Apple इससे ज़्यादा lifetime वाली कोई भी चीज़ रिजेक्ट कर देता है, इसलिए आपको बार-बार regenerate करना पड़ता है — एक टोकन खुला रखने की कोशिश करने के बजाय हर batch के लिए फ्रेश टोकन बनाएँ।
इसे किसी भी ES256-सक्षम JWT library (jsonwebtoken, PyJWT, आपका स्टैक जो भी इस्तेमाल करता हो) से साइन करें, .p8 कंटेंट्स को key के रूप में पास करें, और नीचे दिए हर कॉल में रिज़ल्ट को Authorization header में डालें।
चरण 1 — स्क्रीनशॉट सेट बनाएँ
एक सेट एक locale और एक display type तक सीमित होता है। आप /v1/appScreenshotSets पर POST करते हैं, attributes में screenshotDisplayType और उस appStoreVersionLocalization की ओर इशारा करने वाला relationship देकर, जिसे आप भरना चाहते हैं।
आम display types: APP_IPHONE_67 (6.7", 1290 × 2796), मौजूदा बड़े-iPhone साइज़, और APP_IPAD_PRO_3GEN_129 (12.9", 2048 × 2732)। एक उपयोगी चीज़ जो Apple अब करता है: सबसे बड़े iPhone साइज़ के स्क्रीनशॉट छोटी iPhone क्लासेस में और सबसे बड़े iPad साइज़ छोटे iPads में दोबारा इस्तेमाल हो जाते हैं — इसलिए व्यवहार में अक्सर प्रति locale एक ही iPhone सेट और एक ही iPad सेट चाहिए होता है, हर फ़िज़िकल डिवाइस के लिए अलग नहीं। रिस्पॉन्स आपको एक सेट id देता है जिसे आप अगले कॉल में इस्तेमाल करते हैं। अगर उस locale/type के लिए सेट पहले से मौजूद है तो नया बनाने के बजाय उसी को दोबारा इस्तेमाल करें।
चरण 2 — स्क्रीनशॉट रिज़र्व करें
आप फ़ाइल एक ही झटके में अपलोड नहीं करते। पहले आप उसे रिज़र्व करते हैं: Apple को फ़ाइल का नाम और सटीक बाइट साइज़ बताते हैं, और वह एक अपलोड plan वापस देता है। /v1/appScreenshots पर fileName और fileSize attributes के साथ, और चरण 1 वाले सेट के लिए relationship देकर POST करें।
रिस्पॉन्स ही दिलचस्प हिस्सा है। नए स्क्रीनशॉट के attributes के अंदर आपको uploadOperations मिलता है — एक array जो बताता है कि बाइट्स को कैसे पुश करना है। छोटी फ़ाइल के लिए यह एक ऑपरेशन है; बड़ी फ़ाइल के लिए Apple इसे कई हिस्सों में बाँट देता है। हर एंट्री आपको एक method (PUT), एक pre-signed url, भेजने के लिए बाइट offset और length, और लगाने के लिए requestHeaders देती है।
चरण 3 — बाइट्स को PUT करें
हर ऑपरेशन के लिए, अपनी फ़ाइल का वह हिस्सा पढ़ें जो offset से length बाइट्स तक है, और उसे दिए गए url पर, Apple द्वारा दिए गए requestHeaders के साथ ही PUT करें। ये URLs pre-signed हैं, इसलिए यहाँ आप अपना JWT नहीं भेजते — Authorization header जोड़ना असल में signed request को तोड़ सकता है। कई ऑपरेशन समानांतर में चल सकते हैं, लेकिन Apple आक्रामक अपलोड को rate-limit करता है, इसलिए सब कुछ एक साथ दागने के बजाय इन्हें retry-with-backoff में लपेटें।
चरण 4 — uploaded: true और MD5 के साथ कमिट करें
बाइट्स अपलोड करने से तब तक कुछ नहीं होता जब तक आप Apple को नहीं बताते कि रिज़र्वेशन पूरा हो गया है। स्क्रीनशॉट को uploaded: true और एक sourceFileChecksum के साथ PATCH करें — पूरी फ़ाइल का MD5, lowercase hex स्ट्रिंग के रूप में। यह Apple का इंटीग्रिटी चेक है: अगर checksum उससे मेल नहीं खाता जो पहुँचा, तो प्रोसेसिंग फेल हो जाती है।
PATCH के बाद, asset Apple की तरफ़ प्रोसेसिंग में जाता है। स्क्रीनशॉट की assetDeliveryState को तब तक पोल करें जब तक वह पूर्ण अवस्था तक न पहुँचे — और अगर वह फेल हो तो उसकी errors पढ़ें, क्योंकि यहीं गलत-डाइमेंशन और alpha-channel जैसी समस्याएँ सामने आती हैं। PUT और PATCH दोनों सफल हो सकते हैं और फिर भी स्क्रीनशॉट कुछ मिनट बाद प्रोसेसिंग के दौरान रिजेक्ट हो सकता है। यह न मानें कि कमिट पर 2xx आने का मतलब काम पूरा हो गया; delivery state पर नज़र रखें।
चरण 5 — डिस्प्ले ऑर्डर सेट करें
स्क्रीनशॉट जिस भी क्रम में बनाए गए थे उसी क्रम में वापस आते हैं, जो शायद ही वह क्रम हो जो आप दिखाना चाहते हैं। सेट का ऑर्डर एक अलग relationship है। /v1/appScreenshotSets/{id}/relationships/appScreenshots को स्क्रीनशॉट IDs के array के साथ PATCH करें — array का क्रम ही स्टोर पर दिखने वाला क्रम है।
वे शर्तें जो Apple वाकई लागू करता है
- सिर्फ़ PNG या JPEG। कोई HEIC नहीं, कोई WebP नहीं। अगर आपका renderer कुछ और आउटपुट करता है, तो पहले उसे कन्वर्ट करें।
- कोई alpha चैनल नहीं। ट्रांसपेरेंसी को सॉलिड बैकग्राउंड में फ़्लैटन करें और RGB में एक्सपोर्ट करें। एक भटका हुआ alpha चैनल प्रोसेसिंग के समय सबसे आम साइलेंट रिजेक्शन में से एक है।
- हर display type के लिए एग्ज़ैक्ट डाइमेंशन। इमेज को display type के पिक्सल साइज़ से बिल्कुल मैच करना होगा — न थोड़ा इधर-उधर, न अपस्केलिंग। एक 1290 × 2796 फ़ाइल
APP_IPHONE_67सेट में ही जाती है, कहीं और नहीं। - प्रति सेट अधिकतम 10। प्रति localization प्रति display type अधिकतम दस स्क्रीनशॉट।
- सिर्फ़ एडिटेबल version। आप सिर्फ़ उसी version के सेट्स बदल सकते हैं जो एडिटेबल state में हो; जो version पहले से review में है वह लॉक्ड होता है।
यह दिखने से ज़्यादा काम क्यों है
हर इंडिविजुअल कॉल सिंपल है। लागत इनके इर्द-गिर्द की हर चीज़ में है: 20-मिनट के टोकन को साइन और रोटेट करना, MD5 निकालना, uploadOperations से मेल खाने के लिए फ़ाइलों को स्लाइस करना, आधे-अधूरे अपलोड के retry संभालना, delivery state पोल करना, अपने locales को Apple के locale codes से मैप करना, और एक पूरी तरह localized listing के लिए यह सब 150-प्लस बार करना। इसे रिलायबल बनाना auth, chunked अपलोड, और error handling में एक पूरे वीकेंड का काम है — और फिर हर बार जब Apple कोई नया display type जोड़े, इसे मेंटेन करना आपकी ज़िम्मेदारी है।
अगर आप इसे खुद लिखे बिना यह क्रम चाहते हैं, तो Fastlane का deliver इसी API को रैप करता है और यह स्टैंडर्ड ओपन-सोर्स रास्ता है। यह अपलोड को हल करता है; यह स्क्रीनशॉट डिज़ाइन नहीं करता या कैप्शन नहीं लिखता।
…या यह सब स्किप करें
API सिर्फ़ तैयार इमेज को मूव करता है। कैरोसेल डिज़ाइन करना, कैप्शन लिखना, और हर locale के लिए उन्हें ट्रांसलेट करना — जो असल में समय लेने वाला हिस्सा है — अभी भी किसी को करना पड़ता है। Mokbi यह आज ही करता है: यह स्क्रीनशॉट डिज़ाइन करता है, लिस्टिंग कॉपी लिखता है, और पूरी चीज़ को 50 भाषाओं में ट्रांसलेट करता है, फिर हर इमेज को इस API की माँगी हुई सटीक प्रति-display-type डाइमेंशन में एक्सपोर्ट करता है — PNG, RGB, बिना alpha, सही साइज़ — ताकि delivery-state चरण में अपलोड न अटके।
फिर यह पब्लिश करता है। App Store के लिए, Mokbi इसी reserve-and-commit क्रम को अंदर ही अंदर चलाता है — आपके स्क्रीनशॉट और मेटाडेटा अपलोड करके और version को submit के लिए तैयार करके स्टेज करता है, ताकि आपको कभी JWT या chunked PUT को छूना न पड़े। Apple अभी भी अंतिम Submit और अपना review माँगता है; उस बिंदु तक बाकी सब आपके लिए संभाला जाता है। Google Play के लिए यह सीधे स्टोर पर पुश करता है। दोनों ही तरीकों में आपको कोड लिखे बिना API का नतीजा मिलता है, साथ में डिज़ाइन, कॉपी, और 50-भाषा ट्रांसलेशन पहले से तैयार।