feat: add /retrieve-age-band (SIM swap age band) endpoint - #273
Conversation
…ge band only Returns the standardized simSwapAgeBandEnum only; excludes the swapped Boolean (/check) and the change date (/retrieve-date) per the 2026-04-29 ad-hoc agreement (camaraproject#253) and TEF stance (camaraproject#260).
|
@KeldaAnders For our largest customer (40 million accounts) requires a more granular age band (they use 72 hours). Can you add more age bands in the first week to accomodate this ? |
|
@KeldaAnders And what to return in the first 24 hours if your regulator only allows days as minimum recency ? And what is there is an upper limit that is allowed to be returned ?? |
|
@HuubAppelboom Thanks for sharing both points. On finer granularity in the first week (your 72h case). Granularity at the recent end (where time-sensitive decisions cluster) is reasonable. A couple of things to align on first: The enum is a standardized, cross-operator set (the agreement behind #253/#260), so the goal is to choose standard boundaries that let common decision thresholds land on a band edge — rather than adding customer-specific bands, which would fragment the standard and break interoperability. Your 72h example is a good illustration of a potential gap: 72h = 3d falls inside the current
That makes a 72h cut expressible without any per-customer logic. Could you share the full set of thresholds your customers actually decide on (72h noted) and the associated business value? If we collect those (e.g. 24h / 48h / 72h / 7d), I'll propose a revised standard band set to the WG that keeps fine granularity in the first days and stays coarse later. On day-level granularity and the upper limit. First, adding a The monitored-period / retention context is already fully served by So a consumer that needs the provider's monitored window — or whether the event predates it — uses For
TL;DR: @jpengar @bigludo7 @shilpa-padgaonkar — does that separation work for you? |
|
@KeldaAnders For us it is sufficient to add the 72 hours band, that seems to be the standard here, but maybe others have different use cases as well. In any case, would it hurt to add 96 hours as well ?? Regarding telco's that are only allowed a precision of at least 24 hours, I have a different proposal. In stead of returning a single band as the answer, simply allow for example "<=3" (in stead of 1, 2 or 3). That indicates that it should fall in category 1,2 or 3. Likewise for the upper range, if you don't want to go further than for example than 1 year back, you simply provide ">=13" in stead of 13, 14 or 15. That way it is self contained, no extra parameters are required, and these cases can also be covered. |
|
72h / 96h bands — accepted. I'm adding both boundaries. The old
That gives clean 72h and 96h cut points, and the set runs On the The notation changes the response field from a plain A fixed integer band set ( @jpengar @bigludo7 @shilpa-padgaonkar — assuming the 72h/96h additions look good, can we proceed to approval? |
…enumber to 1-17 Signed-off-by: Kelda <[email protected]>
…missing data), and consistent 'SIM swap event' wording Signed-off-by: Kelda Anders <[email protected]>
|
@KeldaAnders @albertoramosmonagas @yyeAduna Regarding the minimum supported granularity, I think this must be resolved within the endpoint, otherwise API Consumer will be forced to do 2 SIM Swap calls for each check, with will double their costs. The problem with the minimum supported granularity is that if you don't inform customers that bands are not supported, they will start questioning the data quality of all calls they do. We recently had a similar case here with a major customer that did a test with a number of random chosen phone numbers. We use retrieve-date here, but about 50% of the service providers do not support the time of day, but the date of SIM Swap is supported. The time stamp contains then 2026-06-08T22:00:00.000Z or 2026-01-08T23:00:00.000Z (depending on the implemention and conversion of local time to GMT, which depends on summertime). The customer immediately started to challenge the quality of the data, because we forgot to communicate that not all numbers support time, but only date. The customer will now detect this by looking at whether the time states 22:00:00.000, but this is not ideal. This granularity issue for missing time information can for example be solved by adding a boolean whether time of day is supported, or optional a list of not supported bands. Another option of course is putting in the specification that all bands must be supported by all telcos in a country, but that will impact the roll-out of age-band significantly. |
|
@KeldaAnders @albertoramosmonagas @yyeAduna |
|
Thanks @KeldaAnders and @HuubAppelboom. The clarification on activation versus actual SIM swap is helpful. Our remaining concern is granularity and historical lookback. If
Finally, temporary backend or data-source failures should remain server-side errors rather than being mapped to |
|
@KeldaAnders For the 999 result, it may be a good idea to add to the description that the number was also never ported. Porting also has a SIM Swap risk in it. |
…use /check or /retrieve-date), temporary failures are 5xx not 422, and 999 also confirms never-ported Signed-off-by: Kelda Anders <[email protected]>
|
@albertoramosmonagas @HuubAppelboom Thanks for the feedback — it's much appreciated. I've made the changes and pushed them:
Let me know if the documentation/semantic clarifications address your concerns & if the additions look good, can we proceed to review approval? @bigludo7 @jpengar — tagging you for approval on this scope. |
|
@bigludo7 @maxl2287 @fernandopradocabrillo Could you all kindly review and approve the PR if you have no further issues? Thanks in advance. |
|
Thanks for the continued work on this @KeldaAnders. The endpoint isolation is right and we appreciate the iterations. Still, we have blocking points before we can approve. 1. 501 NOT_IMPLEMENTED is missing — this is the core mechanism for an optional endpoint 2. 999 semantics — not tight enough — The spec still conflates "no event found in retained history" with "provider positively confirms no swap ever happened." These are different claims. 999 must mean only the latter. If lookback is limited, the provider must return 422 — or not expose the endpoint at all. 3. 999 is non-ordinal — not stated in the schema: Nothing prevents a consumer from reading 999 as "older than band 17." The schema needs an explicit statement that 999 is a sentinel, not a position in the recency sequence. 4. Open question from #266 still unresolved: 999 vs. null - Left as "please confirm which you prefer" in the PR notes — never resolved. Our position: null is the established CAMARA pattern for "value cannot be returned" (cf. latestSimChange: null in /retrieve-date). A magic integer requires special-casing by every consumer. Please resolve before merge. 5. 422 scope is too broad - "Unexpectedly missing data returns 422" is wrong. SERVICE_NOT_APPLICABLE covers structural non-applicability, not transient backend failures — those must remain 5xx. 6. Band taxonomy governance - The 72h/96h split was added in direct response to one customer's threshold. A standardized enum that adjusts to commercial thresholds is not a standard. The WG needs an objective criterion for what justifies a new band edge before any additions land in the spec. 7. "Full band model required" must be normative - The current prose is a recommendation. It needs to read as a MUST: a provider exposing this endpoint MUST support the complete standardized band model. Partial support creates false interoperability. 8. d is not defined - Band boundaries use d without defining it. UTC? 24h vs. calendar day? How are 1y/2y/3y computed — 365 days or calendar year? Leap years? Two providers computing d differently will assign different bands for the same event. 9. Version target - v2.1.0 was released on 17.09.2025 (Fall25, r3.3). A new endpoint cannot target an already-released version. Please clarify the intended release. |
…nal), 422 for no-profile, MUST full band model, define d, target 2.2.0, fix tables Signed-off-by: Kelda <[email protected]>
ec33f37 to
b43ce88
Compare
|
@albertoramosmonagas Thanks for the detailed review — see the point-by-point below 1. 501 NOT_IMPLEMENTED — ✅ added. 2. "Never happened" semantics — ✅ tightened (and moved off 3. Non-ordinal — ✅ stated in the schema. 4. 5. 422 scope — ✅ narrowed. 6. Band taxonomy governance — 🔄 proposal. 7. "Full band model" normative — ✅ now MUST. 8. 9. Version target — ✅ corrected. 10. ReDoc tables — ✅ fixed. Open items for your call: # 4 (we propose |
|
Hi @KeldaAnders From our side, most of the previous blocking points are now addressed.
A few final housekeeping points before review:
CC: @fernandopradocabrillo, @bigludo7, @jpengar, @HuubAppelboom |
|
Hello Regarding @albertoramosmonagas question about test-cases I'm fine to have them in another PR in order to move forward with this one. Agreed of course about the 2 other cleaning points mentioned by Alberto. As we applied all Commonalities requirements in a previous PR the cleaning will be a bit tricky bit a good exercice for an IA ;) |
| name: Apache 2.0 | ||
| url: https://www.apache.org/licenses/LICENSE-2.0.html | ||
| version: wip | ||
| version: 2.2.0 |
There was a problem hiding this comment.
version and url MUST not be modified.
That is managed vía Release Automation workflow. They have to be set back to wip
|
|
||
| - POST retrieve-age-band : Returns a standardized `simSwapAgeBand` value indicating how recently a SIM swap occurred, expressed as a time band. This operation is an alternative way to expose SIM swap recency for API providers that do not expose the exact SIM swap date; it does not return the actual SIM swap date. This operation is OPTIONAL. A provider that does not implement it returns `501 NOT_IMPLEMENTED`; consumers can then fall back to `check` and/or `retrieve-date`. The returned value is a technical network signal indicating recency; it is not a customer-side risk score or scoring model. Consuming parties apply their own decisioning outside the API contract. | ||
|
|
||
| - Definition of `d`. `d` is the elapsed time between the most recent SIM swap event and the instant the request is processed, both evaluated in UTC. One day (`d`) is a fixed 24-hour period; `1y`, `2y` and `3y` are computed as 365, 730 and 1095 days respectively (fixed-length, leap years not applied) to guarantee that two providers assign the same band to the same event. |
There was a problem hiding this comment.
Suggestion for UX improvement:
- Definition of `d`:
- `d` is the elapsed time between the most recent SIM swap event and the instant the request is processed, both evaluated in UTC. One day (`d`) is a fixed 24-hour period; `1y`, `2y` and `3y` are computed as 365, 730 and 1095 days respectively (fixed-length, leap years not applied) to guarantee that two providers assign the same band to the same event.
Redocly snapshot:
Signed-off-by: Kelda Anders <[email protected]>
Signed-off-by: Kelda Anders <[email protected]>
|
Rebased on Conflict resolution
Per @PedroDiez
Still open for the WG's call (happy to go either way)
@bigludo7 @fernandopradocabrillo @maxl2287 @PedroDiez — ready for another look; please let me know if anything else blocks approval. |
PedroDiez
left a comment
There was a problem hiding this comment.
Comments addressed @KeldaAnders.
Minor comment regarding indentation. A way to check UX can be this:
1.- Go to view file
2.- Select 'raw' option, take the link, e.g.
https://raw.githubusercontent.com/KeldaAnders/SimSwap/b7f1acde77c6b74e3cc0869d754afa603bf22ee8/code/API_definitions/sim-swap.yaml
3.- Open redocly
https://redocly.github.io/redoc/?url=&nocors
4.- Fill with the link obtained for the API Spec, e.g.
That helps to identify renderization topics
| value: | ||
| {} | ||
|
|
||
| AGEBAND_2LEGS: |
There was a problem hiding this comment.
Review the indentation to align with the rest of examples
Signed-off-by: Kelda Anders <[email protected]>
Signed-off-by: Kelda <[email protected]>
|
Thanks @PedroDiez for the review — much appreciated. Latest updates are pushed:
Rendered ReDoc preview of the current state: https://redocly.github.io/redoc/?url=https://raw.githubusercontent.com/KeldaAnders/SimSwap/292805c92498ac046f382ddb6c23b2eb783d078b/code/API_definitions/sim-swap.yaml&nocors#section/Request-body-strictness @PedroDiez @bigludo7 @fernandopradocabrillo @maxl2287 — ready for another look; please let me know if anything else blocks approval. |
|
Thanks all for the reviews — this is approved, and CI is green. @bigludo7 @fernandopradocabrillo @maxl2287 (codeowners) — good to merge whenever you are. I can leave the merge to you so it follows the repo's standard method, but I'm also happy to merge it myself if you'd prefer. Just let me know which method the sub-project uses. |
bigludo7
left a comment
There was a problem hiding this comment.
LGTM
Thanks @KeldaAnders
|
@KeldaAnders Merged ;) |
|
Thank you @bigludo7 @fernandopradocabrillo @PedroDiez & All! |

