API Reference

Base URLhttps://api.pullfirst.com/v1

Authentication

Every request requires an API key in the Authorization header. Sign up at /signup to get your key instantly.

Key format: sk_live_ prefix for production keys.

Example request
curl -H "Authorization: Bearer sk_live_abc123..." \
  "https://api.pullfirst.com/v1/licenses/search?q=Johnson"

Keys are shown once at creation. Store them securely. If lost, revoke and generate a new one from your dashboard.

Rate Limits

Limits are per API key, enforced with a sliding window.

TierRequests/dayRequests/minAPI Keys
Free100101
Starter ($9.99/mo)1,000302
Pro ($49.99/mo)10,0001205

Every response includes rate limit headers:

Rate limit headers
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 847
X-RateLimit-Reset: 1711756800
X-Request-Id: a1b2c3d4e5f67890abcdef1234567890

When rate limited, the API returns 429:

429 Too Many Requests
{
  "error": {
    "code": "rate_limit_exceeded",
    "message": "Daily request limit exceeded. Upgrade at https://pullfirst.com/pricing",
    "retryAfter": 3600
  },
}

Response Format

All responses are JSON.

Paginated list

{
  "data": [...],
  "page": 1,
  "pageSize": 25,
  "totalCount": 150,
  "totalPages": 6
}

Single resource

{
  "licenseNumber": "BC808194",
  "name": "Goat Design Build L.L.C.",
  "status": "Issued",
  "city": "Elk River"
}

Error

{
  "error": {
    "code": "not_found",
    "message": "No contractor found with license number BC999999"
  },
}
CodeStatusMeaning
invalid_request400Missing or invalid parameters
unauthorized401Missing or invalid API key
not_found404Resource doesn't exist
rate_limit_exceeded429Rate limit hit
internal_error500Server error (include X-Request-Id header in support tickets)

Contractors

The core entity. A contractor is identified by license number.

GET/v1/licenses/search

Search contractors by name, DBA, or license number.

NameTypeRequiredDescription
qstringNoName, DBA, or license number (min 3 chars)
citystringNoFilter by city
licenseTypestringNoLicense type (e.g., Electrical, Plumbing, Residential Contractors)
statusstringNoLicense status (Issued, Expired, Revoked)
hasEnforcementbooleanNoOnly contractors with enforcement actions
entityTypestringNoBusiness or Personal
statestringNoState code (e.g., MN)
pageintegerNoPage number (default: 1)
pageSizeintegerNoResults per page (default: 25, max: 100)
Request
curl -H "Authorization: Bearer sk_live_abc123" \
  "https://api.pullfirst.com/v1/licenses/search?q=Johnson+Electric&city=Minneapolis"
Response
{
  "data": [
    {
      "licenseNumber": "EA045231",
      "name": "Johnson Electric Inc",
      "dbaName": "Johnson Electrical Services",
      "status": "Issued",
      "city": "Minneapolis",
      "state": "MN",
      "licenseType": "Electrical",
      "licenseSubtype": "Class A Electrical",
      "expirationDate": "2027-03-31",
      "hasEnforcementAction": false
    }
  ],
  "page": 1,
  "pageSize": 25,
  "totalCount": 3,
  "totalPages": 1
}
GET/v1/licenses/{licenseNumber}

Retrieve a specific contractor by license number.

NameTypeRequiredDescription
licenseNumberstringYesThe license number (e.g., BC808194)
Request
curl -H "Authorization: Bearer sk_live_abc123" \
  "https://api.pullfirst.com/v1/licenses/EA045231"
GET/v1/licenses/{licenseNumber}/osha

OSHA workplace safety inspections linked to this contractor. Includes violations, penalties, and match confidence.

NameTypeRequiredDescription
licenseNumberstringYesThe license number
Request
curl -H "Authorization: Bearer sk_live_abc123" \
  "https://api.pullfirst.com/v1/licenses/EA045231/osha"
GET/v1/licenses/{licenseNumber}/permits

Building permits matched to this contractor.

NameTypeRequiredDescription
licenseNumberstringYesThe license number
Request
curl -H "Authorization: Bearer sk_live_abc123" \
  "https://api.pullfirst.com/v1/licenses/EA045231/permits"
GET/v1/licenses/{licenseNumber}/related

Contractors sharing the same address or phone. Useful for detecting contractors who reopened under a new name after enforcement action.

NameTypeRequiredDescription
licenseNumberstringYesThe license number
Request
curl -H "Authorization: Bearer sk_live_abc123" \
  "https://api.pullfirst.com/v1/licenses/EA045231/related"

Enforcement

DLI disciplinary orders — fines, suspensions, revocations, consent orders.

GET/v1/enforcement

Search enforcement actions with Gridify filtering.

