Monitor New Florida DBA Registrations for Your Brand
A fictitious-name (DBA) filing is how someone registers a trade name to do business under. Polling the Sunbiz Daily API for names that match your brand lets you spot look-alikes within days of registration — a practical, low-cost input to brand and trademark monitoring. Look-alikes also appear as new corporations, LLCs, and partnerships, so a thorough watch covers all three record types.
Search fictitious names
On /api/v2/fictitious-names/, the fictitious_name filter is a
case-insensitive substring match. Pair it with a period for a rolling window of
newly registered trade names. Every mirror name column is trigram-indexed, so this search is
fast — the same speed as the corporate name search below.
curl -H "X-API-Key: sb_your_key_here" \
"https://sunbizdaily.com/api/v2/fictitious-names/?fictitious_name=sunshine&period=30d"Check corporate names and partnerships too
Brand collisions also register as corporations and LLCs (corporation_name on
/api/v2/filings/) and as partnerships (name on
/api/v2/partnerships/). All three name filters behave the same way — substring,
case-insensitive, and trigram-indexed — so cover all three for a complete watch.
# Corporations and LLCs
curl -H "X-API-Key: sb_your_key_here" \
"https://sunbizdaily.com/api/v2/filings/?corporation_name=sunshine&period=30d"
# Partnerships (LP / LLP / GP)
curl -H "X-API-Key: sb_your_key_here" \
"https://sunbizdaily.com/api/v2/partnerships/?name=sunshine&period=30d"
Each business-name filter (fictitious_name, corporation_name, and the
partnership name) matches the whole value you pass as a single substring —
commas included — so a pasted trade name like Sunshine Cafe, Bakery
is one lookup, not two terms. To watch several distinct brand variants, send one request per
variant (see Narrow your terms below).
Get the registrant with include=parties
When a look-alike turns up, the next question is who filed it. Add
include=parties and each row gains its owners (or officers /
partners) inline — so a single list request returns both the matching businesses and
the people behind them, with no per-row detail call.
curl -H "X-API-Key: sb_your_key_here" \
"https://sunbizdaily.com/api/v2/fictitious-names/?fictitious_name=sunshine&period=30d&include=parties"
{
"filings": [
{
"fictitious_name": "SUNSHINE STATE PROPERTY PARTNERS",
"document_number": "G26000083540",
"status": "A", "county": "ORANGE",
"city": "ORLANDO", "state": "FL", "zip": "32801",
"owners": [
{ "position": 1, "name": "BEGINNERS PROPERTY ACADEMY LLC" }
]
}
],
"pagination": { "total": 1, "page": 1, "per_page": 25, "total_pages": 1, "total_capped": false }
}
Without include=parties the owners field is null, keeping
the default payload small. Owners are redaction-masked: a registrant who filed a privacy takedown
returns no parties (the business name and city still appear, so you never miss the match itself).
The reliability gotcha: poll wide, never narrow
This is the one thing that decides whether a brand watch actually catches everything. Sunbiz releases filings to us with a lag, so the most recent filing-dates are always still filling in: a match dated today may not land in the mirror until a day or two later. For fictitious names and partnerships, the API exposes only the filing date — there is no "ingested at" timestamp you can query.
That breaks the obvious design — "every run, fetch the last 7 days." Filings dated inside last week's window keep arriving after this week's window has already slid past them, so a narrow, non-overlapping poll misses them permanently, with no ingestion-time filter to catch up with later.
Corporate filings are the exception: /api/v2/filings/ carries
source_date (the daily file a filing first appeared in) and accepts
source_date_start/source_date_end, so for that one resource you
can ask "what arrived since my last run" directly. Two caveats keep it from replacing
the technique below: it does not exist on the other two resources this recipe watches, and it
is null for filings that predate the daily feed, so it is a feed monitor rather
than a historical filter. The wide-window-plus-dedupe approach works uniformly across all
three, which is why it is what this guide teaches.
The fix is to make the window much wider than your polling interval and dedupe on your side:
- Poll a wide, overlapping window. Run daily or weekly, but always re-scan a
full
period=30d. Late-arriving filings fall inside the next run's window and get picked up.periodacceptsyesterday,7d,14d,30d, andall— any other value silently falls back to 7 days rather than erroring, so for a window wider than 30 days use an explicitstart_date(e.g.start_date=2026-01-01) instead. - Deduplicate persistently. Keep a durable set of
document_number(fictitious / partnership) andcorporation_number(corporate) values you have already alerted on, so the overlap doesn't re-alert. - Treat "new since last run" as "in my window but not in my seen-set" — never as "filed since my last run." The two are not the same here.
This recipe watches for newly registered look-alikes. To instead check whether a name is
already registered (regardless of when), drop the date window entirely — use
period=all, or a start_date far enough back to cover it — since an
existing registration may be months or years old.
Narrow your terms
Substring matching casts a wide net, so a short or generic term pulls in unrelated businesses:
fictitious_name=sun returns SUNNY STYLES and
SUNCOAST SENIOR INSURANCE ADVISORS alongside any real "Sun" brand. Watch distinctive,
specific terms. The filter matches the whole value as one substring (a comma is treated
literally, not as an OR separator), so to track several distinct brand variants send one request
per variant:
# One request per brand variant
curl -H "X-API-Key: sb_your_key_here" \
"https://sunbizdaily.com/api/v2/fictitious-names/?fictitious_name=tesla&period=30d"
curl -H "X-API-Key: sb_your_key_here" \
"https://sunbizdaily.com/api/v2/fictitious-names/?fictitious_name=starlink&period=30d"
Two optional filters tighten the watch further. status=active drops dissolved or
withdrawn registrations (an unknown status value returns 422, never a silent no-op),
and county=MIAMI-DADE scopes to a single county if your brand is regional:
curl -H "X-API-Key: sb_your_key_here" \
"https://sunbizdaily.com/api/v2/fictitious-names/?fictitious_name=sunshine&status=active&county=MIAMI-DADE&period=30d"
One more thing to know about counts: a very broad term reports a capped total. When more than
10,000 rows match, the response returns "total": 10000 with
"total_capped": true — so don't read total as an exact match count for a
loose stem. Specific brand terms stay far under the cap, where total is exact.
Build the watch
Putting it together: scan all three resources over a wide overlapping window, request owners inline, and dedupe persistently so only genuinely new look-alikes alert.
import time
from datetime import date, timedelta
import requests
API_KEY = "sb_your_key_here"
HEADERS = {"X-API-Key": API_KEY}
BASE = "https://sunbizdaily.com/api/v2"
DAYS = 30 # look-back window; set wider than your polling interval
# Persist this between runs (file, DB, Redis, etc.) — it is what makes a
# wide, overlapping window safe to re-scan without re-alerting.
SEEN = set()
# (endpoint, name filter, dedupe key) for each record type.
RESOURCES = [
("fictitious-names", "fictitious_name", "document_number"),
("filings", "corporation_name", "corporation_number"),
("partnerships", "name", "document_number"),
]
# Distinct brand variants — one query each (the name filter matches the whole
# value as one substring, so a comma is literal, not an OR). Keep them specific:
# substring is greedy.
TERMS = ["sunshine widgets", "sunshinewidgets"]
def poll_job(poll_url, timeout_s=120):
# A broad sweep can be handed back as a background job. Poll until it is
# done, then use the same filings + pagination envelope the synchronous
# response would have returned.
deadline = time.monotonic() + timeout_s
while time.monotonic() < deadline:
job = requests.get(BASE.rsplit("/api/v2", 1)[0] + poll_url,
headers=HEADERS, timeout=30)
job.raise_for_status()
body = job.json()
if body["status"] == "done":
return body
if body["status"] == "failed":
raise RuntimeError("search job failed")
time.sleep(2)
raise TimeoutError(f"job still running after {timeout_s}s")
def new_matches(endpoint, name_field, key_field, term):
# Use start_date for the window, not period=Nd: `period` accepts
# yesterday / 7d / 14d / 30d / all and silently falls back to 7 days for
# anything else, so start_date is the only way to widen past 30 days. A
# wide window absorbs late-ingested filings.
params = {
name_field: term,
"start_date": (date.today() - timedelta(days=DAYS)).isoformat(),
"include": "parties", # owners/officers/partners inline, no detail call
"status": "active",
"per_page": 100,
}
url = f"{BASE}/{endpoint}/"
page = 1
while True:
params["page"] = page
resp = requests.get(url, headers=HEADERS, params=params, timeout=30)
resp.raise_for_status()
body = resp.json()
if resp.status_code == 202:
# Broad sweeps can be offloaded to a background job. 202 is a 2xx,
# so raise_for_status() does not catch it and the body carries no
# "filings" key — poll the job instead of indexing into it.
body = poll_job(body["poll_url"])
for row in body["filings"]:
key = row[key_field]
if key not in SEEN:
SEEN.add(key)
yield endpoint, row
pg = body["pagination"]
if page >= pg["total_pages"]:
break
page += 1
for endpoint, name_field, key_field in RESOURCES:
for term in TERMS:
for kind, hit in new_matches(endpoint, name_field, key_field, term):
name = hit.get("fictitious_name") or hit.get("corporation_name") or hit.get("name")
owners = hit.get("owners") or hit.get("officers") or hit.get("partners") or []
who = ", ".join(o["name"] for o in owners) or "(redacted)"
print(f"New {kind}: {name} filed by {who}")- Schedule the script daily or weekly and route new matches to email or a chat webhook. The wide window means the interval only changes how fast you hear, not whether you catch a filing.
- Stay under the 1,000-requests-per-hour limit — a handful of brand terms across three resources is well within it.
Prefer a no-code version?
The Lead Mining workspace runs this kind of watch for you: save a brand search and get daily email alerts when new matching filings land — no script, no dedupe bookkeeping. The API is the right tool when you need the matches inside your own systems; Lead Mining is the right tool when you just want the alerts.
Next steps
Create a free key on your API Keys dashboard, then see the API documentation for the full fictitious-name, corporate, and partnership field lists.
Informational purposes only; provided without warranty. Searches run over a delayed third-party mirror with broad substring matching, so results may miss filings or include unrelated ones. Sunbiz Daily is not affiliated with the Florida Division of Corporations and accepts no liability for matches missed, false positives, or actions taken on its output. Always verify against the official Sunbiz.org records before acting, and consult an attorney for trademark matters. Not legal advice.