# Documentation - [PullFirst API](/docs): REST API for Minnesota contractor, permit, and enforcement data. - [Quickstart](/docs/quickstart): Sign in, create a key, make a request. - [Authentication](/docs/authentication): API keys via the Authorization header. - [Rate limits](/docs/rate-limits): Per-minute and monthly quotas per API key. - [Errors](/docs/errors): Response envelope, error codes, and consumer guidance for every 4xx and 5xx response. - [Filtering](/docs/filtering): Gridify syntax for filtering, sorting, and combining conditions on list endpoints. - Guides: How the PullFirst data model is assembled and how to consume it. - [Matching pipeline](/docs/guides/matching): How PullFirst joins public contractor records across licenses, permits, enforcement, and OSHA, and what the confidence fields on each link mean. - [Data sources](/docs/guides/sources): Where each field on a PullFirst contractor record originates, how it attaches to the record, and how fresh it is. - API Reference - [API Reference](/docs/api): Endpoints grouped by resource. - Ag Enforcement - [Search AG enforcement actions with filtering, sorting, and paging](/docs/api/agenforcement/v1/ag-enforcement/get): Uses Gridify syntax for filtering. Examples: - `companyName=*Construction*` - company name contains - `city=Minneapolis` - exact city match - `orderType=Settlement` - filter by order type - `penaltyAmount>1000000` - penalty greater than $1M - `orderDate>2022-01-01` - orders after date Order types: Lawsuit, Settlement, Judgment, Consent Decree - [Get a specific AG enforcement action by ID](/docs/api/agenforcement/v1/ag-enforcement/id/get) - [Get AG enforcement actions matched to a specific license number](/docs/api/agenforcement/v1/ag-enforcement/by-license/licensenumber/get): Returns Minnesota AG consumer-protection actions matched to this license number via fuzzy name matching (AG records rarely carry a license number natively). Each record carries a tier: VERIFIED (name plus geographic corroboration), LIKELY (unambiguous name match), POSSIBLE (weaker fuzzy score), AMBIGUOUS (name collides with multiple licensees — cannot be uniquely attributed). Callers must handle AMBIGUOUS. - [Get recent AG enforcement actions](/docs/api/agenforcement/v1/ag-enforcement/recent/get) - [Get AG enforcement statistics](/docs/api/agenforcement/v1/ag-enforcement/stats/get): Returns aggregated AG enforcement data including: - Total cases and penalties - Cases by year - Cases by order type - Enforcement - [Search enforcement actions with filtering, sorting, and paging](/docs/api/enforcement/v1/enforcement/get): Uses Gridify syntax for filtering. Examples: - `companyName=*Roofing*` - company name contains - `city=Minneapolis` - exact city match - `orderType=Revocation` - filter by order type - `penaltyAmount>5000` - penalty greater than $5000 - `orderDate>2024-01-01` - orders after date Order types: Consent Order, Administrative Order, Revocation, Cease and Desist - [Get a specific enforcement action by ID](/docs/api/enforcement/v1/enforcement/id/get) - [Get enforcement actions matched to a specific license number](/docs/api/enforcement/v1/enforcement/by-license/licensenumber/get): Returns DLI disciplinary orders matched to this license number. When the order document names a license number explicitly, the match is VERIFIED. When the order only names a company and the normalized name is unique across licensees, the match is LIKELY. When the name collides with multiple licensees, the match is AMBIGUOUS and cannot be uniquely attributed — callers must handle that tier. Every record carries a tier field on the response. - [Get recent enforcement actions](/docs/api/enforcement/v1/enforcement/recent/get) - [Get enforcement trends over time](/docs/api/enforcement/v1/enforcement/trends/get): Returns aggregated enforcement data grouped by: - Year (count and total penalties) - Order type (Consent Order, Administrative Order, etc.) - Violation type (electrical, plumbing, etc.) - Licenses - [Flexible search across name, DBA, and license number with optional filters](/docs/api/licenses/v1/licenses/search/get): Normalizes input (strips punctuation, handles spacing) and searches: - Company/person name - DBA (doing business as) name - License number Can be combined with filters for license type, status, city, and entity type. Results sorted by enforcement actions first, then alphabetically. If no filters are provided, returns all licenses paginated. - [Get search suggestions (autocomplete)](/docs/api/licenses/v1/licenses/suggest/get): Returns contractor names matching the query prefix for autocomplete. Fast and lightweight - returns only name, license number, and type. Respects license type filter if provided. - [Get top cities by license count](/docs/api/licenses/v1/licenses/cities/get) - [/v1/licenses/city/{city}](/docs/api/licenses/v1/licenses/city/city/get) - [Advanced filtering with Gridify syntax](/docs/api/licenses/v1/licenses/get): Uses Gridify syntax for filtering. Examples: - `city=Minneapolis` - exact match - `name=*Roofing*` - contains - `status=Issued,status=Expired` - OR condition - `city=Minneapolis,status=Issued` - AND condition - `expirationDate>2025-01-01` - date comparison Sortable fields: name, city, status, licenseType, expirationDate - [Get a specific license by its license number](/docs/api/licenses/v1/licenses/licensenumber/get) - [/v1/licenses/batch](/docs/api/licenses/v1/licenses/batch/get) - [Get OSHA inspection history matched to a contractor](/docs/api/licenses/v1/licenses/licensenumber/osha/summary/get): Returns OSHA inspections matched to this license number via fuzzy name matching. Each inspection carries a tier (VERIFIED / LIKELY / POSSIBLE / AMBIGUOUS) describing match strength. VERIFIED requires geographic corroboration; AMBIGUOUS means the establishment name collides with multiple licensees and cannot be uniquely attributed. Callers must handle AMBIGUOUS. Summary aggregations (inspection counts, violation totals, penalty sums) use VERIFIED matches only. - [/v1/licenses/{licenseNumber}/osha](/docs/api/licenses/v1/licenses/licensenumber/osha/get) - [Get permit records matched to this contractor](/docs/api/licenses/v1/licenses/licensenumber/permits/summary/get) - [/v1/licenses/{licenseNumber}/permits](/docs/api/licenses/v1/licenses/licensenumber/permits/get): Returns permits matched to this license number. Each permit carries a tier (VERIFIED / LIKELY / POSSIBLE / AMBIGUOUS): VERIFIED requires name plus geographic corroboration; LIKELY is name-alone when the normalized name is unique across licensees; POSSIBLE is a weaker fuzzy score; AMBIGUOUS means the name collides with multiple licensees and cannot be uniquely attributed. Callers must handle AMBIGUOUS. Match quality indicators (score, method, confidence) are returned alongside the tier. - [/v1/licenses/{licenseNumber}/permits/locations](/docs/api/licenses/v1/licenses/licensenumber/permits/locations/get) - [Find contractors related by shared address or phone number](/docs/api/licenses/v1/licenses/licensenumber/related/get): Helps identify contractors operating from the same location or using the same contact info. Useful for detecting contractors who may have had licenses revoked and reopened under a new name. - [Get the full contractor aggregate for a license](/docs/api/licenses/v1/licenses/licensenumber/full/get): Resolves the license to its parent contractor entity and returns every member license plus permits, enforcement, OSHA records, and Google Places for the whole group. Permits are deduplicated by permit id even when multiple member licenses matched them. Match records carry a tier field (VERIFIED / LIKELY / POSSIBLE / AMBIGUOUS); AMBIGUOUS rows cannot be uniquely attributed and must be handled explicitly by callers. When the license has no resolved contractor (edge case), only that single license is returned with `contractorId = null` and no aggregated data. - [/v1/licenses/featured](/docs/api/licenses/v1/licenses/featured/get) - [Get database statistics for display](/docs/api/licenses/v1/licenses/stats/get) - Lookup - [Get all license types (e.g., Residential Contractors, Electrical, Plumbing)](/docs/api/lookup/v1/license-types/get): Use the Code field for filtering licenses by type. - [Get all license subtypes with their parent type](/docs/api/lookup/v1/license-subtypes/get): Subtypes include: Residential Building Contractor, Residential Roofer, etc. - [Get all license statuses](/docs/api/lookup/v1/statuses/get): Statuses include: Issued, Expired, Revoked, Suspended, Voluntary Termination, etc. Use isActive to filter for currently valid licenses. - Osha - [Search OSHA inspections by establishment name, city, or other criteria](/docs/api/osha/v1/osha/search/get): Searches local OSHA inspection data for Minnesota establishments. Data comes from OSHA's Integrated Management Information System (IMIS) and includes both Federal OSHA and State Plan inspections. - [Advanced filtering with Gridify syntax](/docs/api/osha/v1/osha/get): Uses Gridify syntax for filtering. Examples: - `siteCity=Minneapolis` - exact city match - `establishmentName=*Roofing*` - name contains - `totalPenalty>5000` - penalty greater than $5000 - `openDate>2024-01-01` - inspections after date - `totalSeriousViolations>0` - has serious violations Sortable fields: establishmentName, siteCity, openDate, totalPenalty, totalCitedViolations - [Get OSHA inspections matched to a contractor by license number](/docs/api/osha/v1/osha/by-license/licensenumber/get): Returns OSHA inspections matched to the specified license number via fuzzy name matching. Each inspection carries a tier field describing match strength: VERIFIED (name plus geographic corroboration), LIKELY (name match, name is unique across licensees), POSSIBLE (fuzzy name match with weaker score), AMBIGUOUS (name collides with multiple licensees — cannot be uniquely attributed). Callers must handle AMBIGUOUS; typical handling is to surface the collision or exclude from contractor-specific views. Aggregation (counts, totals) is computed against VERIFIED matches only. Only matches with confidence score >= 60 are returned. - [Get OSHA database statistics](/docs/api/osha/v1/osha/stats/get): Returns aggregate statistics about the OSHA inspection database including: - Total inspections and violations - Serious and willful violation counts - Fatality count - Total penalties assessed - Date range of data - Breakdown by violation type - [/v1/osha/cities](/docs/api/osha/v1/osha/cities/get) - [Get detailed inspection information by activity number](/docs/api/osha/v1/osha/activitynumber/get): Retrieves full inspection details including: - Establishment info and address - NAICS code and industry classification - Inspection type, scope, and dates - All violations with penalties - Accident records (if any) - Contractor matches, each with a tier (VERIFIED / LIKELY / POSSIBLE / AMBIGUOUS) The activity number uniquely identifies each OSHA inspection. An inspection may carry AMBIGUOUS matches when the establishment name collides with multiple licensees; callers must handle that tier. - Parcels - [Search parcels by owner, address, or PIN.](/docs/api/parcels/v1/parcels/get) - [Get a single parcel with its joined permits.](/docs/api/parcels/v1/parcels/id/get) - [Paginated permits for a parcel.](/docs/api/parcels/v1/parcels/id/permits/get) - [Parcels whose geometry intersects the given bounding box (SRID 4326).](/docs/api/parcels/v1/parcels/within-bbox/get) - [Parcels within a radius (meters) of a point, ordered by distance.](/docs/api/parcels/v1/parcels/near/get) - [The single parcel whose geometry contains the given point, or 404.](/docs/api/parcels/v1/parcels/containing/get) - Permit Editions - [/v1/permit-downloads/editions](/docs/api/permiteditions/v1/permit-downloads/editions/get) - [/v1/permit-downloads/editions/{scopeType}/{scopeSlug}/{periodType}/{periodKey}](/docs/api/permiteditions/v1/permit-downloads/editions/scopetype/scopeslug/periodtype/periodkey/get) - [/v1/permit-downloads/editions/{scopeType}/{scopeSlug}/{periodType}/{periodKey}/sample](/docs/api/permiteditions/v1/permit-downloads/editions/scopetype/scopeslug/periodtype/periodkey/sample/get) - Permits - [Search stored permit records](/docs/api/permits/v1/permits/get): Searches locally stored permit data across Minnesota jurisdictions. Supports fuzzy contractor name matching, normalized city filter, permit type filter, and Gridify advanced filtering. Examples: - `filter=City=Saint Paul` - exact city match - `filter=Value>50000` - value greater than $50,000 - `filter=PermitType=*Electrical` - permit type contains - `filter=IssueDate>2025-01-01,Status=Issued` - multiple conditions (AND) Filterable fields: Id, PermitNumber, City, State, ContractorName, ContractorAddress, PropertyAddress, PermitType, WorkType, Status, Value, Description, IssueDate, CompleteDate, ApprovedDate, ExpirationDate. - [/v1/permits/address](/docs/api/permits/v1/permits/address/get) - [/v1/permits/block](/docs/api/permits/v1/permits/block/get) - [/v1/permits/address-suggest](/docs/api/permits/v1/permits/address-suggest/get) - [List cities by permit count](/docs/api/permits/v1/permits/cities/get) - [Get permit database statistics](/docs/api/permits/v1/permits/stats/get): Returns aggregate statistics across all stored permits including totals, the issued-date span, summed declared value, geocoding coverage, distinct city count, and the top permit types by count. - [/v1/permits/density](/docs/api/permits/v1/permits/density/get) - [Permit density heatmap with optional filters](/docs/api/permits/v1/permits/heatmap/get): Returns grid clusters (~0.05° cells, identical math to /density) with the applied filters echoed back. Unfiltered calls return the same clusters and total as /density. Each cluster carries both a permit count and a summed declared value, regardless of the active weight — only the sort order changes when weight=value. Time axis: pass either ?year= OR ?from= and ?to=, never both. permitType has no dedicated index — pair it with a time, city, or contractor filter to stay under the 20s p95 ceiling on a state-wide query. - [/v1/permits/points](/docs/api/permits/v1/permits/points/get) - [/v1/permits/viewport-contractors](/docs/api/permits/v1/permits/viewport-contractors/get) - [/v1/permits/area-locations](/docs/api/permits/v1/permits/area-locations/get) - [/v1/permits/{id}](/docs/api/permits/v1/permits/id/get) - [Changelog](/docs/changelog): Customer-visible changes to the PullFirst API — endpoints, response shapes, error envelopes, and authentication.