How to wire POST /v2/logins/assess and trusted devices into
your sign-in flow: assess → challenge → step-up → enroll, and revoke when
the password changes. Field-level details are in
login-risk.md; this page is the order of calls and the
decisions your code makes between them.
requestId (and, if you want the authenticity check, its
inline_token) — pass them to your backend with the form.linkedId after a successful sign-in. The account's device history is
made of the visits tagged with linkedId. Set it on the pages a signed-in
user sees, with exactly the value you send as user_id — byte for byte. Do
not set it from the typed login before the password is checked: a stranger's
failed attempt would become history of that account (unless it is reported
as failed and assessed, step 5).login_risk,
trusted_device_enrolled and trusted_device_revoked in your audit trail,
add them by name to a webhook on payload version 2 or 3 — * does not
include them.After the password check succeeds, before you create the session:
curl -X POST https://api.tracio.ai/v2/logins/assess \ -H "Authorization: Bearer $TRACIO_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{ "user_id": "alice", "request_id": "0b6f5c1e-8a1d-4c3e-9f00-1234567890ab", "login_status": "success", "assessment_id": "login-attempt-77120" }'Use your own id of the login attempt as assessment_id: then a retry of the
same attempt (after a timeout or a 503) returns the same answer instead of
creating a second assessment.
Decide what your login does when the call fails or times out — that choice is yours (fail open: let the user in; fail closed: ask for the second factor). We publish no end-to-end latency figure for assess — see Latency for what was measured; set the timeout from the latency budget of your own login.
| HTTP answer | What to do |
|---|---|
200 | go to step 2 |
400 invalid_request | a bug in your call (see errors): fix it; apply your fallback for this login |
400 session_invalid | the inline_token expired or belongs to another visit: send request_id without session |
409 assessment_conflict | the same assessment_id was used for a different call — your attempt ids collide; fix their generation |
429 rate_limited, 503 unavailable | retry with the same assessment_id after a pause, or apply your fallback |
401, 402 | the key is wrong, revoked, or has no Data API access |
| Answer | What to do |
|---|---|
recommendation: "allow" | create the session |
recommendation: "deny" | do not let the user in: the account has a current self-exclusion (reasons: ["self_excluded"]). A trusted device does not lift it. No step-up, no session |
recommendation: "challenge" | ask for the second factor (step 3) before creating the session |
recommendation: "review" | stronger than challenge: it comes from two rules — a high impossible-travel hop on a new device that is not trusted (reasons has new_device and impossible_travel), and a link to a self-excluded account (self_excluded_link). Hold the login for your manual or stricter flow, at least ask for the second factor |
state: "not_assessed", reason: "visit_not_found" | the visit has not reached us yet, or the request_id is wrong. Apply your fallback policy, or retry once a second later with a new assessment_id |
state: "not_assessed", reason: "bot_traffic" | the visit is a bot; apply your bot policy |
recommendation is null with not_assessed — do not read it as allow. The
one exception is a self-excluded account: not_assessed still carries
recommendation: "deny" (see login-risk.md).
Look at reasons to tell the user why: new_device ("we don't recognise this
device"), ato_alert ("unusual activity on your account"), impossible_travel
("this sign-in comes from a place you could not have reached since your last
one" — ask for the second factor and offer the owner to secure the account).
impossible_travel_anonymized does not raise the recommendation on its own: the
visit came through a VPN, a proxy or a datacenter, so do not challenge the user
for the move alone. An ato_alert and impossible_travel are raised on a
trusted device too; a trusted device lifts only new_device.
deny appears in one case only: the account has a current
self-exclusion, reasons is ["self_excluded"], also with
state: "not_assessed". For any other reason refusing a login is your rule, not ours.
Run your second factor — a one-time code, an email link, a passkey. Tracio is not involved in this step.
If the user fails it, do not create the session and do not enroll the device.
After a passed step-up, and only if the user agreed to be remembered on this device:
curl -X POST https://api.tracio.ai/v2/accounts/alice/trusted-devices \ -H "Authorization: Bearer $TRACIO_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{ "request_id": "0b6f5c1e-8a1d-4c3e-9f00-1234567890ab" }'Send the request_id of the login visit, not session: the token lives 2
minutes from the visit, and a step-up often takes longer. Without ttl_days
the workspace TTL applies (30 days by default; owners and admins change it on
the Devices & logins tab of an account card). 201 means the device is
trusted now, 200 that its trust was renewed. Keep the returned device_ref
if you want to show the user their remembered devices.
From now on, a login from this device is allow with trusted_device, until
the trust expires or you revoke it.
When the password check fails, still call assess with
login_status: "failed" (a new assessment_id per attempt). Once the attempt
is assessed (state: "assessed"), its visit is left out of the account's
history, so an attacker's device stays new on the next attempt; and a
challenge on a failed attempt is a signal worth logging. An attempt that came
back not_assessed / visit_not_found is not left out — retry it with a new
assessment_id once the visit has arrived. Only the 1,000 most recent assessed
failed attempts of an account within 90 days are left out (see
Failed logins).
Revoke every trusted device of the account when:
# list, then revoke each active recordcurl https://api.tracio.ai/v2/accounts/alice/trusted-devices \ -H "Authorization: Bearer $TRACIO_SECRET_KEY"
curl -X DELETE https://api.tracio.ai/v2/accounts/alice/trusted-devices/7978151c54e94838 \ -H "Authorization: Bearer $TRACIO_SECRET_KEY"Revoke the records with "active": true. 204 — revoked; 404 not_found —
there was no live record (already revoked, or it never existed), which is
fine to ignore. An analyst can also revoke from the dashboard — the Revoke
button on the account's Devices & logins tab revokes the device in every
environment of the workspace.
A key works in its own environment: revoke with the key of each environment the account signs in through, or use the dashboard.
If you subscribed (see "Before you start"), you receive login_risk for every
challenge and review, and trusted_device_enrolled / trusted_device_revoked
for every trust change, up to about 5 seconds after the call. Use them for audit
and alerting — for example, notify the user by email that a new device was
remembered. Do not wait for them in the login flow: the decision is the
synchronous answer. Deduplicate by X-Tracio-Event-Id.
requestId of the login page reaches your backend with the form.linkedId is set after sign-in, with the same value as user_id.assessment_id is your login-attempt id; retries reuse it, a re-assess
after visit_not_found uses a new one.challenge and review lead to a second factor; null is not allow.request_id.login_status: "failed".503 is decided and tested.What the recommendation does not tell you — a known device is not proof of the owner, roaming is not detected, an ATO verdict at login is usually absent — is in login-risk.md, "What we do not distinguish".