Report an outcome
Send an account report with yourassess key and a stable Idempotency-Key:
Report account abuse
idempotency_key_reused.
An abuse report adds one account-level reason in this environment. It does not change earlier assessments or affect another workspace’s accounts. A flow decision change sends account.risk_changed.
Apply the event with the highest account risk_version. Use changed-account polling to recover missed deliveries.
The existing POST /v1/assessments/{assessment_id}/outcomes operation keeps its behavior. Its identifier marks are separate from account feedback. Correct a report through the operation that recorded it.
Correct or retract a report
GetGET /v1/accounts/{account_id}/outcomes with a history key. The page’s effective_outcome_id names the current report. Each receipt keeps its original effective report and risk version.
A correction names the current report in supersedes:
Correct a report
outcome_conflict rather than replacing a newer report. legitimate and false_positive clear the account feedback reason. Other evidence can still require review or blocking.
unknown alone records uncertainty without changing the current report. unknown with supersedes retracts that report.
Reports expire under the environment’s retention. Maintenance clears an expired report’s reason and advances the account version. An account read also resolves an expired reason before returning the current decision. Historical decisions keep the reason that applied at their version.
Find accounts and assessments by a submitted identifier
Select an email, phone number or username in an account or assessment to open the corresponding search. The console keeps the workspace, environment, match mode and return path. Results come from bounded server queries. The two search routes accept the sameidentifier object:
Identifier filter
POST /v1/accounts/search for distinct accounts. An account matches when any
retained admitted assessment submitted the identifier, even when its latest
assessment sent a different value. Use POST /v1/assessments/search for the
individual assessments that sent it. Add the required from and to range.
Its decisions are assessment decisions, separate from current account risk.
Combine submitted identifiers
Useidentifier_expression to combine email, phone and customer username terms
with and and or. Each term uses the match rules below.
Choose the scope explicitly:
Suppose an account submitted an email yesterday and a phone today.
An AND search with
scope: "account" can find it.
The same search with scope: "assessment" requires both identifiers in one submission.
Neither mode combines identifiers from different accounts.
This example finds one submission with the phone and either email:
Phone with either submitted email
POST /v1/accounts/search for distinct accounts.
For assessment results, use POST /v1/assessments/search and add from and to.
That range applies to the returned submissions.
Change the scope to account to search across an account’s retained history.
Account scope has no time filter. Effective retention still limits each term’s
matching submissions. Assessment search rejects account scope with 400 invalid_request.
In the console, Submitted together and Across account history express these scopes.
Assessment filters use Submitted together. Account filters offer both choices.
AND requires every child. OR requires at least one child.
Expression limits and duplicate terms
Each node contains exactly one member:term, and or or.
A term contains the existing identifier shape.
A group has 2 to 8 children. The expression allows at most three nested group
levels and eight term occurrences in total. A single root term is allowed.
Repeated terms count toward the eight-term limit.
Terms with the same kind, match rule, namespace and normalized comparison have
one effect and one matching record. Different match rules remain distinct.
Duplicates do not require separate submissions. Empty groups, unknown members,
unsupported scopes and expressions beyond these limits return 400 invalid_request.
Choose one filter: identifier_expression, identifier or legacy email.
POST /v1/accounts/search also takes email_domain, as a fourth choice. It finds the accounts that sent an email of the domain or its subdomains, as an email_domain list entry matches them.
Other filters combine with the chosen filter using AND.
The current permissions and submitted-only boundary apply to every term.
Source discoveries cannot complete a combination.
Read the matching submissions
Expression results includematched_identifiers instead of matched_identifier.
Each entry preserves the original submitted value and its assessment_id.
It also carries kind, match_basis, provenance: "submitted" and the username
namespace when needed.
term_index identifies a term’s first position in the request, starting at zero.
Count leaves in depth-first order, visiting children in their listed order.
The example above numbers the phone 0 and the emails 1 and 2.
The response lists each satisfied unique term in that order.
It includes satisfied terms from OR branches even when another branch already
satisfied the expression.
With assessment scope, every matching record refers to one submission.
Account results choose the newest submission that satisfies the whole expression.
With account scope, each term uses its newest retained matching submission.
These assessment IDs can differ. The account’s latest and current values
keep their separate meanings.
Deleting or expiring one term’s only matching submission can exclude an account
from an AND search. Another retained matching submission can preserve it.
Account deletion excludes all its matches.
Continue a combined search
Expression searches keep the existing ordering, count limits and live-page rules below. Accounts appear once, even when several submissions or branches match. The count describes distinct accounts on account search and assessments on assessment search. A cursor binds the scope, ordered expression, original term values and other filters. Keep operand order, grouping, values and match rules unchanged. Omittingmatch means exact and does not change that binding.
Equivalent spelling or Boolean rearrangement still changes the filter.
Changing only limit is allowed.
TypeScript
IdentifierExpression and IdentifierTermMatch models.
Python supports the same filter on synchronous and asynchronous search methods.
The MCP tools accept the wire shape under request.identifier_expression.
Choose the match rule
exact is the default. It compares the original submitted text, including case
and formatting. NoResponse preserves the original value for every mode.
For example, canonical email matches
Jane.Doe+case@EXAMPLE.COM with
Jane.Doe+case@example.com, while retaining the capitalised local part and tag.
It does not match jane.doe@example.com. Choose mailbox when you intentionally
want the existing alias rules. Those rules are a matching policy, not proof of
who controls either address.
Email and username search values follow the syntax accepted for submitted
identifiers, including Unicode local parts and usernames. Email is bounded to
254 characters overall and 64 in its local part. Usernames are 1 to 64
characters. Controls, invisible format characters and whitespace are refused.
Phone queries allow up to 64 characters so a canonical query can include
formatting. An accepted submission itself is strict E.164. The canonical phone
rule ignores spaces, hyphens, periods and parentheses, and the optional domestic
(0) after an explicit country code. Country codes 39, 378 and 379 keep
that zero. This is syntax matching, not verification that a number is assigned
or controlled by the account.
Explicit mailbox mode keeps the legacy list validator’s byte bounds after
NFC and lowercasing. The local part can contain up to 64 UTF-8 bytes. The full
address can contain up to 254 bytes. Applying alias rules must leave a nonempty
local part.
An admitted Unicode address can therefore be valid for exact and canonical
but return 400 invalid_request for mailbox. Use the first two modes for its
submitted-value searches. Selecting mailbox mode does not relax these bounds.
A username query is explicit about its scope:
Customer username filter
Read why an item matched
A single-identifier search addsmatched_identifier to each item. It contains the original
value, kind, match_basis, provenance: "submitted", matching
assessment_id, and the username namespace when applicable. For an account,
it identifies the most recent retained assessment that matched. The account’s
latest assessment may contain a different identifier.
The account row’s current contains its current account decision and score.
When current is absent, show No current decision. The latest assessment
score does not fill that gap. The match describes deterministic equality and
does not assign an ownership confidence score.
Limits, permissions and page changes
Search is limited to the authenticated workspace and environment. It requires history or investigation access. Filtering by an identifier also requires permission to read identifiers. API keys need legacyhistory, or investigate
with evidence/detailed. Console members need the corresponding current
capabilities. Detailed assessment responses have their own
evidence permission. Revoked access is checked again on every request.
Use one of identifier_expression, identifier or the legacy email field. All other supported
filters combine with AND. Account search also supports exact account_id and needs_action, combined
with the identifier using AND. needs_action additionally requires review
access for console members or management for API keys and returns
waiting_for reasons. Work through those accounts with Reviews. Assessment search keeps its existing time, decision,
revision, evidence and score filters and their combination rules below.
Limits are 1 to 50 items, default 25 accounts or 20 assessments. Every page
includes total and total_capped. The count is exact below 10,000. At or above
10,000 the count is capped and total_capped is true. Unsupported identifier
kinds, namespaces, modes and invalid values return 400 invalid_request.
Missing authentication returns 401. Insufficient current access returns 403.
Pass next_cursor with unchanged filters. A cursor is opaque, expires, and is
bound to its operation, workspace, environment and filters. A modified,
expired or cross-scope cursor returns 400 invalid_cursor. Single and combined
identifier account searches use stable opaque account order, so repeated assessments do not move an existing account
across the cursor. Assessment results use creation time with an ID tie break.
The original directory and legacy email searches keep their existing order.
Items and totals share one database snapshot within a page. Pages are live and
do not share a snapshot. A new match before the cursor needs a fresh search.
Deletion, erasure, expiry or changed filter membership can exclude later results.
Current account risk is read separately and can advance during a page. Start a
new search to refresh the entire result set.
Historical submitted values stay searchable only while their originating
assessment is retained. Deleting or erasing that assessment excludes its match.
Deleting an account excludes all of its matches. Effective retention applies at
read time as well as cleanup. Values from before original inputs were retained
cannot be reconstructed.
For older retained assessments with available original inputs, the existing
maintenance worker and search requests catch up the index in bounded batches.
While catch-up is incomplete, the API returns
503 service_unavailable with Retry-After instead of a partial page or total.
Retry the same query after that interval. A successful page covers all eligible
retained originals in that page’s database snapshot. Historical assessments
whose originals were not retained are outside this guarantee.
Call the public SDK
- TypeScript
- Python
Search by submitted email
search_async on both resources. Account search keeps its
existing operation envelope: read the page from .result, with headers on the response. Assessment search keeps its existing direct-page
return. The generated Go, Java,
C#, PHP and Ruby clients use the same IdentifierSearch and IdentifierMatch
models. The MCP server exposes search_accounts and search_assessments, with
the filter under request.identifier. The API checks the caller’s permissions
for every tool call.
List linked accounts
GET /v1/accounts/{account_id}/links lists the other accounts in the
same workspace and environment that submitted an email, phone or username that
this account also submitted. It runs the account’s own retained submitted
identifiers through the same match rules as search, so you do not search each
identifier by hand. The account itself is not listed.
Linked account page
shared_identifiers entry for each shared kind, in
the order email, phone, username. The entry uses the strictest rule that links
the two accounts: exact, then canonical, then mailbox. account and
linked_account each name that account’s most recent retained assessment that
submitted the identifier, with the original value and submitted_at. Under
canonical or mailbox the two values can differ. A shared identifier is
evidence that both accounts submitted it, not proof that one person owns both.
Only submitted identifiers link accounts. Identifiers that a source found do not.
An expired or deleted assessment links nothing, on either side. An
account with no retained assessments returns an empty page.
Pass limit (1 to 50, default 25) and cursor as query parameters. Accounts
come in a stable order. total counts distinct linked accounts, exactly below
10,000. The cursor is bound to the account, workspace and environment. A changed
or expired cursor returns 400 invalid_cursor. The permissions are those of
identifier search: an API key needs history, or investigate with evidence,
and a console member needs history and evidence access in that environment.
While older assessments are still being indexed the API returns
503 service_unavailable with Retry-After.
- TypeScript
- Python
List linked accounts
list_linked_async. The MCP server exposes
list_linked_accounts, with account_id, limit and cursor under request.
In the console, the account page and the assessment page list linked accounts
and link to each one.
Search and get exact decisions
POST /v1/assessments/search searches the calling key’s environment. Supply an inclusive from and exclusive to, no more than 366 days apart. Optional filters are account, flow, status, assessment recommendation and assessment decision. With "revision": "first", the filters apply to each assessment’s first answer and search returns that answer. Use it to find the assessments behind a count from Assessment counts.
You can also filter by band, reason, impact, incomplete lane, country, asn, score_min, score_max and list_match. Filters combine with AND. impact requires a reason, and score bounds are inclusive. Pending answers without a score do not match score filters.
list_match accepts allow, block or none and requires revision: first. none means NoResponse captured that no list entry matched. Older answers without captured list state do not match any of these values.
The legacy email filter keeps its account-wide mailbox behavior. Search finds each account that sent that mailbox in one of its retained assessments. It returns every assessment of those accounts, also the assessments that sent another address. The mailbox matches as a list entry does. Case does not matter and NoResponse ignores a +tag. At gmail.com and googlemail.com, it also ignores the dots of the local part. So Jane.Doe+promo@googlemail.com finds janedoe@gmail.com. An address without a mailbox answers 400. A search by email needs the permission that reads identifiers. For an API key, that is history, or detailed with investigate or exports. A workspace member needs the evidence permission.
Pass changed: true to find the assessments whose latest decision differs from their first answer’s, such as an allow that a late result turned into block. changed: false finds the others. An assessment whose first answer had no decision yet is not changed. changed cannot be combined with revision: "first".
Pages have at most 50 items. Pass next_cursor back with the same filters. Each page has total, the number of assessments that match the filters across every page, and total_capped. total is exact up to 10,000. At 10,000 or more matches it is 10,000 and total_capped is true.
Search assessments
total. The existing account-only history query keeps its behavior.
Each item also has first_decision, the decision of the assessment’s first answer, so you can show what changed without another read. It is absent when the first answer had no decision yet.
Use decision: "block" to find blocked assessments. The decision is block when the stored assessment has risk.block: true. Otherwise, it is the recommendation. Later account feedback or shared-key changes do not change this filter.
recommendation and decision combine with AND. Neither filter means the account’s current decision.
Get an exact assessment with GET /v1/assessments/{assessment_id}/versions/{version}. It includes the original policy, flow, depth and decision. It excludes the current account projection.
Get an exact account decision with GET /v1/accounts/{account_id}/decisions/{risk_version}. It identifies the original assessment version and the feedback report that applied. Its risk includes the shared-key state that applied then. A version from before capture began returns 404, not a reconstruction from current data.
Both operations need investigate or history. A detailed assessment view also needs evidence or detailed. Submitted identifiers require that sensitive evidence access too.
Diagnose an HTTP attempt
GET /v1/requests needs history. Supply from and to for a range of at most seven days. Optional filters are request_id, normalized route, and HTTP status. Pages have at most 100 records.
Each attempt has a separate id, even when retries share an X-Request-ID. Assessment attempts include the exact public decision prepared for that response. write_complete means the server wrote and flushed the response. It does not prove your platform processed it.
Diagnostics exclude request bodies, authorization values and source payloads. NoResponse keeps them for seven days or the shorter environment retention. Capture has a bounded time budget to protect response latency. Infrastructure failures can leave gaps.
Private metrics count failed diagnostic writes. Check this count and query coverage before using diagnostics for evaluation. An incomplete collection cannot establish complete operational coverage.
Routes use templates without path values. Credential operations use issued_keys in diagnostic templates to preserve the label through security filtering. Their HTTP paths remain /credentials.
Bind a provisional account
A hostedpre_registration check can return a provisional id: pre: followed by the assessment id. After your auth provider creates the user, bind the assessment to the stable opaque account id:
Bind the account
assess. It creates a new assessment version and keeps earlier versions. Pending work continues on that assessment and updates the stable account. Repeating the same binding returns the original receipt.
A different target returns binding_conflict. Bind before adding outcomes, enforcements or issued keys to the provisional account. This operation does not merge arbitrary accounts.
Keep the binding receipt and use the stable id for later actions. While the binding exists, deleting either name deletes the bound account. The binding expires with its assessment. Use the stable id for later data requests.
Associate keys issued by your platform
POST /v1/accounts/{account_id}/credentials accepts {"sha256":"<64 lowercase hex characters>"} and needs assess. Compute the hash from a high-entropy key issued by your platform. Send the hash, never the key.
The operation returns a new association id or the original record for a repeated hash. A hash linked to another account returns a conflict.
GET /v1/accounts/{account_id}/credentials lists associations and needs history. DELETE /v1/accounts/{account_id}/credentials/{credential_id} retires an association and needs delete. Retirement keeps its first actor and time. It is safe to repeat.
Retirement excludes the association from risk calculations. Revoke the actual key through your platform. Repeating registration after retirement keeps the association retired.
Usage batches for active associations use the existing shared-key rules. Historical decisions keep their original shared-key state. Associations remain while retained account activity needs them. Retired records prevent old assessment links from activating the association again.
Deleting the account deletes these associations immediately.
Export an account
GET /v1/accounts/{account_id}/export needs exports and investigate, or the legacy history scope. It returns one consistent snapshot of retained records. Detailed assessment fields also need evidence or detailed.
Each assessment revision in the export carries detail.identifiers. The export includes assessments and revisions, captured account decisions, outcomes, binding receipts, key associations, enforcements and request diagnostics.
The response names its exclusions. These include expired or deleted data, secrets, key hashes, restricted source records and other workspaces’ data. It also excludes legacy free-text notes and historical decisions from before capture began. complete describes the retained records within those boundaries.
A synchronous export supports at most 10,000 records and 8 MiB. Larger accounts return export_too_large, never a truncated success.
For a larger account, create an asynchronous export with POST /v1/workspaces/{workspace_id}/exports. Choose kind: account, the account id and the environment. Poll the returned job, then download its numbered chunks before expiry. Each download checks your current permissions.
An asynchronous job permits up to 64 MiB. Artifacts expire within 24 hours, or sooner when their source retention ends. Use paged history and search for larger collections. Account exports exclude investigator reviews. Export a review separately with review and export access.
For the job lifecycle, chunk downloads, cancellation, retries and limits, see Export jobs.