> ## Documentation Index
> Fetch the complete documentation index at: https://docs.noresponse.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Read a person's records

> Every account and every leaked row that the lookups find for a person comes back as a record. Read its source, its fields, their kinds and roles, and how likely the record is the person.

A record is one answer from one source. An account that NoResponse read on a platform is a record. A row of a leaked table is also a record. Both have the same shape, so one piece of code reads both.

A record keeps every field that its source gave, under the name that the source uses. NoResponse hides nothing. A leaked row keeps its passwords, hashes and salts. Each field also gets a kind and a cleaned value, and a field that many sources share gets a role. Your code reads the kind and the role, and it does not need to know the field names of each source.

## Get the records

The records are in `lookup.records` of a person's report. A screening runs the lookups for each person, so the records are there after a screening reaches the person. Read the report with a key that has the `groups` scope. A member of your workspace needs a role that can read the group.

```bash title="cURL" theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
curl "https://api.noresponse.org/v1/workspaces/$NORESPONSE_WORKSPACE/groups/test/$GROUP_ID/people/$PERSON_ID" \
  -H "Authorization: Bearer $NORESPONSE_API_KEY"
```

```ts title="TypeScript" theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
import { NoResponse } from "@noresponse/node";
import { groupsGetPerson } from "@noresponse/node/funcs/groups-get-person.js";

const nr = new NoResponse(); // reads NORESPONSE_API_KEY
const answer = await groupsGetPerson(nr.core, {
  workspaceId: process.env.NORESPONSE_WORKSPACE ?? "",
  environment: "test",
  groupId: process.env.GROUP_ID ?? "",
  personId: process.env.PERSON_ID ?? "",
});
if (!answer.ok) throw answer.error;

for (const record of answer.value.result.lookup?.records ?? []) {
  const joined = record.fields.find((field) => field.role === "joined");
  console.log(record.source.kind, record.source.site, record.likelihood, joined?.value ?? joined?.original);
}
```

```python title="Python" theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
import os

from noresponse import NoResponse

nr = NoResponse()  # reads NORESPONSE_API_KEY
report = nr.groups.get_person(
    workspace_id=os.environ["NORESPONSE_WORKSPACE"],
    environment="test",
    group_id=os.environ["GROUP_ID"],
    person_id=os.environ["PERSON_ID"],
).result

records = (report.lookup.records or []) if report.lookup else []
for record in records:
    joined = next((field for field in record.fields if field.role == "joined"), None)
    print(record.source.kind, record.source.site, record.likelihood, joined.value if joined else None)
```

```go title="Go" theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
package main

import (
	"context"
	"fmt"
	"log"
	"os"

	noresponse "github.com/noresponsehq/go/v12"
	"github.com/noresponsehq/go/v12/models/components"
)

func main() {
	client := noresponse.NewClient(os.Getenv("NORESPONSE_API_KEY"))
	res, err := client.Groups.GetPerson(context.Background(), os.Getenv("NORESPONSE_WORKSPACE"),
		components.KeyEnvironmentTest, os.Getenv("GROUP_ID"), os.Getenv("PERSON_ID"))
	if err != nil {
		log.Fatal(err)
	}
	lookup := res.Result.GetLookup()
	if lookup == nil {
		return
	}
	for _, record := range lookup.Records {
		for _, field := range record.Fields {
			if field.Role != nil && *field.Role == components.RecordRoleJoined {
				fmt.Println(record.Source.Kind, field.Field, field.Original)
			}
		}
	}
}
```

