cleared, watch or escalated.
The test environment screens with the synthetic connector. It contacts no platform. It makes up profiles, posts and findings from each person’s details, and the same list always gets the same results. A live screening answers 409
screening_unavailable until NoResponse turns on a collector for each platform that you ask for.Routes and access
Every route is under/v1/workspaces/{workspace_id}/groups/{environment}. environment is live or test.
An API key needs the
groups scope for every route. A key acts only in its own workspace and environment. The other environment answers 404 not_found. A key without the scope answers 403 workspace_forbidden.
A workspace member needs one of three actions in the environment:
Roles describes each role.
Ids have a prefix:
grp_ for a group, per_ for a person, scn_ for a screening, arv_ for a decision and srh_ for a search.
Create a group
Location header:
purge_after is 30 days after the group ends. At that time NoResponse purges the raw items that screenings collected and the candidate profiles that matched no person. created_by is the key, or the member, that created the group. An environment holds at most 1000 groups. One more answers 409 group_limit_reached.
List, read and change groups
GET /v1/workspaces/{workspace_id}/groups/{environment} answers the newest groups first:
limit is 1 to 100, and the default is 20. When next_before is present, pass it back as before to read the next page. An unknown or repeated query parameter answers 400.
GET /v1/workspaces/{workspace_id}/groups/{environment}/{group_id} answers one group, in the same form as the create answer.
PATCH changes only the fields that you send. Send expected_version to change the group only if nobody changed it since you read it:
version. A different expected_version answers 409 group_conflict. Read the group again and decide whether your change still applies.
Delete a group
Add people
Send 1 to 500 people in one request, with anIdempotency-Key header. The body is at most 512 KiB.
name. The other fields help the screening find the person. external_id is your own id for the person, unique within the group. Then come email, phone in E.164 form, company, city and up to 20 known handles. role is the person’s role in the group: guest (a ticket holder), crew, vendor or press. Leave it out when you do not know it. A person without a role has no role, not guest. A handle names its platform: instagram, x, bluesky, youtube, tiktok, facebook, threads, reddit, telegram, linkedin, github, scratch, dailymotion, substack, duolingo, dockerhub, wikipedia, internetarchive, spotify, patreon, wordpress, okru, flickr or synthetic. A screening of github or of scratch through flickr reads the public profile the handle names, with no posts.
created: a new person.updated: the row names an existing person of the group and changes its details. A row names a person by the sameexternal_id. A row withoutexternal_idnames a person by the sameemail, and then by the samephone. The row replaces the person’s details. The person goes back tounscreened, because its earlier results describe other details.unchanged: the row names an existing person with the same details.
Idempotent-Replayed: true. The same key with a different body answers 409 idempotency_key_reused. A missing or malformed key answers 400 invalid_idempotency_key. A group holds at most 20000 people. A batch that goes past the bound answers 409 group_limit_reached and adds nobody.
Start a screening
depth is standard, which reads the bio and the last 100 posts of each profile. platforms names 1 to 11 platforms from the list above. synthetic is the synthetic connector’s own platform. In the test environment, the synthetic connector also serves instagram, x, bluesky and youtube.
In the live environment, a screening reads bluesky through Bluesky’s public API, from a bluesky handle that you supply. It reads youtube, once NoResponse turns it on, through the YouTube Data API, from a youtube handle, channel link or channel id that you supply. A screening does not search YouTube by name. When a person has no handle for a platform, the screening has nothing to look up. It lists that platform in the coverage field not_searched, not in platforms. That platform is not none_found and not a failure, and the person is not partial. When no platform that you asked for has anything to look up, the person has no profile to check. See Match states. Both read public profiles and posts only, and never sign in.
The screening covers each person that no screening has reached, and each person on watch or escalated. The answer is 202 with the screening, a Location header and Retry-After: 5:
queued or running answers 409 group_conflict. A retry with the same key and body answers the same screening, with Idempotent-Replayed: true. The start answers 409 screening_unavailable when a platform that you ask for has no collector turned on in the environment. In the test environment, tiktok, facebook, threads, reddit, telegram, linkedin, github and the profile sites scratch through flickr have none yet.
Spend and the daily cap
estimated_spend is the cost estimate of the screening in US cents: the people that it selects, at its depth and on its platforms. spent is the cost that its screened people recorded so far. A failed person records the cost of its lookups only, because the screening keeps them. spend breaks the cost of the screening down by implementation and role. It also counts the other costs of people that failed and of passes that were retried, so its total_minor can be more than spent. The screening_daily_cap setting of the environment limits the spend of all its screenings in one UTC day. The default is 50000, which is 500 US dollars. When the estimate of a new screening is more than the cap has left, the start answers 409 screening_cap_exceeded and queues nothing. Cap screening spend tells you how the API calculates the remainder and how to change the cap.
Each person that a screening screens is one screenings unit in your usage.
Follow a screening
You do not have to poll. When the screening finishes, NoResponse sendsscreening.completed to your verified webhook endpoints, and person.flagged for each person that it flags. The member who started the screening from the console also gets an email with the counts.
To follow it yourself, poll the screening at its Location:
people_flagged counts the people with a finding. spend is what the screening cost so far, in US cents, in total and for each resolver, collector and analyser. A screening waits when its analyser does not answer. It tries again after 30 seconds, then after longer waits, up to 9 tries, as a webhook delivery does. A screening still open at expires_at, 24 hours after you started it, finishes partial or failed with error_code expired. A failed person keeps its earlier state, and the next screening tries it again. The screening keeps the lookups that it made for that person. The next pass or screening uses them again and does not pay for them again.
GET .../screenings answers the group’s screenings, newest first, as items with next_before. It takes limit and before like the group list.
Cancel a screening
Send an empty JSON object:Read the roster
limit is 1 to 500, and the default is 100. When next_after is present, pass it back as after to read the next page. Three filters narrow the roster:
bandiscritical,high,moderate,low,info, ornonefor people with no finding.decisioniscleared,watch,escalated, orundecidedfor people with no decision.roleisguest,crew,vendor,press, orunstatedfor people with no role.
Read a person’s report
profileslists the accounts that the screening matched, with the evidence for each match.findingslists what the screening flagged, the most severe first. Each finding cites its evidence by item id, andquotegives the exact words of the first item that it rests on. A finding on a picture hasmedia_observations, one for each picture:descriptiontells what the picture shows, andread_textgives the words that the analyser read in it. These words come from the picture, not from the text of a post. Thus they are not a quote, and NoResponse cannot check them. Compare them with the picture at itspermalink.postsandmediaare the items that the findings cite, with the permalink, the time and a SHA-256 hash of the screenshot.reviewslists every decision on the person, oldest first.
Match states
A screening gives each profile a match state from its evidence. Email, phone, a handle that you supplied and an account that an assessment source found are hard identifiers. Company, city and school are soft identifiers.
The person’s
match_state is the strongest match among the profiles that the screening could read. When it found only private or locked accounts, the state is inaccessible. When it looked on every platform and found no account, the state is none_found.
When the person gave the screening nothing to look up on each platform that you asked for, nobody looked for a profile. An example is a row with no handle on a platform that a screening reads from handles only, such as Bluesky in live. The person then has no_profile_to_check: true. Its match_state stays unscreened, highest_band is null, and coverage.not_searched names each platform. This is a final result. It is not none_found and not a failure, and it does not make the screening partial. The person’s lookups still run, show on its report and count in the spend. Screen again does not select the person. To screen the person, add a handle to its row. The changed row goes back to unscreened.
Bands and categories
A finding has one band:critical, high, moderate, low or info, the most severe first. The analyser raises a finding one band when its evidence names the group or one of its days. The words that it read in a picture count as evidence. It does the same for a principal, an alias, the host or the venue. The match of its evidence then caps the band. A finding on a possible match is at most moderate. A finding on a probable match is at most high. When the cap lowers a band, capped_from gives the band that the analyser gave.
A finding is about behaviour toward the group’s venue, occasion or principals, never about views in general. Its category is one of these:
Coverage
coverage tells a clean result apart from a person that the screening could not see. It is empty until a screening reaches the person.
The reasons are these:
A platform’s
status is one of these:
A
failed or unavailable platform makes the person partial, and so do items that the analyser could not analyse. The screening counts the person in people_partial.
spend is what the latest screening of the person cost, in US cents, in total and for each resolver, collector and analyser.
Record a decision
Each decision also sendsperson.decided to your verified webhook endpoints, without the note.
decision is cleared, watch or escalated. note is at most 2000 characters, for the venue team and for later reviewers. The answer is 200 with the person:
decision, and the report keeps the earlier ones. A person holds at most 1000 decisions. The next screening of the group covers every person on watch or escalated again.
Whose record it is
A search on a name or a common username finds many people. So each record in the report’slookup.records says how likely it is to be the person:
NoResponse knows the person from two things. One is the person’s row. The other is the values of their own records: those that their email or phone found, and those that your team joined. A record that joins only by its
likelihood adds nothing to what NoResponse knows, so one wrong join does not pull in more. One shared value alone does not take a record to 0.9, unless it is the person’s email or phone.
To overrule a record, send its id and your verdict:
joined makes the record the person’s at any likelihood. rejected keeps it a lead at any likelihood, and a rejected record that the person’s email or phone found then adds nothing to what NoResponse knows. The answer is 200 with the verdict:
not_found. A member needs groups.review in the environment. A key needs the groups scope. The audit log records each verdict as person.record_judged.
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 the value of any field whosekind is email, phone, username, person_name, password, hash or ip. The field can be in a lookup record, in a profile’s record or in a record that an earlier search found. Nothing searches again by itself: a search starts only when you ask for it.
Each such field carries distinctive. Search first the values rated unique or distinctive: those likely find one person. A common value finds many people, and you can still search it.
Name the record and the field. You do not send the value:
searches list every search, newest first. A search that ran is completed when every source it asked answered, partial when some did not, and failed with an error_code when none answered. A failed search does not show whether any source holds the value.
What a search finds joins the person’s lookup.records. Each found record’s matched entry names the search in search, so you can see the value and the record and field that led to it. The records are leads. NoResponse works out their likelihood as for any other record, and you can join or reject them.
Passwords and hashes
Many people choose the same password. So apassword or hash search counts first. It is counting, then counted, and rows says how many breach rows hold the value. Nothing else runs until you run it:
group_conflict.
More rows of one table
A breach search returns at most 20 rows of a table. The lookup’sremaining names each table with rows left out, and the search that left them out. To get them, search the same field again with that table. A search of one table returns up to 100 rows of it.
Limits
Starting the same search while it is still open answers that search. A record or a field that the person does not hold answers 404not_found. A field of another kind answers 400 invalid_request. Each search counts against the environment’s screening_daily_cap, as a screening does, and one that the cap cannot hold answers 409 screening_cap_exceeded. A member needs groups.review in the environment. A key needs the groups scope. The audit log records person.search_started and person.search_run.
How a screening works
A screening runs four steps for each person. The first runs once for the person, and the others run on each platform that you ask for:1
Look up
The signup check’s lookups run on the person’s email, phone and handles: account presence, the phone lookup, the email domain’s registration and the breach search. Their results are context for the reviewer. They do not set a band. The screening keeps the lookups when a later step fails or must try again. The next pass, or the next screening of a person that failed, uses them again and does not pay for them again. A changed row gets new lookups.
2
Resolve
Each resolver that NoResponse turns on for the platform looks for the person’s accounts. It gives each candidate account with the evidence that ties it to the person. The match state comes from that evidence. Two resolvers are on in every environment.
supplied_handles makes a candidate of each handle on the person row. lookup_facts adds an account that a profile found by the lookups lists. Neither searches the platform. When only they cover a platform and have no account to give, the screening lists the platform in not_searched, not as none_found.3
Collect
The first collector that answers reads each candidate account: its profile, and its posts and media. A private or locked account is
inaccessible, not a failure.4
Analyse
The first analyser that answers reads the posts and media of the readable accounts, with the group’s details. It gives findings that cite the items. NoResponse caps each finding’s band by its match.
Lookups
The report’slookup gives what the lookups found. A lookup sends the person’s email, phone and up to three distinct handles, as an assessment would send them. The same source controls and your workspace’s refused sources apply.
checkshas one entry for each lane of each identifier that was looked up.foundmeans a source found the identifier, andnot_foundmeans every source answered and none found it.partialmeans some sources did not answer, andnot_coveredmeans no source answered or none could be asked. These last two come with areason. A lookup that failed is nevernot_found.summaryhas the same type as an assessment’srisk.summary.detailshas the same type as an assessment’srisk.details. Only a member who can read evidence, or a key with thedetailedorevidencescope, gets it.recordshas each account and each breach row that the lookups found, once, with every field that its source gave. Everyone who can read the person getsrecords. Records shows how to read them, and Whose record it is how to overrule one.
lookups resolver line to the screening’s spend. The breach searches of a screening wait for the signup check’s searches and leave part of the breach search capacity to assessments. A large list can take longer to screen. The test environment answers the lookups from the simulator, so they contact no source.
The analysis contract
An analyser reads an analysis request and answers an analysis result. NoResponse can connect an external model through an HTTP form of the same contract:- NoResponse sends the request as a JSON
POSTbody. The headersNR-Analysis-Id,NR-Analysis-TimestampandNR-Analysis-Signaturecome with it. - The signature is
v1=and the lowercase hex HMAC-SHA256 of the timestamp, a.and the raw body, under the analyser’s key. It is the webhook signature scheme. - The analyser answers 200 with the result within 60 seconds.
- NoResponse tries again on a timeout, a 408, a 429 or a 5xx, on the webhook schedule. Any other answer fails the person for that screening.
findings array means that your model flagged nothing in what it read. Put in not_analysed the ids of the items that your model could not read, such as videos or a batch that it refused. The screening counts them as not analysed and the person as partial, never as clean. To give the reason for an item, add it to not_analysed_reasons as item and reason. An item with no reason counts as unspecified. A finding can carry quote, the exact words of its first item. A finding on a picture can carry media_observations, with the media_id of a picture that it cites, its description and its read_text:
Privacy and the audit log
NoResponse stores each person’s details encrypted under your workspace’s key. To find the same person in a later batch, it keeps keyed digests of the external id, the email and the phone, never the values. The role does not identify anyone, so it is stored as it is, for therole filter. Deleting the group, or the workspace, deletes every person. Deleting the workspace also destroys the key.
NoResponse also stores each profile, post, media item and finding encrypted under your workspace’s key. Deleting the group deletes them.
Each change goes into the audit log of the environment, with the member or the key that made it: group.created, group.updated, group.deleted, group.people_added, person.decided, screening.started and screening.cancelled. An audit event names ids and the decision, never a person’s details.