AI Agents
Telecom signals in an automated fraud decision
An agent executing a fraud decision on its own needs a lookup response it can branch on directly: typed fields with a small, known set of values, an explicit null or UNKNOWN state instead of a guess, and no free text a model has to interpret before it can act. Here is what that response contract looks like field by field, and how it plugs into a decision an agent makes without a person reviewing it first.
Why a dashboard-shaped answer doesn't work for an agent
A human risk analyst reading carrier: null alongside an activeSource of VALID understands the number's status could not be fully confirmed and adjusts trust accordingly. An agent making the same call inside an automated flow has no such instinct unless that fallback is written into its branching logic in advance. The response itself has to carry enough structure for the branching to be mechanical, not inferred from tone or context the way a person would read it.
The response contract, field by field
A single carrier lookup call returns the same shape every time, regardless of what the number turns out to be:
{
"phoneNumber": "+447700900000",
"active": true,
"carrier": "EE",
"country": "GB",
"numberType": "mobile",
"simSwap": "UNKNOWN",
"simSwapAt": null,
"_meta": { "activeSource": "LINE_STATUS" }
}
What an agent can safely branch on
- active is a boolean or null. Null means the provider could not tell, which is a different fact from a confirmed false and should route to a different branch, not the same one.
- carrier and country are a string or null. Null is not the same as an empty string, and code that treats them interchangeably will silently misclassify unresolved lookups as resolved ones.
- numberType is always one of a fixed set: mobile, landline, fixedVoip, nonFixedVoip, tollFree or voicemail, never free text an agent would need to parse.
- simSwap is SWAPPED, NO_SWAP or UNKNOWN: a three-state field, not a boolean, because "no data available" and "confirmed not swapped" are different facts that call for different actions.
- _meta.activeSource is LINE_STATUS or VALID, telling the caller how much weight the active field deserves: LINE_STATUS is a live network check, VALID is a weaker format-only check.
A worked example: an autonomous payout-approval step
Consider an agent that approves or holds a payout to a newly added bank detail without a person in the loop for the routine cases. Before releasing funds it runs a lookup on the payee's phone number. If numberType comes back as a VoIP type, it holds for review rather than treating the number the same as a mobile line, since VoIP numbers are cheap to provision in volume and are a known pattern in account takeover and mule activity. If active is false, it holds, since a number that cannot be reached is not one you want receiving a step-up confirmation. If SIM swap detection eventually reports SWAPPED for that number, it holds regardless of every other field, because a recent SIM swap is the single strongest signal available. This is the same logic a human analyst would apply manually when scoring risk by hand, described in more depth in building a risk score from telecom signals, just executed by the agent at the moment the payout is requested instead of afterwards in a review queue.
Treating UNKNOWN as an input, not a gap
SIM swap detection is launching for GB, DE, NL and FR, and while carrier registration in those markets completes, a lookup on a number in one of them returns simSwap: "UNKNOWN" today. That is the honest, current answer, not a placeholder to be worked around. An agent that maps UNKNOWN to NO_SWAP has invented a signal that was never returned, and one that maps it to SWAPPED will hold payouts it has no evidence against. The correct design treats UNKNOWN as "no signal here, fall back to whatever else you have", which is exactly what the field is built to communicate.