API Documentation
Search and retrieve Florida business filing data programmatically
Overview
The Sunbiz Daily API provides read-only access to the full Florida business filing record across three datasets, each its own resource:
| Resource | What it is | Records | Endpoint |
|---|---|---|---|
| Corporate filings | Corporations & LLCs | 13,004,621 | /api/v2/filings/ |
| Fictitious names | DBA registrations | 796,323 | /api/v2/fictitious-names/ |
| Partnerships | General partnerships | 31,274 | /api/v2/partnerships/ |
Record counts as of October 15, 2026.
This is the complete Florida business registry — every corporation, LLC, fictitious name, and partnership on file with the state, not just recently registered businesses. New filings are added every business day, and counts grow continuously.
All responses are JSON. Any registered user can create a key; the records a response returns cost one credit each — 500 free credits every 30 days, then $2 per 1,000 records — unless the account is on the Unlimited plan. Repeating the same request (same path and query) within 30 days only charges for records it did not already return; a different page, filter or date range is a new request. A request your balance cannot cover returns 402.
Interactive API explorer available at
/api/v2/docs/ (requires authentication).
Authentication
All API requests require an API key sent via the X-API-Key header.
API access is available to all authenticated users.
curl -H "X-API-Key: sb_your_key_here" \
https://sunbizdaily.com/api/v2/filings/
Keys use the format sb_<32 characters>. The full key is shown once at creation
time — store it securely. You can create and manage keys from your
API Keys dashboard.
Corporate Filings
Florida corporations and LLCs. Each filing carries officers, a registered agent, and addresses.
GET /api/v2/filings/
List and search corporate filings.
curl -H "X-API-Key: sb_your_key_here" \
"https://sunbizdaily.com/api/v2/filings/?city=miami&filing_type=FLAL&per_page=5"GET /api/v2/filings/{corporation_number}/
Get one filing with its officers and registered agent.
{
"corporation_number": "P24000012345",
"corporation_name": "MIAMI GRILL LLC",
"filing_type": "FLAL",
"filing_type_display": "Florida Limited Liability Company",
"status": "A",
"file_date": "2025-01-15",
"fei_number": "12-3456789",
"principal_address": { "address_1": "123 BISCAYNE BLVD", "city": "MIAMI", "state": "FL", "zip": "33101", "country": "US" },
"mailing_address": null,
"registered_agent": { "name": "JOHN SMITH", "agent_type": "P", "address": { } },
"officers": [ { "position": 1, "name": "JOHN SMITH", "title": "MGR", "officer_type": "P" } ]
}Resource-specific filters:
| Parameter | Description | Example |
|---|---|---|
corporation_name | Business name (substring; matched as a whole value, commas included) | GRILL |
corporation_number | Exact corporation number | P24000012345 |
officer_name | Officer name (word-AND; see name matching below) | smith |
registered_agent_name | Registered agent name (word-AND; see name matching below) | CT CORPORATION |
filing_type | Comma-separated filing type codes (see below) | DOMP,FLAL |
county | County of the principal address (see note below) | Orange |
Recognized filing_type codes for /api/v2/filings/. Any unrecognized code is
ignored; a value containing no recognized code returns no results.
| Code | Entity type |
|---|---|
FLAL | Florida Limited Liability Company |
FORL | Foreign Limited Liability Company |
DOMP | Domestic Profit Corporation |
FORP | Foreign Profit Corporation |
DOMNP | Domestic Non-Profit Corporation |
FORNP | Foreign Non-Profit Corporation |
DOMLP | Domestic Limited Partnership |
FORLP | Foreign Limited Partnership |
TRUST | Trust |
COOP | Cooperative |
NPREG | Non-Profit Registration |
AGENT | Registered Agent |
What each code means in plain language: the filing types glossary.
Date filters use file_date. Sort fields:
corporation_name, corporation_number, filing_type,
file_date (default), city, state, zip,
county, source_date.
sort=zip orders by the 5-digit ZIP code. Filings with no ZIP come
last in both directions, and the ZIP+4 digits do not affect the order.
With filters other than a date range or a numeric ZIP, filings that share a 5-digit
ZIP have no fixed order: they can reorder between pages, so offset paging can
repeat or skip rows within a ZIP.
Corporate filings also carry source_date — the daily file a
filing first appeared in, as opposed to file_date, which is the date
Florida filed the document. Filter it with
source_date_start/source_date_end to ask “what
arrived in the feed since …” rather than “what was filed
since …”, which is the more useful question when polling, because
filings reach the feed days after their file date.
Caveat: source_date is null for
filings that predate the daily feed and were loaded from the state's bulk
extract — the large majority of the registry. A
source_date window therefore excludes them, so use it for feed
monitoring, not for historical queries. Fictitious names and partnerships do
not carry this field.
county for corporate filings is derived from address enrichment,
which is still backfilling across the registry — filings whose address has
not been enriched yet have an empty county and are not matched
by the filter. Coverage grows continuously; a missing county does not mean
the business has none. A trailing “County” is tolerated
(Orange County = Orange).
Fictitious-Name Filings
"Doing business as" (DBA) registrations. Each filing carries owners and a principal address — no registered agent or mailing address.
GET /api/v2/fictitious-names/
List and search fictitious-name filings.
curl -H "X-API-Key: sb_your_key_here" \
"https://sunbizdaily.com/api/v2/fictitious-names/?fictitious_name=bakery&county=PINELLAS"GET /api/v2/fictitious-names/{document_number}/
Get one filing with its owners.
{
"document_number": "G24000056789",
"fictitious_name": "SUNSHINE BAKERY",
"status": "A",
"filing_date": "2025-02-03",
"county": "PINELLAS",
"fei_number": "",
"principal_address": { "address_1": "55 GULF BLVD", "city": "ST PETERSBURG", "state": "FL", "zip": "33706", "country": "US" },
"owners": [ { "position": 1, "name": "MARIA LOPEZ" } ]
}Resource-specific filters:
| Parameter | Description | Example |
|---|---|---|
fictitious_name | Trade name (substring; matched as a whole value, commas included) | bakery |
document_number | Exact document number | G24000056789 |
owner_name | Owner name (word-AND; see name matching below) | lopez |
county | County of filing | PINELLAS |
Date filters use filing_date. Sort fields:
fictitious_name, document_number, filing_date (default),
county, city, state, zip.
Partnerships
Florida general partnerships. Each carries partners, a registered agent, and addresses.
GET /api/v2/partnerships/
List and search partnerships.
curl -H "X-API-Key: sb_your_key_here" \
"https://sunbizdaily.com/api/v2/partnerships/?name=acme&state=FL"GET /api/v2/partnerships/{document_number}/
Get one partnership with its partners and registered agent.
{
"document_number": "A24000098765",
"name": "ACME PARTNERS",
"status": "A",
"filed_date": "2025-01-20",
"fei_number": "98-7654321",
"county": "ORANGE",
"principal_address": { "address_1": "200 MAIN ST", "city": "ORLANDO", "state": "FL", "zip": "32801", "country": "US" },
"mailing_address": null,
"registered_agent": { "name": "JANE ROE", "agent_type": "P", "address": { } },
"partners": [ { "name": "ALICE STONE", "partner_type": "I" } ]
}Resource-specific filters:
| Parameter | Description | Example |
|---|---|---|
name | Partnership name (substring; matched as a whole value, commas included) | acme |
document_number | Exact document number | A24000098765 |
partner_name | Partner name (word-AND; see name matching below) | stone |
registered_agent_name | Registered agent name (word-AND; see name matching below) | CT CORPORATION |
county | County of the principal address (enrichment-derived, same caveat as corporate filings) | Orange |
Date filters use filed_date. Sort fields:
name, document_number, filed_date (default),
city, state, zip, county.
Industry Categories
Every filing is assigned an industry category by matching keyword rules
against the business name. The industry filter accepts a
category from this vocabulary and works on all three resources above.
A value that is not a category name or slug but unambiguously means one
is resolved to it — industry=pressure+washer filters on
Pressure Washing, car+rentals on
Car Rental. This covers inflections and a curated alias list
only; anything that could mean more than one category, or none, still
returns 422. Pass a value from
/api/v2/industries/ when you need the match to be exact.
GET /api/v2/industries/
List the industry vocabulary the industry filter accepts.
curl -H "X-API-Key: sb_your_key_here" \
"https://sunbizdaily.com/api/v2/filings/?industry=Restaurant&city=miami"
Categories are derived from the business name alone — they describe
what a name suggests, not verified activity. A name that matches no rule is
left without a category rather than assigned a catch-all, so a filing may have
no industry value at all. Filter on a value outside the vocabulary
and the API returns 422 rather than silently ignoring it.
Common Parameters
These apply to every resource above. All parameters are optional and combined
with AND logic; most text parameters accept comma-separated values, ORed within
that filter. The business-name filters are the exception —
corporation_name, fictitious_name, and the partnership
name are each matched as a single whole value (commas included), so a
pasted name like Acme Holdings, LLC is one lookup, not two terms.
Shared filters
| Parameter | Description | Example |
|---|---|---|
city | City (case-insensitive) | miami |
state | State code | FL |
zip | ZIP code prefix | 331 |
status | active, inactive, or a raw status code (see note below) | active |
industry | Industry category from the vocabulary (see below); a close variant resolves to it, an unknown value returns 422 | Restaurant |
name_match | How a name filter is matched: substring (default), whole_word (person/agent names), or prefix (business names, see note below); an unknown value returns 422 | prefix |
period | Date preset: yesterday, 7d, 14d, 30d, 90d, all (see note below) | 7d |
start_date | Start of date range (YYYY-MM-DD) | 2025-01-01 |
end_date | End of date range (YYYY-MM-DD) | 2025-01-31 |
include | parties inlines officers / owners / partners and the registered agent on each list row; omit for null | parties |
Value limits. Every free-text filter value
(city, state, zip, county,
the name filters, filing_type, and the document/corporation number
filters) is limited to 200 characters; a longer value returns
422. city additionally accepts at most
10 distinct comma-separated values in one request (repeats
do not count) — listing more returns 422 rather than
answering for only the first ten.
period accepts only the six values listed above. Any other
value — 60d, 180d, 1y — is
silently treated as 7d rather than rejected, so a
window other than these must use start_date/end_date.
Name matching. The person and agent filters
(officer_name, owner_name, partner_name,
registered_agent_name) split your value into words and require every
word to appear on one person, in any order — Florida stores names
surname-first, so John Smith finds SMITH JOHN. A
comma is part of the name; a pipe (|) separates independent names and
ORs them. Each word is matched as a substring by default, so
John Smith also matches GOLDSMITH JOHNNIE; pass
name_match=whole_word to require whole-word matches instead, at the
same cost. Word boundaries are Postgres's, so digits and underscores count as word
characters — whole-word smith does not match SMITH2
(unlike business-name keyword matching, which treats a digit as a boundary). Two
cases fall back to substring matching rather than returning nothing: a word with no
letter or digit at its edges (#1), and a name whose every word is under
3 characters (al). See the
person and agent
search guide for worked examples.
Business-name matching, and typeaheads. The business-name
filters (corporation_name, fictitious_name, and the
partnership name) match your whole value as one substring — a
comma is part of the name, never a separator. & and
and are searched as the same name, so
Johnson & Sons and
Johnson and Sons return the same rows.
A substring search needs a run of 3 or more consecutive letters or
digits somewhere in the value — LC and
B & have none, and a search like that has to read the
entire registry. Because the two spellings above are one search, both
have to qualify: B and M is refused along with
B & M. An unsearchable term returns
422 when the request covers all time; inside a date window
(period, start_date/end_date, or the
source_date bounds) it is attempted, and only returns
422 if it in fact runs too long.
Pass name_match=prefix to match the value against the
start of the name instead
(corporation_name=cod&name_match=prefix finds
CODIFIED LLC but not ENCODED LLC). That drops
the minimum to 2 characters, accepts punctuation, and is the
mode to build a typeahead on — it is answered by an index range scan, so
short and rapidly-changing terms stay fast where a substring search would be
queued as a background job. prefix applies to
business names only; sending it alongside a person or agent name filter returns
422 rather than quietly matching that name some other way.
status=active matches filings with status code A;
status=inactive matches everything else — corporate filings
use I, but partnerships carry several terminal codes (revoked,
dissolved, merged, …), so “inactive” means “not
active”. A raw single-character code (e.g. A, I)
is accepted verbatim; any other value returns 422. Responses keep
emitting the raw code in status.
Date filters operate on each resource's filing date (see each section above). Future-dated values are never returned.
Pagination & Sorting
| Parameter | Type | Default | Description |
|---|---|---|---|
page | integer | 1 | Page number (1-indexed, max 100) |
per_page | integer | 25 | Results per page (max 100) |
sort | string | filing date | Sort field (see each resource) |
order | string | desc | asc or desc |
For very broad filters, counting every match is more expensive than
returning the page itself — so counting stops at 10,000. When that
happens the envelope reports "total": 10000 with
"total_capped": true, meaning at least 10,000 matches;
narrow the filters (a date range helps most) for an exact
total. Exact counts report total_capped: false.
Separately, the page of results is served first and the count gets
whatever is left of the request's time budget. If the count runs out,
you still get your rows — with an approximate total
and "total_estimated": true. Treat that number as a
magnitude, not a match count — it answers “roughly how
many matches”, and only that.
Paging is not affected. Each response fetches one row past the page
it serves, so total_pages is measured rather than derived
from the estimate: it advertises a next page while rows really remain
and lands on the last real page when they stop. Keep reading
total_pages from each response and
while page <= total_pages walks exactly the right
pages — an approximate total will neither cut you
short nor march you through empty pages. Exact counts report
total_estimated: false.
The two flags are never both set. total_capped is a claim
about a real count — it reached the cap, so there genuinely are
at least that many matches — and an estimate can't support that,
so an estimated total is reported as total_capped: false.
Bulk Daily Files
Paging is for finding filings, not for copying the register. If you want a whole day, download it: we write one gzipped JSON Lines file per record type per day — one JSON object per line, no pagination, no row cap. Use these instead of walking the detail endpoints.
Files are built on demand, not in advance, and the
build runs in the background. The first request for a given day (and
field set) returns 202 Accepted with a
Retry-After header instead of the bytes — that
request queued the build. Poll the same URL at the
Retry-After interval: you get another 202
while it is still being written, then 200 and the file,
and every later download is served straight from disk. There is no
guaranteed build time: keep polling at the Retry-After
interval until you receive a 200 or a 503. That means the manifest has two lists: available,
every day you can ask for, and files, the ones that have
already been built.
# first ask — the build is queued, nothing to download yet
curl -sS -D - -H "X-API-Key: sb_your_key_here" \
"https://sunbizdaily.com/api/v2/bulk/corporate/2026-09-04/"
HTTP/2 202
retry-after: 10
{"status":"building","detail":"This file is being built; retry shortly at the same URL.","retry_after":10}
# wait the advertised seconds and ask again — same URL, HTTP 200 once it is written
curl -sS -H "X-API-Key: sb_your_key_here" \
-o corporate-2026-09-04.jsonl.gz \
"https://sunbizdaily.com/api/v2/bulk/corporate/2026-09-04/"
Check the status code, not just for a body: a 202 writes
that small JSON object wherever you pointed -o. Only a
200 is the gzip.
GET /api/v2/bulk/
List what has been built and what can be asked for, newest first.
curl -H "X-API-Key: sb_your_key_here" \
"https://sunbizdaily.com/api/v2/bulk/"
{
"files": [
{
"filing_kind": "corporate",
"target_date": "2026-09-04",
"include": "",
"row_count": 4821,
"byte_size": 402118,
"sha256": "9f2c...",
"generated_at": "2026-09-05T05:12:44Z",
"expires_at": "2026-09-19T00:00:00Z",
"download_url": "/api/v2/bulk/corporate/2026-09-04/"
}
],
"available": [
{"filing_kind": "corporate", "target_date": "2026-09-04"},
{"filing_kind": "fictitious", "target_date": "2026-09-04"}
],
"retention_days": 14
}
A day in available with no matching files entry
is not missing — nobody has downloaded it yet. Ask for it and the
build is queued. Every available day can be requested with
any include combination below.
GET /api/v2/bulk/{filing_kind}/{target_date}/
Download one day's file. filing_kind is
corporate, fictitious or
partnership; target_date is
YYYY-MM-DD. A 200 response is
application/gzip with an ETag equal to the
manifest's sha256, so you can verify the transfer and skip
a re-download; a file nobody has built yet answers 202
first, as above.
curl -H "X-API-Key: sb_your_key_here" \
-o corporate-2026-09-04.jsonl.gz \
"https://sunbizdaily.com/api/v2/bulk/corporate/2026-09-04/"
gunzip -c corporate-2026-09-04.jsonl.gz | head -1
{"corporation_number":"L26000012345","corporation_name":"EXAMPLE VENTURES LLC", ...}
A file holds every filing that was created or amended in that
day's state file, carrying its current values — each row's
change_type is created or updated.
Every row also carries city, state and
zip5 from the principal address, plus the fields for its
record type:
| Record type | Fields |
|---|---|
corporate |
corporation_number, corporation_name,
status, filing_type,
file_date, last_transaction_date,
source_date, fei_number,
state_country, more_than_six_officers
|
fictitious |
document_number, fictitious_name,
status, county,
filing_date, cancellation_date,
expiration_date, fei_number,
number_of_owners, more_than_ten_owners
|
partnership |
document_number, name,
status, filed_date,
effective_date, cancellation_date,
expiration_date, fei_number,
state_country, conversion_flag,
fl_partners, total_partners
|
Asking for more: ?include=
By default a row is entity fields plus the three flat address keys above.
Add include to nest more of the source record. It takes any
combination of three group names, comma-separated, in any order and any
case:
| Group | Adds |
|---|---|
addresses |
principal_address and
mailing_address objects
(address_1, address_2,
city, state, zip,
country). Fictitious-name filings have no
mailing address, so theirs is null.
|
agent |
registered_agent:
{"name", "ra_type", "address"}.
null for fictitious-name filings (no such
record) and for any filing whose source record carries no
agent.
|
parties |
The filing's people, in source order:
officers (corporate),
owners (fictitious) or
partners (partnership).
|
curl -H "X-API-Key: sb_your_key_here" \
-o corporate-full-2026-09-04.jsonl.gz \
"https://sunbizdaily.com/api/v2/bulk/corporate/2026-09-04/?include=addresses,agent,parties"
# corporate
{"corporation_number":"L26000012345","corporation_name":"EXAMPLE VENTURES LLC", ...,
"city":"DELAND","state":"FL","zip5":"32720",
"principal_address":{"address_1":"123 MAIN ST","address_2":"","city":"DELAND","state":"FL","zip":"327201234","country":""},
"mailing_address":{"address_1":"PO BOX 9", ...},
"registered_agent":{"name":"CT CORPORATION SYSTEM","ra_type":"C","address":{...}},
"officers":[{"position":1,"title":"P","officer_type":"P","name":"DOE, JANE","address":{...}}],
"change_type":"created"}
# fictitious (no mailing address and no registered agent exist for this record type)
{"document_number":"G26000012345","fictitious_name":"EXAMPLE DBA", ...,
"principal_address":{...},"mailing_address":null,"registered_agent":null,
"owners":[{"position":1,"name":"ACME HOLDINGS INC","name_format":"C","document_number":"P11000001111","fei_number":"123456789","charter_number":"","address":{...}}],
"change_type":"created"}
# partnership (a partner's address is the source's mail_address)
{"document_number":"A26000012345","name":"EXAMPLE PARTNERS", ...,
"principal_address":{...},"mailing_address":{...},"registered_agent":{...},
"partners":[{"sequence":"1","partner_type":"I","name_format":"","name":"ROE, RICHARD","corp_number":"","mail_address":{...}}],
"change_type":"updated"}
The flat city, state and zip5 keys
stay on every row whichever groups you ask for, so a parser written
against the default keeps working. An unrecognised group name is a
422 rather than being ignored — the group set is part
of the file's identity, so a typo would otherwise hand you a smaller file
than you asked for. Each combination is its own file with its own
sha256, built the first time somebody requests it.
Values are raw state data throughout — the county on a
fictitious-name row is the county from the source record, and the
geocode-derived county and the industry category are not included at any
include level. Use the list and detail endpoints when you
need those. Filings under a privacy takedown are left out of every file
entirely, and existing files are rewritten when a takedown lands.
Files are kept for 14 days. Past that a day
drops off the manifest and its download returns 410; a day
inside the window with no completed ingest — including one whose
ingest is still running — returns 404, so a day
appears in available only once its file is complete.
Three status codes mean “ask again”, all of them carrying a
Retry-After header saying how long to wait.
202 is the normal one: the file is queued or being written,
by your request or somebody else's. 503 means nothing is
being built for you right now — a recent build of that exact file
failed and is cooling off, generation is switched off on the server, or
the build queue is unreachable — and the next request after the
cooldown queues a fresh attempt. Wait the advertised number of seconds
and repeat the same request for either. A 404, by contrast,
means the day is genuinely not there and polling will not help.
Asynchronous Searches
Most queries return immediately. A few are inherently expensive — for example, finding every business tied to a common officer or agent name across the whole registry. Rather than make you wait (or time out), the API runs each list request under a short time budget and, if it would exceed it, hands back a background job instead of the results.
This is automatic and uses the same list endpoints — you don't choose
up front. When a query is offloaded, the list endpoint returns
202 Accepted with a job id and a poll URL:
GET /api/v2/filings/?officer_name=john+smith
202 Accepted
{
"job_id": "f1a2b3c4-...",
"status": "queued",
"poll_url": "/api/v2/jobs/f1a2b3c4-.../"
}GET /api/v2/jobs/{job_id}/
Poll the job until status is done. While it runs,
the status is queued or running. When done, the
response carries the result in the same
filings + pagination envelope the
synchronous endpoints use, paged with the normal page and
per_page parameters. Left unset, they default to the values
from your original request, so the job result matches what the
synchronous response would have been:
GET /api/v2/jobs/f1a2b3c4-.../?page=1&per_page=25
{
"job_id": "f1a2b3c4-...",
"status": "done",
"resource": "corporate",
"truncated": false,
"filings": [ ... ],
"pagination": { "total": 842, "page": 1, "per_page": 25, "total_pages": 34 }
}
Notes: fetchable results are capped — for very common names
truncated is true, meaning only the first
page-load of rows is reachable. total is then held at that cap
with total_capped set — a floor, not the true match count —
and total_pages reflects the reachable rows, not total. Person and
agent name terms must be 3 or more characters, and a business-name
term must be searchable at all (see Business-name matching above);
otherwise 422 — such a term matches most of the registry
and could never finish. Each user may
have up to 3 jobs in flight at once (a 4th returns 429).
Job results expire after 24 hours; polling an expired job returns
410. Jobs are private to the key's owner.
Privacy
Florida business filings are public record. A business owner who used a
home address can request a privacy takedown; for those filings the API
hides the people (officers, owners, or partners), the registered agent,
the mailing address and the FEI/EIN (fei_number is empty),
and generalizes the principal address to city level. The filing itself
still appears in results.
Rate Limits
Rate limits are enforced per API key per clock hour.
Every response includes rate limit headers:
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 998
When the limit is exceeded, the API returns a 429 status with a
Retry-After header: the number of seconds until the allowance
resets at the top of the hour. Wait that long before retrying.
Error Responses
Errors return a JSON object with error and optional detail fields.
| Status | Meaning | When |
|---|---|---|
200 |
OK | Request succeeded |
202 |
Accepted | Query offloaded to a background job (see Asynchronous Searches) |
401 |
Unauthorized | Missing, invalid, or expired API key |
404 |
Not Found | Document/corporation number or job does not exist |
410 |
Gone | Background job result has expired |
422 |
Unprocessable Entity | Invalid filter value — unknown status,
industry, or name_match, a filter value
over 200 characters, more than 10 city values, a
person-name term under 3 characters, or an unsearchable
business-name term |
429 |
Too Many Requests | Rate limit exceeded, or too many in-flight search jobs |
500 |
Internal Server Error | Unexpected server error |
Example error response:
{
"error": "Rate limit exceeded.",
"detail": "1000 requests per hour."
}Get Started
Create an API key to start making requests. Keys are free for every registered user; records cost one credit each per the pricing page.
Manage API Keys Interactive Docs