API Inventory — PADI Mobile, as built
Companion to Logged In Experience. This is the API-documentation half of the ask: what the existing mobile apps actually call today, grouped by feature area, with source citations.
How to read this document
Sections 1–9 are step 1 of the test: what the current mobile apps call. They are evidence that an endpoint works from a mobile client — nothing more. The apps themselves are being replaced by the unified Expo React Native app.
Wherever sections 1–9 say an endpoint is "absent", that means absent from the mobile apps. It does not mean PADI has no such endpoint. Several of those gaps are closed by a web front end — profile read, membership, preferences and recommendations among them.
§10 is step 2, and it supersedes any "absent" in the earlier sections. Read them together or you will reach the wrong conclusion.
Scope. Primarily the PADI app (kms-ios-padi, kms-android-padi), which is what the Logged In Experience card concerns. AWARE and Adventures are covered in §7 for contrast, because they are separate products on separate backends with separate identity.
Provenance. Every mobile endpoint below is grounded in the Kotlin or Swift source. The three OpenAPI files in kms-android-padi (padi-app-openapi.yaml, padi-kms-openapi.yaml, docs/openapi-android.yaml) are untracked and self-described as reverse-engineered from client code — useful as a cross-check, not cited here as contract.
The same caution applies to the web side. Rows marked "exists web-side" cite MyPADI — Unified Diver Profile.docx, which is Axelerant-authored discovery, not a PADI contract — it is titled "API Reference & Dashboard Gap Analysis" and states that "endpoints were captured from the browser Network tab and cross-checked against the PadiWW source." No PADI-published API documentation for MyPADI exists in the corpus; it was requested from Ralph on 2026-04-24 and asked for again on 2026-07-13. Nothing in this inventory is a contract PADI has committed to. Everything here is observed behaviour — reliable as a description of what runs today, unreliable as a guarantee of what will be there in Q1 2027.
⚠️ Some of these endpoints are being replaced, and nobody has told us which.
Ralph Lai (VP Product Engineering, PADI) and Kanikar Phan, in WS 26 — Minutes of Meeting, PADI Technical Workshop #3 (2026-03-31), state that:
- "Existing Postman documentation currently reflects only a subset"
- "APIs are undergoing an active transition"
- "Several endpoints are being rebuilt due to the legacy system sunset"
This inventory is a snapshot of what the apps call today, not a forecast of what will exist in Q1 2027. An "exists" row means the endpoint is live and consumed right now. It does not mean the endpoint survives the legacy sunset, keeps its path, or keeps its response shape.
No list of which endpoints are being rebuilt has been shared. Until PADI provides one, every estimate built on this inventory carries unquantified rework risk — and the risk is largest exactly where mobile depends most: the auth facade, the eCard family, and the learning endpoints all sit on the legacy surface being transitioned.
This is the single highest-value thing to ask PADI for, ahead of any new API: which of these 34 endpoints survive, and on what timeline.
1. Where the contracts live
iOS (kms-ios-padi) |
Android (kms-android-padi) |
|
|---|---|---|
| Endpoint definitions | PADI/data/api/api/*.swift (27 protocols), paths in PADI/data/api/impl/** |
app/src/main/java/com/duns/padiapp/data/api/*.kt (21 Retrofit interfaces) — authoritative |
| DI / index | PADI/component/Components.swift, Components+APIs.swift |
di/AppModule.kt:193-429, di/PADITrainingAppModule.kt:53-78 |
| HTTP client | PADI/data/api/common/ApiClient.swift, impl/ApiClient/ApiClientImpl.swift (Alamofire) |
Retrofit + OkHttp, wired in AppModule.kt |
| Auth header | PADI/data/api/common/HeaderBuilder.swift:65-78 |
data/api/interceptor/AuthenticatorInterceptorRefreshToken.kt:36-42 |
| Refresh / retry | PADI/data/api/common/RequestAdapter.swift:36-118 |
AuthenticatorInterceptorRefreshToken.kt:47-99 |
| Host registry | PADI/component/Configuration.swift (19-case EndPoint enum) |
domain/{Live,Staging}AppSettingIpm.kt:16-53 |
Android's Retrofit interfaces are the single best place to read the whole surface — 21 files, ~516 lines. iOS has no equivalent single view.
2. Hosts and environments
From LiveAppSettingIpm.kt / StagingAppSettingIpm.kt:16-53 and Configuration.swift:97-121.
| Service | Production | Staging |
|---|---|---|
| Auth (Cognito facade) | api.global-prod.padi.com/auth/api/ |
api-stage.global-np.padi.com/auth/api/ |
| eCard | ecard.global-prod.padi.com/api/ |
ecard-stage.global-np.padi.com/api/ |
| eCard v2 | api.global-prod.padi.com/c/cards-new/ |
api.global-np.padi.com/c/cards-new/ |
| Cert photo | api.padi.com/cp/certification-photo/ |
api.global-np.padi.com/cp/certification-photo/ |
| Learning / LMS | learning-prod.padi.com/learning/ |
learning-np.padi-staging.com/learning/ |
| API gateway | api.padi.com/ |
api.global-np.padi.com/ |
| Logbook | logbook.global-prod.padi.com/api/ |
logbook-stage.global-np.padi.com/api/ |
| Pro | api.pro-prod.padi.com/ |
api.pro-np.padi.com/ |
| Checkpoint | checkpoint-api.padi.com/api/ |
checkpoint-stage.global-np.padi.com/api/ |
| Languages | languages.global-prod.padi.com/api/ |
languages-stage.global-np.padi.com/api/ |
| Analytics | analytics-prod.padi.com/api/v1/ |
analytics-np.padi.com/api/v1/ |
| Commerce | api.padi.com/c/commerce/api/ |
api.global-np.padi.com/c/commerce/api/ |
| eCommerce | api-ecomm.ecomm-prod.padi.com/api/v3/ |
api-stage-ecomm.ecomm-np.padi.com/api/v3/ |
| Identity (direct) | cognito-idp.us-west-2.amazonaws.com, pool us-west-2_hGwJiwtcI |
same pool, different client id |
| Cloudinary | api.cloudinary.com/v1_1/padi, res.cloudinary.com/padi |
same |
| Geolocation | geolocation.padi-prod.padi.com |
same |
Environment defects found while compiling this — all worth a ticket independent of the feasibility work:
LiveAppSettingIpm.kt:33— messaging uses the staging URL in productionBaseAppSettingsIpm.kt:152—getCognito2Url()returns the staging auth host in both flavoursProChekApiImpl.swift:14— production URL hardcoded, ignores environmentprocheckUrlis the same production host in both Android flavours
3. Authentication and session
Base: .../auth/api/. A PADI-owned REST facade in front of AWS Cognito.
| Method | Path | iOS | Android |
|---|---|---|---|
| POST | login |
AuthenticationApiImpl.swift:20,37 |
Swapi.kt:31-33 |
| POST | token (refresh) |
RefreshTokenApiImpl.swift:14,29 |
Swapi.kt:86-88 |
| POST | oauth/register |
RegisterApiImpl.swift:14,54,101 |
Swapi.kt:69-71 |
| POST | oauth/confirm |
RegisterConfirmationAPIImpl.swift:13,41 |
Swapi.kt:75-77 |
| POST | oauth/reset |
RegisterConfirmationAPIImpl.swift:14,60 |
Swapi.kt:58-59 |
| POST | password/reset/initiate |
SendConfirmationCodeApiImpl.swift:14,26 |
Swapi.kt:53-54 |
| POST | user/exist/legacy |
GetUserStatusApiImpl.swift:13,20 |
Swapi.kt:37-39 |
| POST | sso/exchange |
JumpPageSSO.swift:128 |
Swapi.kt:81-82 |
| POST | oauth2/token (client credentials) |
EmailTransactServiceImpl.swift:35-44 |
Cognito2Api.kt:9-12 |
| POST | Cognito GetUser |
UserAttribute/UserAttributeApi.swift:21-27 |
absent |
Token model. Three tokens (idToken, accessToken, refreshToken). The idToken is sent as the bearer on both platforms. Endpoints opt out of auth with @Headers("isAuthorized: false") on Android (Swapi.kt:32,38,70,75,87) or the nonAuthKeyAPIs allow-list on iOS (HeaderBuilder.swift:57-63).
Refresh. Reactive, on 401/403, single retry, then forced logout. Android synchronises the refresh; iOS queues concurrent failures in requestsToRetry and replays them after one refresh.
Confirmed absent: server-side logout/revoke · change-password while authenticated (only the forgot-password reset flow) · biometrics · OAuth hosted-UI / PKCE / social login into the app.
user/exist/legacy returns a triage of m2User / cognitoUser / travelUser — evidence that cross-property identity was anticipated.
4. Profile
Read: no endpoint in the mobile apps. (§10: GET /p/profile/web/me is live-tested and returns the full profile.) In the current apps, profile is decoded from the Cognito ID token — see IDToken.swift:11-50 for the full claim set, including contact_id (Salesforce Contact Id), global_uuid, custom:affiliate_id, custom:affiliate_type_id, custom:language.
Write:
| Method | Path | Base | Source |
|---|---|---|---|
| PUT | user/update/{username} — accepts firstName, middleName, lastName, birthDate; used only for guardian email |
auth | Swapi.kt:63-64; SendGuardianPermissionMailApiImpl.swift:13,25 |
| PUT | user/update/language/{email} |
auth | Swapi.kt:43-44; UpdateSelectedLanguageApiImpl.swift:21,24 |
| POST | profiles/{affiliateId}/MailingAddress |
learning portal | LearningApi.kt:22-23; AddressApiImpl.swift:14-16,40 |
Address fields: addressLine1, addressLine2, city, countryId, postalCode, stateId.
Reference data:
| Method | Path | Source |
|---|---|---|
| GET | globals/Countries |
LearningApi.kt:14-15 |
| GET | globals/Countries/{countryId}/States |
LearningApi.kt:17-18 |
| GET | globals/SupportedLanguages |
LearningApi.kt:28-29 |
| GET | api/v1/analytics/{globalUUID} — segmentation: gender, is_pro, is_freediver, is_tec, preferred_language, diver_core_level, home_country, age, user_type, level |
AnalyticsPadiApi.kt:8-11; AnalyticsAPIImpl.swift:14,19 |
The analytics endpoint is the only source of home_country and diver_core_level.
Photos:
| Method | Path | Source |
|---|---|---|
| POST | certifications/photos/upload (multipart) |
ProApi.kt:14-16; UploadPhotoApiImpl.swift:14,21-22 |
| GET | cp/certification-photo/api/CertificationPhoto/GetCertificationPhoto |
CertificationPhotoApi.kt:7-8 |
| GET | p/photos/web/certification (iOS only — exposes moderation state) |
GetCertificationPhotoApilmpl.swift:24,34 |
No delete endpoint. Android's upload returns only {success: Boolean}.
5. Certifications and eCards
Base ecard.global-prod.padi.com/api/, path constants at ECardsApi.kt:9-13.
| Method | Path | Android | iOS |
|---|---|---|---|
| GET | eCard/GetCardListConsumer |
ECardsApi.kt:10,21-22 |
GeteCardsApiV2Impl.swift:61,103 |
| GET | eCard/GeteCardListConsumer |
ECardsApi.kt:9,18-19 |
GeteCardsApiV2Impl.swift:63,93 |
| GET | eCard/HaseCardConsumer |
ECardsApi.kt:11,24-25 |
GeteCardsApiV2Impl.swift:68 |
| GET | eCard/HaseCardPro |
ECardsApi.kt:12,28-29 |
GeteCardsApiV2Impl.swift:68 |
| GET | eCard/GetAlleCardListPro |
ECardsApi.kt:13,31-32 |
GeteCardsApiV2Impl.swift:70,224 |
| GET | eCard/GetAllCardListPro |
ECardsApi.kt:34-35 |
GeteCardsApiV2Impl.swift:61 |
| GET | eCard/GeteCardTopConsumer/{n} (wallet) |
absent | GeteCardsApiV2Impl.swift:64-66,98 |
| GET | c/cards-new/web/licenses (v2, flagged off in RELEASE) |
absent | GeteCardsApiV2Impl.swift:19-32,56-57 |
| POST | Cloudinary padi/resources/search (artwork) |
CloudinaryApi.kt:10-18 |
ECardImageServiceImpl.swift:55-60 |
ECardInfo (domain/models/ECardInfo.kt:14-68) — ~55 fields including cardTitle, padiNumber, certDate, inWaterDate, instructorName/Number, store details, memberSince, lastRenewed, cardExpirationDate, qrCode, canPrint. AffiliateSummary is only four: affiliateID, fullName, birthdate, photo.
No certification-detail endpoint — detail renders from the list. No progression/path endpoint in the mobile apps (§10: {PRO_API}/journey/routing/othercourses is web-captured; §11 adds GET /c/credentials/{id}).
6. Learning, preferences, consent and the rest
Learning / LMS (base learning-prod.padi.com/learning/):
| Method | Path | Source |
|---|---|---|
| GET | v2/entitlements/{affiliateID} → {title, packageId, platform} only |
LearningV2Api.kt:11-12; GetEntitlementsResponse.kt |
| GET | v2/courses/{courseID}/details |
LearningV2Api.kt:16-17 |
| GET | v2/courses/{courseID} (assets) |
LearningV2Api.kt:22-23 |
| GET | offline/learning (Basic auth, iOS only) |
OfflineLearningApiImpl.swift:21-27 |
| POST | offline/learning/{registrationId} (SCORM score, iOS only) |
SCORMApiImpl.swift:14,25 |
| POST | learning/lms/commit (SCORM runtime commit) |
PADIApi.kt:10-13 |
No per-course completion percentage exists — and unlike the others, this one survives §10 too. Progress lives inside the SCORM payload and no web front end reads it back.
Preferences and consent:
| Method | Path | Source |
|---|---|---|
| GET | p/personalization/api/Personalization/attributes/{key} |
PersonalizationApi.kt:11-12 |
| POST | p/personalization/api/Personalization/attributes |
PersonalizationApi.kt:16-17 |
| GET | p/personalization/api/Personalization/attributes (iOS only) |
PersonalizationServiceImpl.swift:46,58 |
| GET | api/Languages/GetLanguage/{platform}/{lang} |
LanguagesApi.kt:8-12 |
The Personalization service is a schemaless key/value store. Its only key in use is china_data_consent. Email/marketing preferences have no API — SSO webview to learning.padi.com/preferences/subscriber (BaseAppSettingsIpm.kt:252-255; Constant.swift:13).
Other:
| Method | Path | Source |
|---|---|---|
| POST | logbook (GraphQL, Hasura-shaped) |
LogbookApi.kt:12-18; LogBookService.swift:28 |
| POST | pros/graphql (ProChek) |
ProApi.kt:20-21 |
| POST | prochek/prochek/GetProChekByMemberNumber/ |
ProCheckApi.kt:9-12 |
| GET/POST/PUT/DELETE | api/CheckPoint/* (Discover Scuba Diving) |
CheckpointApi.kt:11-22; LearnAPIImpl.swift:21-32 |
| GET | c/commerce/api/Product/Search |
NewCourseApi.kt:11; CourseCatalogueServiceImpl.swift:186 |
| POST | v1/email/transact (account-deletion request) |
MessagingApi.kt:9-12; EmailTransactServiceImpl.swift:73 |
| GET | geolocation root (China PIPL gating) | UtilsApi.kt:31-34; GeolocationServiceImpl.swift:26 |
No membership, Club or billing API is called by the mobile apps. (§10 and §11 close most of this.) (§10: GET /c/club/member/subscription is live-tested, and Piccolo serves entitlements.) In the current apps Club is a web link: Constant.swift:135,201, build.gradle.kts:100-101.
7. The other two apps, for contrast
Both are separate products on separate backends. Neither is a source of reusable contract for this card.
PADI AWARE (padi.aware.flutter) — conservation surveys. Clean Retrofit codegen at packages/data/lib/src/http/. Notable: GET /users/me and PUT /users/profile (services/user_service.dart:5-30) are the cleanest profile contract in the estate — but they serve the AWARE backend, not PADI's. Uses hosted PADI SSO (mobile_sso_auth.dart:8-16) plus an SLO session service at slo.global-prod.padi.com/graphql — the one piece of cross-PADI identity plumbing that exists. Zero certification, learning, or membership surface.
PADI Adventures (travel-rn) — booking and commerce on travel.padi.com/api/mobile/v1 (Django). Own login (account/login), Authorization: JWT not Bearer, no refresh token, session in unencrypted AsyncStorage. Has the most granular contact-preference model anywhere (account/preferences/notification/ — per-channel flags across five categories) and a real DELETE account/. Certification data is self-declared free text, not from PADI's cert system.
Identity comparison — the substance of the MYP-08 gap:
| PADI app (KMS) | AWARE | Adventures | |
|---|---|---|---|
| IdP | Cognito via PADI facade | Hosted PADI SSO | Own backend |
| Bearer | idToken |
AWARE-issued JWT | Authorization: JWT |
| Refresh | yes | yes | none |
| Storage | UserDefaults / SharedPreferences (plaintext) |
secure storage | AsyncStorage (plaintext) |
| Server logout | none | DELETE /logout + SLO |
none |
8. Gaps in the documentation itself
- No server-authored API contract exists anywhere. Every spec in the estate is client-derived.
- No Postman/Insomnia collection, no
.httpfiles, no HAR captures in any of the four repos. - No GraphQL schema for any of the three GraphQL endpoints consumed (Logbook, Pros, SLO). Only Logbook has operation documents (
kms-ios-padi/PADI/apollo.graphql). - No ADRs in any repo.
kms-ios-padiandkms-android-padiREADMEs contain no backend integration content — Android's is ten lines of git commands.- No mock server anywhere. The only response-shape fixtures are four plists under
kms-ios-padi/PADITests/.
Items 1–3 are why this inventory was compiled from source rather than from documentation, and why every claim here carries a file:line.
9. Endpoint → item map (Logged In Experience)
Every endpoint the card's 15 items depend on, as called by the current mobile apps.
⚠️ — in the Path column means no endpoint in the mobile apps — not "no endpoint at PADI". Check §10 before treating any of these as a gap. Only two survive as real gaps: per-course completion percentage, and the consent platform.
Identity & session → MYP-08
| Method | Path | Source | Status |
|---|---|---|---|
| POST | login |
Swapi.kt:31-33 |
exists |
| POST | token (refresh) |
Swapi.kt:86-88 |
exists |
| POST | oauth/register |
Swapi.kt:69-71 |
exists |
| POST | oauth/confirm |
Swapi.kt:75-77 |
exists |
| POST | oauth/reset |
Swapi.kt:58-59 |
exists |
| POST | password/reset/initiate |
Swapi.kt:53-54 |
exists |
| POST | user/exist/legacy |
Swapi.kt:37-39 |
exists — returns the m2User/cognitoUser/travelUser triage |
| POST | sso/exchange |
Swapi.kt:81-82 |
exists — the primitive unified login needs |
| — | server-side logout / revoke | SessionRepositoryImpl.swift:152-168 |
absent — sign-out is local only |
| — | change password while authenticated | — | absent — only the forgot-password flow |
Base: api.global-prod.padi.com/auth/api/. Bearer is the idToken, not the access token.
Profile & preferences → MYP-02, PREF-01
| Method | Path | Source | Status |
|---|---|---|---|
| — | profile read | IDToken.swift:11-50 |
absent in mobile — §10 closes it: GET /p/profile/web/me, live-tested |
| PUT | user/update/{username} |
Swapi.kt:63-64 |
exists — accepts name/DOB, used only for guardian email |
| PUT | user/update/language/{email} |
Swapi.kt:43-44 |
exists — backs PREF-01 |
| POST | profiles/{affiliateId}/MailingAddress |
LearningApi.kt:22-23 |
exists |
| GET | globals/Countries |
LearningApi.kt:14-15 |
exists |
| GET | globals/Countries/{countryId}/States |
LearningApi.kt:17-18 |
exists |
| GET | globals/SupportedLanguages |
LearningApi.kt:28-29 |
exists — backs PREF-01 |
| GET | api/v1/analytics/{globalUUID} |
AnalyticsPadiApi.kt:8-11 |
exists — only source of home_country, diver_core_level |
| — | field-level permissions | — | absent — no such system in any repo |
Photos → MYP-06
| Method | Path | Source | Status |
|---|---|---|---|
| POST | certifications/photos/upload |
ProApi.kt:14-16 |
exists — moderated cert photo, not an avatar |
| GET | cp/certification-photo/.../GetCertificationPhoto |
CertificationPhotoApi.kt:7-8 |
exists |
| GET | p/photos/web/certification |
GetCertificationPhotoApilmpl.swift:24,34 |
exists — iOS only; the only one exposing moderation state |
| — | delete photo | — | absent |
Certifications & eCards → MYP-03, MYP-10
| Method | Path | Source | Status |
|---|---|---|---|
| GET | eCard/GetCardListConsumer |
ECardsApi.kt:10,21-22 |
exists — certifications |
| GET | eCard/GeteCardListConsumer |
ECardsApi.kt:9,18-19 |
exists — purchased eCards |
| GET | eCard/HaseCardConsumer |
ECardsApi.kt:11,24-25 |
exists |
| GET | eCard/HaseCardPro |
ECardsApi.kt:12,28-29 |
exists |
| GET | eCard/GetAlleCardListPro |
ECardsApi.kt:13,31-32 |
exists |
| GET | eCard/GetAllCardListPro |
ECardsApi.kt:34-35 |
exists |
| GET | eCard/GeteCardTopConsumer/{n} |
GeteCardsApiV2Impl.swift:64-66 |
exists — iOS wallet view |
| GET | c/cards-new/web/licenses |
GeteCardsApiV2Impl.swift:19-32 |
exists — v2, feature-flagged off in RELEASE |
| POST | padi/resources/search (Cloudinary) |
CloudinaryApi.kt:10-18 |
exists — card artwork |
| — | certification detail | — | absent — renders from the list payload |
| — | certification path / progression | FilterNextCourseType.kt |
absent in mobile — §10 closes it: {PRO_API}/journey/routing/othercourses |
ECardInfo carries ~55 fields (ECardInfo.kt:14-68) — more than MYP-03 requires.
Learning → MYP-04, MYP-12
| Method | Path | Source | Status |
|---|---|---|---|
| GET | v2/entitlements/{affiliateID} |
LearningV2Api.kt:11-12 |
exists — only {title, packageId, platform} |
| GET | v2/courses/{courseID}/details |
LearningV2Api.kt:16-17 |
exists |
| GET | v2/courses/{courseID} |
LearningV2Api.kt:22-23 |
exists — assets |
| POST | learning/lms/commit |
PADIApi.kt:10-13 |
exists — SCORM commit; where progress actually lives |
| — | per-course completion % / last-accessed | — | absent, and it survives §10 — the MYP-04 blocker. One unread response body: see §12 |
| — | recommendation service | — | absent in mobile — §10 closes it: GET /v2/courses?tag=recommended |
Preferences & consent → MYP-07, MYP-09, PREF-02, PREF-03
| Method | Path | Source | Status |
|---|---|---|---|
| GET | p/personalization/api/Personalization/attributes/{key} |
PersonalizationApi.kt:11-12 |
exists — only key in use is china_data_consent |
| POST | p/personalization/api/Personalization/attributes |
PersonalizationApi.kt:16-17 |
exists — schemaless key/value store |
| — | email / marketing preferences (mobile) | BaseAppSettingsIpm.kt:252-255 |
absent in mobile — §10 closes it; the web-side rows follow |
| GET/POST/PUT | {PREFERENCES_URI}/v1/preference[/{id}] |
MyPADI §4.8 | exists web-side — not yet consumed by mobile |
| GET/POST/PUT | {PREFERENCES_URI}/v1/unsubscribe |
MyPADI §4.8 | exists web-side |
| POST | prefapi.scubadiving.com/recipient |
MyPADI §5 | exists web-side — SFMC prefs |
| GET | {PREFERENCES_URI}/v1/MarketingLanguage/{affiliateType} |
MyPADI §4.8 | exists web-side — the PREF-01 open thread |
| — | consent-management platform | — | absent, and it survives §10 — zero hits in all four repos, none in §11 |
Membership → MYP-05
| Method | Path | Source | Status |
|---|---|---|---|
| — | all membership / Club / billing | Constant.swift:135,201 |
absent in mobile — §10 and §11 close status, renewal status and auto-renew; billing history alone is unresolved (§12) |
Target side names the Pro API, the Piccolo entitlements engine and Stripe (MyPADI §5); mobile exposure unverified.
Dashboard & commerce → MYP-01
| Method | Path | Source | Status |
|---|---|---|---|
| — | aggregate dashboard endpoint | — | absent, and it survives §10 and §11 — confirmed on every source |
| GET | Product/Search |
NewCourseApi.kt:11 |
exists — but static X-API-KEY, not user-scoped |
| — | order / purchase history | — | absent in mobile — §10 closes it: POST cart/getOrders, separate auth realm |
Count — mobile surface only
| Exist and are consumed by the mobile apps today | 34 |
| Not called by the mobile apps | 18 |
| Total surface this card touches | 52 |
Do not read the 18 as gaps. Running them through step 2 (§10) closes most: profile read, membership status, preferences, recommendations and order history all have endpoints a web front end calls.
Four gaps survive both steps — and §12 re-tests all four against the MyPADI OpenAPI spec:
- Per-course completion percentage (MYP-04) — absent from the mobile apps, the web capture, the live-tested matrix and the spec. Holds, with one unread response body named in §12.
- Dashboard aggregation (MYP-01) — every section has an endpoint; nothing composes them. Holds.
- Consent and privacy systems (MYP-09, PREF-03) — the source says "Privacy systems not documented". Holds, searched directly.
- Membership billing history (MYP-05) — asked in June 2026, unanswered. Downgraded to unverified — the endpoint that would carry it is GraphQL and nobody has introspected the schema. Do not put this to PADI as a gap until they have.
And read all 34 with the transition caveat attached. Per Ralph Lai and Kanikar Phan (2026-03-31), several endpoints are being rebuilt for the legacy sunset and no list has been shared. Two questions to PADI, in this order:
- Which of these survive the sunset, and when do the replacements land? (PADI-1) — this can invalidate work already scoped as safe.
- Which of the three surviving gaps will be closed, and by when? (Not billing history — that is ours to verify first, per §12.)
The second is the question everyone is asking. The first is the one that can quietly turn a buildable verdict into rework, and it has not been asked.
10. Web front-end endpoints — step 2 of the test
The sections above inventory what the current mobile apps call. That is step 1. This section is step 2: endpoints that PADI web front ends call, which the unified Expo RN app can therefore also call.
Two sources, and the difference between them matters:
- Live-tested — Confluence AWS Cognito Authentication Architecture §7 (2026-06-11). Called outside a browser with a real Cognito idToken, consumer and pro accounts, against non-prod. This is the only evidence class that proves a non-browser client can reach the endpoint.
- Web-captured — MyPADI — Unified Diver Profile.docx. Observed in the browser Network tab. Proves the endpoint exists and serves the data; does not prove it answers a native client.
The access matrix — live-tested
| Data | Endpoint | Auth |
|---|---|---|
| Reference data | /r/references/Countries · /states · /cities · /Languages · /Currency |
none |
| Profile (self) | /p/profile/web/me |
idToken |
| Profile by id | /p/profile/affiliateid/{id} |
idToken |
| Club / membership | /c/club/member/subscription |
idToken |
| Certifications | /c/certification/affiliate/{id} |
idToken |
| Photo | /p/photos/api/certification/{id} |
idToken |
| Courses | /v2/entitlements/{id} · /v2/courses |
idToken |
| Logbook | logbook-stage…/api/Logbook (Hasura GraphQL) |
idToken |
| Orders | frontastic…/cart/getOrders |
commercetools session |
| Travel bookings | travel.padi.com/api/v2/travel/account/bookings/ |
Travel JWT |
Named failure: "Cert / Photo reject the SSO token (401) — need a different client/scope/key from the API owners."
Profile — web-captured
Base learning-prod.padi.com/profiles, Bearer + affiliate-id.
GET /{affiliateId} · GET /{affiliateId}?profile=true · POST /{affiliateId}/MailingAddress
Response fields: photoUrl, fullName, firstName, lastName, dob, memberNumber, memberSince, affiliateId, hasAutoRenewEnabled, languageCommunicationPreference, primaryEmailAddress, phoneNumber, countryCodeISO, teachingAddress{…}, mailingAddress{…}.
Club — web-captured
"The club experience largely calls the Pro API, the core preference API, the Piccolo entitlements engine, and Stripe-backed billing."
| Endpoint | Purpose |
|---|---|
GET piccoloapi.scubadiving.com/customer/entitlements/active |
Active entitlements / benefits |
GET {PRO_API}/journey/clubmember |
Club member status |
GET {PRO_API}/pros/stripe/getcarddata/1/{id} |
Stripe card on file |
GET {PRO_API}/journey/routing/othercourses |
Other-courses routing |
GET {BASE_API}/m/membership/v1.2/store/provision |
Store provision |
Entitlement matching is server-side in a c/club REST BFF — "the client just presence-checks each key" (ADR-0004, axelerant-padi/mobile-poc).
Preferences — web-captured
| Method | Endpoint |
|---|---|
| GET/POST/PUT | {PREFERENCES_URI}/v1/preference[/{id}] · /v1/unsubscribe |
| GET | {PREFERENCES_URI}/v1/MarketingLanguage/{affiliateType} |
| POST | prefapi.scubadiving.com/recipient — push to SFMC |
{PREFERENCES_URI} = https://api.core-prod.padi.com/preference. Categories: diveAbroad, localDiving, environmentConservation, events, diveCentricTrips, padiCommunications, thirdParty.
Defect: returns only numeric preferenceId + status; no endpoint returns the labels (PADI-8).
Learning — web-captured
GET /v2/entitlements/{affiliateId}?filter=idc · GET /v2/courses?tag=recommended · GET /v2/courses/{packageId}/details · GET /v2/courses/{id}/assessmentresults · POST /v2/courses/{packageId}/enroll
?tag=recommended is what makes MYP-12 buildable today — but see §14: the target-state reference places recommendations outside the API surface entirely. No percent-complete or last-accessed field exists on any of these (PADI-5).
Orders — the awkward one
Four steps, and a different credential:
- Cognito
InitiateAuthwith the store client2i83ve0ida4mfe5meeem8i4qe9→ idToken POST account/getAccountForSso {idToken}→ returns afrontastic-sessionresponse header (not a cookie — must be captured and re-sent)POST account/getAccountwith that headerPOST cart/getOrderswith that header →Order[]
Verified negative worth knowing: "the account Orders panel only fires cart/getCart, so it renders empty for every account (verified: a store account with 20 confirmed orders shows none). Verify orders via the API, not the store UI."
Auth model
"Authorization: Bearer <Cognito idToken> plus affiliate-id header. Default for profile, courses, club, pro, checkpoint, preferences, eCards, membership."
idToken expiry is 60 minutes, and aud must match the target client — "the exchange re-issues per target; you do not reuse one token everywhere."
What is not established
- CORS / allowed-origin policy — no statement exists for any of these hosts. Confirmed absence.
- §4.2, §4.5, §4.6 of the MyPADI doc — implied by numbering, content not recovered. The tables above are substantially but not provably complete.
mypadi-api-mappingandmypadi-api-explorer(Spin) — indexed as project records only. The highest-value next read.
11. The MyPADI OpenAPI spec — step 2, formalised
Source: SwaggerHub axeleranttechnologie-a45/mypadi/1.0.0 — OpenAPI 3.0.3, 16 operations, retrieved 2026-09-10 from https://api.swaggerhub.com/apis/axeleranttechnologie-a45/mypadi/1.0.0.
This is the most structured endpoint source in the corpus and it was not consulted when §10 was written. It corroborates the live-tested access matrix on every endpoint they share, and it adds eight operations that appear nowhere else in this study.
It does not overturn INT-4. The SwaggerHub org is
axeleranttechnologie— Axelerant's, not PADI's. This is still Axelerant discovery, now written as a spec rather than prose. It is a better-quality artefact of the same evidence class, not a PADI contract. §8's claim that no PADI-authored contract exists stands; §8's phrasing "no server-authored API contract exists anywhere" should be read as "none authored by PADI", and "no Postman collection in any of the four repos" remains literally true — this lives outside the repos.Where a spec operation and a live test agree, treat the endpoint as live-tested: the spec adds structure, the test adds proof.
Declared servers. api.global-prod.padi.com · api.padi.com · api.pro-prod.padi.com · logbook.global-prod.padi.com, plus non-prod api-stage.global-np.padi.com · api.global-np.padi.com · logbook-stage.global-np.padi.com. Security schemes: bearerAuth and basicAuth.
Coverage against this study
| # | Operation | Summary | In this study? |
|---|---|---|---|
| 1 | POST /auth/api/oauth/login |
Login | Path variant — see below |
| 2 | GET /p/profile/web/me |
Profile by current user | §10, live-tested |
| 3 | GET /p/profile/affiliate/{id} |
Profile by affiliate ID | No — distinct from affiliateid |
| 4 | GET /preference/v1/preference/{id} |
Preference | §10 (as {PREFERENCES_URI}/v1/preference) |
| 5 | GET /c/certification/affiliate/{id} |
Certifications | §10, live-tested |
| 6 | GET /c/club/member/subscription |
Membership & Club benefits | §10, live-tested |
| 7 | POST /pros/graphql |
Membership Data | §6 (as ProChek) — new purpose |
| 8 | POST /api/Logbook |
Logbook | §10 |
| 9 | POST /o/oauth/api/token |
Token (client credentials, basicAuth) | No |
| 10 | POST /p/profile/bulk |
Profile bulk lookup | No |
| 11 | GET /c/codes/search/{email} |
Codes service | No |
| 12 | GET /p/product/catalog/sku/{sku} |
Product catalog | No |
| 13 | GET /c/credentials/{id}?allCredentials=true |
Credentials | No |
| 14 | GET /c/certification/instructor/{id}/attribute-counts |
Certification attribute counts | No |
| 15 | GET /p/profile/affiliateid/{id} |
Account Since | Path in §10 — purpose was not known |
| 16 | GET /c/certification/affiliate/{id} (non-prod) |
Certifications | duplicate of 5 |
Eight operations were missing from this study. None of them creates a new gap, but one removes one — the member query and /c/credentials/ are why MYP-05's billing gap is withdrawn in §12 — and three change how an item should be built.
What the new operations mean for the card
| Operation | Bearing |
|---|---|
GET /c/credentials/{id} |
A credentials surface distinct from /c/certification/*. MYP-03 and MYP-10 both assumed certifications were the only list. allCredentials=true implies a filtered default — worth knowing before building the eCard wallet. |
GET /p/product/catalog/sku/{sku} |
A user-token product lookup on the gateway. §9 flagged that the mobile catalogue call (Product/Search) uses a static X-API-KEY and is not user-scoped. This is the endpoint that fixes that, and it bears on MYP-12 and on Purchase History in MYP-01. |
POST /pros/graphql — Membership Data |
The query in the spec returns memberSince, renewalStatus, autoRenewEnabled, memberNumber, memberStatus, primaryCredentialName, countryId, office{…}. §6 recorded this endpoint only as ProChek. renewalStatus and autoRenewEnabled are membership fields MYP-05 needs — this narrows the MYP-05 gap to billing history alone, which was already the finding, but now with an endpoint behind the status half. |
GET /p/profile/affiliateid/{id} — Account Since |
Named purpose for a path §10 listed without one. |
POST /p/profile/bulk |
Batch profile by uuid[] with ssoLookup and exactMatch. Not a diver-facing need; relevant to account-merge and to MYP-08's identity reconciliation. |
GET /c/codes/search/{email} |
Undocumented purpose. Email-keyed lookup — flag before assuming it is safe to call from a client. |
GET /c/certification/instructor/{id}/attribute-counts |
Pro-side aggregate. Out of scope for this card; note for the Pro sections. |
POST /o/oauth/api/token |
A second token endpoint using basicAuth + client_credentials — a service-to-service grant, distinct from the diver's Cognito flow. Belongs to the BFF, not the device. |
Two discrepancies to resolve
- Two login paths. The spec declares
POST /auth/api/oauth/login. The mobile apps callPOST loginon the same auth base —Swapi.kt:31is literally@POST(value = "login"), resolving toauth/api/login. Both are documented as working. Nobody has said which the unified app should use, or whether one is being retired under PADI-1. - Two profile-by-id paths.
/p/profile/affiliate/{id}and/p/profile/affiliateid/{id}are both declared, with different summaries. §10 knew only the second.
⚠️ Credentials are exposed in this spec
The login operation carries two working-looking credential pairs in its request examples — usernames and plaintext passwords for a PADI test account and a @padi.com account, plus two Cognito clientId values.
The spec is publicly readable. It was retrieved for this study over plain HTTPS with no authentication and no API key. Anyone with the URL, or anyone who finds the SwaggerHub org, has those credentials.
This is not a scope finding — it is a live exposure, and it belongs with INT-3 rather than in a scope readout. Route it the same way: Tashrik first, then PADI in writing. The credentials are deliberately not reproduced here.
12. Gap re-test — what would overturn each gap
Prompted by a direct challenge: we cannot tell PADI something is a gap if it is already present. Every gap claim in this study was re-tested against the SwaggerHub spec, the live-tested matrix, the web capture and the four repos.
The distinction that matters. There are two kinds of "absent":
- Proven absent — we have looked at the thing that would contain it, and it is not there.
- Not yet seen — we have never inspected the response, so absence is an assumption.
The first is a gap. The second is an unverified claim wearing a gap's clothing, and it is the dangerous one: it puts PADI on the hook to build something they may already serve.
Three gaps in this study were resting partly on the second kind. They are marked below.
MYP-04 — course completion percentage · gap holds, one check outstanding
Proven absent from: the mobile apps (searched — the only percentage in either repo is DownloadService.kt:101, an asset-download progress bar), the SwaggerHub spec (which declares no learning endpoints at all), and the live-tested matrix.
Not yet seen: the response body of GET /v2/courses/{packageId}/details. The web capture summarises it as "course + status details" and nobody has recorded the fields. If a percentage exists anywhere, it is there.
Check before the readout: one authenticated
GET /v2/courses/{packageId}/detailsand read the field list. If it carries a percentage or a last-accessed date, MYP-04 stops being a gap and becomes buildable.
MYP-05 — renewal date, tier, billing history · downgrade to unverified
This one was overstated. The study said these fields are "named on no endpoint". That was true of the sources consulted, but three relevant response shapes have never been inspected — and one of them cannot be reasoned about at all from a path:
| Endpoint | Why it could already carry this |
|---|---|
POST /pros/graphql |
GraphQL. The spec's example query returns memberSince, renewalStatus, autoRenewEnabled, memberNumber, memberStatus, primaryCredentialName, countryId, office{…}. That is one query, not the schema. You cannot assert a field is absent from a GraphQL endpoint without introspecting it. |
GET /c/club/member/subscription |
Live-tested as reachable; response fields never recorded. |
GET /c/credentials/{id}?allCredentials=true |
Newly found in the spec, unexamined. primaryCredentialName on the member query suggests a credential/tier model exists behind it. |
renewalStatus and autoRenewEnabled are already served — so the "status" half of MYP-05 was understated too, and it is stronger than the study currently says.
Check before the readout: a GraphQL introspection query against
api.pro-prod.padi.com/pros/graphqlwith a diver idToken, then read/c/club/member/subscriptionand/c/credentials/{id}. This is the single highest-value verification left in the card — it can move MYP-05 from partial gap to buildable.
MYP-01 — dashboard aggregation · gap holds
Proven absent. The SwaggerHub spec declares 16 operations and none composes across domains; POST /p/profile/bulk is a batch lookup by uuid[], not a per-diver aggregate. Confirmed independently from the web side (2026-08-12 review) and the mobile side. Nothing found in any source composes profile + certifications + learning + membership in one call.
This gap is safe to state, and it is Axelerant's BFF to close rather than PADI's — which is the point worth making at the readout.
MYP-09 and PREF-03 — consent and privacy systems · gap holds
Proven absent, and searched directly. The only consent machinery anywhere in the estate is:
china_data_consent, a single key on the generic Personalization key/value store (Personalization.swift:11ConsentKey)TrackingConsent.GRANTED, hardcoded — that is the Datadog analytics SDK (PadiApplication.kt:318), not diver consent- eLearning parental-consent copy in
strings.xmland the localisation files — text shown to a guardian, backed by no consent record
No consent platform, no data-export endpoint, no erasure endpoint, in any repo, capture, live test or the spec. The source's own words: "Privacy systems not documented."
Account deletion is confirmed manual. DeleteAccountViewModel.kt:47 composes an HTML email to privacy@padi.com asking an operator to "deactivate their SSO, remove them from all marketing communication lists, and send a confirmation email." One partial counter-example exists and is worth naming honestly: PADI Adventures has a real DELETE account/ — but it deletes a Travel account on a separate Django backend, not a PADI identity, so it does not close this.
Summary of the re-test
| Gap | Verdict after re-test |
|---|---|
| Dashboard aggregation (MYP-01) | Holds — proven absent |
| Consent & privacy systems (MYP-09, PREF-03) | Holds — proven absent, searched directly |
| Course completion % (MYP-04) | Holds, with one unread response body outstanding |
| Membership billing history (MYP-05) | Downgrade to unverified — a GraphQL schema nobody has introspected |
Two checks stand between this study and a defensible gap list, and both are minutes of work with a valid idToken: introspect pros/graphql, and read /v2/courses/{packageId}/details. Until they are done, MYP-05's gap should be presented as unverified, not as a demand on PADI.
13. The web front ends themselves — step 2, from source
Step 2 of the test can rest on a browser capture written up in a .docx, or on the front-end source itself. The web applications are checked out under existing-web-apps/, so it rests on the source.
Eleven repositories, 2.5 GB: account.padi.com · club.padi.com · learning.padi.com · pro.padi.com · padi.com (Next.js) · languages.padi.com · customer-padi · diviac (Travel) · padi-blt, pro-cms, scuba-diving (Drupal).
252 unique endpoint constructions were extracted from production source across the five Vue/Next front ends, each cited to file:line. Extractor: scratchpad/extract_web_endpoints.py; output scratchpad/web_endpoints.json.
A new evidence class, and it outranks the others
Web-source — the endpoint appears in a checked-out web front end's own code, with a
file:line. This is stronger than web-captured: a capture shows one session's traffic, the source shows the whole surface, including paths nobody happened to exercise while the Network tab was open.It sits just below live-tested, which remains the only class proving a non-browser client is accepted.
What this changes
MYP-05 — billing. The gap was wrong, and so was the withdrawal.
GET {PADI_CLUB_URI}/member/subscription/billing exists and is called by two front ends — club.padi.com/src/api/club/index.js:29 and learning.padi.com/src/api/club/index.js:13.
But read what it returns. The function is getBillingLink, and the consumer is:
// club.padi.com/src/components/notification-bar/NotificationBar.vue:102
window.location.href = `${permissionsStore.billingLink}${isExpiredSoon.value ? '' : '/payment-methods'}`;
It returns a URL to a hosted billing portal, and the web app redirects to it. The web experience does not render billing history natively either. So structured billing history was never part of web parity, and this study was demanding from PADI something no PADI surface provides.
Corrected verdict: buildable at web parity today. Fetch the link with the idToken the app already holds, open it. Two residual points, neither an API gap:
- Opening a payment portal from inside the app is an IAP question (PADI-7), not an endpoint question. That is the real constraint.
- If the requirement genuinely means billing history rendered natively in the app, that exceeds web parity and should be recognised as new scope rather than parity.
Related, also newly found: GET {PRO_API}/pros/stripe/getstripesetupintentsecret/{id}/{id}, /setdefaultcard/{id}/{id}, /refreshstripememberdata/{id}/{id} (pro.padi.com/src/api/pros/index.js:512,522,542) — payment-method management alongside the getcarddata the study already had.
AUTH-11 — waivers. "No document-storage endpoint exists" is false.
Section 2 states: "No waiver, signature, or document-storage endpoint appears in any repo, capture or live test." There is one, and both learning.padi.com and pro.padi.com call it — TrueVault, a compliance-grade document vault:
| Method | Endpoint | Source |
|---|---|---|
| GET | {TRUEVAULT_URI}/Search/Diver |
learning.padi.com/src/store/modules/forms/actions.js:11 |
| GET | {TRUEVAULT_URI}/Search/Member |
…/forms/actions.js:10 |
| GET | {TRUEVAULT_URI}/Search/blob/{id} |
…/forms/actions.js:149 |
| GET | {TRUEVAULT_URI}/Search/document/{id} |
…/forms/actions.js:163 |
| POST | {TRUEVAULT_URI}/Truevault/Data (multipart) |
…/forms/actions.js:41 |
The response carries blobIds, formData, medicalRequired, createdDate, memberName — signed diver forms, listed per diver or per member, uploaded as multipart, retrievable as blob or document.
That is precisely the substrate AUTH-11 was said to lack. INT-6's challenge to the "Low effort" estimate should be revisited: a signing UI over an existing document service is exactly what exists. The Legal prerequisite and the missing family-linking model still gate the item — those are unchanged — but "there is no document service" is no longer a reason.
PADI-12 — answered, yes.
POST {COGNITO_URI}/password/change is live and called by both learning.padi.com/src/store/modules/auth/actions.js:137 and pro.padi.com/src/store/modules/auth/actions.js:165. AUTH-05 can keep all auth traffic on the facade; the direct-IdP route is a fallback, not a necessity. Close PADI-12.
AUTH-13 — B2B account creation. The gap is wrong.
Section 2 states: "No B2B registration endpoint. Nothing creates a dive centre, resort, or professional account." Both exist:
POST {PRO_API}/pros/account/create/member—pro.padi.com/src/api/account/index.js:6POST {PRO_API}/pros/account/create/store—pro.padi.com/src/api/account/index.js:17
The item stays post-Q1 by the artefact's own sequencing, and SCOPE-15 (does B2B belong in a consumer app) is still the right question. But it should be recorded as deferred with endpoints available, not as a gap.
MYP-04 — gap holds; the unread list grows to two
Both front ends call GET /v2/courses/{packageId}/report (learning.padi.com/src/store/modules/report/actions.js:7, pro.padi.com/…:9) alongside /details. Neither response shape is recorded anywhere. /report is now the likelier home for progress than /details.
Also newly surfaced: /v2/courses/{id}/instructor/assessmentresults, /v2/specialties, /v2/courses/enroll/{id}.
MYP-09, PREF-03 — gap holds, and TrueVault does not close it
TrueVault stores signed forms, not consent records. Nothing in any web front end serves marketing-consent history, policy-version acceptance, data export, or erasure. Searched directly across all five. The gap stands.
Two false leads, recorded so nobody re-raises them
completionPercentageinclub.padi.com/src/components/expired/type.ts:13. An optional prop on a UI interface, referenced nowhere else in the repo. Not an API field.progress: 75andlastAccessedinlearning.padi.com/tests/unit/store/modules/report/mutations.spec.js:59. Arbitrary fixture data in a test named "should handle complex report data", exercising a mutation that blindly assigns its argument. Not evidence of a response shape.
Both look like proof of a progress API. Neither is. They are worth naming because the next person to grep will find them too.
Endpoints this card touches that the study had not recorded
| Endpoint | Bearing |
|---|---|
GET {PADI_CLUB_URI}/content/dashboard/general · /specific |
Club dashboard content — MYP-01. Not a cross-domain aggregate, so the aggregation gap holds, but the club section has more than subscription behind it. |
POST {PADI_CLUB_URI}/member/print/opt-out |
Print preference — a preference surface outside the preference API. |
GET {CLUB_API}/p/profile/web/me/addresses/mailing |
Address read on the profile route — MYP-02. |
GET/PUT {PREFERENCES_URI}/v1/unsubscribe/{id} |
Per-id unsubscribe — MYP-07, PREF-02. |
{PREF_API}/attribute |
Personalization attribute from the web side — MYP-09's key/value store. |
{CONTACT_SYNC}/queue |
Contact-sync queue, called after profile writes — bears on PADI-3 (does a write propagate). |
GET {PRO_API}/journey/routing/othercourses |
Confirmed in source (learning.padi.com/src/api/pros/index.js:65) — MYP-10 upgrades from web-captured to web-source. |
GET {LEARNING_URI}/v2/courses?tag=recommended |
Confirmed in source (learning.padi.com/src/store/modules/courses/actions.js:33) — MYP-12 likewise. |
What still has not been read
The five Vue/Next apps are covered. Not yet examined: padi.com (Next.js) beyond its URL constructions, customer-padi, diviac (Travel front end, 1.9 GB), and the three Drupal repos (padi-blt, pro-cms, scuba-diving). diviac is the one that matters for the Travel auth realm in MYP-08 and SCOPE-7.
14. The MyPADI Personalised Home reference — the successor to §11
Source: my-padi-api-reference.spin.axelerant.tech — OpenAPI 3.1.0, 20 operations, version 0.1.0-draft, read 2026-09-18. Produced in the 15 September design-to-API mapping session with the design team and PADI Engineering; organised by persona and home-screen section, with Figma-linked screenshots against each operation.
It supersedes §11's SwaggerHub spec without retiring it: same lineage, eight days later, four operations larger.
| Carried forward | 12 operations |
| Added | 8 — learning/v2/entitlements, learning/v2/courses/{id}/details, learning/v2/courses/{id}/assessmentresults, p/photos/api/certification/{id}, affiliations/affiliated/{packageId}, forms/graphql, pros/renewals/info, c/certification/certificate/all |
| Dropped | 3 — p/profile/web/me, p/profile/affiliate/{id}, preference/v1/preference/{id} |
Three cautions, unchanged from §11 and one new. It is Axelerant-authored, 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." It is scoped to one screen, so absence from it carries no information; only presence does. And nobody at PADI has said it replaces anything — it is a design-to-API mapping, not a deprecation notice. INT-4 stands.
What it settles. §11's discrepancy 2 (two profile-by-id paths) resolves toward /p/profile/affiliateid/. §11's discrepancy 1 (two login paths) does not — two specs in a row now name auth/api/oauth/login and neither mentions the apps' auth/api/login. A third path pair joins it: certification photo on /p/photos/api/ here versus /p/photos/web/ in §9.
What it changes. The full item-by-item re-verdict is in Logged In Experience, which now carries a Target state block per item. The four that move:
- MYP-12 is downgraded — the reference lists "the recommended course/specialty modules" under Not API-backed, its own category for link-outs and Drupal content. This is the one absence that is not silence.
- MYP-03's payload collapses — the target
Certificationschema carries 4 fields againstECardInfo's ~55. A certifications list is not an eCard. - MYP-08 becomes architecture — two credentials, and the one serving profile, certifications, club, codes and catalog is a client-credentials grant that cannot ship in a binary. The BFF becomes a prerequisite.
- MYP-04 gains a route —
assessmentresultsreturns per-section booleans, making a percentage derivable. No client has ever called it.
One environment note for §2. In production the reference puts every /p/ and /c/ operation on api.padi.com except the certification catalog, still on api.global-prod.padi.com — which its author flags as a likely oversight rather than a real split.