
What the AI crawlers actually fetched from this site in one week
SAGARIS5 min
BlogEngineering
The label lives in someone else's database, written by a company you have no contract with. There is no setting in your dialer that removes it.

The label is a row in a database you do not own, written by a company you have no contract with, on evidence you cannot inspect. That is why it feels so much like a bug and behaves so little like one. Bugs have owners you can escalate to. This has a claimant, a decision and an appeals process.
Three companies do most of the labelling on US mobile networks. Our dispute-routing module names them and maps each to the carrier whose labelling it powers: Hiya behind AT&T, TNS behind Verizon, First Orion behind T-Mobile. The Free Caller Registry is the shared front door that fans out to all three, and its own site, fetched 14 August 2026, names the same three as the recipients of a registration.
So when a rep says the dialer is showing spam likely, the accurate translation is: one of three specific companies has scored one of your identifiers, a carrier has acted on that score, and the number of settings in your dialer that can change it is zero.
Briefly, because the signal side has its own article. The verdict is computed from what is observable at the network boundary: how often calls from a number are answered, how quickly those answers end, how many calls it places in a day, and whether the calls are signed. No content, no context, no consent record.
The consequence is that earning a label requires no wrongdoing. A compliant, consented, well-run campaign that concentrates too much volume on too few numbers produces the same observable shape as an abusive one, because the observer cannot see the difference. That is not unfairness so much as the limit of what the instrument can measure.
Here is the line in our own codebase that reframed this for me, in the header of the dispute-routing module: rotation stops the number being used and does nothing to clear the carrier-side spam label. Those are two problems that get discussed as one. Pulling a number out of the pool protects today's calls. It removes nothing from anyone's database, and a rested number does not heal by sitting still.
Clearing means filing a dispute with the provider that set the label: a per-provider process, with per-provider evidence, on a per-provider timeline, gated on a human in a way worth explaining properly, because it looks like a missing integration and it is not.
Start with the registry. FreeCallerRegistry accepts numbers one of two ways, per its own site fetched 14 August 2026: you type up to 20 into a form, or you upload an xlsx or txt file no larger than 100 KB. There is no documented API. More decisively, the registry states that it is for businesses that make calls on their own behalf, and prohibits registering numbers on behalf of another company.
Read that second condition again, because it settles an architectural question. A vendor cannot legitimately register your numbers for you. Even if the registry shipped an API tomorrow, a dialing platform submitting on a customer's behalf would be doing the thing the registry says not to do. The human upload is not a gap in someone's roadmap. It is the shape the process was designed to have.
So our side does the only honest thing available. An export route produces the file, a human uploads it, and a submit route records that they did, advancing the affected DIDs from unregistered or rejected to submitted. Its doc comment is explicit that it never contacts a provider and never invents a registered result, and it will neither downgrade a DID already marked registered nor re-submit one awaiting a decision.
The lifecycle is four states, tracked per DID per authority across FreeCallerRegistry, First Orion, Hiya and TNS, with both the provider list and the status list pinned to the database CHECK constraints by a migration contract test so the code and the schema cannot drift apart. The states are the interesting part:
- unregistered: never submitted. Also the default for a DID with no row, so the question "which numbers still need registering" is answered over every owned number rather than only the ones somebody remembered to create a record for. - submitted: uploaded, awaiting somebody else's decision. There is no polling API here. This state ends when a human checks. - registered: the provider recognises it. - rejected: the provider declined. A real, reachable state, and the one nobody plans for.
A tracker that can only represent success quietly lies during exactly the period you need it. Modelling rejected and submitted as first-class states is what makes it possible to say honestly that nothing is happening yet.
There is a remediation queue table with a row per flagged number, a dispute portal per provider, and four statuses: open, filed, cleared, abandoned. Its read route is workspace-scoped, GET-only, and defaults to the open queue, with an unknown status falling back to open rather than widening the result. The table's RLS makes members read-only and leaves the service role as the sole writer.
Two things in that design are worth stealing. The first is abandoned as a terminal state alongside cleared. Some disputes do not get won, and a queue whose only exit is success accumulates permanently open rows everyone learns to ignore. The second is that the stored portal URLs are documented as known public landing pages rather than verified deep links, with the human filer confirming the current URL at filing time. A hardcoded URL into somebody else's support portal has a short shelf life, and saying so beats discovering it during an incident.
Now the honest part, and it is the reason this section exists. The functions that open remediation tasks have no non-test caller at the commit this was written against. A grep for them across the source tree returns only the file that defines them, while the same grep shape run against the health-scoring function returns four non-test files, so the search is working. The queue's reader is wired. The bridge from a detected flagged number to a task a human can file is not.
So the queue is today a table and a view over it rather than a working pipeline. Rotation runs, the auto-pause runs, and the dispute task that should follow is not created by anything on a production path. That is the same failure shape this codebase keeps finding in itself: a capability that exists completely, correctly, and with nothing calling it.
Disputes are also deliberately not auto-filed even once that bridge exists. The module states the reason plainly: the registry and carrier portals are ToS-gated, and filing needs human judgement about business identity, attestation and call-purpose evidence. An automated dispute filer is a compliance liability wearing an automation costume.
The part that is live is the detection side. A scheduled job pulls Telnyx number-reputation verdicts onto each DID and stores each one on the phone-number row the dial path already reads, so the dial path never talks to the provider. The Terraform entry sets it to weekdays at 13:30 UTC. I have not verified that job exists or has fired in the live project; reading Terraform is reading an intention.
The decision that verdict feeds is aggressively conservative in one direction. Only a fresh high-risk verdict pauses a number. A medium does not, on the stated reasoning that medium is where a healthy high-volume number normally sits and pausing on it would rest the whole pool. A snapshot older than seven days is treated as absent. An undated snapshot, an unparseable one, a wildly future-dated one, a read error: all of them resolve to leaving the number in the pool. A number nothing has ever scored stays in, with the reason recorded as absence not being evidence of harm.
A second lane can pause on our own telemetry rather than the carrier's, and it is off by default behind an explicit environment flag. The reason is precise and generalises: the short-call count that lane reads does not exclude machine-answered calls, so a voicemail-heavy day is arithmetically identical to carrier suppression. The verdict that would separate the two exists elsewhere in the system and is not read by that aggregation. Rather than quietly repairing that on the way past, and changing the meaning of every number-health score in the process, the lane is flagged off and the reason is written down.
And there is a floor under all of it. If pausing would empty a workspace's pool, nothing is paused and the warning gets loud instead, because funnelling every call through a single fallback caller ID burns exactly the reputation the control exists to protect.
Treat the label the way you would treat an adverse decision from any institution you do not control. Somebody weighed evidence you cannot see and reached a conclusion. There is an appeals route, it is slow, it wants documentation, and it is staffed by people who receive a great many bad-faith appeals.
Which means the operational answer is almost never remediation. It is arithmetic: enough numbers that no single one carries a suspicious pattern, a ceiling per number that holds under concurrency, registration done before the first dial rather than after the first complaint, and enough telemetry to see a number sliding before somebody else decides it has slid. Remediation is the expensive path you take when the cheap ones were skipped, and it runs on a clock nobody at your company gets to set.
Thirty minutes, your own data, no setup.
SAGARIS opens fully in October 2026. Join the waitlist and we will be in touch before launch.