API reference · v1

Docs

A small, boring REST API. JSON in, JSON out, keyed auth, cursor pagination. You should be pulling records within about five minutes.

Authentication

Every request carries a bearer token. Generate keys in your dashboard under Settings → API keys. A sandbox key is issued with your trial, so you can integrate before you pay.

curl https://api.formationsfeed.com/v1/formations \
  -H "Authorization: Bearer ff_live_your_key_here"

Keep keys server-side. A key grants access to every state on your plan. If one leaks, roll it from the dashboard — old keys stop working immediately, and nothing else about your feed changes.

Quickstart

Pull yesterday’s Florida filings. This is the whole thing:

# Yesterday's Florida filings
curl -G https://api.formationsfeed.com/v1/formations \
  -H "Authorization: Bearer $FF_KEY" \
  -d state=FL \
  -d filed_date=2026-07-16

# Everything new since your last sync, any state on your plan
curl -G https://api.formationsfeed.com/v1/formations \
  -H "Authorization: Bearer $FF_KEY" \
  -d since=2026-07-15T00:00:00Z \
  -d limit=100

Errors & limits

Standard HTTP status codes. Errors return a JSON body with a stable code you can branch on — don’t parse the message, it may change.

StatusCodeWhat it means
400invalid_requestA parameter is missing or malformed. The message names the field.
401bad_keyMissing, malformed, or rolled API key.
403state_not_on_planYou asked for a state you aren’t subscribed to. Add it in the dashboard.
404not_foundNo entity with that ID, on your plan.
429rate_limitedSlow down. Check the Retry-After header.
503registry_downAn upstream registry is unavailable. The batch will backfill.

Rate limits are 10 requests/second on Feed + API. Every response carries X-RateLimit-Remaining.

List formations

GET/v1/formations

The endpoint you’ll live in. Returns new formations, newest first, filtered by any combination below.

ParameterTypeDescription
statestringTwo-letter code. Repeat for several: ?state=FL&state=TX. Omit for every state on your plan.
filed_datedateExact filing date, YYYY-MM-DD.
filed_afterdateInclusive lower bound.
filed_beforedateInclusive upper bound.
sincetimestampISO 8601. Everything added to the feed after this moment — the right way to sync.
entity_typestringllc_domestic, corp_domestic, lp, llp, nonprofit, foreign.
agentstringRegistered agent name, normalized match.
citystringPrincipal address city.
zipstringPrincipal address ZIP.
has_emailbooleanOnly records carrying a verified email.
limitinteger1–500. Default 50.
cursorstringFrom next_cursor. See pagination.
curl -G https://api.formationsfeed.com/v1/formations \
  -H "Authorization: Bearer $FF_KEY" \
  -d state=FL -d entity_type=llc_domestic \
  -d has_email=true -d limit=50

Get one entity

GET/v1/formations/{entity_id}

Fetch a single record by its entity_id. Useful for re-checking status on something you pulled weeks ago.

curl https://api.formationsfeed.com/v1/formations/FL-L26000418822 \
  -H "Authorization: Bearer $FF_KEY"

List states

GET/v1/states

Which states are live, which are on your plan, and when each last published. Poll this rather than hard-coding a coverage list — we add registries continuously.

{
  "data": [
    { "state": "FL", "live": true, "on_plan": true,
      "last_published": "2026-07-17T06:12:04Z",
      "fields": { "officers": true, "principal_address": true } },
    { "state": "TX", "live": true, "on_plan": true,
      "last_published": "2026-07-17T06:30:51Z" }
  ]
}

Webhooks

POST/v1/webhooks

Register an endpoint and we’ll post each morning’s batch the moment it clears validation, so you never poll.

curl https://api.formationsfeed.com/v1/webhooks \
  -H "Authorization: Bearer $FF_KEY" \
  -d url=https://yourapp.com/hooks/ff \
  -d events=batch.ready

Events: batch.ready when a state’s daily batch is available, batch.late when a registry hasn’t published on time, state.added when new coverage goes live.

Verify the signature. Every payload carries an X-FF-Signature header — an HMAC-SHA256 of the raw body using your webhook secret. Compare it before you trust the contents, and use a constant-time comparison.

The formation object

Identical shape in every state. Fields we don’t have come back null — never guessed, never filled with a placeholder. Full fill rates are on the data page.

{
  "entity_id": "FL-L26000418822",
  "name": "HARBOR LINE LOGISTICS LLC",
  "name_normalized": "harbor line logistics",
  "entity_type": "llc_domestic",
  "state": "FL",
  "filed_date": "2026-07-16",
  "status": "active",
  "registered_agent": {
    "name": "MERIDIAN AGENTS INC.",
    "address": "1201 Brickell Ave Ste 400",
    "city": "Miami", "state": "FL", "zip": "33131",
    "is_commercial": true
  },
  "officers": [
    { "name": "Dana Ruiz", "title": "MGR", "address": null }
  ],
  "principal_address": "84 Coral Way, Miami, FL 33145",
  "contact": {
    "email": "druiz@harborlinelog.co",
    "email_confidence": 0.91,
    "phone": "+13055550148",
    "website": null
  },
  "added_at": "2026-07-17T06:12:04Z"
}

Sort on added_at, not filed_date — a registry occasionally publishes late, so a record filed Tuesday can land in Thursday’s batch. Syncing on since handles this for you.

Pagination

Cursor-based. Keep calling with the returned next_cursor until has_more is false. Cursors are opaque and expire after 24 hours.

{
  "data": [ /* … 50 formation objects … */ ],
  "has_more": true,
  "next_cursor": "eyJvIjoxNTAsInMiOiJGTCJ9"
}
# Page through a whole day
cursor=""
while :; do
  resp=$(curl -sG https://api.formationsfeed.com/v1/formations \
    -H "Authorization: Bearer $FF_KEY" \
    -d state=FL -d filed_date=2026-07-16 -d limit=500 \
    ${cursor:+-d cursor=$cursor})
  echo "$resp" | jq -c '.data[]'
  [ "$(echo "$resp" | jq -r '.has_more')" = "true" ] || break
  cursor=$(echo "$resp" | jq -r '.next_cursor')
done

Sandbox keys come with the trial.

Two days free, no card. Integrate against real records before you pay for anything.

Start free trial →
Scroll to Top