NameTypeRequiredDescription
filterstringNoGridify filter expression
orderBystringNoSort field (default: OrderDate desc)
pageintegerNoPage number (default: 1)
pageSizeintegerNoResults per page (default: 20, max: 100)
Request
curl -H "Authorization: Bearer sk_live_abc123" \
  "https://api.pullfirst.com/v1/enforcement?filter=PenaltyAmount>10000&orderBy=PenaltyAmount%20desc"
Response
{
  "data": [
    {
      "id": 42,
      "companyName": "ABC Construction Inc.",
      "dbaNames": ["ABC Builders"],
      "individualNames": ["John Smith"],
      "licenseNumbers": ["BC123456"],
      "city": "Minneapolis",
      "orderType": "Consent Order",
      "actionsTaken": ["censured", "civil penalty"],
      "penaltyAmount": 15000.00,
      "orderDate": "2024-06-15",
      "violationTypes": ["unlicensed work", "failure to supervise"]
    }
  ],
  "page": 1,
  "pageSize": 20,
  "totalCount": 52,
  "totalPages": 3
}
GET/v1/enforcement/{id}

Retrieve a specific enforcement action.

NameTypeRequiredDescription
idintegerYesEnforcement action ID
GET/v1/enforcement/by-license/{licenseNumber}

All enforcement actions for a specific contractor.

NameTypeRequiredDescription
licenseNumberstringYesThe license number
GET/v1/enforcement/recent

Most recent enforcement actions.

NameTypeRequiredDescription
limitintegerNoNumber of results (default: 10, max: 50)
GET/v1/enforcement/trends

Aggregated enforcement data by year, action type, and violation type.

OSHA Safety

Federal workplace safety inspections and violations.

GET/v1/osha/search

Search OSHA inspections by establishment name, city, or date range.

NameTypeRequiredDescription
namestringNoEstablishment name
citystringNoCity
hasViolationsbooleanNoFilter to inspections with violations
fromDatestringNoInspection date range start (YYYY-MM-DD)
toDatestringNoInspection date range end (YYYY-MM-DD)
pageintegerNoPage number
pageSizeintegerNoResults per page
Request
curl -H "Authorization: Bearer sk_live_abc123" \
  "https://api.pullfirst.com/v1/osha/search?name=Roofing&city=Minneapolis&hasViolations=true"
GET/v1/osha/{activityNumber}

Full inspection details including violations and penalties.

NameTypeRequiredDescription
activityNumberintegerYesOSHA activity number
GET/v1/osha/stats

OSHA database statistics — total inspections, violations, and penalties.

Building Permits

1.27M+ building permits across 50+ Minnesota jurisdictions.

GET/v1/permits

Search stored permits by contractor name, city, or permit type.

NameTypeRequiredDescription
contractorNamestringNoContractor name (fuzzy match)
citystringNoFilter by city
permitTypestringNoFilter by permit type
pageintegerNoPage number
pageSizeintegerNoResults per page
GET/v1/permits/address

All permits pulled at a specific property address.

NameTypeRequiredDescription
addressstringYesStreet address
citystringNoCity
GET/v1/permits/cities

List of all jurisdictions with permit data and record counts.

AG Enforcement

Minnesota Attorney General contractor enforcement actions.

GET/v1/ag-enforcement

List AG enforcement actions with Gridify filtering.

NameTypeRequiredDescription
filterstringNoGridify filter expression
pageintegerNoPage number
pageSizeintegerNoResults per page
GET/v1/ag-enforcement/by-license/{licenseNumber}

AG enforcement actions for a specific contractor.

NameTypeRequiredDescription
licenseNumberstringYesThe license number

Reference Data

Lookup tables for filtering.

GET/v1/license-types

All license types — Residential Contractors, Electrical, Plumbing, etc.

Response
[
  { "id": 1, "name": "Residential Contractors", "code": "RC" },
  { "id": 2, "name": "Electrical", "code": "EL" }
]
GET/v1/license-subtypes

Detailed license categories within each type.

GET/v1/statuses

All license status values with isActive flag.

Response
[
  { "id": 1, "name": "Issued", "isActive": true },
  { "id": 2, "name": "Expired", "isActive": false },
  { "id": 3, "name": "Revoked", "isActive": false }
]

Filtering (Gridify)

List endpoints support Gridify syntax for complex queries.

OperatorMeaningExample
=Equalsstatus=Issued
!=Not equalsstatus!=Expired
=*Containsname=*Roofing*
^Starts withlicenseNumber^BC
> < >= <=ComparisonexpirationDate>2025-01-01
,ANDcity=Minneapolis,status=Issued
|ORstatus=Revoked|status=Suspended
/iCase insensitivename=*roofing/i