SuperMegaMindSuperMegaMind

API & MCP · Pilot

Real-world audience data, one tool call away.

Profile a customer list against 264,746,311 U.S. consumer records from your own code or AI agent. You send hashed match keys. You get back percentages and index scores against the national baseline. Nobody gets a record.

Overview

SuperMegaMind matches your list against a licensed database of 264.7M U.S. consumers and up to 307 attributes each, then returns aggregate statistics for the people who matched. Every attribute comes back as percentages and an index, where 100 is the national rate. 457 means your customers are 4.57 times as likely as the average American to have that trait.

You sendYou get backYou never get
Hashed emails, phones, or name + ZIPPercentages and index scores per attribute, plus a private report linkAny individual's record, including your own customers'

Two ways in, one implementation behind both: an MCP server for AI agents and assistants, and a REST API for your own code.

Use cases

Profile a client's customers

Send a client's customer list and get back who is actually on it, attribute by attribute: hobbies, home, household, geography, purchasing. Use it before the media plan, the creative brief or the pitch.

Check a synthetic audience

Generate personas cheaply with a model, then test them against real people. Keep the traits that hold up, rebuild the ones that don't, and show the client an audience backed by real data.

Build it into your AI app

Give your agent or client-facing assistant a tool that answers "who are these customers?" with real statistics instead of a guess. Your app hashes the list, so we never receive an email address.

Quickstart

  1. 1. Get a key. The API is in pilot and keys are issued by hand. Write to help@supermegamind.com. Your key starts with smm_live_ and is shown once.
  2. 2. Hash your list. On your own systems, using the exact rules in Hashing. Raw values are rejected.
  3. 3. Connect. Add the MCP server to your agent, or call the REST endpoint from your code.
  4. 4. Profile. Call profile_list with 200 to 100,000 hashed records. You get the profile back, plus a link to open it as a report.

Connect over MCP

Streamable HTTP, stateless, authenticated with your key on every request.

SettingValue
URLhttps://supermegamind.com/api/mcp
HeaderAuthorization: Bearer smm_live_...
Toolsdescribe_attributes, profile_list

Claude Code

terminal
claude mcp add --transport http supermegamind https://supermegamind.com/api/mcp \
  --header "Authorization: Bearer smm_live_YOUR_KEY"

Cursor

In .cursor/mcp.json:

json
{
  "mcpServers": {
    "supermegamind": {
      "url": "https://supermegamind.com/api/mcp",
      "headers": { "Authorization": "Bearer smm_live_YOUR_KEY" }
    }
  }
}

Claude Desktop

Through the mcp-remote bridge, in claude_desktop_config.json:

json
{
  "mcpServers": {
    "supermegamind": {
      "command": "npx",
      "args": ["mcp-remote", "https://supermegamind.com/api/mcp", "--header", "Authorization:${SMM_AUTH}"],
      "env": { "SMM_AUTH": "Bearer smm_live_YOUR_KEY" }
    }
  }
}

Your own app (TypeScript)

typescript
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";

const client = new Client({ name: "my-agency-app", version: "1.0.0" });
await client.connect(
  new StreamableHTTPClientTransport(new URL("https://supermegamind.com/api/mcp"), {
    requestInit: { headers: { Authorization: `Bearer ${process.env.SMM_KEY}` } },
  })
);

const result = await client.callTool({
  name: "profile_list",
  arguments: { records }, // [{ email: "<sha256>" }, ...], hashed on your side
});

Connecting a model straight to the MCP server works for up to 200 to 300 records. For real customer lists, see Sending a real list.

Sending a real list

When a model calls an MCP tool, it writes every argument itself, one token at a time. Each hash is a 64-character string, so a model can only practically send 200 to 300 records, and it can miscopy one without anyone noticing. A model also can’t compute SHA-256 on its own, so in a plain chat app it needs a hashing tool before it can call profile_list at all.

How you connectRecords per runBest for
Model calls our MCP server directly200 to 300Trying it out, describe_attributes, small test lists
Your app wraps our REST API (recommended)Up to 100,000Agencies and enterprises profiling real customer lists

Wrap us

Give your model a tool of your own, such as profile_customer_file. Your tool reads the file, hashes it on your systems, calls our REST endpoint, and hands the model only the result. The model passes a file reference in and gets a profile out. The hashes never pass through it, so the list can be as large as a run allows, and your customers’ data stays inside your app until it’s hashed.

