Complete API reference for the FFL Verify service.
graph LR
Client[Client]
subgraph "API Endpoints /api/v1"
Health["/health<br/>Service Status"]
Stats["/stats<br/>Database Stats"]
Lookup["/ffl/:number<br/>Lookup by Number"]
Search["/ffl/search<br/>Search & Filter"]
end
Client -->|GET| Health
Client -->|GET| Stats
Client -->|GET| Lookup
Client -->|GET| Search
http://localhost:8080/api/v1
No authentication required. API is rate-limited to 100 requests per minute per IP address.
Check service status and record count.
Endpoint: GET /health
Response (200 OK):
{
"status": "ok",
"records_loaded": 45892
}Get database statistics and last updated timestamp.
Endpoint: GET /stats
Response (200 OK):
{
"total_records": 45892,
"last_updated": "2025-11-08T19:40:13Z"
}Look up FFL license information by license number.
Endpoint: GET /ffl/{ffl_number}
Parameters:
ffl_number(path) - FFL number in any of the following formats:- Short with dashes:
X-XX-XXXXX(e.g.,5-84-12875) - Short without dashes:
XXXXXXXXXorXXXXXXXX(e.g.,584012875or58412875) - Full with dashes:
X-XX-XXX-XX-XX-XXXXX(e.g.,5-84-059-01-8C-12875) - Full without dashes:
XXXXXXXXXXXXXXXorXXXXXXXXXXXXXX(e.g.,584059018C12875)
- Short with dashes:
Success Response (200 OK):
{
"ffl_number": "1-66-00332",
"ffl_number_full": "1-66-003-01-6L-00332",
"active": true,
"exp": "2026-12-01",
"exp_formatted": "December 1, 2026",
"ezcheck_url": "https://www.ffls.com/ffl/redirecttoezcheck/166003016l00332",
"license_name": "AGUADA ARMORY SHOOTING AND RENTAL CORP",
"business_name": "",
"premise_street": "4 CALLE COLON SUITE 3",
"premise_city": "AGUADA",
"premise_state": "PR",
"premise_zip_code": "00602",
"mail_street": "PO BOX 1234",
"mail_city": "AGUADA",
"mail_state": "PR",
"mail_zip_code": "00603",
"voice_phone": "(787) 555-1234",
"definition": {
"irs_region": "1",
"district": "66",
"fips_code": "003",
"type": "01",
"type_description": "Dealer in Firearms Other Than Destructive Devices (Includes Gunsmiths)",
"expires": "December 1, 2026",
"sequence_number": "00332"
}
}Response Fields:
ffl_number- Display format FFL number (X-XX-XXXXX)ffl_number_full- Full 15-digit FFL number (X-XX-XXX-XX-XX-XXXXX)active- Whether the license is currently active (not expired)exp- Expiration date in ISO 8601 format (YYYY-MM-DD)exp_formatted- Human-readable expiration dateezcheck_url- Direct link to verify license on eZ Checklicense_name- Name on the FFL licensebusiness_name- Business name (if different from license name)premise_*- Licensed premise address fieldsmail_*- Mailing address fields (if different from premise)voice_phone- Contact phone numberdefinition- Detailed breakdown of FFL components
Error Responses:
400 Bad Request- Invalid FFL number format404 Not Found- FFL number not found429 Too Many Requests- Rate limit exceeded
Examples:
# Abbreviated format (with dashes)
curl http://localhost:8080/api/v1/ffl/1-66-00332
# Abbreviated format (without dashes)
curl http://localhost:8080/api/v1/ffl/16600332
# Full format (with dashes)
curl http://localhost:8080/api/v1/ffl/1-66-003-01-6L-00332
# Full format (without dashes)
curl http://localhost:8080/api/v1/ffl/166003016L00332
# Pretty print
curl -s http://localhost:8080/api/v1/ffl/1-66-00332 | jq .Search for FFL licenses by location, name, or other criteria.
Endpoint: GET /ffl/search
Query Parameters:
Location-Based Search (requires server started with -geocode-ffls=true):
zip- 5-digit US ZIP code (server geocodes to lat/lon)lat- Latitude (-90 to 90)lon- Longitude (-180 to 180) - required iflatis providedradius- Search radius in miles (1-500, default varies by search type)
Text/Filter Search (always available):
name- Search by license name or business name (case-insensitive partial match)city- Filter by city name (case-insensitive)state- Filter by state (2-letter code, e.g., AZ, CA)type- Filter by license type code (e.g., 01, 07)
General Parameters:
active- Filter to active licenses only (true/false)limit- Maximum results to return (1-100, default 50)offset- Result offset for pagination (default 0)sort- Sort field (currently supports "distance" for proximity searches)
Requirements:
- At least one search criterion required (
zip,lat/lon,name,city,state, ortype) latandlonmust be provided togethersort=distancerequires location-based search (ziporlat/lon)- Proximity search requires FFL geocoding - Server must be started with
-geocode-ffls=trueflag
Success Response (200 OK):
{
"results": [
{
"ffl_number": "1-66-00332",
"ffl_number_full": "1-66-003-01-6L-00332",
"active": true,
"exp": "2026-12-01",
"exp_formatted": "December 1, 2026",
"license_name": "AGUADA ARMORY SHOOTING",
"business_name": "",
"premise_street": "4 CALLE COLON SUITE 3",
"premise_city": "AGUADA",
"premise_state": "PR",
"premise_zip_code": "00602",
"latitude": 18.3937,
"longitude": -67.1897,
"geocoded": true,
"distance": 12.5,
"definition": {
"irs_region": "1",
"district": "66",
"fips_code": "003",
"type": "01",
"type_description": "Dealer in Firearms Other Than Destructive Devices",
"expires": "December 1, 2026",
"sequence_number": "00332"
}
}
],
"total": 45,
"limit": 50,
"offset": 0
}Response Fields:
results- Array of FFL records matching search criteriatotal- Total number of matches (before pagination)limit- Applied result limitoffset- Applied result offsetdistance- Distance in miles (only present for proximity searches)geocoded- Whether coordinates are available for this FFL
Error Responses:
400 Bad Request- Invalid parameters, missing required criteria, or proximity search attempted without FFL geocoding enabled429 Too Many Requests- Rate limit exceeded500 Internal Server Error- Server error
Common Error Messages:
{
"error": "proximity search requires FFL geocoding - restart server with -geocode-ffls=true flag"
}This error occurs when attempting ZIP/lat/lon search without FFL geocoding enabled.
Examples:
# Proximity search by ZIP code
curl "http://localhost:8080/api/v1/ffl/search?zip=85001&radius=25"
# Proximity search by coordinates
curl "http://localhost:8080/api/v1/ffl/search?lat=33.4484&lon=-112.0740&radius=50"
# Search by business name
curl "http://localhost:8080/api/v1/ffl/search?name=gun+shop"
# Search by city and state
curl "http://localhost:8080/api/v1/ffl/search?city=Phoenix&state=AZ"
# Filter by license type
curl "http://localhost:8080/api/v1/ffl/search?state=CA&type=01&active=true"
# Combined search with pagination
curl "http://localhost:8080/api/v1/ffl/search?zip=90028&radius=100&type=01&limit=25&offset=0"
# Pretty print results
curl -s "http://localhost:8080/api/v1/ffl/search?zip=85001&radius=25" | jq .Common FFL license type codes:
| Code | Description |
|---|---|
01 |
Dealer in Firearms Other Than Destructive Devices |
02 |
Pawnbroker in Firearms |
03 |
Collector of Curios and Relics |
06 |
Manufacturer of Ammunition |
07 |
Manufacturer of Firearms Other Than Destructive Devices |
08 |
Importer of Firearms Other Than Destructive Devices |
09 |
Dealer in Destructive Devices |
10 |
Manufacturer of Destructive Devices |
11 |
Importer of Destructive Devices |
- Limit: 100 requests per minute per IP address
- Burst: 10 requests allowed in burst
- Response: HTTP 429 when limit exceeded
- Headers: Rate limit information available in response headers
All error responses follow this format:
{
"error": "Error message describing what went wrong"
}Common HTTP status codes:
200- Success400- Bad Request (invalid parameters)404- Not Found (FFL not found)429- Too Many Requests (rate limit exceeded)500- Internal Server Error