Children’s data is regulated more strictly than adult data in almost every jurisdiction. COPPA in the US prohibits collecting personal information from children under 13 without verifiable parental consent. GDPR in the EU sets the consent age at 13–16 depending on the member state. Building a size recommendation feature for a children’s clothing brand means operating in this regulatory environment — whether or not your legal team has flagged it yet.
The good news: a stateless API architecture eliminates most of the exposure. If you compute a size prediction at request time and return only the result, there’s nothing to delete, audit, or disclose — because nothing was stored.
This guide walks through a complete implementation: the API call, a European height-based size chart, and the data handling pattern that keeps you out of regulatory trouble.
What makes children’s sizing different
The DimensionsPot pediatric engine uses CDC and WHO LMS Box-Cox growth reference models. Unlike the adult engine, which uses population regression from height and weight alone, the pediatric engine incorporates age_years as a third input — because at the same height and weight, a 5-year-old and a 10-year-old have meaningfully different body proportions.
Age precision matters. Use age_years as a decimal: a child who is 7 years and 4 months old is 7.33. You can compute this from a birth month/year without storing the full birth date:
// Compute age in years from birth year and month
// Store neither the birth date nor the resulting age — compute at request time
function ageFromBirthYearMonth(birthYear, birthMonth) {
const now = new Date();
const months = (now.getFullYear() - birthYear) * 12 + (now.getMonth() + 1 - birthMonth);
return Math.max(0, months / 12);
}
Alternatively, accept age_years directly from the parent at sizing time. Either way: don’t store it.
The data handling pattern
The key principle is compute-and-discard: the API receives height, weight, and age in the request body; returns dimensions; the dimensions are used to compute a size label; only the size label is retained.
Parent enters: height + weight + birth year/month (or age)
↓
Server computes: age_years (ephemeral, not stored)
Server calls: DimensionsPot pediatric engine (server-side only)
Server receives: body_dimensions{}
Server computes: size label from dimensions
↓
Server returns to browser: { recommended_size: "104" }
Server retains: nothing
If storing in shopping cart: store "104" — not height, weight, or age
This architecture means there’s no biometric data to breach, no data to delete on request, and no consent mechanism needed for the sizing feature itself (consent may still be required for other user data, but the sizing feature doesn’t add to the risk surface).
Step 1: The server endpoint
const express = require('express');
const app = express();
app.use(express.json());
const API_URL = 'https://dimensionspot-bodysize-engine.p.rapidapi.com/v1/predict';
const RAPIDAPI_KEY = process.env.RAPIDAPI_KEY;
// European children's size chart — height in cm, chest/waist in mm
// Based on typical EN 13402 garment labeling intervals
const EU_CHILDREN_SIZES = [
{ label: '62', minH: 58, maxH: 65, maxChest: 450, maxWaist: 470 },
{ label: '68', minH: 65, maxH: 72, maxChest: 480, maxWaist: 500 },
{ label: '74', minH: 72, maxH: 80, maxChest: 510, maxWaist: 530 },
{ label: '80', minH: 80, maxH: 86, maxChest: 530, maxWaist: 540 },
{ label: '86', minH: 86, maxH: 92, maxChest: 545, maxWaist: 550 },
{ label: '92', minH: 92, maxH: 98, maxChest: 560, maxWaist: 560 },
{ label: '98', minH: 98, maxH: 104, maxChest: 576, maxWaist: 572 },
{ label: '104', minH: 104, maxH: 110, maxChest: 592, maxWaist: 584 },
{ label: '110', minH: 110, maxH: 116, maxChest: 608, maxWaist: 598 },
{ label: '116', minH: 116, maxH: 122, maxChest: 628, maxWaist: 614 },
{ label: '122', minH: 122, maxH: 128, maxChest: 648, maxWaist: 630 },
{ label: '128', minH: 128, maxH: 134, maxChest: 668, maxWaist: 646 },
{ label: '134', minH: 134, maxH: 140, maxChest: 692, maxWaist: 664 },
{ label: '140', minH: 140, maxH: 152, maxChest: 720, maxWaist: 682 },
{ label: '152', minH: 152, maxH: 164, maxChest: 760, maxWaist: 710 },
{ label: '164', minH: 164, maxH: 176, maxChest: 820, maxWaist: 740 },
];
function recommendChildrensSize(heightCm, chestMm, waistMm) {
// Primary: find by height range
const byHeight = EU_CHILDREN_SIZES.filter(s => heightCm >= s.minH && heightCm < s.maxH);
if (byHeight.length === 0) {
// Outside chart range — return nearest
return heightCm < EU_CHILDREN_SIZES[0].minH
? EU_CHILDREN_SIZES[0]
: EU_CHILDREN_SIZES[EU_CHILDREN_SIZES.length - 1];
}
// Secondary: within height range, pick size where chest/waist fit
const byFit = byHeight.find(s => chestMm <= s.maxChest && waistMm <= s.maxWaist);
// If chest/waist exceed the height-matched size, size up one
if (!byFit) {
const idx = EU_CHILDREN_SIZES.indexOf(byHeight[byHeight.length - 1]);
return EU_CHILDREN_SIZES[Math.min(idx + 1, EU_CHILDREN_SIZES.length - 1)];
}
return byFit;
}
app.post('/api/size/children', async (req, res) => {
const { gender, height_cm, weight_kg, age_years } = req.body;
if (!gender || !height_cm || !weight_kg || age_years === undefined) {
return res.status(400).json({ error: 'gender, height_cm, weight_kg, and age_years are required' });
}
const heightMm = Math.round(parseFloat(height_cm) * 10);
const weight = parseFloat(weight_kg);
const age = parseFloat(age_years);
if (age < 0 || age > 20) return res.status(400).json({ error: 'age_years must be 0–20' });
if (heightMm < 500 || heightMm > 1800) return res.status(400).json({ error: 'Height out of pediatric range' });
if (weight < 3 || weight > 120) return res.status(400).json({ error: 'Weight out of pediatric range' });
let apiData;
try {
const apiRes = await fetch(API_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-rapidapi-key': RAPIDAPI_KEY,
'x-rapidapi-host': 'dimensionspot-bodysize-engine.p.rapidapi.com',
},
body: JSON.stringify({
input_data: {
input_unit_system: 'metric',
subject: {
gender: gender.toLowerCase(),
exact_age: age,
input_origin_region: 'GLOBAL',
},
anchors: {
body_height: heightMm,
body_mass: weight,
},
},
output_settings: {
calculation: {
calculation_model: 'AUTO',
target_region: 'GLOBAL',
body_build_type: 'CIVILIAN',
},
requested_dimensions: { bundle: 'TORSO' },
output_format: {
unit_system: 'metric',
confidence_score_threshold: 0,
include_range_95: false,
include_iso_codes: false,
},
},
}),
});
if (!apiRes.ok) return res.status(502).json({ error: 'Prediction service unavailable' });
apiData = await apiRes.json();
} catch {
return res.status(502).json({ error: 'Could not reach prediction service' });
}
const dims = Object.fromEntries(
Object.entries(apiData.body_dimensions).map(([key, d]) => [key, d.value])
);
const heightCm = heightMm / 10;
const chest = dims.chest_circumference ?? 0;
const waist = dims.waist_circumference_natural ?? 0;
const { label: recommended_size } = recommendChildrensSize(heightCm, chest, waist);
// Return only the size label — not the inputs
res.json({ recommended_size });
});
app.listen(3000);
Step 2: Age from birth year and month (without storing DOB)
If your form collects birth month and year (common in parent-facing UIs), compute age on the fly before the API call and don’t persist the birth date:
app.post('/api/size/children', async (req, res) => {
const { gender, height_cm, weight_kg, birth_year, birth_month } = req.body;
// Compute age — do not store birth_year or birth_month
const now = new Date();
const monthsOld = (now.getFullYear() - parseInt(birth_year)) * 12
+ (now.getMonth() + 1 - parseInt(birth_month));
const age_years = Math.max(0, monthsOld / 12);
// Proceed with age_years — birth data is not referenced after this point
// ...
});
If your data store requires a birthday for account creation, that’s a separate concern governed by your privacy policy. The sizing feature itself should not be the reason you’re holding a child’s birth date.
What to store in the shopping cart
When adding a recommended size to a cart or user profile, store only the output:
// DO store
await db.cartItems.update(cartItemId, {
recommended_size: '104',
sized_at: new Date().toISOString(),
});
// DO NOT store alongside the recommendation
// height_cm: 106 ← children's biometric data
// weight_kg: 18 ← children's biometric data
// age_years: 5.2 ← children's biometric data
The size label is derived data, not biometric data. It’s not meaningfully sensitive on its own (knowing a child wears size 104 reveals very little). The height, weight, and age that generated it are a different matter.
What this architecture doesn’t cover
Stateless sizing handles the sizing feature. If your broader application stores other data about child users — account profiles, purchase history, marketing preferences — those require separate COPPA/GDPR analysis. Stateless sizing reduces your regulatory surface for this specific feature but doesn’t substitute for a complete data protection review of your platform.
For verification of age-appropriate consent flows in EU markets, refer to the Data Protection Authority guidance for your primary market — each member state interprets the GDPR’s Article 8 digital consent age slightly differently.