Skip to content

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/dashecorp selskap/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 production environment, 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-only production-auto environment. 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 on production for the operator. See ci-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.com is 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.com was the only owner of the rig's project and of six others. On 2026-09-29 stig@dashecorp.com became Owner of all seven and an administrator of billing account 015BBC-422A59-EB7AF4, and post@nutrieapp.com was removed from every project. The account itself is retired by 2026-11-04 (deadline #36). Updated 2026-10-01: the $2,000 startup credit on 015BBC ran out in August 2026 (Nutri-E's grant of 2025-11-07), so there was nothing left to keep there. The rig's project and cuti-e moved to Dashecorp Billing (0151A6-EDAFD0-29E42A) that day; three ~$0 projects, including Reward-E's Firebase project reward-e-app (owned by codiedev42@gmail.com), wait on a project-quota increase (dashecorp/infra#530). stig@dashecorp.com is Administrator and primary contact on the 015BBC payments 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 name stig@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 SUBSCRIPTIONS store 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.xcconfig setting: dashe-fast-e#22, dashe-count-e#17, dashe-reward-e#159 and dashe-nutri-e#55.
    • fastlane match reads the dashecorp/ios-certs branch named after the Team ID. The branch V9GL74NMBR is a copy of master, 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.
  • 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-cutie to succeed. Migration 0058 is applied to both D1 databases, and all 9 production apps have apns_port 443. 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 from Stig-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-cutie has no production environment, 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_sub path.
    • The operator runbook is docs/siwa-team-transfer.md in dashe-reward-e. It covers the rehearsal on reward-e-dev, generate with the Invotek key before and on transfer day, the catch-up generate, and exchange with the Dashecorp key after acceptance, followed by verify.
    • The keys are needed only when the operator runs it.

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 V9GL74NMBR is hard-coded in:
  • dashe-count-e/ios/project.yml:31
  • dashe-fast-e/ios/project.yml:63
  • dashe-drink-e/ios/project.yml:30
  • dashe-heart-e/ios/project.yml:31
  • dashe-reward-e/ios/project.yml:82
  • Nutri-E, which has no XcodeGen: ios/Nutri-E/Nutri-E.xcodeproj/project.pbxproj:315
  • CI TEAM_ID: every build-and-upload.yml sets env TEAM_ID: V9GL74NMBR and passes it to match team_id, xcodebuild DEVELOPMENT_TEAM and a generated ExportOptions teamID:
  • 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_id and itc_team_id)
  • Fastfile:41,96
  • ios/ExportOptions.plist:8, plus a legacy profile entry at :25-26
  • ios/Nutri-E/ExportOptions.plist:8
  • scripts/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-E project.pbxproj:409). The profiles come from dashecorp/ios-certs, read-only in CI. Reward-E also has fastlane/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 main fails 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_ID and ASC_KEY_P8_BASE64 hold an Invotek ASC API key, and MATCH_PASSWORD protects the Invotek cert store. They are provisioned by OpenTofu (dashecorp/infra github/dashecorp/dashe-secrets.tf) and must be replaced there, never by hand. The same secrets serve dashe-cutie, dashecorp/rig-cockpit-ios and dashecorp/.github ios-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-E
  • dashe-reward-e/ios/RewardETests/SubscriptionConfigValidationTests.swift:153
  • dashe-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 .Widget extension share group.no.invotek.<App>, one group per app. The entitlements are CountE.entitlements:9, FastE.entitlements:11, DrinkE.entitlements:9, HeartE.entitlements:7 and RewardE.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:7 and dashe-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: DeviceAuthService stores the device ID and secret as kSecClassGenericPassword items 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 the appAccountToken KV 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:

  1. Invotek gets a client_credentials token with scope user.migration, signed with Invotek's Sign in with Apple key. It then calls the migration endpoint with target set to the Dashecorp Team ID, which gives a transfer_sub for each user. TN3159 recommends doing this before initiating. It is also allowed up to 60 days after acceptance.
  2. Dashecorp exchanges each transfer_sub for the new sub and email within 60 days of acceptance. ID tokens carry transfer_sub for 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.
  3. Firebase has no documented transfer_sub handling. In firebase-ios-sdk#7190 the reporter could not work out how, and there is no maintainer answer. We need our own mapping from transfer_sub to 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) and projdd4fe8ba (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 to api.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-cutie worker stores credentials per app in the D1 apps table (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 global env.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 uses 57285S276D. 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-environment is 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_ID and 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 v3 and sandbox-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-E HealthKitService.swift:229-230) or no.invotek.FastE.schedule (Fast-E HealthKitService.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

  1. Operator: a Dashecorp Apple account.
  2. Confirm the exact legal entity name.
  3. Get a D-U-N-S number (look it up first; up to about 7 business days).
  4. 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.
  5. Accept the agreements, set up tax and banking, and enter and verify EU trader status.
  6. Record the Dashecorp Team ID in this doc.
  7. Rig prep now, independent of the new account:
  8. 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 workflow TEAM_ID, ExportOptions, fastlane, and the StoreKit config/test/validator in Reward-E. A switch is then one line per repo.
  9. 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.
  10. Reward-E Sign in with Apple migration design: a transfer_sub generation script and a mapping from transfer_sub to Firebase UID, with a dry run.
  11. The Cuti-E worker DeviceCheck for two teams.
  12. Repoint Nutri-E notifications to v3.
  13. 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.
  14. 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.
  15. Per-app cleanup before each transfer (operator):
  16. Delete the Xcode Cloud product, and clear TestFlight builds, testers, groups and Test Information.
  17. Remove draft review submissions. Delete or complete Fast-E's subscriptions.
  18. Generate the shared secret. Ungroup Sign in with Apple if grouped.
  19. Delete or note ASC webhooks. Export sales and metadata.
  20. Check for regulated-medical-device declarations.
  21. After each transfer:
  22. A new Dashecorp distribution certificate, profiles (match write run into ios-certs) and ASC API key, applied via OpenTofu.
  23. A new APNs key in the Cuti-E rows and global value, and in Firebase where used.
  24. The RevenueCat IAP key, ASC key and shared secret.
  25. Register the App Groups.
  26. The Sign in with Apple transfer_sub exchange and the Firebase Apple provider and App Check settings (Reward-E).
  27. Re-check the notification URL.
  28. Ship one update from the Dashecorp team.
  29. 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

  1. 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?
  2. Who owns the live RevenueCat projects proj2c32896f and projdd4fe8ba? Can proj61d576be be retired, given it holds Cuti-E Admin and a Cuti-E webhook?
  3. How many Reward-E users signed in with Apple, and where are their sub values (Firebase providerData)? Is Reward-E grouped with another app for Sign in with Apple? Which Services ID and key does Invotek hold?
  4. Is Firebase App Check enforced on Reward-E, and with which Team ID and DeviceCheck key?
  5. 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.)
  6. Fast-E's two MISSING_METADATA subscriptions: delete them (the app has no live paywall) or complete them?
  7. Does any app carry a regulated-medical-device declaration (e.g. Heart-E's blood-pressure log)?
  8. 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.