Skip to content

Package and file formats

Column names on this page show the shape of each file. Your onboarding contact confirms the exact template for your account, and the upload preview shows how each column was read.

CSV, UTF-8, with a header row. Send whichever identifiers you hold. Each agreeing identifier counts toward strictness.

people.csv
email,phone,first_name,last_name,address,city,state,zip
dana.reyes@example.com,(555) 010-4477,Dana,Reyes,12 Elm St,Springfield,IL,62704
lee.m@example.org,,Lee,Morgan,,,,

One row per person who did it, with the time it happened. A result is attributed by when it happened, not when the file arrived.

results.csv
email,phone,event_time,outcome
dana.reyes@example.com,,2026-10-14T15:42:07Z,Book a consultation

event_time is ISO 8601 in UTC. Only confident matches count. See Results returning.

The same identifier columns as a people file. Everyone matched is kept out of every list built after the upload.

A ZIP built on Moonbrush servers. See Delivery for the folder layout.

An illustrative manifest.json:

manifest.json
{
"campaign": "Spring consultations",
"built_at": "2026-10-14T09:00:00Z",
"destination_layout": "meta",
"overlap_policy": "ship_once_highest_priority",
"audiences": [
{
"name": "Lapsed members",
"priority": 1,
"members": 48210,
"withheld": { "suppressed": 1312, "held_back": 0 },
"groups": [
{ "name": "Researches first", "members": 17420, "sharpness": "Clear", "score_version": "2026.10" },
{ "name": "Deal seekers", "members": 12980, "sharpness": "Sharp", "score_version": "2026.10" }
]
}
]
}

Withheld people appear only as counts. No identifier for a withheld person is in the package.

One row per hashed identifier. A person with two emails has two rows.

members.csv
row_key,email_sha256,phone_sha256,fn_sha256,ln_sha256,zip,country
r_5c1e9a,3c8d1f…e07b,9a41c2…5d10,7f2a0e…c3b9,d06b4c…81aa,62704,US

Hash values are truncated here for width. Real values are 64 hexadecimal characters.

SHA-256, lowercase hexadecimal, unsalted. Normalize first, then hash. Never hash an empty value, and never hash ZIP.

hash_identifiers.py
import hashlib
import re
def sha256(value: str) -> str:
return hashlib.sha256(value.encode("utf-8")).hexdigest()
def hash_email(email: str) -> str | None:
e = email.strip().lower()
return sha256(e) if e else None
def hash_phone_e164(phone: str, default_country_code: str = "1", keep_plus: bool = True) -> str | None:
digits = re.sub(r"\D", "", phone)
if not digits:
return None
if len(digits) == 10: # national number without country code
digits = default_country_code + digits
e164 = ("+" if keep_plus else "") + digits
return sha256(e164)
def hash_name(name: str) -> str | None:
n = name.strip().lower()
return sha256(n) if n else None
print(hash_email(" Dana.Reyes@Example.com "))

Every figure on these pages carries the date it was measured. See Published figures.

© 2026 Moonbrush Inc. Moonbrush, atlas, stage and Gravity are pending trademarks of Moonbrush Inc.