Normalize a list of phone numbers
Turn a phone list into consistent international numbers with Node or Python. Keep each original record.
On this page
Run the example
Download the complete source below. In a macOS or Linux terminal, install the SDK, set your secret API key, then run the file.
npm install @parseapi/sdkexport PARSEAPI_KEY='YOUR_SECRET_API_KEY'node normalize-phone-numbers.node.mjsNormalize the list
Turn phone records into consistent international numbers. (415) 555-2671 with country US becomes +14155552671. Every result keeps its original record.
Choose Node or Python above, install the SDK, and set PARSEAPI_KEY to a secret key. Run the downloaded script with no arguments to normalize its four-record sample. No input file is needed for that first run.
1. Normalize one record
Each record uses the same Phone API call. number is text; country is the record's explicit two-letter country when needed:
const data = await parse.phone(number, country ? { country } : {});
results.push(foldResult(row, data));International numbers starting with + carry their own calling code. National numbers need a country, so (415) 555-2671 requires US. Missing context produces country_required; the script never guesses from your location.
2. Supply your own list
Save this as phones.json:
[
{ "number": "(415) 555-2671", "country": "US" },
{ "number": "020 7946 0958", "country": "GB" },
{ "number": "+33 1 42 68 53 00" },
{ "number": "123", "country": "US" }
]Keep numbers as strings so leading zeroes survive. The list repeats the same operation for each record, using one SDK client.
Run your selected implementation with phones.json after its filename. For example:
node normalize-phone-numbers.node.mjs phones.jsonFor Python, use python3 normalize-phone-numbers.python.py phones.json. Both write the same JSON shape.
The complete script processes up to 100 records sequentially, with a ten-second timeout and no automatic retries. An authentication, access, or rate-limit error stops later requests.
3. Use the normalized value
return {
input,
valid: data.valid,
phone: data.valid ? data.phone : null,
country: text(data.country),
national: data.valid ? text(data.national) : null,
international: data.valid ? text(data.international) : null,
error: null
};Store phone beside input. The sample's first three numbers normalize; 123 returns valid: false and phone: null. A failed request has valid: null and an error, so it cannot be mistaken for an invalid number. Later checks stopped by an account error use not_attempted.
Original order, repeated records, and extra fields such as IDs remain intact. A shared phone number is not permission to merge two people. Keep extensions separately in your application.
Add > normalized.json to write a new output file; never redirect onto the input filename. Exit 0 means checks completed, 2 means some checks need attention, and 1 means the script could not start. Input files must be smaller than 64 KiB.
Normalization checks the numbering plan. It does not establish that a number is connected or belongs to a person.
Complete example
The complete Node source. The script reads your API key from PARSEAPI_KEY.
View and copy the complete source
import { readFile, stat } from 'node:fs/promises';
import { pathToFileURL } from 'node:url';
import { parseAPI, ParseAPIError } from '@parseapi/sdk';
const sample = [
{ number: '(415) 555-2671', country: 'US' },
{ number: '020 7946 0958', country: 'GB' },
{ number: '+33 1 42 68 53 00' },
{ number: '123', country: 'US' }
];
async function readRows() {
if (process.argv.length > 3) throw new Error('Use: node normalize-phone-numbers.node.mjs [phones.json]');
if (!process.argv[2]) return sample;
if ((await stat(process.argv[2])).size > 65536) throw new Error('Use a JSON file smaller than 64 KiB.');
const rows = JSON.parse(await readFile(process.argv[2], 'utf8'));
if (!Array.isArray(rows) || rows.length === 0 || rows.length > 100) {
throw new Error('Supply an array containing 1 to 100 phone records.');
}
return rows;
}
function emptyResult(input) {
return { input, valid: null, phone: null, country: null, national: null, international: null, error: null };
}
function foldResult(input, data) {
if (!data || typeof data.valid !== 'boolean' ||
(data.valid && (typeof data.phone !== 'string' || !/^\+[1-9]\d{1,14}$/.test(data.phone)))) {
return { ...emptyResult(input), error: { code: 'unexpected_response', status: null } };
}
const text = (value) => typeof value === 'string' && value.length > 0 ? value : null;
// example:result
return {
input,
valid: data.valid,
phone: data.valid ? data.phone : null,
country: text(data.country),
national: data.valid ? text(data.national) : null,
international: data.valid ? text(data.international) : null,
error: null
};
// /example:result
}
export async function normalize(rows, parse) {
const results = [];
let stopped = false;
for (const row of rows) {
const result = emptyResult(row);
const number = typeof row?.number === 'string' ? row.number.trim() : '';
const country = typeof row?.country === 'string' ? row.country.trim().toUpperCase() : undefined;
if (!number || number.length > 100 || (row?.country != null && !/^[A-Z]{2}$/.test(country ?? ''))) {
result.error = { code: 'invalid_input', status: null };
results.push(result);
continue;
}
if (!number.startsWith('+') && !country) {
result.error = { code: 'country_required', status: null };
results.push(result);
continue;
}
if (stopped) {
result.error = { code: 'not_attempted', status: null };
results.push(result);
continue;
}
try {
// example:request
const data = await parse.phone(number, country ? { country } : {});
results.push(foldResult(row, data));
// /example:request
} catch (error) {
result.error = error instanceof ParseAPIError
? { code: error.code, status: error.status }
: { code: 'request_failed', status: null };
results.push(result);
// Fix key/access/allowance errors before spending more attempts on this file.
if (error instanceof ParseAPIError && [401, 403, 429].includes(error.status)) stopped = true;
}
}
return results;
}
async function main() {
const rows = await readRows();
// Reads PARSEAPI_KEY. Keep this secret key on the server.
const parse = parseAPI(undefined, { timeoutMs: 10000, retries: 0 });
const results = await normalize(rows, parse);
process.stdout.write(`${JSON.stringify(results, null, 2)}\n`);
// An invalid number is a completed check. A failed check needs attention.
if (results.some((row) => row.error !== null)) process.exitCode = 2;
}
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
main().catch(() => {
console.error('Could not start. Check PARSEAPI_KEY and provide a JSON array of 1 to 100 records (under 64 KiB).');
process.exitCode = 1;
});
}
Need a key? Set up this server example. For every field and parameter, see Phone API reference.