typescript
// A tool in YOUR app. The model passes a file reference and gets a profile back.
// The hashes never pass through the model, so the list can be up to 100,000 records.
import { readFileSync } from "node:fs";
import { parse } from "csv-parse/sync";
import { hashEmail, hashPhone, hashNameZip } from "./smm-hash"; // from the Hashing section

export async function profileCustomerFile(path: string) {
  const rows: Record<string, string>[] = parse(readFileSync(path), { columns: true });
  const records = rows
    .map((r) => ({
      email: hashEmail(r.email),
      phone: hashPhone(r.phone),
      namezip: hashNameZip(r.first_name, r.last_name, r.zip),
    }))
    .map((r) => Object.fromEntries(Object.entries(r).filter(([, v]) => v)))
    .filter((r) => Object.keys(r).length > 0)
    .slice(0, 100_000); // one run is at most 100,000 records

  const res = await fetch("https://supermegamind.com/api/v1/profiles", {
    method: "POST",
    headers: { Authorization: `Bearer ${process.env.SMM_KEY}`, "Content-Type": "application/json" },
    body: JSON.stringify({ records }),
  });
  return res.json(); // give the model this: matched, attributes, claimUrl, usage
}

One run, one profile

A run is one call to profile_list with up to 100,000 records, and it returns one profile and one report. There is no batching during the pilot: runs are not combined. To profile the next 100,000 records, start a new run, which gives a separate profile.

Tools

describe_attributes

Lists every attribute a profile can return: name, label, category, kind, and values (code to label) where the attribute is coded. No input. Not metered.

profile_list

Profiles one list. Input is records, an array of 200 to 100,000 objects. Each carries at least one of email, phone, namezip, and every value is a 64-character lowercase hex SHA-256.

input
{
  "records": [
    { "email": "1a7694688f8c5a79b1adb2bdd23937bea3c3ebd27ab4108fa5190c105c21a3f2" },
    { "phone": "<sha256>", "namezip": "<sha256>" }
  ]
}

Output, trimmed to one attribute:

output
{
  "slug": "q3Xk9aLm",
  "claimUrl": "https://supermegamind.com/r/q3Xk9aLm/claim#t=...",
  "claimExpiresAt": "2026-10-21T18:04:11.000Z",
  "uploaded": 1200,
  "matched": 948,
  "matchRate": 79,
  "recordsBilled": 1200,
  "usage": { "used": 412000, "limit": 500000, "remaining": 88000,
             "resets": "2026-11-01T00:00:00.000Z", "overageEnabled": false },
  "attributes": [
    {
      "field": "CAMPING_ENTHUSIAST",
      "label": "Camping Enthusiast",
      "category": "Enthusiasts",
      "coverage": 54.9,
      "medianCustomer": null,
      "medianNational": null,
      "values": [
        { "label": "Y", "count": 520, "pct": 54.9, "index": 457, "decoded": true,
          "significant": true, "conservativeIndex": 422 }
      ]
    }
  ]
}
FieldMeaning
pctShare of matched records with this value
indexAgainst the national rate, where 100 is average
significantFalse means the difference can't be told apart from noise. Treat it as average
conservativeIndexThe index at the edge of its confidence interval nearest 100. The safer number to quote
coverageShare of matched records with any value for this attribute
usageRecords used this month on your key, your limit, what remains, and when it resets
claimUrlOpens the saved private report in a browser. Works once, within 7 days. Treat it like a password until used

REST API

Same operations, same key, base URL https://supermegamind.com.

MethodPathDoes
GET/api/v1/attributesdescribe_attributes
POST/api/v1/profilesprofile_list, with the input above as the JSON body
terminal
curl https://supermegamind.com/api/v1/attributes \
  -H "Authorization: Bearer $SMM_KEY"
terminal
curl https://supermegamind.com/api/v1/profiles \
  -H "Authorization: Bearer $SMM_KEY" \
  -H "Content-Type: application/json" \
  -d @body.json

Hashing

Each key is the lowercase hex SHA-256 of a normalised UTF-8 string. Normalisation must match exactly: a hash that is off by one character matches nobody, and we can’t tell you why, because we never see the raw value.

