Track a Login
Call /v1/evaluate/login on every login attempt, including failed ones. Pinch correlates the attempt with device, IP, and historical patterns to detect account takeover, credential stuffing, and session hijack.
Failed-login data is as valuable as successful logins — it's how Pinch sees brute-force attempts. Send both.
Request
POST /v1/evaluate/login HTTP/1.1
Host: api.pinch.ai
Authorization: {api-key}
Content-Type: application/json
Body
| Field | Type | Required | Description |
|---|---|---|---|
providerCustomerId | string | yes | Your customerId for the user attempting to log in. For attempts on a non-existent account, set to the attempted username. |
loginStatus | string | yes | $success or $failure. |
email | string | yes | Email on the login attempt. |
username | string | yes | Username on the login attempt. Often the same as email. |
siteDomain | string | yes | Domain where the login happened. |
verificationPhoneNumber | string | no | Phone used for SMS or 2FA, if any. |
authMethod | object | yes | Details about how the user authenticated. See below. |
Auth method
{
"type": "password" | "sso" | "magic_link" | "passkey" | "oauth",
"mfaUsed": true,
"mfaMethod": "app_authenticator" | "sms" | "email" | "hardware_key",
"deviceFingerprints": [{ "deviceIdentifier": "{visitorId}" }]
}
| Field | Type | Required | Description |
|---|---|---|---|
type | string | yes | Primary auth method. |
mfaUsed | boolean | no | Whether MFA challenge was completed. |
mfaMethod | string | no | Which MFA method was used. |
deviceFingerprints | array | recommended | [{ "deviceIdentifier": "{visitorId}" }] from the JS SDK. |
Example request — successful login
- curl
- Node
- Python
curl https://api.pinch.ai/v1/evaluate/login \
-H "Authorization: $PINCH_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"providerCustomerId": "user_12345abcde",
"loginStatus": "$success",
"email": "johndoe@example.com",
"username": "johndoe@example.com",
"verificationPhoneNumber": "+15551234567",
"siteDomain": "demostore.com",
"authMethod": {
"type": "password",
"mfaUsed": true,
"mfaMethod": "app_authenticator",
"deviceFingerprints": [
{ "deviceIdentifier": "abc123visitorId" }
]
}
}'
const res = await fetch("https://api.pinch.ai/v1/evaluate/login", {
method: "POST",
headers: {
Authorization: process.env.PINCH_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
providerCustomerId: customerId,
loginStatus: "$success",
email,
username,
siteDomain: "demostore.com",
authMethod: {
type: "password",
mfaUsed: true,
mfaMethod: "app_authenticator",
deviceFingerprints: [{ deviceIdentifier: visitorId }],
},
}),
});
const decision = await res.json();
import os, requests
res = requests.post(
"https://api.pinch.ai/v1/evaluate/login",
headers={
"Authorization": os.environ["PINCH_API_KEY"],
"Content-Type": "application/json",
},
json={
"providerCustomerId": customer_id,
"loginStatus": "$success",
"email": email,
"username": username,
"siteDomain": "demostore.com",
"authMethod": {
"type": "password",
"mfaUsed": True,
"deviceFingerprints": [{"deviceIdentifier": visitor_id}],
},
},
)
decision = res.json()
Example request — failed login
Send these too. Pinch needs them to detect brute-force.
{
"providerCustomerId": "johndoe@example.com",
"loginStatus": "$failure",
"email": "johndoe@example.com",
"username": "johndoe@example.com",
"siteDomain": "demostore.com",
"authMethod": {
"type": "password",
"mfaUsed": false,
"deviceFingerprints": [{ "deviceIdentifier": "xyz789visitorId" }]
}
}
When the account doesn't exist, you may not have a customerId. Pass the attempted username as providerCustomerId in that case so Pinch can group attempts.
Response
{
"providerLoginID": "user_12345abcde",
"pinchLoginID": "plgn_9d8c7b6a",
"Decisions": [
{
"decisions": [
{
"decisionType": "APPROVE/DECLINE",
"decision": "APPROVE",
"subDecision": "Default Approve",
"decisionReason": "Known device, MFA verified",
"abuseScore": 6
}
]
}
]
}
Decision semantics
| Decision | Recommended action |
|---|---|
APPROVE | Allow the session normally. |
DECLINE | Block. For a successful login, terminate the session and require step-up auth (MFA challenge, email verification). For a failed login, increment your block counter and rate-limit. |
REVIEW | Force step-up auth — email magic link or MFA prompt — before granting the session. |
For high-stakes accounts (payouts, admin), consider treating both DECLINE and REVIEW as forcing MFA, regardless of the user's normal flow.
Common abuse patterns detected at login
- Credential stuffing — many
$failureattempts across many accounts from a small set of IPs/devices - Account takeover (ATO) — a
$successfrom a device that has never been associated with this account, especially right after a sequence of$failureattempts on the same account - MFA bypass attempts — patterns of
mfaUsed=falseon accounts that have MFA enabled - Session hijack — a device that suddenly logs into accounts it has never touched, in quick succession
Errors
| HTTP | Code | Meaning |
|---|---|---|
| 400 | invalid_request | Required field missing |
| 422 | unprocessable_entity | loginStatus not one of $success / $failure |
| 429 | rate_limited | Slow down |
See also
- Signup API — track account creation
- Account update API — track sensitive changes after login