●FREETRY — Rork announced on X that Rork Max is free to try for a limited time, a chance to touch the $200/month tier without paying up front●ONESHOT — Rork Max claims to one-shot almost any app for iPhone, Watch, iPad, TV, and Vision Pro, including builds that lean on AR and 3D●XCODE — Positioned as a website that replaces Xcode: one click to install on device, two clicks to publish to the App Store, powered by Swift, Claude Code, and Opus●PAPERLINE — Rork acquired the app builder Paperline and says it will stay acquisitive to bring in engineering talent●SKILLS — Rork's app-store-connect-cli-skills repo was updated on July 9, a sign the submission tooling keeps getting attention●MARKET — Gartner expects 75% of new applications to be built with low-code or no-code tools by the end of 2026, up from under 25% in 2020●FREETRY — Rork announced on X that Rork Max is free to try for a limited time, a chance to touch the $200/month tier without paying up front●ONESHOT — Rork Max claims to one-shot almost any app for iPhone, Watch, iPad, TV, and Vision Pro, including builds that lean on AR and 3D●XCODE — Positioned as a website that replaces Xcode: one click to install on device, two clicks to publish to the App Store, powered by Swift, Claude Code, and Opus●PAPERLINE — Rork acquired the app builder Paperline and says it will stay acquisitive to bring in engineering talent●SKILLS — Rork's app-store-connect-cli-skills repo was updated on July 9, a sign the submission tooling keeps getting attention●MARKET — Gartner expects 75% of new applications to be built with low-code or no-code tools by the end of 2026, up from under 25% in 2020
The Update That Failed Because a Profile Expired Three Months Ago
Apple signing assets expire quietly and nothing tells you. Here is how to count the days left with the App Store Connect API and put the audit on a weekly Cloudflare Workers cron.
I opened an old app to make a small fix. The build succeeded. Submission stopped cold. The provisioning profile had expired three months earlier.
The expiry itself was not the problem. The problem was that nothing existed to tell me it had happened. Certificates and profiles lapse in silence. The next person to attempt a build finds out — and that person is always in a hurry.
Rork and Rork Max deepen this blind spot. Compilation happens in the cloud, submission takes two clicks, and the whole thing feels frictionless. Meanwhile your signing assets sit inside your own Apple Developer account, aging where you cannot see them.
Running several apps in parallel as a solo developer, I could not hold "which profile on which app expires when" in my head. So I handed the counting to a machine.
What Expires, and What Actually Breaks
Get the mechanics right before designing a response. Misreading this table leads to panic over problems that do not exist.
Asset
Typical lifetime
What happens at expiry
Apple Distribution certificate
~3 years
You cannot sign new builds. Shipped apps keep running
Apple Development certificate
~1 year
Device debugging signatures stop working
App Store provisioning profile
~1 year
You cannot produce submission builds. Shipped apps are unaffected
Ad Hoc / Development profile
~1 year
Builds distributed with it stop launching
APNs auth key (.p8)
No expiry
Never lapses. Only rotate on loss or exposure
App Store Connect API key
No expiry
Never lapses. Rotate on personnel change or exposure
The two bolded rows are where the confusion lives.
Apps already distributed through the App Store do not stop working when your certificate or profile expires. Apple validates signatures at review and distribution time, not on every launch on every device. Get this backwards and you will respond to an emergency that is not happening.
Ad Hoc and development-signed builds behave differently. There, the profile expiry is enforced on-device, and your testers really do find the app refusing to open.
So the practical damage sorts out like this:
Live apps: no user impact — but you cannot fix anything until signing is restored
Internal or tester builds: real impact, on the expiry date, with no warning
An urgent bug fix: the worst pairing. You rebuild your signing chain exactly when you have no time for it
I went three months without noticing precisely because nothing broke. The absence of damage is what removed every opportunity to find out.
Who Holds the Keys in Rork and Rork Max
Worth pinning down, because a vague answer here becomes "surely Rork handles that for me."
Path
Where signing assets live
What remains yours to manage
Rork (React Native + Expo) via EAS Build
Expo's servers (remote credentials)
The certificates in your Apple account, and the slot limit
Rork Max (cloud Mac compilation)
Auto-generated through an API key Rork holds
The key's role, plus auditing what got generated
Local Xcode archive
Your local keychain
Everything, backups included
Whichever path you take, the certificates and profiles themselves live in your Apple Developer account. They are your assets. Build services merely issue and fetch them on your behalf. The slot limit fills up in your account. The clock runs down in your account.
Automatic generation is convenient. Convenience is not the same thing as awareness. I have written more on where these boundaries fall in the Rork Max and Expo responsibility split, if that context is useful.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦What actually breaks when a certificate expires — and the widely misunderstood case that does not
✦A dependency-free Node.js script that reads expiry dates straight from the App Store Connect API
✦A weekly Cloudflare Workers cron that only speaks up when something is genuinely close to lapsing
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
Apple exposes certificates and profiles over its API, so you can count for yourself.
You need three things:
A Team Key (App Store Connect → Users and Access → Integrations → App Store Connect API)
Your Issuer ID (the UUID shown at the top of that same screen)
The .p8 private key file (downloadable exactly once, at creation)
One caveat. API keys come in Individual and Team flavours, and certificate and profile endpoints require a Team Key. An insufficient role returns 403 FORBIDDEN even for read-only calls, so give the key an Admin-level role.
The Token Trap Everyone Hits Once
App Store Connect uses ES256 JWTs. There is a pothole here that catches most first implementations.
Hand an EC key to Node's crypto.createSign() and the signature comes back DER-encoded. ES256 in a JWT requires the raw 64-byte R‖S concatenation (IEEE P1363). Base64URL the DER bytes and your request returns a flat 401 UNAUTHORIZED — correct key, correct payload, silent rejection.
dsaEncoding: "ieee-p1363" fixes it. That single option costs people an afternoon, so it goes near the top here.
// audit-signing.mjs — no dependencies (Node.js 18+)import { createSign } from "node:crypto";import { readFileSync } from "node:fs";const ISSUER_ID = process.env.ASC_ISSUER_ID; // e.g. 57246542-96fe-1a63-e053-0824d011072aconst KEY_ID = process.env.ASC_KEY_ID; // e.g. 2X9R4HXF34const PRIVATE_KEY = readFileSync(process.env.ASC_P8_PATH, "utf8");const base64url = (input) => Buffer.from(input).toString("base64") .replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");function createToken() { const now = Math.floor(Date.now() / 1000); const header = { alg: "ES256", kid: KEY_ID, typ: "JWT" }; const payload = { iss: ISSUER_ID, iat: now, exp: now + 15 * 60, // 20 minutes is the ceiling; 15 leaves room aud: "appstoreconnect-v1", }; const signingInput = `${base64url(JSON.stringify(header))}.${base64url(JSON.stringify(payload))}`; // The whole point. Omit dsaEncoding and you get DER, and then a 401. const signature = createSign("SHA256") .update(signingInput) .sign({ key: PRIVATE_KEY, dsaEncoding: "ieee-p1363" }); return `${signingInput}.${base64url(signature)}`;}
exp cannot exceed 20 minutes from issuance. Reach for longer and the token is rejected, so just mint a fresh one per run.
Fetching the Lists
With a token in hand, two endpoints do the rest. Narrowing fields[...] keeps responses small and readable.
🔴 -87d profile IOS_APP_ADHOC Ad Hoc — wallpaper-legacy
🔴 11d profile IOS_APP_STORE App Store — relax-sounds
🟡 52d profile IOS_APP_STORE App Store — wallpaper-hd
210d profile IOS_APP_STORE App Store — affirmation
688d certificate DISTRIBUTION Apple Distribution: Masaki H.
That top row expired 87 days ago. It belongs to an app I no longer ship, so it cost me nothing. But lining everything up is exactly how the things you stopped seeing become visible again.
Watch profileState too. A profile can have days remaining and still be INVALID — revoking the certificate it hangs off invalidates it immediately. Sorting on days alone will miss that, so read the state alongside the number.
Running It Weekly on Cloudflare Workers
A script you run by hand is a script that stops existing the moment you stop running it. I was not going to remember a maintenance script for an app I touch twice a year.
So it went onto a Cloudflare Workers cron. I already keep other operational jobs there, which made the marginal cost near zero.
Web Crypto builds the same JWT, and sign() returns raw R‖S natively — no DER problem to work around this time.
[triggers]crons = ["0 0 * * 1"] # Mondays at 00:00 UTC
Load the .p8 contents with wrangler secret put ASC_P8. Put it in a config file and it lives in your repository forever.
Sixty days is deliberate. Thirty is too late. Renewing a certificate forces profile regeneration, which forces a rebuild — a chain that takes weeks to work through when development happens in the gaps between other commitments.
The silence matters as much as the threshold. Nothing is sent when nothing is close. A weekly "all clear" goes unread by the third week. I want the notification to mean something on the day it arrives.
Renew in the Right Order
Once a warning lands, sequence matters. Get it wrong and you do the work twice.
Check the certificate first. Profiles hang off certificates. Regenerating a profile against a certificate that is about to expire buys you nothing
Free a slot before you need one. Apple caps you at two distribution certificates. Creating the replacement early requires room to create it in
Regenerate profiles against the new certificate. Rork Max and EAS usually handle this step for you
Push one build all the way through. Nothing is confirmed until a submission actually completes
On step two: revoking the old certificate frees the slot, but every Ad Hoc build signed with it stops launching that instant. App Store distribution is untouched. If testers are holding builds, plan the swap before you revoke.
I settled on: create the new certificate before the old one lapses, revoke the old one, use the new one on the next submission. Its real virtue is that no decision gets made while I am in a hurry.
Where This Falls Short
Being straight about the limits.
This audit only sees what lives in your Apple Developer account. It cannot see EAS remote credential state, or whatever Rork holds internally. When those drift apart, the API returns Apple's view of the truth and says nothing about the build service's inconsistency.
It also reports; it does not renew. I considered automating renewal and backed off. Reissuing signing material is hard to undo, and at solo-developer scale, leaving a human in the loop is the better trade.
If there is an app you have not touched in a while.
Issue one Team Key and run the script above, once. Fifteen minutes, give or take. You will probably see a red row or two. I did.
Decide about the cron afterwards, once you have seen what was hiding. Start by finding out how many days are actually left.
I spent about three years telling myself I would organise this eventually, right up until I built it. If you are stalled in the same place, I hope this gets you moving.
Share
Thank You for Reading
Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.