Overview › Logged In Experience

Section 1 — Logged In Experience

Card: Logged in Experience · 15 items · Source: PADI Mobile Scope Alignment v-ceb35f4d Consumer: the unified PADI app — Expo React Native, shipping as an update to the existing PADI main app.

This document answers one question per item, and since 18 September 2026 it answers it twice:

  1. Current state — can the unified app build this with an endpoint that exists today?
  2. Target state — does the MyPADI Personalised Home API Reference provide for it?

It is not an audit of kms-ios-padi or kms-android-padi. Those apps are being replaced. They appear here only as evidence — the strongest kind available, because an endpoint the current app calls is proven to work from a mobile client with a diver's token.

Why this card was re-verdicted against a second source

This card is the study's analysis of the MyPADI dashboard, and MyPADI now has a declared target-state API surface: the PADI Personalised Home — API Reference (OpenAPI 3.1.0, 20 operations, v0.1.0-draft), produced in the 15 September design-to-API mapping session with the design team and PADI Engineering, and published at my-padi-api-reference.spin.axelerant.tech.

Each item below therefore carries a Target state block naming what that reference provides — or does not. Where the two states disagree, the disagreement is the finding.

Three cautions, and they govern everything below.

It is not a PADI contract. Its own introduction says: "Response shapes are inferred from the fields the doc names. They need confirming against the real services before this reference is shared outside the team." It is a better artefact of the same evidence class as the SwaggerHub spec in the API inventory §11 — Axelerant discovery, now persona-organised. Finding 5 below still stands.

Absence from it is not deprecation. The reference is scoped to one screen. It names the sections it deliberately excludes because they resolve to a link, Drupal content or the Dive Shop Locator. So only presence is evidence here; an endpoint missing from it may simply not appear on that screen.

Presence in it is not a commitment either. Nobody at PADI has said which of today's endpoints are being retired in favour of these. That is still PADI-1, and this reference is the closest thing to a partial answer the study has.


The test

  1. Does the existing mobile app call an endpoint for this? → usable, lowest risk.
  2. If not — does a PADI web front end call one? → usable. RN speaks the same HTTP as Vue.
  3. Only if neither → a genuine gap PADI must close.

Evidence classes, strongest first

Class What it means
Live-tested Called outside a browser with a real Cognito idToken and confirmed working. Source: Confluence AWS Cognito Authentication Architecture §7 (2026-06-11) — "Tested live against non-prod with a real idToken (id & access token, consumer & pro accounts)." This is the only class that proves non-browser callability.
Mobile-proven The current native apps call it. Cited to file:line.
Web-captured A web front end calls it, observed in the browser Network tab. Source: MyPADI — Unified Diver Profile.docx. Proves the endpoint exists and serves the data; does not prove it answers a non-browser client.
Requirement only Named in an epic or ticket, with no endpoint anywhere. This is a gap.

Overlapping items — the card has 15 entries, not 15 capabilities

Two pairs in the artefact describe the same capability under two IDs, and a third pair reads like a duplicate but is not. That is a property of the source list, not of this study, but it is worth naming up front so the analysis is not read twice — and so the two real duplicates are not estimated twice.

Pair Relationship How this document handles it
MYP-07 Email communication preferences ≡ PREF-02 Manage contact preferences Same endpoints, same auth, same defect. No stated difference. Analysed once under MYP-07. PREF-02 carries only what is genuinely its own — the granularity question.
MYP-09 Consent / preference governance ≡ PREF-03 Manage privacy and consent options Same missing systems. MYP-09 is the orchestration framing, PREF-03 the diver-facing framing. Analysed once under MYP-09. PREF-03 carries only the deletion-path exposure.
MYP-10 Certification path tracker ≈ MYP-12 Next-course recommendations Adjacent, but not the same — they resolve to different endpoints (journey/routing/othercourses vs /v2/courses?tag=recommended). Analysed separately. The overlap is in the requirement wording, not the API.

Effective distinct capabilities: 13 — 15 IDs less the two real duplicates. All 15 IDs are retained below because they are the artefact's own identifiers and estimation will be tracked against them; the duplicates carry a pointer instead of a second analysis. SCOPE-6 and SCOPE-9 confirm the pairings; only SCOPE-6 can change the count.

Two further titles overlap in wording without overlapping in work: MYP-02 says "preferences" in its title but its scope text is personal information only, and PREF-01 (language) is a preference written through a different endpoint than the rest.


Read this first — six findings that govern every item

1. The auth model is settled, and it is one header

"Authorization: Bearer <Cognito idToken> plus affiliate-id header. Default for profile, courses, club, pro, checkpoint, preferences, eCards, membership." — MyPADI doc §2

The unified app already holds this token. Most of the card is reachable with a header pair it will have on day one.

2. One token does not open everything — plan for token exchange

"The minted token's aud matches the target client — each property validates against its own client id… The exchange re-issues per target; you do not reuse one token everywhere." — Cognito Architecture

Three separate realms, and this is architecture, not a detail:

Realm Credential
PADI gateway — profile, club, courses, certs, logbook Cognito idToken (60-min expiry)
Store — orders commercetools session, bridged from a store-client idToken
Travel — bookings Travel JWT

Known failure: "Cert / Photo reject the SSO token (401) — need a different client/scope/key from the API owners."

3. Language is written in three places and does not propagate

"Language is stored in 3 places — Cognito custom:language, preference communicationLanguage, and profile languageCommunicationPreference — written by different endpoints, so an edit on one platform doesn't propagate." — MyPADI doc §11.1

This directly complicates PREF-01: the endpoints are fine, the data model is not.

4. There is still no aggregate endpoint — and the target state makes it worse

Confirmed from both sides. Every dashboard section is its own call. Axelerant owns a BFF/middleware layer per the 2026-08-12 review, so composing is in scope — but a BFF can only compose what exists.