Two SDKs change one name. In Python, `source.date` is `source.date_`. In Ruby, a record's `fields` is `fields_`. The other names are the same as in the API. [Read a person's report](https://docs.noresponse.org/reference/groups.md) in the groups reference shows the full report.

> [!TIP]
>
> To get records with a test key, add a person with the email `late@test.noresponse.org` and a handle to a test group, then screen the group. The simulator answers the lookups with accounts and leaked rows, and it contacts no source.

## The parts of a record

| Field | What it holds |
| - | - |
| `id` | The id of the record, such as `rec_5c1e0d7a9b3f4e2d8a6c0b1f9e7d3a52`. NoResponse makes it from the source and the fields. The same answer found again has the same id. |
| `source` | Where the record came from. See [Sources](#sources). |
| `matched` | Each search that found the record: `by` is the kind of value searched, such as `email` or `username`, and `field` is the field that it hit. `search` names the search a person started, when one found it. See [Search again](#search-again). |
| `fields` | Every field that the source gave. See [Fields](#fields). |
| `observed_at` | When the source last gave this answer. |
| `likelihood`, `agreed`, `clashed`, `joined`, `verdict` | How likely the record is the person. See [Whose record it is](#whose-record-it-is). |

### Sources

`source.connector` names the connector that read the record. A leaked row has the connector `breach`. `source.site` is the website that a person knows the source by. A leaked row also has its `table` and its `date`, as a year or a year and month.

`source.kind` tells you what the record is:

| `source.kind` | What one record is |
| - | - |
| `platform` | An account that NoResponse read on the live platform. |
| `site_breach` | The person's account on the site, as it was when the site's data leaked. |
| `credential_list` | A login and a password from a password list, with no site. |
| `stealer_log` | A login, a password and the site where the person used them, taken from one computer by malware. |
| `scrape` | A public profile that somebody copied. |
| `hacking_forum` | An account on a hacking forum. |
| `abuse_service` | A customer of an abuse service, such as a DDoS service or a card shop. |
| `darkweb_site` | An account on a dark-web site. |

NoResponse can add a kind of source. Read an unknown `source.kind` as a leaked row.

### Fields

| Field | What it holds |
| - | - |
| `field` | The name that the source uses for the field. A nested value has a dotted name, such as `links.0`. |
| `original` | The value as the source wrote it. It is always there. |
| `kind` | What sort of value it is. See [Kinds](#kinds). |
| `role` | Which common field it is, such as `handle` or `joined`. Only a field that many sources share has a role. See [Roles](#roles). |
| `value` | The value cleaned to the one form of its kind. It is absent when NoResponse cannot clean the value. Then use `original`. |
| `detail` | What the value shows of itself: the likely algorithm of a hash, the likely coin of a wallet or the currency of an amount. |
| `distinctive` | How distinctive a value is that you can search on. See [How distinctive a value is](#how-distinctive-a-value-is). |

Compare values with `value`, not with `original`. Two sources can write the same date or phone number in different ways, but their cleaned values are the same.

## A platform account

The lookups found this account by a username. The person's report gives it like this:

```json title="Platform account" theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
{
  "id": "rec_5c1e0d7a9b3f4e2d8a6c0b1f9e7d3a52",
  "source": {
    "connector": "github",
    "kind": "platform",
    "site": "github.com"
  },
  "matched": [
    {
      "by": "username",
      "field": "login"
    }
  ],
  "fields": [
    {
      "field": "login",
      "original": "jordan-ellis",
      "kind": "username",
      "role": "handle",
      "value": "jordan-ellis",
      "distinctive": {
        "rating": "distinctive",
        "reason": "34 bits, about one in 17 billion usernames"
      }
    },
    {
      "field": "id",
      "original": "48151623",
      "kind": "account_id",
      "role": "account_id",
      "value": "48151623"
    },
    {
      "field": "name",
      "original": "Jordan  Ellis",
      "kind": "person_name",
      "role": "display_name",
      "value": "Jordan Ellis",
      "distinctive": {
        "rating": "distinctive",
        "reason": "in 3 breach rows"
      }
    },
    {
      "field": "html_url",
      "original": "https://GitHub.com/jordan-ellis",
      "kind": "url",
      "role": "profile_link",
      "value": "https://github.com/jordan-ellis"
    },
    {
      "field": "avatar_url",
      "original": "https://avatars.githubusercontent.com/u/48151623",
      "kind": "image",
      "role": "picture",
      "value": "https://avatars.githubusercontent.com/u/48151623"
    },
    {
      "field": "bio",
      "original": "Security engineer. Climbing on weekends.",
      "kind": "long_text",
      "role": "bio",
      "value": "Security engineer. Climbing on weekends."
    },
    {
      "field": "location",
      "original": "Leeds, UK",
      "kind": "text",
      "role": "location",
      "value": "Leeds, UK"
    },
    {
      "field": "company",
      "original": "Northwind Example",
      "kind": "organisation",
      "role": "organisation",
      "value": "Northwind Example"
    },
    {
      "field": "twitter_username",
      "original": "jordan_ellis",
      "kind": "username",
      "role": "linked_account",
      "value": "jordan_ellis",
      "distinctive": {
        "rating": "distinctive",
        "reason": "33 bits, about one in 8.6 billion usernames"
      }
    },
    {
      "field": "followers",
      "original": "212",
      "kind": "count",
      "role": "followers",
      "value": "212"
    },
    {
      "field": "created_at",
      "original": "2014-03-07T09:30:00Z",
      "kind": "date_time",
      "role": "joined",
      "value": "2014-03-07T09:30:00Z"
    },
    {
      "field": "hireable",
      "original": "true",
      "kind": "flag",
      "value": "true"
    }
  ],
  "observed_at": "2026-11-14T10:01:40Z",
  "likelihood": 0.74,
  "agreed": [
    "name",
    "twitter_username"
  ],
  "clashed": [],
  "joined": true,
  "verdict": "joined"
}
```

* `login` has the role `handle`. On a different platform, the handle can have a different field name, but it has the same role.
* `name` keeps the two spaces of the original. Its `value` has one space.
* `twitter_username` has the role `linked_account`. The name of the field names the network.
* `hireable` has no role, because few sources give it. It is still in the record, with its kind.
* The person's row named this X handle, so `twitter_username` is in `agreed`. A reviewer joined the record, so `verdict` is `joined`.

## A leaked row

The lookups found this row by the person's email. It has every column of the row, in name order:

```json title="Leaked row" theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
{
  "id": "rec_8e2b4f6a1c3d5e7f9a0b2c4d6e8f1a3b",
  "source": {
    "connector": "breach",
    "kind": "site_breach",
    "site": "forum.example.net",
    "table": "FORUM_EXAMPLE_NET_2019",
    "date": "2019-03"
  },
  "matched": [
    {
      "by": "email",
      "field": "email"
    },
    {
      "by": "username",
      "field": "username",
      "search": "srh_01JC8Y5B7C9D1E3F5G7H9J1K3M"
    }
  ],
  "fields": [
    {
      "field": "birthday",
      "original": "07/03/1990",
      "kind": "date",
      "role": "born"
    },
    {
      "field": "email",
      "original": "Jordan.Ellis@Example.com",
      "kind": "email",
      "value": "jordan.ellis@example.com",
      "distinctive": {
        "rating": "unique",
        "reason": "an email address belongs to one person"
      }
    },
    {
      "field": "lastip",
      "original": "3405803781",
      "kind": "ip",
      "role": "last_ip",
      "value": "203.0.113.5",
      "distinctive": {
        "rating": "common",
        "reason": "many people share one IP address"
      }
    },
    {
      "field": "password",
      "original": "Leeds1990!",
      "kind": "password",
      "value": "Leeds1990!",
      "distinctive": {
        "rating": "distinctive",
        "reason": "seen twice in breach data; needs about 10^9 guesses"
      }
    },
    {
      "field": "password_hash",
      "original": "$2y$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy",
      "kind": "hash",
      "value": "$2y$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy",
      "detail": "bcrypt",
      "distinctive": {
        "rating": "distinctive",
        "reason": "in 1 breach row"
      }
    },
    {
      "field": "regdate",
      "original": "1394184600",
      "kind": "date_time",
      "role": "joined",
      "value": "2014-03-07T09:30:00Z"
    },
    {
      "field": "salt",
      "original": "x9Q",
      "kind": "salt",
      "value": "x9Q"
    },
    {
      "field": "usergroupid",
      "original": "2",
      "kind": "label",
      "role": "site_role",
      "value": "2"
    },
    {
      "field": "username",
      "original": "jellis",
      "kind": "username",
      "role": "handle",
      "value": "jellis",
      "distinctive": {
        "rating": "common",
        "reason": "19 bits, about one in 520 thousand usernames"
      }
    }
  ],
  "observed_at": "2026-11-14T10:01:42Z",
  "likelihood": 0.99,
  "agreed": [
    "regdate"
  ],
  "clashed": [],
  "joined": true
}
```

* `password`, `password_hash` and `salt` are fields like the others. `detail` gives the likely algorithm of the hash.
* `lastip` holds an IPv4 address as a whole number, as some forums store it. Its `value` is the address.
* `regdate` is a Unix time. Its `value` is the moment in UTC. It has the role `joined`, as `created_at` has on the platform account. Thus you can compare the two directly.
* `birthday` has no `value`. `07/03/1990` can be the 7th of March or the 3rd of July, and the site does not tell its country. `original` keeps the value as written.

## Kinds

The kind of a field says what sort of value it holds. The kind sets the form of `value`. There are 45 kinds. A field of any kind is in the record, and `text` holds what has no other kind.

| Kind | Holds | `value` |
| - | - | - |
| `email` | An email address. | In lower case, without a mailto: prefix. |
| `phone` | A phone number. | In E.164 form, such as +447700900123. A national number is cleaned only when the country of the source's site is known. |
| `username` | A handle a person chose. | As written. Compare it in lower case. |
| `account_id` | A platform's own id for an account. | As written. |
| `url` | A web address. | The scheme and host in lower case, the path, query and fragment as written. |
| `domain` | A host name with no path. | In lower case, also when the source wrote a URL. |
| `image` | A picture, as a link or a file. | The link, cleaned as a `url`. |
| `person_name` | A whole name or a part of one. | With each run of spaces made one space. |
| `gender` | A stated gender. | `female`, `male` or `other` when the value names one of them, else as written. |
| `age` | An age in years. | A whole number from 0 to 150. |
| `language` | A language. | Its ISO 639 code, two letters when the language has them. |
| `organisation` | A company, school or group by name. | With each run of spaces made one space. |
| `job_title` | A position or occupation. | As written. |
| `country` | A country. | Its ISO 3166-1 alpha-2 code, such as GB. |
| `region` | A state, province or county. | As written. |
| `city` | A city or town. | As written. |
| `address` | A street address or one line of it. | As written. |
| `postcode` | A postal code. | In upper case with single spaces. |
| `coordinates` | A latitude and longitude. | `latitude,longitude` in decimal degrees. |
| `timezone` | A timezone or an offset. | An IANA zone name, UTC, or an offset such as +05:30. |
| `date` | A calendar date. | In ISO form at the precision the source gives: 2014, 2014-03 or 2014-03-07. |
| `date_time` | A moment. | In ISO form in UTC, such as 2014-03-07T09:30:00Z, or a date when the source gives no time. |
| `duration` | A length of time. | A number of seconds. |
| `ip` | An IPv4 or IPv6 address. | Its standard text form. An IPv4 address stored as a whole number is written as IPv4. |
| `mac_address` | A network card address. | In lower case with colons. |
| `device_id` | An id of a phone, computer or browser. | As written. |
| `user_agent` | A browser or app signature. | As written. |
| `password` | A password in plain text. | Exactly as written, spaces included. |
| `hash` | A password hash. | As written. detail names the likely algorithm, such as bcrypt. |
| `salt` | The salt for a hash. | Exactly as written. |
| `token` | A session, reset or API token. | As written. |
| `security_answer` | A security question or its answer. | Exactly as written. |
| `card_number` | A payment card number. | Its 12 to 19 digits only. |
| `bank_account` | An IBAN or account number. | Its letters and digits only, in upper case. |
| `crypto_address` | A wallet address. | As written. detail names the likely coin, such as BTC. |
| `amount` | Money, with its currency when given. | A plain decimal. detail names the currency, such as USD. |
| `id_number` | A passport, tax, national or licence number. | As written. |
| `vehicle` | A number plate or a VIN. | In upper case with no spaces. |
| `label` | One of a small set, such as a status or level. | As written. |
| `long_text` | Free writing, such as a bio or signature. | As written. |
| `count` | A whole number of things. | A whole number with no thousands separators. |
| `score` | A rating, rank or points. | A plain decimal. |
| `flag` | A yes or no. | `true` or `false`. |
| `structured` | A nested value that could not be split. | As written. |
| `text` | Anything else. | As written. |

## Roles

The role of a field says which value of its kind it is. A join date and a birth date are both dates, but their roles are `joined` and `born`. An account field and a column of a leaked row with the same meaning have the same role. Each role has values of the kinds in its row.

| Role | What the value is | Kinds |
| - | - | - |
| `account_id` | The platform's own id for the account. | `account_id` |
| `account_status` | The state of the account, such as active or suspended. | `label` |
| `account_type` | The sort of account, such as personal or business. | `label` |
| `alternate_phone` | A mobile, alternate or fax number beside the main one. | `phone` |
| `balance` | Money held on the account. | `amount` |
| `banned` | Whether the site banned the account. | `flag` |
| `bio` | What the person wrote about themselves. | `long_text` |
| `born` | The date of birth. | `date`, `date_time` |
| `bot` | Whether the site marks the account as a bot. | `flag` |
| `caller_category` | The category callers give a phone number, such as telemarketer. | `label` |
| `chargebacks` | How many chargebacks a shop recorded against the account, or whether it has one. | `count`, `flag` |
| `cover_picture` | The banner picture of the profile. | `image` |
| `declines` | How many of the account's payments a shop declined. | `count` |
| `deleted` | Whether the account is deleted or inactive. | `flag` |
| `deleted_on` | When the account was deleted. | `date`, `date_time` |
| `department` | A department the source names. | `text` |
| `display_name` | The name the account shows. | `person_name` |
| `email_verified` | Whether the site verified the email. | `flag` |
| `expires` | When the account or its subscription expires. | `date`, `date_time` |
| `first_name` | A first name. | `person_name` |
| `followers` | How many accounts follow the account. | `count` |
| `following` | How many accounts the account follows. | `count` |
| `friends` | How many friends the account has. | `count` |
| `full_address` | A whole postal address on one line. | `address` |
| `full_name` | A whole name. | `person_name` |
| `handle` | The handle of the account. | `username` |
| `headline` | A headline or user title shown with the name. | `text` |
| `interests` | Interests the person lists. | `text` |
| `job_title` | A job title the source states. | `job_title` |
| `joined` | When the account was made. | `date`, `date_time` |
| `last_active` | When the account was last active. | `date`, `date_time` |
| `last_ip` | The IP the account last used. | `ip` |
| `last_name` | A last name. | `person_name` |
| `last_post` | When the account last posted. | `date`, `date_time` |
| `last_updated` | When the account or its profile last changed. | `date`, `date_time` |
| `level` | A level or rank on the site. | `label` |
| `likes_given` | How many likes the account gave. | `count` |
| `likes_received` | How many likes the account received. | `count` |
| `line_type` | The line type of a phone number, such as mobile or VoIP. | `label` |
| `linked_account` | An account on another network. The field's name names the network. | `username`, `account_id`, `url` |
| `location` | A place as the person wrote it. | `text` |
| `marked_spam` | Whether callers mark a phone number as spam. | `flag` |
| `middle_name` | A middle name. | `person_name` |
| `nationality` | A nationality. | `country` |
| `organisation` | An organisation the source names, such as a company. | `organisation` |
| `other_ip` | An IP whose use the source does not say. | `ip` |
| `paid` | Whether the account is paid or premium. | `flag` |
| `password_changed` | When the password last changed. | `date`, `date_time` |
| `payment_method` | The payment method or card brand the account paid with. | `label` |
| `picture` | The profile picture. | `image` |
| `points` | Points on the site. | `score` |
| `posts` | How many posts the account made. | `count` |
| `private` | Whether the account is private. | `flag` |
| `profile_link` | The link to the profile. | `url` |
| `profile_views` | How many times people viewed the profile. | `count` |
| `rating` | A rating of the account. | `score` |
| `referrals` | How many people the account referred. | `count` |
| `referrer` | Who or what referred the account. | `text` |
| `report_count` | How many reports callers made on a phone number. | `count` |
| `reputation` | Reputation on the site. | `score` |
| `reputation_label` | A reputation label for a phone number. | `label` |
| `residence` | The country the person lives in. | `country` |
| `salutation` | A salutation or suffix, such as Dr or Jr. | `text` |
| `school` | A school the source names. | `organisation` |
| `signature` | A signature the person posts under. | `long_text` |
| `signup_ip` | The IP the account was made from. | `ip` |
| `site_role` | A role on the site, such as admin or moderator. | `label` |
| `spam_score` | How likely a phone number is spam, as the source scores it. | `score` |
| `street` | A street line of an address. | `address` |
| `trades` | How many trades a forum account completed. | `count` |
| `verified` | Whether the site verified the account. | `flag` |
| `warnings` | Warnings and infractions on the account. | `count` |
| `website` | A website the person lists. | `url` |

## How distinctive a value is

A value that you can search on has `distinctive`: an email, a phone, a username, a password, a hash, a person name or an IP. `rating` tells you if a new search on the value can find one person. `reason` tells you why, in words.

| `rating` | Meaning |
| - | - |
| `unique` | An email or a phone. It belongs to one person. |
| `distinctive` | The value is rare. A new search on it usually finds one person. |
| `common` | Many people share the value. A new search on it finds strangers. |

A username is `common` below 30 bits of its character model, or when more than 100 leaked rows hold it. A password is `common` when leaked data holds it 10 times or more, or when it takes fewer than 10^6 guesses. A hash is `common` in 10 leaked rows or more. A full name is `common` in more than 50 leaked rows, and one word of a name is always `common`. An IP is always `common`. When NoResponse could not count a value, the value has no `distinctive`.

## Search again

A username or a password in one record often leads to the person's other accounts. You can start a new search on any value that has `distinctive`. Name the record and the field with `POST .../people/{person_id}/searches`, and do not send the value. Nothing searches again by itself.

Search the `unique` and `distinctive` values first. You can still search a `common` value. A password or a hash search counts the leaked rows that hold the value first. It stops at `counted` until you run it with `POST .../searches/{search_id}/run`.

What a search finds joins `lookup.records` as leads. Each found record's `matched` entry names the search in `search`. The report's `searches` list gives each search's value, and the record and field that it came from. [Search again](https://docs.noresponse.org/reference/groups.md#search-again) gives the requests, the answers and the rules.

## Whose record it is

A search on a username or a name can find accounts of other people. So each record says how likely it is the person:

* `likelihood` is from 0.01 to 0.99. A record that the person's own email or phone found starts at 0.99.
* `agreed` names the fields whose values agree with what NoResponse knows of the person. `clashed` names the fields that differ from the person's birth date or full name.
* `joined` is `true` when `likelihood` is 0.9 or more, or when your team joined the record. A record that does not join is a lead.
* `verdict` is `joined` or `rejected` when your team overruled the record.

To overrule a record, send `POST .../people/{person_id}/records` with the record's `id` and your verdict. [Whose record it is](https://docs.noresponse.org/reference/groups.md#whose-record-it-is) gives the request, the answer and the rules.

## Limits and `remaining`

NoResponse never cuts an answer short without a sign. A search of leaked data keeps at most 20 rows of one table and 100 rows in all, from its first 64 tables by name. A row always keeps all its columns. When a search matched more rows, `lookup.remaining` tells you how many more, for each search and table:

```json title="lookup.remaining" theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
[
  {
    "source": {
      "connector": "breach",
      "kind": "credential_list",
      "table": "COMBOLIST_EXAMPLE_2021",
      "date": "2021"
    },
    "by": "username",
    "search": "srh_01JC8Y5B7C9D1E3F5G7H9J1K3M",
    "rows": 37
  }
]
```

`by` is the kind of value searched, as in `matched`. `rows` counts the rows that `records` does not hold. `remaining` is absent when `records` holds every row that matched. To get the rows of one table, search the same field again with that `table`. A search of one table returns up to 100 rows of it.

Everyone who can read the person can read every record and every field. No other permission applies to leaked rows or passwords.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.