KeyNormaliseThen
emailTrim surrounding whitespace, lowercaseSHA-256
phoneKeep ASCII digits only. If 11 digits starting with 1, drop the 1SHA-256 of the 10 digits
namezipFirst and last name: uppercase, keep only A-Z and 0-9. ZIP: trim, first 5 charactersSHA-256 of FIRST|LAST|ZIP5

Send every key you have for a record. More keys mean more chances to match.

TypeScript

typescript
import { createHash } from "node:crypto";

const sha256 = (s: string) => createHash("sha256").update(s, "utf8").digest("hex");

export function hashEmail(email?: string | null): string | null {
  const normalized = (email ?? "").trim().toLowerCase();
  return normalized ? sha256(normalized) : null;
}

export function hashPhone(phone?: string | null): string | null {
  let digits = (phone ?? "").replace(/\D/g, "");
  if (digits.length === 11 && digits.startsWith("1")) digits = digits.slice(1);
  return digits ? sha256(digits) : null;
}

export function hashNameZip(first?: string | null, last?: string | null, zip5?: string | null): string | null {
  const f = (first ?? "").toUpperCase().replace(/[^A-Z0-9]/g, "");
  const l = (last ?? "").toUpperCase().replace(/[^A-Z0-9]/g, "");
  if (!f || !l) return null;
  return sha256([f, l, (zip5 ?? "").trim().slice(0, 5)].join("|"));
}

Python

python
import hashlib
import re


def _sha256(value: str) -> str:
    return hashlib.sha256(value.encode("utf-8")).hexdigest()


def hash_email(email):
    normalized = (email or "").strip().lower()
    return _sha256(normalized) if normalized else None


def hash_phone(phone):
    digits = re.sub(r"\D", "", phone or "", flags=re.ASCII)
    if len(digits) == 11 and digits.startswith("1"):
        digits = digits[1:]
    return _sha256(digits) if digits else None


def hash_namezip(first, last, zip5):
    first = re.sub(r"[^A-Z0-9]", "", (first or "").upper())
    last = re.sub(r"[^A-Z0-9]", "", (last or "").upper())
    if not first or not last:
        return None
    zip_ = (zip5 or "").strip()[:5]
    return _sha256("|".join([first, last, zip_]))

Check your implementation

KeyInputMust hash to
email"andy@example.com"1a7694688f8c5a79b1adb2bdd23937bea3c3ebd27ab4108fa5190c105c21a3f2
phone"(201) 555-0123"5219e4c445ea51892ac95fb11e51a0c53d792f8aeecfac58f5c1d02c0e7431e5
namezip"John", "Smith", "97201"f4ee6a17a6e143645da379b79b0387e4480751b9494cfed9b74e9e3a150389f0

Errors

Every error has one shape. MCP tools return the same object with isError: true.

json
{ "error": { "code": "insufficient_matches", "message": "Fewer than 200 records matched, so no profile is returned." } }
StatusCodeMeaningBilled
400invalid_requestBad body, raw values, or wrong sizeNo
401unauthorizedMissing, malformed, or revoked keyNo
402budget_exhaustedThis call would pass your monthly record budgetNo
422insufficient_matchesFewer than 200 records matched, so no profile is returnedYes
429rate_limitedMore than 10 calls a minuteNo

insufficient_matches doesn’t say how many records matched. On a small list, that number is itself information about the people on it.

Limits and billing

Billing unitRecords submitted, at least 1,000 per profile call
Records per run200 to 100,000. One run is one profile, and runs are not combined
Matched floor200 matched records per profile
Rate10 profile calls a minute per key
BudgetMonthly, per key, resets on the 1st (UTC). Calls past it are refused and not charged, unless overage is on
WarningsEmails to your billing contact at 80% and 100% of the month's records
describe_attributesFree

Plans start at $1,500 a month for 500,000 records, with higher tiers for more volume. Ask about plans.

Privacy and terms

  • We receive hashes, never raw emails, phones, or names.
  • Output is aggregate only. No record about any individual is ever returned.
  • Sensitive attributes (ethnicity, religion, political donor flags, credit, income, net worth, and date of birth) are never available through the API.
  • Every call is logged against its key, as counts and ids only.
  • Every key is issued under terms that prohibit re-identification attempts, eligibility decisions (credit, employment, housing, insurance), and political use.

See also the privacy policy and terms. Questions: help@supermegamind.com.

(c) 2026 - SuperMegaMind, a Rosicodex brand, all rights reserved.

Privacy · Terms · API & MCP