The target reference confirms this and quantifies it. It documents 20 operations across six hosts for one screen, with no composition layer of any kind. And the persona model makes it worse than a fan-out: which dashboard renders is itself derived from API responses — GET /c/codes/search/{email} and the certifications list decide whether a diver sees First Visit, DSD Completed, eLearning In Progress or Teach Diving. So the shell cannot render until identification resolves, which serialises part of a launch sequence that was already wide.

This is not a theoretical worry: the 15 September session recorded an action to "simulate all API requests to measure call count and latency." Someone on that call had already seen it.

5. Nothing here is a PADI-published contract

Every endpoint below is either observed in production traffic or live-tested by Axelerant. PADI has published no API documentation; it was requested 2026-04-24 and again 2026-07-13. And per PADI-1, an unknown subset is being rebuilt for the legacy sunset.

The target reference does not change this. It is Axelerant-authored, self-declared as inferred, and version 0.1.0-draft.

6. The target state needs two tokens at once, and routes per endpoint

This is the single most consequential thing the reference adds, and it upgrades Finding 2 from a warning to a specification. The reference marks on each operation which credential it takes:

Credential Minted by Serves
bearerAuth — the idToken POST /auth/api/oauth/login learning, pro, logbook
oauthToken — an access_token POST /o/oauth/api/token (client credentials) profile, certifications, codes, catalog, club

Read that split carefully: certifications and profile move off the diver's idToken and onto a client-credentials token. A client-credentials grant is a service credential — it is minted from a Client ID and Secret Key, which cannot ship in a mobile binary.

So in the target state the unified app cannot call profile, certifications, club, codes or catalog directly. Something server-side has to hold the secret and mint that token. That makes the Axelerant BFF a hard requirement rather than a performance optimisation, and it is the strongest architectural statement in this card.

It also quietly explains Finding 2's known 401: "Cert / Photo reject the SSO token". They reject it because in the target model they do not take it.


MYP-01 — Authenticated Dashboard Shell & Navigation

Area 11 Platform Capabilities · Jira E9 API status: Gap · Feasibility: buildable section by section; no aggregate

What it needs. A logged-in landing surface with five sections — Profile, Certifications, eLearning Progress, Membership, Purchase History — each loading independently.

Endpoints available. Four of the five sections now have a path:

Section Endpoint Class
Profile GET /p/profile/web/me Live-tested
Certifications GET /c/certification/affiliate/{id} · eCard/* Live-tested + mobile-proven
eLearning GET /v2/entitlements/{affiliateId} Live-tested
Membership GET /c/club/member/subscription Live-tested
Purchase History POST cart/getOrders Live-tested, different auth realm

Gap. The aggregation layer, not the sections. Five calls across three auth realms on every cold launch — and the store call needs a session bridge before it can fire at all.

Notes for the RN client. This is the strongest case in the card for the Axelerant-owned BFF: one call from the device, fan-out server-side, one place to hold the store-session bridge and the token exchange. Without it the launch sequence is five round trips and three credentials. PLT-01 (offline foundation, committed) also means each section needs a cache and staleness policy the web requirement never had to specify.

Target state — MyPADI Personalised Home reference. The gap is confirmed and enlarged.

The reference documents 20 operations across six hosts for this one screen and provides no aggregation of any kind. Worse, the persona model adds a dependency the current requirement never had: which dashboard renders is derived from API responses — GET /c/codes/search/{email} (matching sku against OPEN_WATER_SKUS / DSD_SKUS, with processedDate distinguishing completed from merely assigned) plus the certifications list decide between First Visit, DSD Completed, eLearning Purchased, In Progress, Completed, Keep Diving and Teach Diving.

So identification must resolve before the shell can choose what to render. That serialises the front of a launch sequence that was already a wide fan-out — and it is why the 15 September session logged an action to "simulate all API requests to measure call count and latency."

Purchase History, meanwhile, has no entry in the reference at all, and Offers is listed under Not API-backed.

Verdict. The dashboard is buildable. The performance and auth shape is the open question, and it is Axelerant's to design rather than PADI's to provide.

Questions: PADI-2 (aggregate endpoint) · SCOPE-1 (Purchase History in Q1 scope)


MYP-02 — Profile — personal information, preferences, view & edit (field-level permissions)

Area 01 Identity, Profile & Account · Jira E10 API status: Existing — web · Feasibility: all five fields available

What it needs. Display "full name, profile photo, preferred language, home country, and communication preferences"; allow edits; GDPR controls; degrade gracefully.

Endpoints available.

Method Endpoint Auth Class
GET /p/profile/web/me idToken Live-tested
GET learning-prod.padi.com/profiles/{affiliateId} Bearer + affiliate-id Web-captured
POST /profiles/{affiliateId}/MailingAddress Bearer + affiliate-id Web-captured + mobile-proven
PUT /user/update/{email} — name, DOB, gender Bearer Web-captured + mobile-proven
PUT /user/update/language/{email} Bearer Web-captured + mobile-proven
GET /r/references/Countries[/States/cities] none Web-captured

The profile response carries every field scope line 1 asks for:

photoUrl · fullName · firstName · lastName · dob · memberNumber · memberSince · affiliateId · hasAutoRenewEnabled · languageCommunicationPreference · primaryEmailAddress · phoneNumber · countryCodeISO · teachingAddress{…} · mailingAddress{…}

Home country is countryCodeISO on the profile, and profile photo is photoUrl — both on the same response, so neither needs the analytics feed as a fallback.

Gap. None for read or write. What remains is a correctness problem, not an availability one: no web front end writes to Salesforce. Every documented write goes to Cognito or the learning profiles API. Salesforce write-back exists only as unbuilt scope (PADI-68, endpoint "TBC"). If Salesforce is the system of record, the propagation path is undefined for web and mobile alike.

Notes for the RN client. "Real time" write-back conflicts with PLT-01: an edit made at a dive site queues and syncs later, so two devices need a conflict rule nobody has specified. And /p/profile/affiliateid/{id} exists alongside the self route — the doc's own §11.1 asks "what authorizes by-affiliateId access?", which matters if the app ever renders another diver's profile.

Target state — MyPADI Personalised Home reference. Covered, on a different path from the one this card records.

Need Target endpoint Credential
Full profile record GET /p/profile/affiliateid/{affiliateId} oauthToken
Lookup by global UUID POST /p/profile/bulk oauthToken

Two things follow. First, GET /p/profile/web/me — the endpoint this card currently leans on, and the one that made MYP-02 Existing — webdoes not appear in the reference. Per the absence caution above that is not a retirement notice, but it is the second spec in a row to prefer the affiliateid path, and the API inventory §11's discrepancy 2 (two profile-by-id paths) is now resolved in that direction.

Second, and larger: profile is an oauthToken operation in the target model, not an idToken one. See Finding 6 — the app cannot mint that credential itself.

Field-level permissions remain absent. Nothing in the reference addresses them.

Verdict. Buildable now. The risk is data integrity across systems, not missing APIs.

Questions: PADI-3 (Salesforce propagation) · SCOPE-2 (field-level permissions) · SCOPE-3 (offline conflict rule)


MYP-03 — Certifications & Digital eCard Display

Area 02 Certifications & eCard · Jira E12 API status: Existing — mobile · Feasibility: buildable, one auth caveat

Endpoints available. The best-covered item in the card. Mobile-proven: the whole eCard/* family (ECardsApi.kt:9-35) with a ~55-field ECardInfo, plus Cloudinary artwork. Live-tested alternative: GET /c/certification/affiliate/{id} on the gateway.

Caveat that matters. "Cert / Photo reject the SSO token (401) — need a different client/scope/key from the API owners." The current app reaches these with its own client; a new app minting tokens through the unified SSO flow may not. Confirm the client/scope before treating this as free.

Gap. None for display. The 24-hour LMS auto-surfacing webhook remains unverified and is a freshness question, not a capability one.

Notes for the RN client. List-shaped and cacheable — suits CERT-01 (offline certification history, committed).

POC coverage. poc/padi-cert-offline-poc builds exactly that on Expo + Realm: certifications as a "durable local cache that reconciles (pull-only) from the server", rendering instantly online or offline. Certifications are read-only by design — they originate in the LMS, M2/Macola and Salesforce and the app never edits them — so the offline model is settled.

Target state — MyPADI Personalised Home reference. Covered, and the payload is dramatically thinner than the eCard needs.

Need Target endpoint
Certifications GET /c/certification/affiliate/{affiliateId}
Professional credentials GET /c/credentials/{affiliateId}
Certification photo state GET /p/photos/api/certification/{affiliateId}

The reference's Certification schema carries four fieldscertificationName, certificationNumber, certificationDate, diverNumber. The current mobile ECardInfo carries ~55 (ECardInfo.kt:14-68), including every field the rendered card needs: stock ID, card title, diver lines, instructor lines, store block, logos, footer lines, canPrint, qrCode.

So the target reference describes a certifications list, not an eCard. If it is read as the replacement for eCard/GetCardListConsumer, the card-rendering payload disappears. That is the most likely place for this transition to break something that works today, and it should be asked directly rather than assumed to be an omission of convenience.

Two further constraints came out of the 15 September session and are not in the spec text: the certifications API returns rows in arbitrary order and the query parameters for sorting by completion date do not work; and pinning has no API at all — it must be stored server-side to stay in sync between web and mobile, and only one certification can be pinned.

View all certifications and Order a card are both listed under Not API-backed — link-outs to learning.padi.com and store.padi.com.

Verdict. The strongest item in the card. Recommended for Q1 subject to PADI-4 — confirm the client and scope before treating it as free.

Questions: PADI-1 (sunset) · PADI-4 (cert token scope)


MYP-04 — eLearning Progress Section

Area 05 Learning & Course Progress · Jira E13 API status: Gap · Feasibility: not buildable as specified

What it needs. In-progress courses with completion percentage and last-accessed date.

Endpoints available. Courses yes, progress no:

Endpoint Returns
GET /v2/entitlements/{affiliateId}?filter=idc Items[]{packageId, platform, title}
GET /v2/courses/{packageId}/details course + status details
GET /v2/courses/{id}/assessmentresults · /report results
GET /v2/courses?tag=recommended recommended courses

Gap — the clearest in the card, and it survives step 2. No endpoint or field named for a completion percentage or last-accessed date exists anywhere: not in the mobile apps, not in the web capture, not in the live-tested matrix. It appears only as a requirement, in PADI-70, whose own acceptance criterion is still unmet and which depends on PADI-67 ("API credentials and documentation for LMS" — status Pending).

The one unread response body has now been read, and it confirms the gap. The gap was already proven absent from the mobile apps (the only percentage in either repo is DownloadService.kt:101, a download progress bar), from the MyPADI OpenAPI spec (which declares no learning endpoints at all), and from the live-tested matrix — but GET /v2/courses/{packageId}/details was summarised only as "course + status details" and its fields had never been recorded.

It has been read, but it answers a narrower question than first reported. The endpoint is the LMS course-detail call — GET https://learning-prod.padi.com/learning/v2/courses/{courseId}/details (padiww/ios-padi-app PADI/data/api/impl/LanguageCourses/LanguageCoursesApiImpl.swift:26) — and the iOS model that decodes it (PADI/model/view/OfflineLearning/LanguageCourse.swift:10-29) declares packageId, hasAnalytics, heroUrl, title, isSampleCourse, dateStarted, status, template, hasLegacy, availableLanguages[]. No percentage, and no last-accessed date — dateStarted is a start, not a last access.

Correction. An earlier revision of this card read that as proving the response carries no percentage. It does not. A Swift Decodable declares only the fields the client uses, so this establishes that the iOS app does not consume a percentage from this endpoint — not that the server omits one. The target-state block below sets out what the three surviving sources actually support.

One narrower question replaces it. A different model — PADI/model/view/OfflineLearning/UserCourse.swift:25, the offline-learning course payload — declares percentRemaining: String? alongside complete: Int?. Neither is read anywhere in the app, and Android declares no equivalent. Whether the server populates it is one live call, tracked as PADI-31. Full analysis in 18-learning-course-progress.md.

POC coverage — this settles both the gap and the fallback. poc/mobile-poc's eLearning spike ([PAM-17]) reports MY COURSES: WORKING · COURSE DETAIL: WORKING · OFFLINE PLAYBACK: WORKING · PROGRESS/QUIZ COMMIT: WORKING.

But read what it actually built for progress:

Completion badge (LessonStatusBadge) — 3-state (not-started / in-progress / complete), not a progress bar. PLP uses the server's per-lesson asset.status; KD7 uses local CMI (cmi.core.lesson_status).

A team that had every incentive to render a percentage built a three-state badge instead — because the server serves a status, not a percentage. That is the MYP-04 gap, confirmed by construction rather than by absence of evidence.

It also means the reduced form in SCOPE-4 is not a proposal — it is already working, with per-lesson status, offline playback and progress commit back to PADI's real hosts. Two honest limits the POC records: EVICTION POLICY: NOT IMPLEMENTED and TWO-DEVICE MERGE: NOT PROVEN.

Notes for the RN client. SCORM commit had to be re-architected for RN — the native apps intercept CMI commits with an override.js shim and an Android scheme-handler, and react-native-webview has neither, so the commit path runs as injected JS. That work is done and proven; it is not a fresh risk.

Target state — MyPADI Personalised Home reference. This is where the target state moves the item most, and it replaces the check this card previously reported as closed.

Three endpoints form a chain:

Step Endpoint Returns
1 GET /learning/v2/entitlements/{affiliateId} Entitlement{packageId, sku, assignedDate}
2 GET /learning/v2/courses/{packageId}/details CourseDetails{packageId, customCourseId, title, status, progressPercent}
3 GET /learning/v2/courses/{customCourseId}/assessmentresults AssessmentResult{sectionId, title, isCompleted}per-section completion

Step 3 is the finding. Per-section booleans make a completion percentage derivable — count completed sections over total. That is precisely the reduced form this study proposed as SCOPE-4, and it now has a named endpoint behind it rather than being a suggestion.

It also matches what PADI said. From the 15 September session: "PADI Engineering said section progress not determinable; Vatsal found APIs that do it, needs confirmation." Both halves are true — PADI does not serve progress, and an endpoint exists from which it can be computed.

And it corrects an overstatement in this card. An earlier revision recorded the /details check as closed, on the grounds that the iOS decoder for that endpoint declares no percentage field. That inference was too strong: a Swift Decodable declares only the fields the client uses, so it proves non-consumption, not absence. Three sources now expect progress data to be reachable and none has been observed:

  • CourseDetails.progressPercent — asserted by the reference, which flags its own response shapes as inferred
  • UserCourse.percentRemaining (padiww/ios-padi-app PADI/model/view/OfflineLearning/UserCourse.swift:25) — declared, never read, absent from Android
  • AssessmentResult.isCompleted — the derivable route

No mobile client calls /assessmentresults — zero references across both native apps, axelerant-padi/mobile-poc and PadiWW/padi.mobile.app. But a web client does, in production: PadiWW/pro.padi.com src/store/modules/course/actions.js:48-70 calls it to render the eRecord, branching on affiliateId between the learner's own record and the instructor's view of a student. (An earlier draft of this card said no client had ever called it; that was wrong.) So the endpoint is live and reachable — what is unverified is its response body, which the reference itself declares inferred.

Learning Dashboard, Continue Learning and Course archive are all listed under Not API-backed.

Verdict. Not buildable as written. Offer the reduced form and let PADI decide.

Questions: PADI-5 (does the LMS expose progress at all) · SCOPE-4 (is the reduced form acceptable)


MYP-05 — Membership & Billing Section (Club API)

Area 08 Club & Membership · Jira E14 · Flags IAP, parking API status: Existing — web (partial) · Feasibility: status yes, billing no, purchase blocked

Endpoints available.

Endpoint Auth Class
GET /c/club/member/subscription idToken Live-tested
GET piccoloapi.scubadiving.com/customer/entitlements/active Bearer + affiliate-id Web-captured
GET {PRO_API}/journey/clubmember Bearer + affiliate-id Web-captured
GET {PRO_API}/pros/stripe/getcarddata/1/{id} Bearer + affiliate-id Web-captured
POST /pros/graphqlmember { … } idToken + affiliate-id Spec-declared
GET /c/credentials/{id}?allCredentials=true idToken Spec-declared

An architecture note worth carrying: club.padi.com does entitlement matching server-side in a c/club REST BFF reachable with the idToken the app already holds — "the client just presence-checks each key" (ADR-0004, axelerant-padi/mobile-poc, 2026-07-24). The app does not need entitlement logic.

More is served than the endpoint paths suggest. The MyPADI OpenAPI spec's member query on POST /pros/graphql returns memberSince, renewalStatus, autoRenewEnabled, memberNumber, memberStatus, primaryCredentialName, restrictedCountry, countryId and office{…}. Renewal status and auto-renew are already available, not just membership status.

Gap — but state it as unverified, not as a demand on PADI. Renewal date, tier and billing history are not named on anything we have inspected. That is not the same as their being absent:

  • POST /pros/graphql is GraphQL. The above is one example query, not the schema. Field absence cannot be asserted from a GraphQL endpoint without introspecting it, and nobody has.
  • GET /c/club/member/subscription is live-tested as reachable; its response fields were never recorded.
  • GET /c/credentials/{id} is newly found and unexamined — and primaryCredentialName implies a credential/tier model sits behind it.

The question was asked directly in #eng-padi-web on 2026-06-23 — "how much data can be exposed through this api like status, tier, renewal date, billing summary?" — and is unanswered. Introspect the schema before putting this to PADI as a gap (see the API inventory §12).

POC coverage — and it answers part of the outstanding check. poc/mobile-poc's Pro & Club spike reports MEMBER DASHBOARD: WORKING · RENEWAL: WORKING (WebView checkout) · NATIVE CLUB (c/club BFF): WORKING · MEMBER-TYPE GATING: VERIFIED (2/3/9 + lapsed/restricted).

It ran the member query against pros/graphql and recorded the response: memberSince, renewalStatus (e.g. "timetorenew"), autoRenewEnabled, primaryCredentialName, memberNumber, memberStatus, restrictedCountry, countryId, office{…} — plus per-credential {credentialName, expirationDate, autoRenewEnabled}.

So renewal status, auto-renew and a credential expiry date are proven served, and the membership dashboard is proven buildable. What is still unseen is a billing history and a membership-level renewal date distinct from credential expiry. The §12 introspection is still worth doing, but it is now a narrower question.

Blocked, separately. IAP gates anything that initiates a paid renewal. Owner PADI business/legal, point person Kevin Braun, open since April with no date.

The billing endpoint exists, and it is a link. GET {PADI_CLUB_URI}/member/subscription/billing is called by two web front ends (club.padi.com/src/api/club/index.js:29, learning.padi.com/src/api/club/index.js:13). It returns a hosted billing-portal URL, and the web app redirects to it — window.location.href = billingLink + '/payment-methods' (NotificationBar.vue:102).

So web does not render billing history natively either. Structured billing history was never part of web parity, and this study was asking PADI for something no PADI surface provides. See the API inventory §13.

Target state — MyPADI Personalised Home reference. Covered for status, and it adds a candidate endpoint for the unresolved billing half.

Need Target endpoint Returns
Club membership GET /c/club/member/subscription ClubSubscription{status, renewalDate, autoRenew}
Pro membership record POST /pros/graphql memberNumber, autoRenewEnabled, memberSince, renewalStatus, memberStatus, home office
Renewal information GET /pros/renewals/info new — not in any earlier source

GET /pros/renewals/info is the first endpoint named anywhere in this study that plausibly carries renewal history. It does not close the billing-history question, but it makes it answerable: read it, rather than ask PADI whether such a thing exists.

One caution on autoRenew. The reference presents it as a clean boolean. The 15 September session records that it is inferred from the absence of term.cancelAt, and that the NEVER case is an open issue. A schema field that is actually a derived inference is exactly the kind of thing that survives into an estimate as "one field, already served".

A lighter-weight membership check also surfaced in that session and is not in the reference: GET api.pro-prod.padi.com/journey/clubmember, returning isMember and firstTimeUser.

The pros/graphql introspection remains outstanding. The reference lists fields, but it infers them; field absence still cannot be asserted against a GraphQL endpoint nobody has introspected.

Verdict. Status, renewal status, auto-renew and benefits are buildable today. Billing is buildable at web parity today — fetch the link, open it. Purchase is blocked by IAP, and opening a payment portal in-app is itself an IAP question (PADI-7), not an endpoint one. If the requirement means billing history rendered natively, that exceeds web parity and is new scope, not a gap.

Questions: PADI-6 (billing history endpoint) · PADI-7 (IAP decision date)


MYP-06 — Profile image upload

Area 01 Identity, Profile & Account API status: Existing — web · Feasibility: buildable; requirement still ambiguous

Endpoints available. photoUrl on the profile response for display; GET /p/photos/api/certification/{id} (live-tested) and {PRO_API}/certifications/photos/getphoto for the certification photo; POST certifications/photos/upload (mobile-proven) for upload.

The ambiguity is unchanged and unresolved by any endpoint. The only photo pipeline is the moderated certification photo — reviewed, rejectable, printed on the eCard. If the item means a casual avatar, that pipeline is wrong regardless of which endpoint serves it.

Caveat. Photo shares the cert 401 problem — the SSO token is rejected.

Target state — MyPADI Personalised Home reference. Read only, on a path that differs from the one this card records.

GET /p/photos/api/certification/{affiliateId} returns a PhotoStatus. Note the path segment: the reference uses /api/ where this card records p/photos/web/certification (GetCertificationPhotoApilmpl.swift:24,34). Two paths, one purpose, and nobody has said whether the web variant survives — the same shape of question as §11's two login paths.

No upload operation appears in the reference. The target state as documented describes reading photo state, not writing a photo. Under the absence caution that is not proof the upload is going away — but this card's upload evidence (certifications/photos/upload, ProApi.kt:14-16) remains the only source for the write half, and it is a moderated certification photo rather than an avatar.

Like profile and certifications, this is an oauthToken operation — see Finding 6.

Verdict. Buildable. Which artefact it means is a product decision, and it should be answered once for MYP-02 and MYP-06 together.

Questions: SCOPE-5 (avatar or cert photo) · PADI-4 (photo token scope)


MYP-07 — Email communication preferences

Area 10 Engagement API status: Existing — web · Feasibility: buildable

This entry carries the analysis for the MYP-07 / PREF-02 pair. PREF-02 is not re-analysed; it records only the per-channel granularity question.

Endpoints available.

Method Endpoint Auth
GET/POST/PUT {PREFERENCES_URI}/v1/preference[/{id}] · /v1/unsubscribe Bearer + affiliate-id
GET {PREFERENCES_URI}/v1/MarketingLanguage/{affiliateType} Bearer + affiliate-id
POST prefapi.scubadiving.com/recipient — push to SFMC Bearer + affiliate-id

{PREFERENCES_URI} = https://api.core-prod.padi.com/preference. Named categories: diveAbroad, localDiving, environmentConservation, events, diveCentricTrips, padiCommunications, thirdParty.

Known defect, from the source itself. "The recipient/preference API returns only numeric preferenceId + status; no endpoint returns the email-category labels or descriptions." The app would have to hardcode the labels — brittle, and wrong the moment PADI adds a category.

Security note. A separate unauthenticated path on the same host was verified in #eng-padi-web (2026-08-17): "Calls to the API currently succeed without credentials and without IP restrictions." Not a blocker for this item; worth raising on its own.

Target state — MyPADI Personalised Home reference. Not covered.

The preferences endpoint that appears in the API inventory §11's SwaggerHub spec — GET /preference/v1/preference/{id} — is absent from this reference, and no communication-preference operation replaces it.

Apply the absence caution strictly here: email preferences are not a section of the personalised home, so their absence is expected and says nothing about whether the endpoint survives. This item's verdict is unchanged, and it still rests on the web-captured evidence below.

What it does mean is that the target-state source offers no corroboration for MYP-07 — so of all the items on this card, this is the one whose evidence base has not improved.

Verdict. Buildable now. Ask for a label endpoint before building a hardcoded list. Estimate this once across MYP-07 and PREF-02 unless SCOPE-6 comes back saying PREF-02 means per-channel.

Questions: PADI-8 (category label endpoint) · SCOPE-6 (is this the same item as PREF-02)


MYP-08 — Unified login

Area 01 Identity, Profile & Account API status: Existing — mobile · Feasibility: buildable; multi-realm is the real work

Endpoints available. The full Cognito facade is mobile-proven (Swapi.kt:31-88) — login, refresh, register, confirm, reset, user/exist/legacy, and sso/exchange.

The finding that reshapes this item. Unified login is not one session. It is a token-exchange architecture across three realms — PADI gateway (idToken), store (commercetools session via store-client idToken), Travel (Travel JWT). The idToken expires in 60 minutes and its aud must match the target client.

That is a genuinely new requirement for the unified app. The current PADI app never needed it because it never reached the store or Travel. Adventures had its own login entirely.

Gap. None in endpoints. The work is client architecture: a token broker holding three credential types, refreshing on a 60-minute cycle, bridging the store session.

Target state — MyPADI Personalised Home reference. Covered — and it turns this item from a login question into an architecture one.

Operation Yields Serves
POST /auth/api/oauth/login idToken (bearerAuth) learning, pro, logbook
POST /o/oauth/api/token access_token (oauthToken), via client credentials profile, certifications, codes, catalog, club

See Finding 6. The second grant needs a Client ID and Secret Key, which cannot ship in a mobile binary — so in the target model the app cannot reach profile, certifications, club, codes or catalog on its own credentials at all. A server-side component must mint and hold that token.

This is the strongest architectural claim in the card, and it arrives from the target state rather than from the current one: the BFF stops being a performance optimisation and becomes a functional prerequisite.

The two login paths discrepancy is unresolved. The reference declares POST /auth/api/oauth/login; the shipped apps call auth/api/login (Swapi.kt:31). Two specs in a row now name the former and neither mentions the latter.

Verdict. Buildable, and larger than "unified login" sounds. Scope it as session architecture, not a sign-in screen.

Questions: SCOPE-7 (does "unified" include Travel and store realms in Q1)


Area 01 Identity, Profile & Account · Jira E101 API status: Gap · Feasibility: not buildable as specified

This entry carries the analysis for the MYP-09 / PREF-03 pair. PREF-03 is not re-analysed; it records only the data-export and deletion increment.

What it needs. "Single source of truth opt-in / opt-out synchronization across systems · transactional vs marketing classification · blacklist / unsubscribe governance."

Endpoints available. The preference CRUD from MYP-07, and /v1/unsubscribe. That is the mechanism, not the governance.

Gap, and the source names it. MyPADI doc §8.2 lists "Privacy (data export/delete, consent) → Privacy systems not documented." No consent platform appears in any repo or capture. And the language triple-write problem is this item in miniature: three stores, three endpoints, no propagation.

Target state — MyPADI Personalised Home reference. Nothing. No consent, preference-governance, export or erasure operation appears anywhere in the reference.

That is consistent with every other source, and consent is not a dashboard section, so the absence caution applies. But the gap has now survived a fourth independent check: the four repos, the live-tested matrix, the SwaggerHub spec, and this reference.

Verdict. Not buildable as specified. The Low impact / Low effort estimate should be challenged — it describes cross-system orchestration over systems that are undocumented.

Questions: PADI-9 (system of record for preferences and consent) · INT-1 (challenge the estimate)


MYP-10 — Certification path tracker

Area 02 Certifications & eCard API status: Existing — web (unverified fit) · Feasibility: requirement missing

Endpoints available. GET {PRO_API}/journey/routing/othercourses"Other-courses routing" — is real and web-captured, on the Pro API with Bearer + affiliate-id.

Gap. Not the endpoint. Nobody has said what a "certification path" is: an ordered prerequisite graph, or a flat next-course list. Those are different builds, and journey/routing/othercourses may serve one and not the other. Its name suggests routing, not progression.

Not the same as MYP-12. They read as a pair but resolve to different endpoints: this item is progression ({PRO_API}/journey/routing/othercourses, Pro API), MYP-12 is recommendation (/v2/courses?tag=recommended, learning API). Both are analysed in full because both are separate builds.

Target state — MyPADI Personalised Home reference. Covered — by a different route from the one this card records, and the two imply different builds.

GET /c/certification/certificate/all returns CertificateDefinition for every certification PADI defines. Combined with the diver's own GET /c/certification/affiliate/{affiliateId}, a path tracker becomes a client-side computation: the full syllabus, minus what the diver holds.

This card's current route is server-side — {PRO_API}/journey/routing/othercourses returns what comes next, already reasoned. So there are now two candidate designs:

Route Where the progression logic lives Cost
journey/routing/othercourses PADI's server Cheap client, opaque rules
certificate/all + the diver's certs The app (or the BFF) Full control, and Axelerant owns the syllabus rules

Neither is wrong; they are different products. SCOPE-8 (what a certification path is) now has a concrete second option to choose between, which makes it answerable.

One environment wrinkle: this is the single operation the reference still pins to api.global-prod.padi.com while its nine /c/ siblings moved to api.padi.com — the reference's own author calls that a likely oversight.

Verdict. Probably buildable. Unverified until someone states the requirement.

Questions: SCOPE-8 (what is a certification path) · SCOPE-9 (is this distinct from MYP-12)


MYP-11 — Contact QR code (share diver contact)

Area 01 Identity, Profile & Account · Flags post-Q1 API status: Deferred · Feasibility: out of Q1 scope

Sequenced after Q1 2027 by the artefact; its detail entry is an empty stub. eCard records already carry a qrCode field, and CERT-06 (diver-initiated QR) is also deferred — scope them together when they return.

Target state — MyPADI Personalised Home reference. Not covered, as expected — the item is Post Q1 2027 and a contact QR code is not a personalised-home section. No change.


MYP-12 — Next-course recommendations (personalized)

Area 05 Learning & Course Progress API status: Existing — web · Feasibility: buildable; "personalized" undefined

Endpoints available. GET /v2/courses?tag=recommended — returns courseTitle, customCourseId, image.url, Bearer + affiliate-id. A recommendation endpoint exists and is already called by a web front end.

Not the same as MYP-10. Different endpoint, different API, different question: MYP-10 asks what comes next in the syllabus, this asks what should we show this diver. Merging them would be a scope decision, not a technical one.

Gap. None, unless "personalized" means CDP-driven next-best-action, which the Digital Overhaul Proposal names and no endpoint serves. If ?tag=recommended is sufficient, this is buildable today.

Target state — MyPADI Personalised Home reference. This item is downgraded.

The reference lists "the recommended course/specialty modules" explicitly under Not API-backed — its category for sections that "resolve to a link, Drupal content, or the Dive Shop Locator rather than a call."

So the target state does not serve recommendations at all. GET /v2/courses?tag=recommended — the endpoint this card's verdict rests on — does not appear in the reference, and unlike the other absences this one is not silence: the reference affirmatively places the capability outside the API surface.

The 15 September session is consistent: an open action to "clarify specialty definition and course ranking with Ayush (design team)" — a design question, because there are no rules to read off an API.

The nearest thing the reference does provide is GET /p/product/catalog/sku/{sku}, a user-token catalog lookup. That resolves a known SKU; it does not choose one.

So MYP-12 splits by which state you build for. Against today's endpoints it is buildable at the rules-based reading. Against the declared target state it is a gap with a design question in front of it — and PAM-36, its delivery story, sits at Later - Release 2027, which is consistent with the target state not serving it.

Verdict. Buildable at the rules-based reading. Confirm the intent before estimating.

Questions: SCOPE-9 (distinct from MYP-10) · SCOPE-10 (CDP or rules-based)


PREF-01 — Manage preferred language

Area 01 Identity, Profile & Account API status: Existing — mobile · Feasibility: buildable; propagation is broken

Endpoints available. PUT /user/update/language/{email} (mobile-proven), GET globals/SupportedLanguages, GET {PREFERENCES_URI}/v1/MarketingLanguage/{affiliateType}.

The problem, quoted from the source. "Language is stored in 3 places — Cognito custom:language, preference communicationLanguage, and profile languageCommunicationPreference — written by different endpoints, so an edit on one platform doesn't propagate."

So the app can set a language. It cannot set the language. A diver changing it in the app may still receive email in the old one — which is precisely the failure the MyPADI vision promises to fix, quoted in MYP-02's own context note.

The doc raises it as an open question: "What is the system of record for language, and can a single write propagate to Cognito + Salesforce + SFMC + LMS?"

Target state — MyPADI Personalised Home reference. Not covered. No language or preference operation appears. Language is not a personalised-home section, so the absence caution applies and the verdict is unchanged.

The three-store propagation defect in Finding 3 is untouched by the target state — and since the reference's course-detail operation notes that culture "needs to follow the app's selected language rather than being pinned to en-US", the target state inherits the same problem on a new surface.

Verdict. The screen is buildable in a day. Whether it works depends on an unanswered data-ownership question. Do not present this as commit-ready without stating that.

Questions: PADI-10 (system of record for language)


PREF-02 — Manage contact preferences

Area 10 Engagement API status: Existing — web · Feasibility: buildable

Duplicate of MYP-07. Same endpoints, same auth, same missing-labels defect — see MYP-07 for the analysis; it is not repeated here. This entry records only what is specific to PREF-02.

What is specific to it: granularity. "Contact preferences" implies channels; MYP-07's wording implies email only. The preference API serves seven flat categories with no per-channel dimension. PADI Adventures, by contrast, already models per-channel flags (email / SMS / push) across five categories — accountNotifications, reminders, divingOpportunities, adventuresPromotionsUpdates, travelInspirationalNews.

So if PREF-02 means channels, the existing endpoint is the wrong shape and this stops being a duplicate. If it means the same seven email categories, it is MYP-07 under a second name.

Target state — MyPADI Personalised Home reference. Not covered — same position as MYP-07, of which this is the second identifier. No corroboration either way.

Verdict. Buildable on MYP-07's endpoints at the email reading. Unresolved at the multi-channel reading.

Questions: SCOPE-6 (same item as MYP-07, or is per-channel intended) · PADI-8 (category labels)


Area 01 Identity, Profile & Account API status: Gap · Feasibility: not buildable as specified

Same gap as MYP-09, from the diver's side rather than the platform's — see MYP-09 for the analysis; it is not repeated here. This entry records only what is specific to PREF-03.

What is specific to it: deletion and export, not consent. MYP-09 is about keeping opt-ins synchronised. PREF-03 additionally implies data export and account deletion — GDPR subject rights, which no endpoint in any source serves at all.

The current app's deletion path is an email to privacy@padi.com asking an operator to deactivate the account by hand (v1/email/transact). A manual step in a GDPR deletion path is an exposure whichever way this item is scoped, and it does not go away by building a new app.

Target state — MyPADI Personalised Home reference. Nothing — see MYP-09. Export and erasure appear in no source, now including this one.

Verdict. Not buildable. Scope alongside MYP-09, but note that closing MYP-09 does not close this — export and deletion are additional.

Questions: PADI-9 (consent system of record) · INT-2 (manual deletion is a compliance exposure)


Summary

Current state — what exists today

Status Items Count
Existing — mobile MYP-03, MYP-08, PREF-01 3
Existing — web MYP-02, MYP-05*, MYP-06, MYP-07, MYP-10, MYP-12, PREF-02 7
Gap MYP-01*, MYP-04, MYP-09, PREF-03 4
Deferred MYP-11 1

* partial — MYP-05 has status but not billing; MYP-01 has sections but not aggregation.

Ten of fifteen items have an endpoint today. That holds only because step 2 was run — counting what the current mobile apps call would have put eight items in the gap column.

Target state — what the MyPADI Personalised Home reference provides

Position Items Count
Covered — endpoint named MYP-02, MYP-03, MYP-04, MYP-05, MYP-06, MYP-08, MYP-10 7
Confirmed gap MYP-01, MYP-09 2
Affirmatively not API-backed MYP-12 1
Out of the reference's scope — verdict unchanged MYP-07, MYP-11, PREF-01, PREF-02, PREF-03 5

* covered with a material caveat — see each item.

Read the fourth row carefully. Those five are not target-state gaps. The reference covers one screen and names the sections it excludes; their absence carries no information. Only the first three rows are evidence.

Where the two states disagree — the four that matter

  1. MYP-12 is downgraded. The reference places "the recommended course/specialty modules" under Not API-backed. This is the one absence that is not silence — the capability is affirmatively outside the API surface, and PAM-36 sitting at Later - Release 2027 is consistent with that.
  2. MYP-03's payload collapses. The target Certification schema carries 4 fields; the shipped ECardInfo carries ~55. A certifications list is not an eCard. If the transition is read as a swap, card rendering loses its data — the likeliest place for this to break something that works.
  3. MYP-08 becomes an architecture item. The target model needs two credentials at once, and the one serving profile, certifications, club, codes and catalog is a client-credentials grant that cannot live in a mobile binary. The BFF stops being an optimisation and becomes a prerequisite — see Finding 6.
  4. MYP-04 gains a route and loses a proof. Per-section booleans from /assessmentresults make a percentage derivable, which is SCOPE-4's reduced form with an endpoint behind it. No mobile client calls it, but PadiWW/pro.padi.com does in production (src/store/modules/course/actions.js:48-70), so the route is live rather than theoretical. The claim that the gap was "proven by field list" is withdrawn — see below.

Gap re-test — three hold, one withdrawn, one correction

Every gap claim was re-tested against the specs, the live-tested matrix, the web capture and the four repos — because we cannot tell PADI something is a gap if they already serve it. The re-test is the API inventory §12.

  1. Dashboard aggregation (MYP-01) — holds, and the target state enlarges it: 20 operations, six hosts, no composition, plus a persona-identification step that must resolve before the shell can render. Axelerant's BFF to close, not PADI's.
  2. Consent and privacy systems (MYP-09, PREF-03) — holds, now against a fourth independent source.
  3. Course completion percentage (MYP-04) — holds as specified, but the "proven by field list" claim is withdrawn. That inference read the iOS decoder for /v2/courses/{packageId}/details as the response shape. A Decodable declares only the fields a client uses, so it proves non-consumption, not absence. Three sources now expect progress data to be reachable and none has been observed: the reference's progressPercent (self-declared as inferred), UserCourse.percentRemaining (declared, never read, iOS-only), and AssessmentResult.isCompleted (the derivable route — called in production by PadiWW/pro.padi.com, never by a mobile client, and never with its body captured by this study). PADI-31 is the one live call that settles it, and it is now a confirmation rather than a discovery.
  4. ~~Membership billing history (MYP-05)~~ — downgraded to unverified. POST /pros/graphql is GraphQL and field absence cannot be asserted without introspecting it. renewalStatus and autoRenewEnabled are served, and the target state adds GET /pros/renewals/info — the first candidate endpoint for the history half.

Two checks now stand between this and a defensible gap list, both minutes of work with a valid credential:

  • Introspect pros/graphql — unchanged, and the target state does not retire it.
  • Call /learning/v2/courses/{customCourseId}/assessmentresults — replaces the /details check, which was answered incompletely.

And four risks that are not about missing endpoints

  • Language does not propagate across its three stores (PREF-01), and the target state inherits it — the reference's own note that culture must follow the app's language rather than being pinned to en-US is the same defect on a new surface.
  • Cert and Photo reject the SSO token — 401 (MYP-03, MYP-06). Finding 6 explains why: in the target model they do not take that token. This stops being a bug report and becomes a design constraint.
  • Two unresolved path pairs — login (auth/api/oauth/login vs auth/api/login) and certification photo (/p/photos/api/ vs /p/photos/web/). Two specs in a row have named one of each and ignored the other.
  • An unknown subset of these endpoints is being rebuilt for the legacy sunset (PADI-1).

PADI-1 is still the one to raise first, and this card is now the best argument for it: there are two documented API surfaces for the same dashboard, produced eight days apart by the same team, and they disagree in four places. Neither is a PADI contract. Every Existing verdict above — in both states — is only as durable as PADI's answer.