iOS app transfer: Invotek AS to Dashecorp¶
Tracking issue: Stig-Johnny/claude-3#954. Research date: 2026-09-25. It was read-only against the live App Store Connect (ASC), RevenueCat and Cuti-E accounts and shallow clones of the app repos. A skeptic pass then re-checked the Apple rules against Apple's pages.
Goal and scope¶
The operator on 2026-09-25: "One thing that we should shift focus on is moving all the ios apps from Invotek to Dashecorp".
The GitHub repos already moved to dashecorp/dashe-* on 2026-06-09 (see apps.md). This doc covers the Apple
side:
- the App Store Connect app transfer from Invotek AS (Team ID
V9GL74NMBR) to a Dashecorp organization account; - everything else that is bound to the Apple team: signing, keys, App Groups, keychain, Sign in with Apple, RevenueCat, the Cuti-E backend.
In scope: all 10 apps in the Invotek account.
Phase 2 (later):
- Reward-E's Google Play listing (no.invotek.rewarde). This is a separate Google transfer, and RevenueCat then needs
new Play credentials, which take about 36 h to propagate.
- Nutri-E's Cloudflare account (invotekas.workers.dev). It is not Apple-bound, but moving it changes every backend
URL in the app, in ASC and in RevenueCat. The Cuti-E SDK also pins invotekas.workers.dev hosts
(ios-sdk/Sources/CutiE/CutiECertificatePinning.swift:51-52).
Status (2026-10-22)¶
- Invotek AS membership: renewed on 2026-09-25 for one year. Apple's notice said it would expire about 2026-09-27. The next renewal is due by 2027-09-27 and is tracked. Had it lapsed, all apps would have left the store on the expiry date, and transfers would not have been possible (see Membership expiry).
- Dashecorp's Apple Developer organization account: enrollment submitted on 2026-09-25, enrollment ID
Q9ATJ2JTS9. It is the first prerequisite, because a transfer cannot even be started without the recipient's Team ID. - Legal entity: DASHECORP AS, org.nr 937596804 (Brønnøysund; Invotek AS is 931930354).
- D-U-N-S 348443217: Apple's lookup found an existing number, so no D&B request was needed.
- Account Holder: the operator, with the stig@dashecorp.com Apple Account. Website https://dashecorp.com.
- Accepted on 2026-09-30, three business days after submission. Apple's email "Your enrollment request has been accepted" reached the operator, who forwarded it in chat at 12:59:13Z. Deadline #35 (check by 2026-10-02) is met.
- Paid on 2026-10-22. The operator accepted the Apple Developer Program License Agreement, version LYL260, and
bought the membership: Apple order W1697812260, kr 779 for one year (the local price). A copy of the agreement and
the company's records are in the Dashecorp brain,
dashecorp/dashecorpselskap/apple.md. Apple activates the membership within 24 h of payment. Deadline #35 is complete, and #49 tracks the Team ID. - Next, operator only: record the Dashecorp Team ID here (Plan step 1). Then, in App Store Connect: the Paid Apps agreement (needed for subscriptions), tax and banking, and EU trader status.
- Moving apps between the two companies is an intercompany transfer of assets, which is a question for the accountant, not this doc.
- Cuti-E production deploys are automatic; only destructive migrations wait (2026-09-29). infra#468 first made the
operator the required reviewer of dashe-cutie's
productionenvironment, and dashe-cutie#39 put the Pages deploys behind it. The same day, claude-3#976 replaced that with automatic deploys through the ungated,main-onlyproduction-autoenvironment. The Worker and Pages sites deploy on merge, and the Worker rolls back when a production smoke test fails. A pending D1 migration that is destructive or unclassifiable still waits onproductionfor the operator. Seeci-cd.md§ dashe-cutie production deploys. - nutrieapp.com was to lapse on 2026-10-05, unrenewed (claude-3#975), but Domeneshop auto-renewed it on 2026-10-06 until 2027-10-05. It is kept until
post@nutrieapp.comis deleted (operator-inbox#42), because whoever registers a released domain could receive that account's recovery mail; the lapse is decided by 2027-08-15 (deadline #46). The live 1.0.5 links to dashecorp.com; 1.0.4 and older still link to nutrieapp.com, which gets 301s (dashecorp/infra#646). Nutri-E's App Store URLs, the iOS and Android apps and Cuti-E's data already point at dashecorp.com. The last dependency was Google Cloud:post@nutrieapp.comwas the only owner of the rig's project and of six others. On 2026-09-29stig@dashecorp.combecame Owner of all seven and an administrator of billing account015BBC-422A59-EB7AF4, andpost@nutrieapp.comwas removed from every project. The account itself is retired by 2026-11-04 (deadline #36). Updated 2026-10-01: the $2,000 startup credit on015BBCran out in August 2026 (Nutri-E's grant of 2025-11-07), so there was nothing left to keep there. The rig's project andcuti-emoved to Dashecorp Billing (0151A6-EDAFD0-29E42A) that day; three ~$0 projects, including Reward-E's Firebase projectreward-e-app(owned bycodiedev42@gmail.com), wait on a project-quota increase (dashecorp/infra#530).stig@dashecorp.comis Administrator and primary contact on the015BBCpayments profile. The Google for Startups contact no longer matters, and Dashecorp's own application was declined (no venture investor); the recommendation is to let it go. The repo cleanup merged on 2026-09-29: rig-gitops#871 and infra#485 (the runbooks namestig@dashecorp.com), claude-3#987 (the skills and the website inventory) and dashe-nutri-e#60 (the deploy workflow and the Cloudflare setup files are gone; the page tree stays as the record). - Decisions of 2026-09-25, driven by the real user base:
- The Cuti-E consented-device counts are, for the last 30 days: Fast-E 82, Reward-E 22 (1 in the last 7 days), Count-E 15, Nutri-E 3.
- Nutri-E: no keychain migration. The operator is the only paying subscriber (the production
SUBSCRIPTIONSstore holds 1 record), so he taps Restore Purchases once after the first Dashecorp update. Free users get a fresh allowance, and their data stays on the phone. - Reward-E: only the operator's family pays. The Sign in with Apple migration is a one-off script, run twice by the operator: once before the transfer and once after.
- All four apps transfer together once the Dashecorp membership is active. There is no Count-E pilot first.
- The code work is specified file by file in ios-app-transfer-spec.md (revision 2, adversarially reviewed).
- Item A is done in all four apps. The Team ID is a single
Team.xcconfigsetting: dashe-fast-e#22, dashe-count-e#17, dashe-reward-e#159 and dashe-nutri-e#55.- fastlane match reads the
dashecorp/ios-certsbranch named after the Team ID. The branchV9GL74NMBRis a copy ofmaster, and until every Invotek-team app reads it, a match write must go to both branches. - A guard fails CI if the Team ID appears anywhere else, including the plist, JSON and
[sdk=…]shapes (count-e#23, fast-e#25, reward-e#162, nutri-e#57). - The guard is not yet a required check: these repos have no branch protection, which is tracked in dashecorp/infra#459.
- fastlane match reads the
- Live proof from the merge-triggered TestFlight runs. Count-E's run went green end to end, including the upload. For Fast-E and Reward-E, the read step, match from the new branch, archive and export were all green. Their uploads were refused only because versions 1.4 and 1.21.5 are already approved, so both need a version bump before their next upload, and the first Dashecorp build needs one anyway. Nutri-E's workflow runs only when started by hand. Count-E's run leaves one internal TestFlight build, which must be expired in the pre-transfer cleanup.
- Cuti-E push notifications (C1) are merged and deployed to sandbox and production (dashe-cutie#15, closed
2026-09-26).
- Dev-E wrote it as dashe-cutie#16. Review-E blocked that at 917 lines, so it went in as four slices, each approved: #25, #28, #33 and #34.
- The slices add a safe credential PATCH for apps and customers, a per-app
apns_port(443 or 2197, migration 0058), and a test push aimed at one device. -
33 also adds a direct unit test for
apnsOrigin(), which every APNs send now uses.¶ - Deployed on 2026-09-26 by run 36278137915, the first worker deploy from
dashe-cutieto succeed. Migration 0058 is applied to both D1 databases, and all 9 production apps haveapns_port443. Both smoke tests passed. - Two things blocked it. The repo had no
CLOUDFLARE_API_TOKEN, which dashecorp/infra#465 now provisions through OpenTofu. The deploy also re-uploaded five Worker secrets (RESEND_API_KEY,APNS_*) from GitHub secrets that never moved fromStig-Johnny/cutie. dashe-cutie#37 stopped that re-upload. The Workers already held the values, and a deploy never deletes a Worker secret. - Open gap, dashecorp/infra#467:
dashe-cutiehas noproductionenvironment, so its worker deploys reach production with no approval step.
- The Reward-E Sign in with Apple script is merged: 13 PRs (dashe-reward-e#168 to #200), after five adversarial
review rounds. On main, 152/152 of its tests pass, and so does the Team ID guard.
- It keeps each Firebase UID, and so each family, through Apple's
transfer_subpath. - The operator runbook is
docs/siwa-team-transfer.mdin dashe-reward-e. It covers the rehearsal onreward-e-dev,generatewith the Invotek key before and on transfer day, the catch-upgenerate, andexchangewith the Dashecorp key after acceptance, followed byverify. - The keys are needed only when the operator runs it.
- It keeps each Firebase UID, and so each family, through Apple's
The 10 apps¶
This is the live ASC state on 2026-09-25 (read-only). All 22 registered bundle IDs are under V9GL74NMBR.
| App | App Store id | Bundle ID | Latest version and state | Transferable | Blockers and prep |
|---|---|---|---|---|---|
| Nutri-E | 6753125391 | no.invotek.Nutri-E |
1.0.5 READY_FOR_SALE (all 5 versions released) | Yes | 160 TestFlight builds and 2 beta groups; Xcode Cloud product; shared secret; keychain device ID (below) |
| Fast-E | 6759206116 | no.invotek.FastE |
1.4 READY_FOR_SALE | Yes, blocked | Both subscriptions in group Premium (21934607) are MISSING_METADATA: delete them or complete them to Ready to Submit. Also 23 builds and an Xcode Cloud product |
| Count-E | 6759394446 | no.invotek.CountE |
1.1 READY_FOR_SALE; 1.2.0 PREPARE_FOR_SUBMISSION | Yes | 15 builds (all expired); 2 unsubmitted draft review submissions; Xcode Cloud product. No subscriptions, so this is the pilot |
| Reward-E | 6756241157 | no.invotek.RewardE |
1.21.5 READY_FOR_SALE | Yes | Sign in with Apple user migration; 1 unsubmitted draft submission; 185 builds; Xcode Cloud product; shared secret; Firebase App Check |
| Drink-E | 6759526344 | no.invotek.DrinkE |
1.0 PREPARE_FOR_SUBMISSION, never released | No | 40 builds uploaded, so the bundle ID stays with Invotek for good |
| Heart-E | 6759680314 | no.invotek.HeartE |
1.0 PREPARE_FOR_SUBMISSION, never released | No | 1 build uploaded |
| Rig Cockpit | 6782443501 | no.invotek.rig-cockpit |
1.0 PREPARE_FOR_SUBMISSION, never released (TestFlight only) | No | 10 builds, newest build 10 from 2026-09-10 |
| Cuti-E Admin | 6755743222 | no.invotek.Cuti-E-Admin |
1.0 REJECTED, never released | No | Subscription group Cuti-E Premium (READY_TO_SUBMIT); Sign in with Apple; in RevenueCat proj61d576be |
| Cuti-E - Feedback | 6756207927 | no.invotek.Cuti-E-Feedback |
1.0.7 REJECTED, never released | No | — |
| CutiE Super Admin | 6757225299 | no.invotek.CutiE-SuperAdmin |
1.0 DEVELOPER_REJECTED, never released | No | — |
4 apps were released, so they can transfer: Nutri-E, Fast-E (after its subscription fix), Reward-E and Count-E. They keep their bundle IDs, users, reviews and subscribers.
6 were never released, so they cannot transfer. Apple requires at least one released version. They will get new
Dashecorp app records with new com.dashecorp.* bundle IDs. The reason: once a build has been uploaded, Apple says
"your bundle ID can't be reused", and "you cannot delete an explicit App ID for an app you uploaded to App Store
Connect". Every one of the 6 has uploaded builds. Rig Cockpit, Drink-E and Heart-E are listed above; the three Cuti-E
apps went through App Review, which needs a build. So the no.invotek.* IDs stay with Invotek forever.
The only way to keep one of those IDs would be to release the app under Invotek first and then transfer it. Recreating also means: - new widget IDs and App Groups; - an app name that is locked until Invotek removes the old record; - before that removal, taking the app and its IAPs off sale everywhere, and the app must not be in Waiting for Review, In Review, Rejected or Metadata Rejected.
Apple's rules (verified)¶
These come from Apple's pages, re-checked on 2026-09-25. Items marked UNVERIFIED are ones Apple does not state.
Transfer criteria¶
Source: App transfer criteria.
- Release: the app must have at least one version released on the App Store, and must not be on pre-order.
- Blocking statuses: no version may be in Processing for Distribution, Waiting for Review, In Review, Accepted, Pending Developer Release or Pending Apple Release. PREPARE_FOR_SUBMISSION does not block. Freeze releases of an app while its transfer runs.
- In-App Purchases: each must be Approved, Ready to Submit, Developer Removed from Sale or Rejected. Its product
IDs must not exist anywhere in the recipient account, so never create test products with
no.invotek.*IDs in Dashecorp. - Also blocked: Apple-hosted asset packs in Waiting for Review or In Review, Apple Arcade apps, and sandboxed Mac apps that share an App Group container. None of these applies to us.
- Account state: neither account may be in a pending or changing state. Both must have accepted the latest Paid and Free agreements. If Invotek accepted the EU Alternative Terms Addendum, Dashecorp must accept it too.
- Draft review submissions: Apple lists only version statuses. It does not say whether an unsubmitted draft submission (Reward-E 1, Count-E 2) blocks a transfer. Remove the drafts first to be safe.
Who does what, and timing¶
Sources: Initiate an app transfer and Accept an app transfer.
- Initiate: only Invotek's Account Holder can start a transfer. The path is App information > Additional Information > Transfer App. They enter the recipient Account Holder's Apple Account and the recipient Team ID (2FA may be asked). The recipient account must therefore already exist.
- One app at a time: each transfer covers one app record. Apple documents no bulk transfer, and does not say whether several can run in parallel. We start all four on the same day (operator decision, 2026-09-25).
- Accept: only Dashecorp's Account Holder can accept, under Business > Agreements. They must set:
- the support URL (always), and the marketing and privacy URLs if the app had them;
- App Review and App Store contacts;
- user access;
- App Privacy.
- Timing: Dashecorp has 60 days to accept, or the request expires. Then comes "Processing App Transfer" for up to 2 business days, or "Waiting for Export Compliance" if Apple needs documents. Either side can cancel or decline while the app is in Waiting for Recipient.
- While pending: the app keeps its status with Pending App Transfer added. Invotek cannot edit metadata, pricing, availability or IAPs, and open App Review threads are closed. The app stays on sale and users keep getting updates.
What carries over and what does not¶
Source: Overview of app transfer.
| Carries over | Does not carry over, or must be redone |
|---|---|
| Bundle ID and App ID; a wildcard App ID becomes explicit | The App ID prefix: it becomes Dashecorp's Team ID (TN2311), so the app needs a new certificate and profiles |
| Reviews and ratings; users keep getting updates | Keychain access: lost once the first Dashecorp-signed update is released (TN2311) |
| Subscriptions and subscribers (same bundle ID) | APNs certificates and keys: the recipient uses its own key |
| iCloud containers and KVS identifiers (we use neither) | App Groups: Invotek deletes each one after the transfer, then Dashecorp registers it |
| Sign in with Apple Services ID, unless its association is removed first | Sign in with Apple user identifiers: they are team-scoped and need the migration below |
| ASC webhooks: delete them first, or repoint them afterwards | TestFlight and Xcode Cloud data: both must be removed before initiating |
| App Analytics, back to first availability or 2015-04-01 | Pre-transfer sales and payments stay with Invotek, which keeps Sales and Trends but loses Analytics |
| Accessibility Nutrition Label details, OS data-transfer info | Promo codes: none can be generated after a transfer "regardless of the app's ownership status". Use offer codes |
| Bundle history and featuring nominations; Apple Pay merchant IDs, Wallet pass IDs and Game Center matchmaking (we use none) | |
| Verified regulated-medical-device contact info, which must be re-verified. Whether Heart-E or Nutri-E declared one is UNVERIFIED |
- Back up first: Apple says to back up metadata, pricing, availability dates and sales before transferring, because the app is removed from the sender's account. Export Invotek's sales and finance reports too, since Invotek's accounting needs them.
- Widgets: Apple speaks only of "its associated App ID". It does not say whether extension App IDs
(
no.invotek.*.Widget) move with the app. A 2017 forum report (thread 82600) describes a widget ID left behind that blocked uploads. Check Certificates, Identifiers & Profiles right after the pilot, and contact Apple Developer Support if an ID is missing. - Seller name: Apple implies the product page shows the new seller but does not spell it out (UNVERIFIED).
Pre-transfer cleanup (sender side)¶
- TestFlight: "turned off for all beta versions … removing all builds and testers … and clearing each information field below Test Information for every localization". Apple does not say whether expired builds count as removed.
- Xcode Cloud: "All Xcode Cloud related data from the app you want transferred must be removed prior to initiating app transfer" (App Store Connect > Settings > Xcode Cloud). The account still holds 9 Xcode Cloud products: Nutri-E, Reward-E, Fast-E, Count-E, Drink-E, Cuti-E-Admin, Cuti-E Super Admin, Cuti-E Feedback and SupTrackAI. The June cutover only disabled their workflows. Xcode Cloud is retired as a build path (xcode-cloud.md), so deleting them loses nothing.
- Auto-renewable subscriptions (Nutri-E, Reward-E, and Fast-E if its group is kept): generate an app-specific shared secret before initiating and hand it to Dashecorp through Bitwarden, never in chat. Dashecorp points its servers at it, then generates a new one after the transfer. A regenerated secret can take up to 24 h to take effect (Generate a shared secret).
- Sign in with Apple: ungroup the app first if it is grouped with another app. A grouped App ID also cannot be deleted.
Recipient side¶
- Agreements, tax and banking: Dashecorp must accept the latest Paid and Free agreements, and the EU addendum if Invotek accepted it. RevenueCat staff advise that every agreement show "Active" before initiating (RevenueCat Community, 2026-02-20).
- EU trader status (Digital Services Act): since 2025-02-18, apps without verified trader status are removed from EU storefronts. The Account Holder or an Admin enters the status per account (DSA trader requirements, Apple news). Apple's transfer help does not mention it, so its effect on a transfer is our inference (UNVERIFIED). Set it up and have it verified before accepting, or the transferred apps may drop out of all 27 EU storefronts.
- Small Business Program: transferred apps' proceeds count toward both accounts' totals for that calendar year (ADPLA, Schedule 2, App Store Small Business Program). This affects whether Dashecorp qualifies for the reduced commission.
Membership expiry¶
Sources: Program Renewal, renewal support, purchase and activation, certificates and the ADPLA.
This is why Invotek must stay renewed until the last app has moved.
- On expiry: "your apps will no longer be available for download", and no new apps or updates can be submitted. Certificates, Identifiers & Profiles become inaccessible. Apple describes no grace period. Schedule 2 §1.4's 30-day phase-out covers only Apple's role as sales agent.
- Installed apps keep working. Whether past buyers can re-download is our reading of Schedule 2 §1.2(d)/§1.4, not an Apple statement. §11.3 allows Apple to delete the binaries "within a reasonable time" later.
- Subscriptions: renewals due during a lapse fail and go into billing retry, and recover once the account is resolved. The source is an App Store Commerce Engineer on forum thread 749345, Apr 2024, answering about an expired Paid Applications Agreement rather than an expired membership. Apple retries a failed renewal for 60 days (subscriptions). Apple can claw back refunds (Schedule 2 §7.1).
- Contractually, after expiry: no In-App Purchase API (Attachment 2 §4.2) and no APNs (Attachment 1 §1.1). Apple does not document whether it cuts these off technically. iCloud private user data stays accessible; CloudKit public data may be deleted (Attachment 4 §1.2). Sign in with Apple and TestFlight behaviour at expiry are not documented.
- Late renewal: possible any time on the web. Free apps return within 24 h. Paid apps and IAPs return once the Paid Applications Agreement is accepted again, with no resubmission.
- Renewal and auto-renew: the Account Holder renews, from 30 days before expiry. Renewing early starts the new term at expiry, with no gap. Auto-renew on the web is only available in some regions. Apple Account balances are not accepted as payment.
- Transfers from an expired account: Apple does not say outright that they are impossible. But the criteria need current agreements, and a lapsed account must accept them again. In forum thread 769698 (Nov 2024, no Apple staff answer) a developer could not initiate until Apple Support intervened after more than 24 days. Never let Invotek lapse mid-migration.
- Test devices: registered test devices are removed 180 days after expiry.
Organization enrollment and D-U-N-S¶
Sources: Become a member, Program enrollment, Enrolling in the app, enrollment support, app account, D-U-N-S and D-U-N-S support.
Requirements: - a legal entity that can contract with Apple. A Norwegian AS qualifies; DBAs, trade names and branches do not; - a D-U-N-S Number for that entity, whose name must match exactly; - an enrolling person with legal binding authority: owner or founder, executive, senior project lead, or an employee given authority by a senior employee. That person becomes the Account Holder. If they are not the owner or founder, a reference must confirm their authority; - an Apple Account with 2FA; - a work email on the organization's domain (dashecorp.com); - a public, working website on that domain. Social pages and parked domains are rejected; - possibly notarized business documents; - 99 USD per year, in local currency. The NOK price appears only at checkout.
Route: organizations can enrol through the Apple Developer app or the web. Only the Enterprise Program is web-only. Payment comes after Apple verifies the details and emails the next steps. Membership confirmation follows within 24 h of purchase.
D-U-N-S: look the entity up first, because D&B may already have assigned a number. If not, request one free. D&B takes up to 5 business days, and Apple then needs up to 2 more. Expediting does not help; email D&B after 2 weeks.
Verification time: Apple publishes none. Secondary sources report 3-10 days, and some cases run 3 weeks or more (e.g. forum thread 815009). Budget 2-4 weeks from start to an active Dashecorp membership.
Alternatives:
- Changing the legal entity name on the Invotek account resets identifierForVendor for all users on their next
update and cannot be undone
(Updating your account information).
- Apple does not say whether one Apple Account can be Account Holder of both memberships (UNVERIFIED). A separate
dashecorp.com Apple Account avoids the question.
Team-bound dependencies per app¶
File references are repo/path:line at the commits researched: nutri-e 3c82647, fast-e 80bc166, count-e 5325986,
drink-e 671dc66, heart-e 0cfb397, reward-e f01b46c, ios-sdk 5d55132. Keep the identifier strings themselves
(no.invotek.* bundle IDs, group.no.invotek.*, product IDs, HealthKit metadata keys): they either move with the app
or are stored on users' devices.
Signing and CI (all apps)¶
- DEVELOPMENT_TEAM
V9GL74NMBRis hard-coded in: dashe-count-e/ios/project.yml:31dashe-fast-e/ios/project.yml:63dashe-drink-e/ios/project.yml:30dashe-heart-e/ios/project.yml:31dashe-reward-e/ios/project.yml:82- Nutri-E, which has no XcodeGen:
ios/Nutri-E/Nutri-E.xcodeproj/project.pbxproj:315 - CI
TEAM_ID: everybuild-and-upload.ymlsetsenv TEAM_ID: V9GL74NMBRand passes it to matchteam_id, xcodebuildDEVELOPMENT_TEAMand a generated ExportOptionsteamID: - fast-e, count-e and reward-e: lines 14, 63, 117, 128
- heart-e: 14, 63, 121, 133
- nutri-e: 13, 57, 168, 179
- drink-e: line 14
- Nutri-E extras:
ios/Nutri-E/fastlane/Appfile:1,5,8(team_idanditc_team_id)Fastfile:41,96ios/ExportOptions.plist:8, plus a legacy profile entry at:25-26ios/Nutri-E/ExportOptions.plist:8scripts/setup-testflight-secrets.sh:28- fastlane match: all apps sign manually with
match AppStore <bundle>profiles (e.g.dashe-count-e/ios/project.yml:65,119,dashe-reward-e/ios/project.yml:123,210, Nutri-Eproject.pbxproj:409). The profiles come fromdashecorp/ios-certs, read-only in CI. Reward-E also hasfastlane/Matchfile:5. - One shared Invotek distribution certificate,
C8788TMK34, signs every dashe-* app (dashe-drink-e/.github/workflows/generate-match-profiles.yml:5-6,33). After a transfer, Dashecorp needs a new certificate and new profiles for every app and widget bundle. That means one match write run with a Dashecorp ASC API key. - It is not renewed (decision of 2026-10-22). Deadline #2, the nearest live distribution certificate, expires on
2026-10-23. Apps already on the App Store keep working, and no app had built since 2026-09-30. The transfer brings
a Dashecorp certificate anyway. Until then, a push to an app's
mainfails its TestFlight upload. If a live app needs a fix before the transfer, renew it with one match write run. - Secrets:
ASC_KEY_ID,ASC_ISSUER_IDandASC_KEY_P8_BASE64hold an Invotek ASC API key, andMATCH_PASSWORDprotects the Invotek cert store. They are provisioned by OpenTofu (dashecorp/infragithub/dashecorp/dashe-secrets.tf) and must be replaced there, never by hand. The same secrets servedashe-cutie,dashecorp/rig-cockpit-iosanddashecorp/.githubios-testflight.yml. During a staggered migration both credential sets must exist side by side, or swapping the org-wide key breaks the apps that have not moved yet. - StoreKit config and tests embed the team:
Configuration.storekit:11(_developerTeamID) in Nutri-E and Reward-Edashe-reward-e/ios/RewardETests/SubscriptionConfigValidationTests.swift:153dashe-reward-e/scripts/validate-subscription-config.sh:114-117
Reward-E CI goes red unless all of these change in the same PR.
- Docs naming the team: dashe-count-e/CLAUDE.md:147, dashe-heart-e/CLAUDE.md:118,
dashe-reward-e/CLAUDE.md:365, dashe-reward-e/AppStore/SUBMISSION_CHECKLIST.md:183 and
dashe-nutri-e/ios/Nutri-E/DEPLOYMENT.md:67.
- Dead workflows: some reference secrets that no longer exist (APP_STORE_CONNECT_* in nutri-e, count-e, drink-e
and heart-e, from the old monitor-xcode-cloud.yml). Delete them rather than migrate them.
Widgets and App Groups (Fast-E, Count-E, Drink-E, Heart-E, Reward-E)¶
- Each app and its
.Widgetextension sharegroup.no.invotek.<App>, one group per app. The entitlements areCountE.entitlements:9,FastE.entitlements:11,DrinkE.entitlements:9,HeartE.entitlements:7andRewardE.entitlements:15, each with a matching*Widget.entitlements:7. - The group IDs are also hard-coded in Swift. Two examples:
dashe-fast-e/.../Services/WidgetDataProvider.swift:7anddashe-reward-e/.../WidgetSync/WidgetDataProvider.swift:34. - Order: Invotek deletes the group after the transfer, then Dashecorp registers the same identifier. Whether existing widget data in the container survives is UNVERIFIED.
- Nutri-E has no widget and no App Group.
Keychain: Nutri-E device ID and Cuti-E App Attest key¶
- Nutri-E:
DeviceAuthServicestores the device ID and secret askSecClassGenericPassworditems with no access group (dashe-nutri-e/ios/Nutri-E/Nutri-E/Services/DeviceAuthService.swift:101-153). The default access group is prefixed with the Team ID. - TN2311: access "will only be lost after an update to the app is released on the App Store". The first Dashecorp-signed update therefore loses the device ID.
- With it go the RevenueCat
appUserID(SubscriptionManager.swift:45-59) and theappAccountTokenKV key that the notification worker uses (cloudflare-worker-apple/worker.js:41-90,337). - Paying subscribers regain entitlements only through Restore or RevenueCat TRANSFER forwarding. Server data keyed by the old device ID is orphaned.
- Cuti-E SDK: it keeps its App Attest key ID and API state the same way
(
ios-sdk/Sources/CutiE/CutiEAppAttest.swift:71-76,350-357,CutiEAPIClient.swift:41,62,72). Expect a one-time re-attestation and device re-registration in every app on its first Dashecorp build. - Mitigation: Apple's guidance is to accept one forced re-login. Moving the items into an App Group-based keychain access group before the transfer is an untested idea, not Apple guidance. The Nutri-E migration (plan step 2) must be proven end to end before it is relied on.
- No app declares
keychain-access-groups, so no keychain sharing is in use.
Sign in with Apple (Reward-E; also Cuti-E Admin and Rig Cockpit, which are recreated)¶
Reward-E signs in with Apple (RewardE.entitlements:5-8, AuthService.swift:93-205) through Firebase Auth. Firestore
family data is keyed by the Firebase UID (dashe-reward-e/docs/troubleshooting.md:467). Apple's user sub and private-relay emails
are team-scoped, so after a transfer Apple returns a new sub. Without migration, a returning user gets a new Firebase
account and loses their family data.
Apple's process is described in Transferring your apps and users to another team, Bringing new apps and users into your team and TN3159:
- Invotek gets a
client_credentialstoken with scopeuser.migration, signed with Invotek's Sign in with Apple key. It then calls the migration endpoint withtargetset to the Dashecorp Team ID, which gives atransfer_subfor each user. TN3159 recommends doing this before initiating. It is also allowed up to 60 days after acceptance. - Dashecorp exchanges each
transfer_subfor the newsuband email within 60 days of acceptance. ID tokens carrytransfer_subfor those 60 days, and after that the endpoint stops. If the window is missed, Apple's fix is to transfer the app back and redo the process. - Firebase has no documented
transfer_subhandling. In firebase-ios-sdk#7190 the reporter could not work out how, and there is no maintainer answer. We need our own mapping fromtransfer_subto Firebase UID.
Firebase Apple provider: its code-flow config holds a Services ID, the Apple Team ID, a Key ID and a private key. The Firebase noreply sender must also be registered with Apple's private email relay under the team. All of this must be redone under Dashecorp (Firebase: Authenticate using Apple). Also: - Send anything to relay addresses before the transfer. Invotek can no longer email relay users who use no other Invotek app. - Decide whether the Services ID should move with the app.
Firebase App Check (Reward-E)¶
- Release builds use App Check with App Attest and fall back to DeviceCheck
(
RewardE.entitlements:9-10,AppCheckService.swift:48-68). - App Check validates App Attest against the Team ID registered on the Firebase iOS app, and the DeviceCheck provider needs a team DeviceCheck key (App Attest config, DeviceCheck provider, App Attest provider).
- If enforcement is on, update the Team ID and upload a Dashecorp DeviceCheck key before the first Dashecorp build ships. Otherwise Firestore and Functions may reject every updated user.
- Whether enforcement is on is UNVERIFIED.
RevenueCat (Nutri-E, Reward-E)¶
- Which project is live is not settled. The shipping apps embed public SDK keys that
revenuecat.md maps to
proj2c32896f(Nutri-E) andprojdd4fe8ba(Reward-E). Our RevenueCat API key cannot read either project, so their owner account is unknown. proj61d576be("Invotek AS", created 2025-12-31) may be live too. It holds Reward-E iOS (app5492dc5361, ASC and IAP keys configured), Reward-E Play (app071914b5e8), Nutri-E iOS (app28417729c9, no keys), Cuti-E Admin (appe2ea59cd17) and a Test Store. It also has one webhook toapi.cuti-e.com/v1/webhooks/revenuecat. Before moving or retiring any project, check in the RevenueCat dashboard which project actually receives each app's purchases.- Keep the app records. Subscriber history carries over only through Apple's official transfer with the same bundle ID. Moving an app between RevenueCat projects needs client-side migration, so keep the records and swap the credentials.
- The three Apple credentials to replace, right after each transfer, IAP key first:
- the In-App Purchase key, which authenticates the App Store Server API and StoreKit 2 validation;
- the App Store Connect API key;
- the app-specific shared secret.
The SDK key does not change (In-App Purchase key configuration). IAP keys are created under Users and Access > Integrations (Generate keys for In-App Purchases). Their team scope is our inference. Dashecorp can create and validate its keys in advance. - Ownership: RevenueCat transfers whole projects only. The owner uses Settings > Transfer ownership, and the recipient must be an admin collaborator. It takes about 1-2 business days, and projects over 2,500 USD monthly tracked revenue need billing on file. Service credentials must be regenerated (Projects). The 2021 community article (Transferring Apps in RevenueCat) predates native ownership transfer.
Cuti-E backend: APNs and DeviceCheck¶
- APNs: the
dashe-cutieworker stores credentials per app in the D1appstable (apns_key_id,apns_team_id,apns_auth_key;cloudflare-worker/src/handlers/apps.js:273-319,migrations/0028_apns_full_per_app.sql:14). It also reads a globalenv.APNS_TEAM_ID(src/utils/notifications/apns.js:11-32,83-87,queue.js:654,722). - Who uses which key: Nutri-E, Reward-E, Fast-E and Drink-E share Invotek APNs key id
635XA3L42Q; Cuti-E Admin uses57285S276D. Count-E has no APNs config. Heart-E is not registered in Cuti-E. - At each cutover: update the app's row and the global value with a new Dashecorp APNs key, or Cuti-E reply pushes stop.
- Push entitlements:
aps-environmentis set in Nutri-E, Fast-E, Count-E and Drink-E (development) and in Reward-E (production). Neither app uses FCM, so no APNs key sits in Firebase on the push path. - DeviceCheck: the worker signs DeviceCheck JWTs with
APPLE_TEAM_IDand an Invotek DeviceCheck key (src/handlers/device-check.js:16-17,56,343-348). One worker-level key cannot serve apps split across two teams. A staggered migration needs per-team DeviceCheck credentials, which is a code change. - App Attest: the worker never checks
rpIdHash(src/handlers/app-attest.js:383-413: "For now, increment sign count and accept"). So the transfer does not break it. It is a pre-existing weakness: App Attest is not really enforced.
App Store Server Notification URLs¶
- The URL is set per app (App information > App Store Server Notifications). Apple does not say whether it survives a transfer (Enter server URLs). Re-check it after each transfer.
- Nutri-E: V2 notifications go to
nutrie-apple-webhook-worker-v2.invotekas.workers.dev(sandbox:…sandbox-v2…), not to RevenueCat. The worker has no Team ID or bundle check. - Pre-existing drift: CI deploys only
v3andsandbox-v3(deploy-workers.yml:63-66,196-199), and v2 may predate the 2026-09-11 JWT verification fix. Repoint the ASC URL to v3 before or during the transfer. - Reward-E and Cuti-E Admin send to
api.revenuecat.com. The other apps have no URL set.
Other capabilities¶
- HealthKit (Nutri-E, Fast-E, Heart-E): Apple's transfer help has no HealthKit section, and the data is on-device.
Enable the capability on the Dashecorp profiles. Do not rename the metadata keys
no.invotek.nutri-e.*(Nutri-EHealthKitService.swift:229-230) orno.invotek.FastE.schedule(Fast-EHealthKitService.swift:75), because they are stored in users' Health data. - Firebase config is keyed by bundle ID, so it survives: Reward-E
GoogleService-Info.plist:11-14, and Nutri-E's plist, which CI generates. - Not used by any app: iCloud/CloudKit, associated domains, Apple Pay, Wallet and Game Center.
Plan¶
- Operator: a Dashecorp Apple account.
- Confirm the exact legal entity name.
- Get a D-U-N-S number (look it up first; up to about 7 business days).
- Enrol as an organization with a dashecorp.com email and website. The Account Holder must have legal authority.
Done: submitted 2026-09-25 (
Q9ATJ2JTS9), accepted 2026-09-30; the license agreement and the purchase are next. - Accept the agreements, set up tax and banking, and enter and verify EU trader status.
- Record the Dashecorp Team ID in this doc.
- Rig prep now, independent of the new account:
- One team setting per app instead of the hard-coded
V9GL74NMBR. Each repo should read the team from a single place: project.yml or xcconfig, the workflowTEAM_ID, ExportOptions, fastlane, and the StoreKit config/test/validator in Reward-E. A switch is then one line per repo. - Nutri-E keychain migration, shipped BEFORE the transfer, so the first Dashecorp-signed update does not lose the device ID. Alternatively, a tested Restore / TRANSFER recovery path. Prove it end to end.
- Reward-E Sign in with Apple migration design: a
transfer_subgeneration script and a mapping fromtransfer_subto Firebase UID, with a dry run. - The Cuti-E worker DeviceCheck for two teams.
- Repoint Nutri-E notifications to v3.
- Transfer: all four together (operator decision 2026-09-25: no Count-E pilot). Each app is its own request. Initiate Count-E, Fast-E, Nutri-E and Reward-E on the same day, and check the widget App IDs and App Groups as soon as the first one is accepted.
- The 6 never-released apps get new Dashecorp app records with new
com.dashecorp.*bundle, widget and App Group IDs: Drink-E, Heart-E, Rig Cockpit, Cuti-E Admin, Cuti-E Feedback and CutiE Super Admin. Invotek removes the old records afterwards to free the names. - Per-app cleanup before each transfer (operator):
- Delete the Xcode Cloud product, and clear TestFlight builds, testers, groups and Test Information.
- Remove draft review submissions. Delete or complete Fast-E's subscriptions.
- Generate the shared secret. Ungroup Sign in with Apple if grouped.
- Delete or note ASC webhooks. Export sales and metadata.
- Check for regulated-medical-device declarations.
- After each transfer:
- A new Dashecorp distribution certificate, profiles (match write run into
ios-certs) and ASC API key, applied via OpenTofu. - A new APNs key in the Cuti-E rows and global value, and in Firebase where used.
- The RevenueCat IAP key, ASC key and shared secret.
- Register the App Groups.
- The Sign in with Apple
transfer_subexchange and the Firebase Apple provider and App Check settings (Reward-E). - Re-check the notification URL.
- Ship one update from the Dashecorp team.
- Keep Invotek renewed until the last app and all its credentials have moved.
Operator-only actions¶
- Renewing the Invotek membership (next by 2027-09-27), and later the Dashecorp one.
- The D-U-N-S request and the Dashecorp organization enrollment, as or with the Account Holder.
- Signing agreements, tax, banking and EU trader status on both accounts.
- Initiating (Invotek Account Holder) and accepting (Dashecorp Account Holder) each transfer.
- Creating every credential: distribution certificate, ASC API key, In-App Purchase key, APNs key, DeviceCheck key, Sign in with Apple key and shared secrets. They are stored in Bitwarden and applied via OpenTofu or the owning dashboard. Agents never see the values.
- App Store Connect writes: deleting Xcode Cloud products, TestFlight builds, testers, draft submissions, IAPs and app records; completing Fast-E's subscriptions; every App Review submission.
- RevenueCat and Firebase console changes, and any RevenueCat project ownership transfer.
Open questions¶
- What is Dashecorp's exact legal name ("Dashecorp AS" or "Dashe Corp AS")? Does it already have a D-U-N-S number or an Apple membership? Which Apple Account will be its Account Holder?
- Who owns the live RevenueCat projects
proj2c32896fandprojdd4fe8ba? Canproj61d576bebe retired, given it holds Cuti-E Admin and a Cuti-E webhook? - How many Reward-E users signed in with Apple, and where are their
subvalues (FirebaseproviderData)? Is Reward-E grouped with another app for Sign in with Apple? Which Services ID and key does Invotek hold? - Is Firebase App Check enforced on Reward-E, and with which Team ID and DeviceCheck key?
- Do widget extension App IDs move with the app? Do expired TestFlight builds and unsubmitted draft submissions count against the criteria? (Settle these on the first transfer that is accepted.)
- Fast-E's two MISSING_METADATA subscriptions: delete them (the app has no live paywall) or complete them?
- Does any app carry a regulated-medical-device declaration (e.g. Heart-E's blood-pressure log)?
- Should the Nutri-E Cloudflare account and the Reward-E Play listing (phase 2) move in the same program?
Sources¶
Apple, beyond those linked above: - Remove an app - Delete an App ID - Sign and update agreements - TestFlight overview - forum threads 694416 (an App ID prefix after a transfer) and 706128 (App Group and keychain after a prefix change)
Secondary: - LaunchBuddy app transfer checklist (2026-08-26) - Lance enrollment guide
Research inputs: ~/jobs/docs-954/ios-transfer-factbase.json and apple-expiry-facts.json on the Mac Mini.