Your Company

🛡️ 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:

DefinitionageRange
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 residenceGenderAgeReturned residence
AmsterdamanyanyAmsterdam (never flagged)
AadorpanyanyAadorp (small, but not flagged at k=10)
't Haantjefemale18Coevorden (Vrouwen 18–24 has <10 people)
't Haantjefemale60't Haantje (Vrouwen 55–64 is above the threshold)
't Haantjeother18Coevorden (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_ttl setting
  • 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)