BLA
BLA (Blind LLM Agents) is the open-source Blue Defense MCP and CLI built to protect software artifacts against automated AI reverse-engineering workflows.
Robocall mitigation software that runs beside your switch. Verifies STIR/SHAKEN, blocks by signer, checks FCC standing, and answers tracebacks. Free and self-hosted.
SecurityGo · JavaScriptGitHub @CallerAPIOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
Falcon is an open-source SIP risk engine that runs next to your switch. Verifies STIR/SHAKEN and names the signer, scores every INVITE, and blocks robocalls and fraud by number, IP, or signer SPC. One binary, local data, no call-path latency. By CallerAPI
Taken from the repository README
Loading the full README.
Read the full README ↗Falcon is a SIP risk engine that runs next to your switch. It reads each INVITE, verifies STIR/SHAKEN, names the provider behind the source IP, applies your allow and deny rules, scores the request, and returns an action. By default the switch keeps signaling and media, and Falcon never sits in the call.
In bridge mode Falcon is also a carrier on your switch. It fails clean calls back before any answer, so the switch routes them to the next vendor. It carries the suspicious calls, records both sides, and catches false answer supervision (FAS).
One binary. One SQLite file. Falcon works without an external service.
Optional redacted telemetry is on by default. FALCON_SHARE=false turns it
off. A laptop and a fleet of a thousand nodes run the same build with the
same configuration.
Installs that share redacted telemetry get the network back for free: a reputation score for every signer and dialer tool that any install saw, and the FCC standing of every provider. See Share and get.
X-Falcon-Signer header.allow, flag, challenge, or reject, with
the reasons./metrics for Prometheus.X-Falcon-BCID-Name and
Remote-Party-ID when the switch can set those headers.Every Falcon that shares is a sensor on a different network. Together they see a bad signer or a new dialer tool on calls that never reached you. Your install gets that back for free.
| You get | Local only | Sharing (the default) | With a CallerAPI key |
|---|---|---|---|
| Screening, STIR/SHAKEN, lists, dashboard, bridge, FAS guard, traceback | Yes | Yes | Yes |
| Network reputation: a score for every signer and dialer tool, from the installs that share | No | Yes | Yes |
| FCC carrier check: the FCC standing of your upstream carriers | No | Yes | Yes |
| Business Caller ID verification | No | No | Yes, free |
| Spam database feed, live reputation, voice AI | No | No | Paid add-ons |
What it costs you:
SECURITY.md, "What leaves the host".What it is worth:
FALCON_FCC_MODE=enforce adds deny rules.How the network stays honest:
FALCON_SHARE=false turns sharing off. Telemetry stops, and the engine
still screens on its own.
Falcon shares redacted screening events with CallerAPI. This is on by
default. Every install that shares makes detection better for every other
install, and it is how a free engine stays maintained. Telemetry never
carries the called number. FALCON_SHARE=false turns it off.
A call with a signal travels as an event, every 30 seconds, in batches of up to 100. A signal is a decision other than allow, a call to a trap number, a failed verification, or a voice category. An event carries:
| Field | Sent |
|---|---|
| Decision, score, reasons | Yes |
| Calling number (the party being scored) | Yes |
| Source IP, User-Agent, Call-ID, switch label | Yes |
| Signer SPC and name, verification result | Yes |
| SIP request line and headers | Yes, after redaction below |
| Install id, Falcon version | Yes |
A clean inbound call travels as a count. Once an hour Falcon sends the
clean calls of the hour before, per verified signer, per sending tool, per
calling number with 3 or more calls, and for the whole install: calls,
verified calls, answered calls, and talk time. A calling number also gets
the count of different numbers it called in 24 hours. The called numbers
stay here. One clean call in 20 also travels as a full event, for header
analytics (FALCON_SHARE_CLEAN_EVERY, 0 sends none). A clean outbound
call is never sent, and neither is a caller that shows the called number.
What is never sent:
| Field | What happens |
|---|---|
| Called number (your subscriber) | Replaced with REDACTED in the to field, the Request-URI, To, P-Called-Party-ID, and every other header, the Call-ID, and reason text |
| Called display name | Removed with the header |
Forwarding numbers (Diversion, History-Info, Referred-By) | Replaced with REDACTED |
PASSporT (Identity header) | Removed. It carries the called number in its dest claim. The verification result is sent instead |
| SDP body | Dropped. It carries media addresses and SRTP keys |
| Recordings and transcripts | Never in telemetry. Only the voice category and score travel |
| Calling number when it equals the called number | Replaced with REDACTED and marked from_equals_to |
A keyed hash of the called number (to_hmac) travels instead of the number.
The key is a random secret created on this install and never sent, so
CallerAPI can count how many of your subscribers one campaign reached
without learning who they are, and cannot join it across installs.
Falcon logs the telemetry state on every boot. Settings shows the same state and the fields that never leave the host. Every shared event is also in your local database, so you can audit what left.
To opt out, set this and restart:
FALCON_SHARE=false
The environment is the only switch. The dashboard shows the state and cannot change it, so a change of the setting is always a change of the deployment. Demo mode never shares, because its calls are synthetic.
An event on a bridged call waits until the call ends, so the FAS verdict travels with it. A recorded call waits three more minutes for the voice verdict.
Each event carries its local event id. CallerAPI stores an event once per
install and id, so a batch sent again after a timeout is not counted twice.
When CallerAPI answers with Retry-After, Falcon waits that long. After
other failures the wait doubles from 30 seconds up to 15 minutes. Events
wait in the local database until they are sent.
If you screen outbound calls on the same switch, the calling number is your
subscriber. Run that switch with FALCON_SHARE=false.
Set CALLERAPI_API_KEY to have your account credited for the contribution.
Without a key the install is anonymous.
Every install signs its telemetry with an Ed25519 key created on first boot and kept in the local database. CallerAPI binds the install id to that key on first sight and refuses a later post with another key. The private key never leaves the host.
Installs that share get back what the whole network has seen. Once an hour Falcon fetches a feed with a score per signer (STIR/SHAKEN Service Provider Code) and per sending tool (SIP fingerprint, see below), built from every sharing install over the last 7 days. CallerAPI serves the feed to installs that have shared for at least a day, or that carry a key.
A score is a rate of failed verifications, rejects, and spam database hits,
capped by evidence: one install behind it caps at 50, two at 70, five with
a few hundred calls can reach 100. Threats, Signers shows it in the
Network score column.
By default the feed is corroboration. A signer or tool with a score of 70
or more across at least 3 installs adds weight to the local score and can
lift a call to flag or challenge, never to reject on its own. Set
FALCON_REPUTATION_ENFORCE=true and a signer with a score of 90 or more
across at least 5 installs is a hard reject with X-Falcon-Block: network_signer. FALCON_REPUTATION=false turns the feed off.
The same data is sold as the CallerAPI signer reputation API
(GET /api/signer-reputation/{spc}) to carriers and analytics vendors that
do not run Falcon. Sharing installs get it free, inside the engine.
Falcon hashes the habits of the software that sent each INVITE: header
order, the Allow, Supported, and User-Agent values, the shape of the
Call-ID and tags, the transport, and the SDP codec order and attributes.
Never a number, an address, a port, a key, or a time. The result is a 16
character fingerprint on every event, searchable in Calls and grouped
in /v1/parties?by=fingerprint.
A fingerprint names a tool, not a party, and a common tool is shared by good and bad traffic alike. That is why the feed scores it by what the network saw from it and why it is corroboration, never a verdict on its own. Where it earns its keep: a scam operation rotates numbers weekly and signers rarely, but it almost never rotates its dialer. New numbers from a tool the network already scored badly arrive pre-flagged.
Outbound screening checks the calls your own customers send. Falcon compares each caller id with the numbers registered for that customer, labels every call with the customer, and alerts on reject rates.
Register each customer and the numbers it may present:
curl -X PUT -H 'X-Falcon-Token: ...' -H 'Content-Type: application/json' \
-d '{"id":"acme","name":"Acme Dialer","dids":["+13125550100","+1312555*"]}' \
http://127.0.0.1:8090/v1/customers
Mark outbound calls with direction: "outbound" and customer: "acme"
(or the X-Direction and X-Customer headers on a raw SIP post; the
adapters read falcon_direction and falcon_customer channel variables).
Then:
caller_id_not_owned, SIP 603, X-Falcon-Block: caller_id) and the
webhook fires at once with customer_id, calling_number, the
customer's numbers, and suggested_action.alert_customer_reject_pct (default 30% over 50 calls in 15 minutes).Report how calls end and Falcon scores behaviour too:
curl -X POST -H 'X-Falcon-Token: ...' -H 'Content-Type: application/json' \
-d '{"call_id":"[email protected]","answered":true,"duration_s":7,"hangup_cause":"NORMAL_CLEARING"}' \
http://127.0.0.1:8090/v1/outcome
The Asterisk and FreeSWITCH adapters do this from the hangup hook. The
Kamailio and OpenSIPS files show an optional hook in their comments. With
outcomes, every screen sees the calling number's last hour: caller_fanout (30 different numbers,
FALCON_FANOUT_PER_HOUR), sequential_dialing (5 consecutive numbers,
FALCON_SEQUENTIAL_N), caller_low_asr (20 completed calls, at most 20%
answered), caller_short_calls (10 answered, at most 12 seconds on
average), and caller_hits_honeypots (calls to trap numbers, below).
They run on your own switch.
The webhook payload a platform can act on without parsing prose:
{"text": "[falcon critical] Customer acme presented a caller id it does not own\n...",
"falcon": {"key": "spoof:acme", "severity": "critical", "title": "...", "detail": "...",
"install_id": "...", "version": "...", "at": "...",
"data": {"kind": "outbound_spoof", "customer_id": "acme", "calling_number": "+18005551234",
"customer_dids": ["+13125550100", "+1312555*"], "source_ip": "10.0.0.5",
"call_id": "...", "event_id": 4471, "suggested_action": "suspend_outbound_and_review"}}}
kind is one of outbound_spoof, customer_reject_rate,
repeat_recording, voice_scam. The platform unassigns the DID or
suspends outbound for the account and opens a case; Falcon has already
kept the evidence.
A number nobody has complained about yet cannot be in a list. It is found by what it does:
honeypot
({"kind":"honeypot","subject":"number","value":"+1415555999*"}).
Nobody legitimate dials an unassigned number. A call to one scores 70;
a caller that hits two in an hour scores 60 more.A model that hears every call is a cost nobody carries. Falcon lets
signaling choose. A call is sampled when it is to a trap number, a
sequential dialer, a fan-out caller, a low-answer or short-call caller,
scored by the network, or an outbound call that is not clean. The switch
gets X-Falcon-Sample: 1 and X-Falcon-Sample-Seconds, records the first
seconds with the legs on separate channels, and posts the WAV to
POST /v1/audio?call_id=... at hangup (the adapters do this). Budget:
FALCON_VOICE_SAMPLES_PER_HOUR (60) and
FALCON_VOICE_SAMPLES_PER_CUSTOMER_HOUR (10).
Two checks run on the host for free, before any model:
repeat_recording. No transcript needed.Then, if a provider is configured, the clip is transcribed and classified into the CallerAPI complaint categories. A stereo clip is transcribed one channel at a time. Falcon puts both channels on one timeline, so the transcript shows who said what: the caller on the left channel, the callee on the right.
# Any OpenAI-compatible pair: OpenAI, Groq, xAI for chat, a local Whisper server for speech
FALCON_VOICE_PROVIDER=openai
FALCON_VOICE_STT_BASE_URL=https://api.openai.com/v1
FALCON_VOICE_STT_API_KEY=sk-...
FALCON_VOICE_STT_MODEL=whisper-1
FALCON_VOICE_CHAT_BASE_URL=https://api.x.ai/v1
FALCON_VOICE_CHAT_API_KEY=xai-...
FALCON_VOICE_CHAT_MODEL=grok-3-mini
# Or the CallerAPI scan API on your account key, billed by the minute
FALCON_VOICE_PROVIDER=callerapi
CALLERAPI_API_KEY=...
Transcripts, categories, and summaries live in the local database and in
the call view. They are never shared. Telemetry carries the category and
score only. A scam verdict at 0.7 or above fires voice_scam on the
webhook with the category, summary, customer, and calling number. With
the CallerAPI provider and FALCON_VOICE_REPORT=true (default), a scam
verdict also files the calling number in the spam database.
The provider receives the audio for transcription. Falcon sends a long recording in pieces and joins the text, so the whole call is transcribed.
cp .env.example .env
go test ./...
go run ./cmd/falcon
Docker, built from source:
sed "s/^FALCON_TOKEN=.*/FALCON_TOKEN=$(openssl rand -hex 32)/" .env.example > .env
FALCON_VERSION=0.9.26 docker compose up --build
FALCON_VERSION is the release you build, such as 0.9.26. The dashboard
and the boot log show it. Compose publishes port 8090 on 127.0.0.1 only.
Docker, the published image:
FALCON_TOKEN=$(openssl rand -hex 32); echo "FALCON_TOKEN=$FALCON_TOKEN"
docker run -d --name falcon -p 127.0.0.1:8090:8090 -v falcon-data:/data \
-e FALCON_LISTEN=0.0.0.0:8090 -e FALCON_TOKEN="$FALCON_TOKEN" \
ghcr.io/callerapi/falcon:latest
The image keeps its database in /data. Pin a version tag in production.
Keep the printed token. The switch and the dashboard need it.
Open http://127.0.0.1:8090. Set FALCON_LISTEN=0.0.0.0:8090 if the process
runs in a container.
On a listen that is not loopback, Falcon refuses to start unless
FALCON_TOKEN has at least 32 characters and is not an example value.
Publish port 8090 to a private address only, never to the internet. Send
that token in
X-Falcon-Token, in Authorization: Bearer, or as ?token= for the
dashboard and event streams.
curl -sS -X POST http://127.0.0.1:8090/v1/screen \
-H 'Content-Type: application/json' \
-H "X-Falcon-Token: $FALCON_TOKEN" \
-d '{"switch":"curl","source_ip":"198.51.100.20","raw_sip":"INVITE sip:+15551212@ex SIP/2.0\r\nVia: SIP/2.0/UDP 198.51.100.20;branch=z9hG4bK1\r\nFrom: <sip:+14155550100@ex>;tag=1\r\nTo: <sip:+15551212@ex>\r\nCall-ID: demo\r\nUser-Agent: friendly-scanner\r\nMax-Forwards: 70\r\n\r\n"}'
Switch adapters for Asterisk (and the FreePBX family), FreeSWITCH (and
FusionPBX), Kamailio, and OpenSIPS live in adapters/. Each one is tested
in CI against a live Falcon on a real switch container.
On a class 4 or wholesale ingress run FALCON_PROFILE=carrier. The default
scoring is tuned for a PBX trunk and rejects a normal call center CLI
within its first hour there. The carrier profile keeps hard blocks and
turns everything else into a flag. See adapters/README.md.
Falcon fails open when it cannot parse the request. The switch continues the call.
A switch or SBC with no script hook routes the INVITE to Falcon first, as
it would to a redirect server. Falcon answers 302 to continue or 603
to drop and is out of the dialog. Signaling and media stay on the switch.
FALCON_SIP_LISTEN=0.0.0.0:5060
FALCON_SIP_PEERS=203.0.113.10,198.51.100.0/24
FALCON_SIP_PEERS is the gate: only those IPs and CIDRs get an answer.
OPTIONS gets 200 OK for health probes. UDP and TCP on one port. This is
the path for Sansay, Sippy, PortaSwitch, Metaswitch, Ribbon, Oracle,
AudioCodes, Cisco CUBE, and any proprietary class 4 platform. Details and
per-vendor notes are in adapters/README.md.
The bridge puts Falcon in the call itself. Falcon screens each call, carries it, records both sides, and checks the answer. It needs no script on the switch and no Asterisk.
On the Falcon host:
FALCON_SIP_MODE=bridge
FALCON_SIP_LISTEN=0.0.0.0:5060
FALCON_SIP_PEERS=203.0.113.10 # the switch addresses, comma separated
FALCON_MEDIA_IP=203.0.113.20 # Falcon's public address
FALCON_BRIDGE_CARRY=all # carry every call, like a PBX
Open UDP and TCP 5060 and UDP 20000 to 29999 (the RTP ports) from the
switch to Falcon. Falcon drops all SIP from addresses that are not in
FALCON_SIP_PEERS.
Outbound calls are calls your customers send to the switch. They reach Falcon through the switch's carrier routing.
FALCON_MEDIA_IP, port 5060.
Give it a rate card for the destinations you want screened, for example
prefix 1 for all of North America.482 Loop Detected.Inbound calls are calls from a carrier to a DID. Most switches send a DID call straight to its destination and skip the carrier routing, so the outbound steps do not put DID calls through Falcon. Point the DID at Falcon, and tell Falcon where the DID goes:
7001. Vendor name: the customer.
Send to: the address that receives the DID now, as host:port. Strip
prefix: Yes. Carry: Every call.7001<DID>@<FALCON_MEDIA_IP>:5060.Some destinations can only be reached through the switch, for example a phone that registers to it. Then point the DID at Falcon with the number of a second DID, the one that rings the phone, and add no route. Falcon sends the call back to the switch, and the switch delivers that number to the phone. The switch must deliver a call to one of its own DIDs internally, not through a carrier.
Do not leave Send to empty for a DID route. An empty Send to sends the
call back to the switch, which sends it to the DID, which points at
Falcon. Falcon refuses that loop with 482 and cancels its own call at
once, so the switch does not try its other carriers.
A call can pass Falcon twice: out from one of your customers, and back in to one of your DIDs. Falcon carries both legs, because the DID route sends the second one to another address.
When a test call fails:
| What you see | Cause and fix |
|---|---|
| The call is not in Falcon | The switch address is not in FALCON_SIP_PEERS, or the firewall blocks 5060. The Falcon log counts dropped requests and names the last address |
The switch gets 503 with X-Falcon-Bridge: outbound-failed | Falcon could not send the call back. The Falcon log names the reason |
The Falcon log says the switch refused Falcon with 401, 403, or 407 | Step 3 of the outbound setup is missing, or the switch firewall blocks Falcon's address |
The switch gets 482 Loop Detected | Step 4 of the outbound setup is missing, the DID destination has no tech prefix, or a DID route has an empty Send to |
| An inbound call rings nowhere and never reaches Falcon's Calls page | The DID destination does not point at Falcon |
With FALCON_BRIDGE_CARRY=all (Settings, Call bridge, Carry: Every call),
Falcon carries every call that is not a hard block. A hard block in
enforce mode gets 603. Past FALCON_BRIDGE_MAX_CALLS (1000) calls, a
call gets 503 and the switch routes it to the next carrier. Size the
host and the RTP ports for your peak.
With FALCON_BRIDGE_CARRY=suspicious (the default), Falcon carries only
the calls it wants to hear. The switch must try the next carrier after a
503:
| Decision | Bridge |
|---|---|
| Allow, flag | 503 before any answer. The switch routes the call to the next carrier. Nothing is recorded |
| Reject, enforce mode | 603. The switch drops the call |
| Challenge, or reject in monitor mode | Falcon carries the call and records both sides |
Flag from caller_fanout | One call in FALCON_BRIDGE_FANOUT_EVERY (20) is carried and recorded. The rest get 503 |
| Allow | One call in FALCON_BRIDGE_CLEAN_EVERY (0, off) is carried, so the FAS guard also checks ordinary traffic |
Falcon decides the direction of each call. A caller ID that one of your
customers owns is outbound for that customer. A call to a number that
one of your customers owns is inbound for that customer. Else a call
that Falcon sends back to the switch is outbound, and a call on a route
with a Send to address is inbound. A switch can also send
X-Falcon-Direction: inbound or outbound, and name the customer with
X-Falcon-Customer. FALCON_SIP_DIRECTION=inbound or outbound sets one
direction for every call.
The bridge does not create FAS of its own:
FALCON_RTP_TIMEOUT (30s)
ends. No call lasts longer than FALCON_BRIDGE_MAX_DURATION (4h).The bridge fails clean. A call it cannot carry gets 503, so the switch
routes it: no early offer, no free RTP port, more than
FALCON_BRIDGE_MAX_CALLS (1000) calls up, or a far end that refuses
Falcon. If the switch refuses Falcon with 401, 403, or 407, the log
says to add Falcon as a customer with IP authentication.
FALCON_RTP_PORT_MIN and FALCON_RTP_PORT_MAX (20000 to 29999) are the
media ports. Open them on the firewall. FALCON_MEDIA_IP is the address
Falcon puts in SDP. Set it when the host has more than one address.
To check the answers of one vendor, give it a tech prefix. Threats, Vendors saves the route: a prefix, a name, and where to send the call.
host:port.Vendors then lists the bridged calls, answers, and false answers per vendor. The longest matching prefix wins.
On every bridged call Falcon listens to the far end after it answers. It hears these kinds of false answer supervision:
| Kind | What Falcon hears |
|---|---|
ringback | A ringing tone after the answer: US, UK, and European cadences |
busy | A busy or reorder tone after the answer |
sit | The three intercept tones in front of an announcement |
dead_air | The far end answers and never speaks, for 4 seconds or more |
FALCON_FAS_MODE sets what Falcon does. Settings changes it at runtime.
| Mode | Effect |
|---|---|
monitor (default) | Falcon records the false answer, marks the call, and alerts. The call goes on |
enforce | Falcon holds the answer. The caller hears the far end as early media and is not billed. The answer goes through when a person speaks |
off | No detection |
In enforce mode a tone that goes on past FALCON_FAS_MAX_RINGBACK (60s),
or an intercept, fails the call back to the switch with 480, and the far
end is hung up. The switch can then route again. Silence for
FALCON_FAS_HOLD (2s) lets the answer through, so a quiet person is never
cut off.
The call view shows the timeline: setup, false answer, and conversation. Vendors totals the false answers and the billed seconds per vendor.
Each bridged call is one stereo WAV: the caller on the left channel, the
callee on the right. The file is in FALCON_RECORDINGS_DIR (default: a
recordings folder next to the database). Falcon keeps it for
FALCON_RECORDING_RETENTION_DAYS (7), which Settings can change. A
recording stops at FALCON_RECORD_MAX_SECONDS (1800).
The call view plays the recording in the browser. Lanes under the player show when each side spoke. With a voice provider, the transcript is split by speaker. A click on a line plays the audio from that point.
A recording is served only to a signed-in operator, at
GET /v1/recordings/{id}.wav. It never goes into telemetry.
The engine adds weights and caps the total at 100.
| Band | Score | Action |
|---|---|---|
| low | 0-39 | allow |
| medium | 40-59 | flag |
| elevated | 60-79 | challenge |
| high | 80-100 | reject |
Thresholds are FALCON_FLAG_SCORE, FALCON_CHALLENGE_SCORE, and
FALCON_REJECT_SCORE. Settings, Sensitivity changes them at runtime. The
presets are Relaxed, Balanced, Strict, and Carrier. The three levels stay
editable one by one.
Hard rejects skip the score: a deny rule, a spam feed hit, or an IP intel
block row. The response carries X-Falcon-Block with the source.
Verification is on by default. Falcon loads the trusted CA list from the
public STI-PA endpoint and the CRL from the STI-PA download, and refreshes
both on a timer. Each screened Identity header gets a verstat:
| Verstat | Meaning |
|---|---|
TN-Validation-Passed | Signature, chain, validity, CRL, freshness, and orig all check out. |
TN-Validation-Failed | At least one check failed. The reasons say which. |
No-TN-Validation | No Identity header, or the certificate could not be fetched in time. |
The switch never waits on a cold cache. A fetch that misses the per-call
budget (FALCON_SHAKEN_BUDGET, default 400 ms) returns No-TN-Validation
and warms the cache for the next call. Chains are cached for an hour.
The signer's SPC and organization are on the event, in X-Falcon-Signer,
and summed in Threats, Signers. A deny rule on an SPC rejects every call
that provider signs.
Air-gapped hosts: set FALCON_SHAKEN_CA_FILE to a PEM bundle and set
FALCON_SHAKEN_CA_URL=off and FALCON_SHAKEN_CRL_URL=off.
The CA list from the STI-PA is a JWS. Falcon checks its ES256 signature with
the certificate the list names, requires that certificate to come from the
same host as the list and to name the STI-PA, checks its validity period
and the list's exp, and refuses a list whose sequence number is lower
than the last one accepted. A refresh that fails any check keeps the last
good list. Two optional tightenings:
FALCON_SHAKEN_PA_PIN=<base64 SHA-256 of the list-signing certificate's SPKI>
FALCON_SHAKEN_PA_ROOT_FILE=/etc/falcon/sti-pa-root.pem
To compute the pin from the certificate the list currently names:
openssl x509 -in ca-listCert.crt -noout -pubkey | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | base64
FALCON_SHAKEN_VERIFY_LIST=false turns the check off for a lab rig and
logs a warning at start.
The table is a CSV of CIDR blocks:
cidr,provider,risk,tags,source
192.0.2.128/27,Northwind Voice,trusted,official|voice,provider
198.51.100.64/27,Unregistered cloud voice,block,voip,abuse
risk is trusted, neutral, suspicious, hostile, or block. A block
row is a hard reject. hostile adds 60, suspicious adds 30. Longest prefix
wins. A bare IP is a /32.
FALCON_IP_INTEL_FILE=/data/ipintel.csv
FALCON_IP_INTEL_URL=https://api.callerapi.com/api/ip-intel/v1/list.csv
FALCON_IP_INTEL_REFRESH=1h
Load a file, a URL, or both. A local row overrides a hosted one. With a
CallerAPI key, FALCON_IP_INTEL=true loads the hosted table. A key alone
gets the abuse list. The provider overlay needs the admin switch. The switch
gets X-Falcon-Provider. A starter file is in examples/ipintel.csv.
Zoom walkthrough: falcon demo seed, then FALCON_DEMO=true and
falcon demo swap paid. See examples/demo/README.md.
List the carriers that send you calls in Rules, Upstream carriers: a name, the RMD number or FRN, the addresses they send from, and their signer codes. Every 6 hours Falcon checks each one against the FCC standing of every provider: the Robocall Mitigation Database, FCC removal orders, and final determination orders.
| Standing | What it means |
|---|---|
| In the RMD | The provider has a filing and no FCC order. |
| Not in the RMD | The filing is gone. Under 47 CFR 64.6305(g) you may not accept its calls directly. |
| Removed from the RMD | An FCC order removed it. The same rule applies. |
| FCC block order | A final determination order names it. Under 47 CFR 64.1200(n)(3) downstream providers must block its traffic. |
| Number not found | The RMD number or FRN you entered is not on the list. Check it. |
Falcon blocks an upstream only when its RMD number or FRN matches. A name alone gives a warning and never a block. When one FRN has an order on one filing and a clean filing next to it, Falcon also asks for the RMD number.
monitor (the default) records each change of standing, alerts on the
webhook, and puts the upstream on the Overview with the one action that
settles it. enforce also adds a deny rule for every address and signer
code of each upstream under an order. The autopilot owns those rules. It
replaces them on each pass, removes them when the FCC lifts the order or the
upstream leaves the registry, and never touches your own rules. A failed
fetch keeps the last list, so a network fault never lifts a block.
Settings, FCC autopilot changes the mode at runtime. FALCON_FCC_MODE is
the start value.
CallerAPI serves the list to installs that share telemetry for a day or
carry a key. Falcon downloads the whole list and matches on this host, so
CallerAPI never learns who your upstreams are. A host with no outbound
access reads the same JSON from FALCON_FCC_FILE. FALCON_FCC=false turns
the autopilot off. The upstream registry stays.
Overview also names calls signed by a provider under an FCC order. That match uses the company name on the signing certificate, so the action is a signer rule you choose.
curl -sS -X POST http://127.0.0.1:8090/v1/rules -H "X-Falcon-Token: $FALCON_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"kind":"deny","subject":"spc","value":"1234","note":"gateway under traceback","ttl":"168h"}'
subject is number, ip (an address or a CIDR), or spc. ttl is
optional. Rules apply on the next INVITE. The dashboard adds and removes them
from any row.
All endpoints take the token. Ranges are range=15m|1h|6h|24h|7d|30d or
from and to as RFC 3339.
| Method and path | Purpose |
|---|---|
POST /v1/screen | Score one request. |
GET /v1/overview | The home screen in one request: protection state, totals, the latency Falcon adds, the trend, the items that need a decision, and the latest stopped callers. |
POST /v1/insights/dismiss | Hide one item of the overview for hours (default 24). Body: key, hours. |
GET /v1/events | Events. Filters: verdict (blocked, flagged, allowed, stopped), recorded, fas, bridged, action, verstat, ip, spc, provider, fingerprint, direction, customer, number, q. Cursor: before. |
GET /v1/events/{id} | One event with raw SIP, the verification result, the reasons in words, the bridged call, the recording URL, and the transcript turns. |
GET /v1/events.csv | CSV export with the same filters. |
GET /v1/recordings/{id}.wav | A recording. Range requests work, so the player can seek. |
GET /v1/suspects | Calling numbers worth acting on. The most flagged calls first. |
GET /v1/vendors | Bridged calls, answers, and false answers per vendor route. |
| `GET | POST /v1/routes, DELETE /v1/routes/{id}` |
GET /v1/modules | The built-in add-ons and their state. |
GET /v1/numbercheck/view, POST /v1/numbercheck/import, `GET | PUT /v1/numbercheck/schedule` |
GET /v1/stats | Counts, top lists, and a dense timeseries. |
GET /v1/histogram | Ten score buckets and the live thresholds. |
| `GET /v1/parties?by=signer | provider |
Every add-on is part of the binary. There is nothing to install. Settings,
Add-ons lists each one as on, off, or needing a key, with the variable
that turns it on. GET /v1/modules returns the same list.
| Add-on | Turn it on |
|---|---|
| STIR/SHAKEN verification | On by default |
| Telecom IP abuse list | FALCON_IP_INTEL=true with a key, or FALCON_IP_INTEL_FILE |
| Spam list | CALLERAPI_API_KEY and FALCON_SPAM_FEED=true |
| Live reputation | CALLERAPI_API_KEY and FALCON_VOICE_FIREWALL=true |
| Business Caller ID | CALLERAPI_API_KEY |
| Network reputation | FALCON_SHARE=true and FALCON_REPUTATION=true (both default) |
| FCC autopilot | FALCON_FCC=true (default) and sharing, a key, or FALCON_FCC_FILE |
| Call bridge and recording | FALCON_SIP_LISTEN and FALCON_SIP_MODE=bridge |
| FAS guard | The call bridge |
| AI voice analysis | FALCON_VOICE_PROVIDER=callerapi or openai |
| Number check | CALLERAPI_API_KEY |
Threats, Your numbers keeps a list of your own numbers and shows which ones carriers and apps flag. Import a file. Falcon sends it to CallerAPI with the API key, and CallerAPI reads the numbers out of it. CSV, spreadsheets, PDF, JSON, and plain text all work. The file does not need a fixed column. Each check costs 1 credit. A repeat check of the same number costs 1 credit. Flagged numbers are first.
The same page rechecks that list on a schedule. Pick the interval, the days, the hours, and the time zone. Cron runs the checks. Each recheck costs 1 credit per number. A list that is still running does not start a second pass. If the account is out of credits, the current results stay and the next slot tries again.
FALCON_MODE=monitor scores every INVITE and stores the real decision.
The answer to the switch is allow. X-Falcon-Monitor is reject or
challenge when that decision was held. The dashboard still shows the
real action, so you can see what enforce would drop.
FALCON_MODE=enforce is the default. The switch applies the decision.
Use monitor for the first week on a live ingress. Hard blocks are held
too. The Overview counts the calls that monitor let through. When the
calls look right, open Settings, Protection and choose Enforce. No restart
is needed. FALCON_MODE is the value a new install starts with.
Falcon compares its version with the public GitHub release every six hours. A newer tag raises one alert. The webhook and the Overview show it. Falcon does not download or install that release. You pin the image.
FALCON_UPDATE_CHECK=false stops the check. A dev build does not check.
One Falcon screens one switch. A second process, the hub, is the place you look at all of them. The hub is the same binary.
Generate one fleet token on the hub with openssl rand -hex 32. Copy that
same value to every member. A hub or member refuses to start when the
token is shorter than 32 characters or is an example value.
On the hub:
FALCON_FLEET_HUB=true
FALCON_FLEET_TOKEN=<output of openssl rand -hex 32>
On every switch, including the spare:
FALCON_FLEET_URL=http://hub:8090
FALCON_FLEET_TOKEN=<the same value as the hub>
Each member copies new events to the hub every 30 seconds. The copy includes the called number and stays on your network. It does not change what telemetry sends to CallerAPI.
The hub's own allow and deny rules are the shared list. Members pull them and keep their own local rules. A local rule with the same value wins. Post a deny rule on the hub and every switch has it within 30 seconds.
Members also pull caller counts for the other switches and add them in memory at score time. There is no network call on the INVITE. A hub that is down leaves each switch with its own counts. Counts older than two intervals are ignored.
Point the switch at the primary Falcon. On timeout, send the next INVITE to the spare, or continue the call. The spare pulls the same rules and the same counts. It does not need a copy of the primary database.
Each switch exposes GET /metrics. Prometheus stores the samples on disk.
Grafana reads Prometheus. A restart of Falcon does not delete that history.
rate() treats the counter reset as a new process.
PROMETHEUS_RETENTION sets how long Prometheus keeps samples. The default
is 30 days. Examples are 15d, 30d, and 90d. The value is read when
the Prometheus container starts.
Call records on a switch use other settings. FALCON_RETENTION_DAYS
(default 30) keeps decisions. FALCON_RAW_SIP_RETENTION_DAYS (default 7)
keeps raw SIP.
FALCON_METRICS_TOKEN to one long random value on every switch.monitoring/, copy .env.example to .env.GRAFANA_ADMIN_PASSWORD and PROMETHEUS_RETENTION in that file.prometheus/token.example to prometheus/token.prometheus/token. Do not write Bearer.prometheus/targets.yml. Add one block per switch. Set switch and site.docker compose up -d in monitoring/.http://127.0.0.1:3000. Sign in as admin. Open Falcon, then Falcon fleet.Prometheus reads targets.yml every 30 seconds. A new switch appears after
that read. A change to prometheus.yml needs a restart of the Prometheus
container.
The Grafana password is stored in the Grafana volume on first start. A
later edit of .env does not replace it. Delete the volume to set a new
password.
To use a Grafana you already run, add a Prometheus data source and import
monitoring/grafana/dashboards/falcon.json. The data source uid in that
file is falcon. Change the uid in the file if your source uses another.
A watcher evaluates the last 15 minutes every minute and posts to a
webhook when a threshold is crossed: overall reject rate, STIR/SHAKEN
failure rate, per-signer reject rate, and a stale STI-PA list. Each rule
sits behind a minimum call count and fires at most once an hour per key.
Set the webhook and thresholds in Settings, Alerts or with
PUT /v1/alerts/settings. POST /v1/alerts/test sends one. Fired alerts
are listed at GET /v1/alerts. The payload carries a Slack compatible
text field and a falcon object with the details, so a Slack incoming
webhook works as is and any JSON receiver gets the structure.
Every change made through the API is recorded with the actor, the action,
the subject, and the detail: rules added or deleted, settings, alert
settings, reloads, traceback exports. GET /v1/audit lists them. Settings,
System shows the latest changes. The actor is the dashboard user, the
token, or the query token, with the client address.
Under 47 CFR 64.1200(n)(1) you must answer a traceback request within 24 hours. Threats, Tracebacks takes the request as it arrives: the calling number, the called number, the call time in UTC, and the case reference. Falcon searches its own call log within the window you choose (10 minutes by default), picks the nearest call, and names the upstream that owns its source address or signer code in Rules, Upstream carriers. It writes the answer: who handed the call over, their RMD number and traceback contact, their FCC standing, the Call-ID, and the STIR/SHAKEN result. Copy it into the portal, mark the case answered, and keep the pack. A call your own customer sent is answered with that customer. The called number never leaves this host.
Each open case is on the Overview with the time left. Six hours before the deadline it turns critical.
Threats, Upstreams is the month per upstream: calls, blocks, flags, spam
list and trap number hits, failed verification, unsigned or C-attested calls,
traceback cases, and the FCC standing. Traffic from an address that no
upstream owns gets its own row. GET /v1/upstreams/report.csv?month=YYYY-MM
is the file to keep as evidence that you know your upstreams.
curl -sS -X POST http://127.0.0.1:8090/v1/tracebacks -H "X-Falcon-Token: $FALCON_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"reference":"TB-2041","from":"+13125550101","to":"+14155550123","call_at":"2026-10-21 13:36:00"}'
GET /v1/traceback.zip?spc=8080&range=7d (or number=, ip=,
fingerprint=, provider=) builds the archive a traceback request asks
for: events.csv, events.json, one raw INVITE per call under sip/, the
signer certificate chains still in the verifier cache under
certificates/, summary.json, and a README.txt that explains every
column. Threats, Signers has a Traceback button per signer, and the call
view has one for the calling number. The export is written to the audit
log.
Every screen writes a row to the SQLite file at FALCON_DB_PATH. Decisions
are kept for FALCON_RETENTION_DAYS (default 30). The raw SIP message on each
row is scrubbed after FALCON_RAW_SIP_RETENTION_DAYS (default 7) while the
decision stays. Both are editable at runtime from Settings or
PUT /v1/settings. FALCON_STORE_RAW_SIP=false never writes raw SIP.
Recordings have their own window, described under the call bridge.
If you set S3 variables, Falcon writes JSONL objects to your bucket. That path is your storage.
Telemetry to CallerAPI is separate from both and is described above. It is redacted; the S3 export is not, because that bucket is yours.
On when CALLERAPI_API_KEY is set. FALCON_BCID=false turns it off.
Falcon calls POST /api/bcid/v1/verify on each screen. The telco is not
charged. The registered business pays 2 credits when it announces the
call. A verified check gives the telco 1 credit.
A spoofed registered brand is a hard reject (X-Falcon-Block: bcid).
A verified or unverified name is returned in bcid_name and in
headers_to_set. Adapters that can inject SIP headers set
X-Falcon-BCID, X-Falcon-BCID-Name, and Remote-Party-ID. The SIP
redirect listener also copies Remote-Party-ID and Call-Info.
Do not put a business API key or a publish token on the switch.
The engine, verification, lists, IP intel, dashboard, API, and Business Caller ID stay free for the telco.
Spam database feed:
CALLERAPI_API_KEY=...
FALCON_SPAM_FEED=true
Falcon downloads /api/spam-reports/csv on a timer. A From number on that
list is a hard reject.
Live reputation:
CALLERAPI_API_KEY=...
FALCON_VOICE_FIREWALL=true
Falcon calls /api/lookup/{phone} on each screen. A spam hit adds score
and flags the call. It does not listen to audio. The audio is the call
bridge and the AI voice analysis above.
See .env.example. Every value has a default. Secrets never appear in
/v1/config; it shows set or empty.
Read SECURITY.md before the first install. In short: untrusted URLs are
fetched only to public addresses over https with tight caps, the switch is
never held longer than the budget, mutations refuse cross-site form
submissions, the dashboard runs under a strict content security policy, a
missing, short, or example token on a public listen stops startup, raw SIP
has its own short retention, and telemetry never carries the called party.
SECURITY.md lists every outbound request.
If Falcon will not start after upgrading, check the log: on a non-loopback
listen it now refuses tokens under 32 characters or example values. Set a
new FALCON_TOKEN (openssl rand -hex 32) and update the same token in
your switch adapter config. While Falcon is down the adapters fail open, so
calls go through unscreened.
A push to main tags the next patch (v0.9.1, then v0.9.2) and publishes
it. That release has binaries for linux and darwin on amd64 and arm64, a
checksums.txt signed with cosign (keyless, GitHub OIDC), an SBOM per
archive, and multi-arch images at ghcr.io/callerapi/falcon:<version>
signed the same way. The release notes carry the verify commands. Pin a
version in production. An install does not update itself.
make build VERSION=0.9.23
The value card is the summary the assistant reads: calls screened, calls
blocked and the percentage, spoofed caller ids stopped leaving your
platform, failed verifications, trap number hits, replayed recordings, voice
scams, customers flagged, and an estimate of the minutes of blocked traffic
you did not carry. It is computed from your own database with no model.
GET /v1/value?range=24h returns it.
Ask Falcon (top right) opens a chat that answers questions about this
install's data: what was blocked and from whom, which customer to look at
first, which signer is behind the rejects, what to tighten. The model only
sees the value card and the window. It is told to use nothing else and to
never invent a number. Bring your own model or use your CallerAPI key:
FALCON_ASSISTANT_PROVIDER=openai # any OpenAI-compatible chat endpoint
FALCON_ASSISTANT_BASE_URL=https://api.x.ai/v1
FALCON_ASSISTANT_API_KEY=xai-...
FALCON_ASSISTANT_MODEL=grok-3-mini
FALCON_ASSISTANT_PROVIDER=callerapi # on CALLERAPI_API_KEY, no other setup
Without a provider the button is hidden. POST /v1/assistant still
answers with the sentence of the value card.
AGENTS.md is a runbook for a coding agent: stand Falcon up in five
minutes, connect a switch, the variables that matter first, how to verify a
change, and the rules that must not be broken. Point your agent at it.
Falcon and CallerAPI are trademarks of CallerAPI LC. The Apache License
grants the code, not the marks. Run it, fork it, sell services on it; do
not call a fork "Falcon" or imply CallerAPI endorsement. Adopters who agree
to be named are listed in ADOPTERS.md.
Apache License 2.0. See LICENSE.
GET /v1/rules, POST /v1/rules, DELETE /v1/rules/{id} |
| Allow and deny rules. |
| `GET | POST /v1/upstreams, GET |
GET /v1/compliance | FCC autopilot mode, list state, and log. |
| `GET | POST /v1/tracebacks, GET |
GET /v1/upstreams/report?month=YYYY-MM, GET /v1/upstreams/report.csv | Traffic, blocks, and traceback cases per upstream for one month. |
GET /v1/stream | Server-sent events, one per screened request. |
POST /v1/reload | Reload the IP intel table, the trust list, the CRL, and the rules. |
GET /v1/settings, PUT /v1/settings | Runtime settings: mode, fas_mode, fcc_mode, retention, raw SIP retention, recording retention, bridge sampling, and the three score levels. Telemetry is not a runtime setting. |
GET /v1/alerts, `GET | PUT /v1/alerts/settings, POST /v1/alerts/test` |
GET /v1/audit | Who changed what. |
| `GET /v1/traceback.zip?spc= | number= |
POST /v1/outcome | How a call ended: call_id, answered, duration_s, hangup_cause. |
POST /v1/audio?call_id= | A sampled call as WAV (body or multipart audio). A stereo file keeps the caller left and the callee right. |
POST /v1/voice/transcript | A transcript a switch made itself, joined to the event by call_id. |
GET /v1/voice/samples | Clips with features, transcript, turns, category. |
| `GET | PUT /v1/customers, GET |
GET /v1/status, GET /v1/config, GET /v1/health | State, redacted configuration, liveness. |
GET /metrics | Prometheus text. Send FALCON_METRICS_TOKEN, or FALCON_TOKEN when the metrics token is empty. |