Title: feat: add
/retrieve-age-band(SIM swap age band) endpointAdds a standalone, single-purpose operation that returns a standardized SIM swap recency band (
simSwapAgeBand) only. Supersedes #266 (which was opened frommainby mistake) and incorporates that PR's review feedback. Targeting release 2.1.0.What type of PR is this?
What this PR does / why we need it:
Adds
POST /retrieve-age-bandto the SIM Swap API: an alternative way to expose SIM swap recency for providers that do not expose the exact SIM swap date. It returns a time-bucketed value indicating how recently a SIM swap/activation occurred — and nothing else./checkand/retrieve-date(and their schemas, request bodies, and examples) are preserved unchanged. The only additions are the new path, theSimSwapAgeBand/SimSwapAgeBandInfo/CreateSimSwapAgeBandschemas, theAGEBAND_*examples, the new scope, and the related description updates.swappedBoolean (/check) or the SIM change timestamp (/retrieve-date).check/retrieve-date; support depends on the provider's capabilities and commercial use case.1–15represent increasing recency bands (4h → 3y+);999indicates no SIM swap event was found. When the information is unavailable for the subscriber, the request returns422 SERVICE_NOT_APPLICABLE.sim-swap:retrieve-age-band, alongsidesim-swap:check,sim-swap:retrieve-date, and the API-levelsim-swapscope.Adding a new endpoint is backward-compatible, so this targets a MINOR bump to 2.1.0 (URL path unchanged:
/sim-swap/v2).Which issue(s) this PR fixes:
Fixes #260
Fixes #253
Changes after the #266 review
simSwapAgeBandEnumtosimSwapAgeBand(the schema already defines it as an enum). — per @jpengar-1is removed; unavailable information is now an error (422 SERVICE_NOT_APPLICABLE) rather than mixing time-bucket and error semantics. — per @jpengar / @bigludo7Special notes for reviewers:
999for "no SIM swap found." @jpengar proposed instead making the fieldnullable: trueand returningnull, keeping the enum1–15; @bigludo7 raised whetherinteger+nullis valid (it is, vianullable: truein OAS 3.0.x). Please confirm which you prefer and I'll align.SERVICE_NOT_APPLICABLE. Please confirm this is the right Commonalities code or whether a dedicated one is preferred./checkor/retrieve-date. Supersedes Update sim-swap | add /retrieve-age-band endpoint returning age band … #266 and addresses the scope concern from Update sim-swap.yaml with age band #254.Changelog input
Additional documentation
This section can be blank.
This PR is opened from the feature branch
KeldaAnders:feat/sim-swap-age-band-endpointand supersedes the previously opened PR #266 which was created frommain.