🛡️ Anonymized Profile Data
Format and structure of anonymized user profile data
To protect user privacy, all profile data returned from our platform is anonymized and abstracted. This data can be used safely for content personalization and ad placement without risking individual identification.
Available Fields in the Profile Response
type Gender = 'male' | 'female' | 'other';
type Residence = string;
// Example: "Aadorp", or "Almelo" when the original residence had to be abstracted.
// Residence values are normalized Dutch place-of-residence (`woonplaats`) names sourced
// from the CBS BAG via opendata.cbs.nl and PDOK Locatieserver.
// To preserve k-anonymity, residences where the user's (gender, ageRange, residence)
// triple would be identifiable are abstracted to their gemeente (municipality).
// E.g. someone aged 18–24 in 'Aalsum' (population <10 in that bucket) is reported
// as residing in 'Noardeast-Fryslân' instead.
type AgeRange = [number, number | null];
// Represented as an inclusive lower and upper bound.
// If the upper bound is `null`, it represents an open-ended range.
// See **Standard Age Ranges** for the ranges in use
type ProfileResponse = {
gender: Gender;
residence: Residence;
ageRange: AgeRange;
};Example API Response (JSON)
{
"gender": "male",
"residence": "Aadorp",
"ageRange": [25, 34]
}Standard Age Ranges
These ranges are used to segment audiences:
| Definition | ageRange |
|---|---|
| Under 18 | [0, 17] |
| 18–24 | [18, 24] |
| 25–34 | [25, 34] |
| 35–44 | [35, 44] |
| 45–54 | [45, 54] |
| 55–64 | [55, 64] |
| 65+ | [65, null] |
Bounds are inclusive on both sides. The final range is open-ended (upper bound null). A user aged 24 falls into [18, 24]; a user aged 25 falls into [25, 34].
Residence Data
Residence values are Dutch place-of-residence (woonplaats) names — the same level as a postal address line, e.g. "Aadorp", "Amsterdam", "Oudeschild". They come from the CBS BAG registry.
Anonymization
For most users the residence is returned as-is. It's abstracted to the gemeente (municipality) when the user's (residence, gender, ageRange) triple would otherwise be k-anonymity-unsafe — meaning fewer than ~10 real people share that combination according to CBS population data per 4-digit postcode area.
- Source: CBS dataset 83502NED — population by gender × age × PC4 postcode.
- Default threshold: ≤ 10 people in any
(gender, ageRange)bucket within the woonplaats's PC4 footprint flags the residence for abstraction. - Abstraction target: the residence's gemeente, which is itself k-safe at this threshold (no gemeente has any flagged bucket).
- Affected scope at the current threshold: 514 of ~2,500 woonplaatsen, almost all small villages and hamlets in rural Friesland, Drenthe, and Groningen. Major cities (Amsterdam, Rotterdam, …) are never abstracted.
- Non-binary users:
gender: "other"has no CBS bucket; users with'other'in a flagged woonplaats are treated conservatively and their residence is abstracted on any matching age-range hit.
Example
| Original residence | Gender | Age | Returned residence |
|---|---|---|---|
Amsterdam | any | any | Amsterdam (never flagged) |
Aadorp | any | any | Aadorp (small, but not flagged at k=10) |
't Haantje | female | 18 | Coevorden (Vrouwen 18–24 has <10 people) |
't Haantje | female | 60 | 't Haantje (Vrouwen 55–64 is above the threshold) |
't Haantje | other | 18 | Coevorden (conservative for non-binary) |
Privacy Considerations
- No persistent identifiers are included—this data is transient and context-limited
- Anonymized at source: Data is abstracted to prevent individual identification
- Purpose-limited: Designed for privacy-safe audience targeting only
- No personal information: No names, emails, or other identifying data
Usage Examples
Checking Gender
if (profile.gender === 'male') {
// Show male-targeted content
} else if (profile.gender === 'female') {
// Show female-targeted content
} else {
// Show gender-neutral content
}Checking Age Range
const [minAge, maxAge] = profile.ageRange;
if (maxAge === null) {
console.log(`User is ${minAge} or older`);
} else {
console.log(`User is between ${minAge} and ${maxAge} years old`);
}Checking Residence
if (profile.residence.includes('Amsterdam')) {
// Show Amsterdam-specific content
} else if (profile.residence.includes('Rotterdam')) {
// Show Rotterdam-specific content
}Notes for Integration
- Purpose: The response is designed to support privacy-safe audience targeting
- Custom mapping: You're free to map the
[number, number | null]ranges into your own segment labels if needed - Graceful degradation: Always handle cases where profile data might be unavailable
- Cache considerations: Profile data can be cached, but respect the
cache_ttlsetting - Browser vs server: As of this moment, profile retrieval can only happen using the Upod browser client’s
fetch(), which requires an active session on the page. Servers cannot substitute a proxy for that call; use a browser fetch and then pass anonymized data to your backend if needed (details)
Related Documentation
- API Reference - Complete client library API documentation
- Getting Started - How to fetch and use profile data in your integration