# Bonfire Bids Scraper - Open & Awarded Public Procurement (`neverempty/bonfire-bids-scraper`) Actor

Open bids, RFPs and tenders from Bonfire public procurement portals of US and Canadian agencies: one row per opportunity with agency, reference, status (open, reopened, awarded, cancelled), closing time in UTC and local time, days left. Filter by keyword, type, country. No login.

- **URL**: https://apify.com/neverempty/bonfire-bids-scraper.md
- **Developed by:** [NeverEmpty](https://apify.com/neverempty) (community)
- **Categories:** Lead generation, Business, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $9.00 / 1,000 opportunity returneds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Bonfire Bids Scraper - Open & Awarded Public Procurement

Get every open bid, RFP, RFQ and tender from Bonfire (Euna Procurement) public portals in one table: US, Canadian and Caribbean cities, counties, school districts, universities, transit agencies, utilities and more. Each row is one opportunity with the agency, reference number, title, a clear status (open, reopened, evaluating, awarded, cancelled), the closing time in UTC **and** in the agency's own time zone, and how many days are left.

Type an agency name like `dallascityhall`, paste a portal link, or pick agency types and countries from the built-in directory of verified Bonfire portals. No login, no API key, no browser.

### What you get

- **One row per opportunity** across as many agency portals as you choose, with duplicates removed.
- **A status you can filter on.** Bonfire's own portal shows only "Open", "Closed" or "Awarded". This Actor separates `open`, `reopened`, `evaluating`, `closed`, `awarded` and `cancelled`, and keeps Bonfire's raw status codes next to it.
- **Closing time three ways:** `closesAt` (UTC), `closesAtLocal` (the agency's local time with its UTC offset, daylight saving handled) and `daysLeft` (decimal days, for open bids).
- **Reopened bids with their history:** `previousClosesAt` shows the original closing date when an agency reopens a bid with a new deadline.
- **Past and awarded bids** when you add `past` to the lists, newest first, with a date filter (`closedOnOrAfter`) so you get last month's awards instead of ten years of history.
- **Agency details on every row:** name, type (city, county, k12, higher-ed, transit, utility, housing, airport, port, healthcare, workforce, state, other), country and time zone.
- **Whole-word keyword filters:** `IT` matches "IT Services" but not "Unit Pricing". End a word with `*` to match its beginning (`pav*` finds paving and pavement). Add words to exclude.
- **Clear reason rows when something is off.** A misspelled agency, a list that could not be read, or a search with no matches each come back as a free row that says why, instead of an empty dataset.

### Who uses it

- **Bid hunters and sales teams** who track public tenders in their category every morning.
- **Government contractors** watching specific cities, counties or universities for new RFPs.
- **Market researchers** studying who buys what and when: award rates, cancellations, seasonal timing.
- **Lead-gen and alert services** that feed public procurement opportunities into a CRM, Slack or email digest.

### How to use it

1. Put agency portals into **Agencies**: the part before `.bonfirehub.com` (for example `dallascityhall`), the full host (`athabascau.bonfirehub.ca`) or a copied portal link.
2. Or leave Agencies empty and choose **Agency types** and/or **Countries** to read every matching portal from the directory.
3. Choose the lists: **open** (default) and/or **past** (closed, evaluating, awarded, cancelled and reopened).
4. Optionally add keywords, words to exclude, statuses, "closing within N days" or "closed on or after".
5. Run, then download the table as CSV, Excel or JSON, or read it through the API.

#### Input fields

| Field | What it does |
|---|---|
| `agencies` | Agency portals to read: `dallascityhall`, `athabascau.bonfirehub.ca` or a portal link. |
| `agencyTypes` | Adds every directory portal of these types (city, county, k12, higher-ed, transit, utility, ...). |
| `countries` | Adds every directory portal in these countries (US, CA, BS, BB, KY, HT, SX). |
| `include` | Which lists to read: `open` (default) and/or `past`. |
| `statuses` | Keep only these bid statuses: open, reopened, evaluating, closed, awarded, cancelled. |
| `keywords` | Keep bids whose title or reference number contains any of these whole words (`pav*` matches the beginning). |
| `excludeKeywords` | Drop bids that contain any of these whole words. |
| `closingWithinDays` | Keep bids closing between now and this many days from now. |
| `closedOnOrAfter` | For the past list: keep bids that closed on or after this date (YYYY-MM-DD). |
| `maxResults` | The most opportunity rows returned (your cost ceiling). Default 1000. |

#### Example input

```json
{
  "agencies": ["dallascityhall"],
  "include": ["open"],
  "maxResults": 100
}
```

Every open bid at the City of Dallas, soonest deadline first.

```json
{
  "countries": ["CA"],
  "include": ["open"],
  "keywords": ["software", "IT", "technology", "cloud*", "data"],
  "closingWithinDays": 30
}
```

IT, software and cloud bids from every Canadian portal in the directory (school boards, counties, universities, airports, utilities) that close within 30 days. Add `"agencyTypes": ["higher-ed"]` to keep only universities and colleges.

```json
{
  "agencies": ["dallascityhall"],
  "include": ["past"],
  "statuses": ["awarded"],
  "closedOnOrAfter": "2026-09-01"
}
```

Bids the City of Dallas has publicly awarded since September 1.

### Output example

```json
{
  "status": "ok",
  "agency": "dallascityhall",
  "agencyName": "City of Dallas",
  "agencyType": "city",
  "country": "US",
  "agencyTimezone": "America/Chicago",
  "opportunityId": "246143",
  "referenceNumber": "BYZ26-00030828",
  "title": "Temporary Stagehand and Tech Labor",
  "bidStatus": "reopened",
  "closesAt": "2026-10-22T18:00:00.000Z",
  "closesAtLocal": "2026-10-22T13:00:00-05:00",
  "daysLeft": 1.3,
  "previousClosesAt": "2026-09-11T18:00:00.000Z",
  "publiclyAwarded": false,
  "departmentId": "1013",
  "departmentName": null,
  "listedAs": "past",
  "projectStatusCode": 6,
  "projectSubStatusCode": 1,
  "url": "https://dallascityhall.bonfirehub.com/opportunities/246143",
  "portalUrl": "https://dallascityhall.bonfirehub.com/portal/?tab=pastOpportunities",
  "checkedAt": "2026-10-21T12:00:00.000Z"
}
```

#### Fields

| Field | Meaning |
|---|---|
| `status` | `ok` for an opportunity row. Other values are free reason rows (below). |
| `agency`, `agencyName`, `agencyType`, `country`, `agencyTimezone` | The agency portal the bid came from. `country` is an ISO code (US, CA, BS, ...) or null when it cannot be told from the portal (international bodies). |
| `opportunityId`, `referenceNumber`, `title` | Bonfire's project ID, the agency's own bid number (spaces trimmed) and the title. |
| `bidStatus` | `open`, `reopened`, `evaluating`, `closed`, `awarded` or `cancelled`. |
| `closesAt`, `closesAtLocal`, `daysLeft` | Closing time in UTC, in the agency's time zone, and days left (open and reopened bids only, otherwise null). |
| `previousClosesAt` | The earlier closing date of a reopened bid, otherwise null. |
| `publiclyAwarded` | true when the agency published the award, false when not, null on the open list (Bonfire does not say there). |
| `departmentId`, `departmentName` | The agency's department ID, and its name when the portal publishes department names. |
| `listedAs` | Which Bonfire list the row came from: `open` or `past`. |
| `projectStatusCode`, `projectSubStatusCode` | Bonfire's raw codes, kept so you can check our status labels. |
| `url`, `portalUrl`, `checkedAt` | Link to the opportunity page, link to the agency's portal tab, and when the row was read. |

#### Free reason rows

| `status` | When |
|---|---|
| `bad-input` | An input value could not be used. The message says which one and how to write it. |
| `unknown-agency` | The agency name does not exist on Bonfire, or the address is not an agency portal. |
| `failed` | A list could not be read (for example Bonfire asked us to slow down for too long). `retryable: true` means running again later should work. |
| `no-results` | An agency has no opportunities on that list, or nothing matched your filters. |
| `incomplete` | The run was close to its time limit and stopped early. The message says how many agencies were not read. |

Reason rows are never charged.

### Speed

Bonfire slows down anyone who reads its lists too quickly, so the Actor waits about 3 seconds between agency lists and backs off when asked. One agency takes a few seconds; the whole directory takes roughly 15 to 30 minutes for the open lists. Past lists of large agencies (City of Dallas has over 5,000 past bids) come in one response and are filtered on our side.

### FAQ

#### How do I scrape open bids from Bonfire portals?

Put the agency name (the part before `.bonfirehub.com`) into Agencies and run. You get one row per open opportunity with reference number, title, closing time in UTC and local time, and days left.

#### Can I get awarded and cancelled bids from Bonfire?

Yes. Add `past` to the lists. Rows then carry `bidStatus` `awarded`, `cancelled`, `closed`, `evaluating` or `reopened`, and you can filter with `statuses` and `closedOnOrAfter`.

#### Which agencies are covered?

Any public portal on `bonfirehub.com` or `bonfirehub.ca`: type its name or paste its link. The built-in directory lists the portals we have verified, and lets you pick them by type and country without knowing their names.

#### Can I monitor Bonfire for new bids every day?

Schedule the Actor daily with `closingWithinDays` or your keywords, and compare `opportunityId` with your previous run, or sort by `closesAt`.

#### Does it need a Bonfire account?

No. It reads the same public lists that anyone sees on an agency's portal without signing in.

### Limitations

- The bid description, documents, addenda and Q\&A sit on each opportunity page behind a browser check, so they are not included. Every row links to that page (`url`).
- Bonfire does not publish the agency's state or province on its portal, so there is no state filter. Choose agencies by name, type or country instead.
- The directory covers the Bonfire portals we found and verified. Portals missing from it still work when you type their name.
- `daysLeft` and `bidStatus` are computed at the time of the run from Bonfire's closing time.

### Thanks for using this Actor

We build these tools for people who run them every day, and we improve them from what users tell us.

- **Missing a field, or need another filter?** Tell us in the **Issues** tab. If the data is there, we add it.
- **Found a bug or a wrong value?** Post the run ID in the **Issues** tab. Wrong data is the thing we fix first.

If this Actor saved you time, a short review helps other people find it.

# Actor input Schema

## `agencies` (type: `array`):

Bonfire agency portals to read: the part before .bonfirehub.com (for example dallascityhall), the full host (athabascau.bonfirehub.ca) or a copied portal link. Leave empty and pick agency types or countries below to read every matching portal from the built-in directory. Leave everything empty to read the whole directory.

## `agencyTypes` (type: `array`):

Adds every directory portal of these types to the agencies above. Empty = any type.

## `countries` (type: `array`):

Adds every directory portal in these countries to the agencies above. Combined with agency types, a portal must match both. Empty = any country.

## `include` (type: `array`):

Which portal lists to read. Open is the bids accepting submissions now. Past holds every earlier bid, including reopened bids with a new deadline and publicly awarded ones. Empty = open only.

## `statuses` (type: `array`):

Keep only rows with these statuses. Reopened, evaluating, closed, awarded and cancelled bids come from the past list, so add past to the lists for them. Empty = any status.

## `keywords` (type: `array`):

Keep a bid when its title or reference number contains any of these words, as a whole word, ignoring case. End a word with \* to match its beginning: pav\* finds paving and pavement. Empty = every bid.

## `excludeKeywords` (type: `array`):

Drop a bid when its title or reference number contains any of these whole words, for example janitorial or uniforms.

## `closingWithinDays` (type: `integer`):

Keep only bids whose deadline is between now and this many days from now, for example 7 for the coming week. Empty = any deadline.

## `closedOnOrAfter` (type: `string`):

YYYY-MM-DD (UTC). Past-list rows are kept only when their closing date is on or after this day, so you get recent awards instead of years of history. Open-list rows are not affected.

## `maxResults` (type: `integer`):

The most opportunity rows returned. You are charged per opportunity row, so this is your cost ceiling. Reason rows are free and do not count. Empty = 1000.

## Actor input object example

```json
{
  "agencies": [
    "dallascityhall"
  ],
  "include": [
    "open"
  ],
  "maxResults": 100
}
```

# Actor output Schema

## `results` (type: `string`):

One row per public procurement opportunity on the chosen Bonfire agency portals: agency name, type, country and time zone, opportunity ID, reference number, title, bid status, closing time in UTC and in the agency's local time, days left, previous closing date for reopened bids, whether the award is public, and the link to the opportunity, plus one free row when an input was rejected, an agency does not exist, a list could not be read, or nothing matched.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "agencies": [
        "dallascityhall"
    ],
    "include": [
        "open"
    ],
    "maxResults": 100
};

// Run the Actor and wait for it to finish
const run = await client.actor("neverempty/bonfire-bids-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "agencies": ["dallascityhall"],
    "include": ["open"],
    "maxResults": 100,
}

# Run the Actor and wait for it to finish
run = client.actor("neverempty/bonfire-bids-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "agencies": [
    "dallascityhall"
  ],
  "include": [
    "open"
  ],
  "maxResults": 100
}' |
apify call neverempty/bonfire-bids-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,neverempty/bonfire-bids-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/Z96KfMD4ZZqFDseDm/builds/CJGoO9iaV4aJ8DLFo/openapi.json
