# BidWisely - African Procurement Matcher & AI Agent (`preciousvictory/bidwisely-procurement-matcher`) Actor

AI-powered procurement intelligence for African SMEs. Crawls Nigerian public tender portals, extracts opportunity data via GPT-4o-mini, matches to your SME profile, and delivers an executive briefing. Pay-per-event monetisation.

- **URL**: https://apify.com/preciousvictory/bidwisely-procurement-matcher.md
- **Developed by:** [Victory Abiodun-Omoniyi](https://apify.com/preciousvictory) (community)
- **Stats:** 2 total users, 1 monthly users, 87.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.01 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## BidWisely – African Procurement Matcher & AI Agent

> **AI-powered procurement intelligence for African SMEs.**\
> Discover Nigerian tenders, extract structured data via GPT-4o-mini, match opportunities to your business profile, and receive an executive briefing — all in one live Actor run.

***

### What It Does

African SMEs lose billions in potential government and private contracts every year. Procurement information is **scattered across hundreds of poorly formatted portals, inaccessible PDFs, and fragmented government websites**. Identifying a relevant tender and determining if a business meets the eligibility criteria usually takes days of manual effort.

**BidWisely** is an autonomous, end-to-end procurement intelligence engine that automates this entire pipeline. It acts as an elite, AI-driven procurement analyst for your business:

1. **Aggressive Web Crawling:** Deep-crawls popular Nigerian and pan-African procurement portals (e.g., BPP, NoCoPo, Lagos PPA, eTenders), traversing nested lists and complex pagination.
2. **Boilerplate Destruction:** Intelligently cleans raw, squished HTML DOMs, stripping away headers, footers, and modal pop-ups to isolate the pure procurement text.
3. **Multi-Model AI Extraction:** Uses advanced LLMs (OpenAI, Gemini, or Claude) to structure messy, unstructured tender articles into clean JSON (extracting title, buyer, category, location, deadline, exact contract values, and strict eligibility requirements).
4. **Algorithmic Profiling:** Runs a transparent, weighted scoring engine against your exact SME Profile (evaluating financial capacity, location, industry, and certifications) to calculate a definitive `matchScore` and flag missing requirements.
5. **Executive AI Briefing:** Instead of just giving you a spreadsheet, an AI Agent generates a highly detailed, personalized executive summary—explaining *exactly why* top tenders fit your business, detailing the missing requirements, and providing actionable next steps.

```text
Procurement Portals
      ↓
  CheerioCrawler
      ↓
  DOM Cleansing & Multi-LLM Extraction (OpenAI/Gemini/Claude)
      ↓
  Weighted SME Matching Engine
      ↓
  Dataset (Ranked Tenders)
      ↓
  AI Agent Executive Briefing (KV Store)
```

***

### Technologies & Stack

BidWisely is built for scale, speed, and multi-model intelligence:

- **[Apify SDK](https://sdk.apify.com/) & [Crawlee](https://crawlee.dev/):** Utilizes `CheerioCrawler` for blazing-fast, concurrent, HTML-only scraping, circumventing the massive performance overhead of headless browsers.
- **TypeScript:** Fully typed architecture ensuring robust data contracts from the scraper to the AI Agent.
- **Zod:** Strict runtime schema validation for LLM outputs, ensuring the AI never hallucinates invalid fields or corrupted datasets.
- **Multi-LLM Fallback Engine:** Natively integrates the official SDKs for **OpenAI** (GPT-4o-mini), **Google Gemini** (1.5 Flash), and **Anthropic** (Claude 3.5 Haiku). It seamlessly falls back across providers if one rate-limits, and gracefully degrades to a fast, regex-based heuristic extractor if AI is disabled.
- **Pay-Per-Event (PPE) Monetization:** Native integration with Apify's PPE billing framework, tracking precise, transparent micro-charges for AI extractions and agent briefings.

***

### Input Fields

| Field | Type | Required | Description |
|---|---|---|---|
| `startUrls` | Array | ✅ | Procurement portal listing URLs to crawl |
| `aiProvider` | String | – | AI provider to use: `openai`, `gemini`, or `claude` (default: `openai`) |
| `aiApiKey` | String (secret) | – | Your API key for the selected provider |
| `aiModel` | String | – | (Optional) Override the default model (e.g. `gemini-1.5-flash-latest`) |
| `smeProfile` | Object | ✅ | Your business profile (see shape below) |
| `maxItems` | Integer | – | Max tender pages to process (default: 10) |
| `maxRequestsPerCrawl` | Integer | – | Hard cap on total HTTP requests (default: 200) |
| `keywords` | Array of strings | – | Only process pages containing these keywords |
| `enableAiExtraction` | Boolean | – | Use AI to extract structured fields. When disabled, totally ignores AI and falls back to a fast heuristic extractor (no API charges). (default: true) |

#### SME Profile Shape

```json
{
  "industry": "Construction",
  "location": "Lagos",
  "capacity": 20000000,
  "certifications": ["CAC", "Tax Clearance"],
  "services": ["Building and Construction Services", "Renovation"],
  "experience": ["Construction Projects", "Government Contracts"]
}
```

***

### Sample Output — Dataset Item

```json
{
  "title": "Provision of Catering Services for Staff Canteen",
  "buyer": "Federal Ministry of Finance",
  "category": "Catering",
  "location": "Lagos",
  "deadline": "2026-10-15",
  "contractValue": "₦18,000,000",
  "contractValueMax": 18000000,
  "requirements": [
    "CAC registration",
    "Tax clearance certificate"
  ],
  "sourceUrl": "https://www.globaltenders.com/nigeria-tenders/12345",
  "publishedAt": "2026-09-24T19:30:00.000Z",
  "extractionSource": "gemini",
  "matchScore": 90,
  "matchLabel": "Excellent Match 🟢",
  "matchBasis": "industry (35%), location (25%), capacity (20%), certifications (10%), keywords (10%)",
  "matchChecks": {
    "industry": true,
    "location": true,
    "capacity": true,
    "certifications": true,
    "keywords": true
  },
  "matchExplanation": "Match score: 90% based on industry, location, capacity, certifications, keywords. Passed 5/5 criteria. No immediate gaps detected.",
  "missingRequirements": []
}
```

***

### Sample AI Agent Briefing (Key-Value Store: `AGENT_SUMMARY` & `ALL_OPPORTUNITIES`)

At the end of the run, the Actor writes two files to your Key-Value Store:

1. `ALL_OPPORTUNITIES.json`: A full dump of every tender processed, separating the `opportunityDetails` from the `matchingConclusion`.
2. `AGENT_SUMMARY.json`: An AI-generated executive briefing.

```json
{
  "generatedAt": "2026-09-24T20:01:23.456Z",
  "totalOpportunities": 8,
  "topMatches": [
    {
      "title": "Provision of Catering Services for Staff Canteen",
      "matchScore": 90,
      "matchLabel": "Excellent Match 🟢",
      "deadline": "2026-10-15",
      "contractValue": "₦18,000,000"
    }
  ],
  "aiGenerated": true,
  "aiProvider": "gemini",
  "urgentDeadlines": [],
  "briefing": "Your top opportunity is the Federal Ministry of Finance catering tender at ₦18M — a near-perfect match. You hold all required certifications and your capacity comfortably covers the value. Recommended next steps: (1) Download the full RFP from the source URL, (2) Prepare your CAC and tax clearance certificates for submission, (3) Submit a competitive bid by 15 October. Two other partial matches in Abuja may require location flexibility..."
}
```

***

### Pricing — Pay-Per-Event (PPE)

BidWisely uses **Pay-Per-Event** monetisation on the Apify Store. You only pay for the work actually done, making it incredibly cost-effective.

| Event | Price per 1,000 | Triggered When |
|---|---|---|
| `apify-actor-start` *(synthetic)* | $0.01 | Actor run starts |
| `ai-extraction` | $0.50 | AI structures one opportunity. **(Skipped if `enableAiExtraction` is false or you provide your own API key)** |
| `agent-insight` | $1.00 | The AI Agent generates the actionable executive briefing |
| `apify-default-dataset-item` *(synthetic)* | $0.01 | Each matched record pushed to the dataset |

#### Example cost for a typical run (1,000 opportunities)

- Actor Start → $0.00001
- 1,000 AI extractions (without own key) → $0.50
- 1 actionable insight briefing → $0.001
- 1,000 dataset item results → $0.01
- **Total ≈ $0.51** for 1,000 fully matched, AI-briefed procurement opportunities!

> Set `ACTOR_MAX_TOTAL_CHARGE_USD` in your run configuration to cap your total spend.

***

### Use Cases

| Who | How They Use BidWisely |
|---|---|
| **African SMEs** | Daily scan for new tenders matching their profile; instant match score tells them where to focus |
| **Procurement Consultants** | Run on behalf of multiple clients (different `smeProfile` per run) to identify opportunities |
| **NGOs & Development Orgs** | Monitor for grant/service contracts in their sector |
| **Government Agencies** | Audit procurement transparency by tracking what's published across portals |
| **Researchers** | Build datasets of Nigerian procurement activity for policy analysis |

***

### Scheduling — Continuous Intelligence

Set up an **Apify Scheduler** to run BidWisely automatically:

- **Daily** at 08:00 WAT → fresh morning briefing
- **Twice weekly** for lower-volume SMEs

Every run pushes new tenders to the dataset and overwrites `AGENT_SUMMARY` with the latest briefing.

***

### FAQ

**Q: Do I need to provide my own OpenAI API key?**\
A: Yes. Your key is marked as `isSecret` in the input schema — it's never logged or stored by BidWisely. You can also set `enableAiExtraction: false` to use the heuristic extractor (free, but less accurate).

**Q: Why CheerioCrawler and not Playwright?**\
A: Most Nigerian procurement portals serve static HTML. CheerioCrawler is ~10× faster and cheaper than a browser crawler. Playwright would only be needed for JavaScript-heavy SPAs.

**Q: Can I add more portals?**\
A: Yes — simply add more URLs to `startUrls`. BidWisely's universal link-detection strategy adapts to any HTML structure.

**Q: What happens if a page blocks the crawler?**\
A: Requests are retried up to 3 times. Add Apify Proxy to your run configuration (Residential proxies) to bypass blocks on government portals.

**Q: Is my API key safe?**\
A: The `isSecret: true` flag prevents it from appearing in logs. It's stored only in the Actor's input for the duration of the run.

**Q: How accurate is the matching?**\
A: The match score is a **relevance indicator**, not a win prediction. It shows how well the opportunity aligns with your stated profile. Always read the full tender before bidding.

***

### Project Structure

```
.actor/
├── actor.json          # Actor metadata, memory bounds, categories
├── input_schema.json   # Input validation & Apify Console form
├── output_schema.json  # Dataset + KV Store output links
└── dataset_schema.json # Output tab column definitions
src/
├── main.ts             # Entry point: input validation, crawler setup, agent trigger
├── routes.ts           # Cheerio router: LISTING + DETAIL handlers
├── extractor.ts        # AI (GPT-4o-mini) + heuristic extraction
├── matcher.ts          # Weighted scoring engine
├── agent.ts            # AI Agent: ranking, briefing, KV Store save
└── types.ts            # Shared TypeScript interfaces
Dockerfile              # Container image
```

***

### Quick Start

#### Step 1 — Install dependencies

```bash
npm install
```

#### Step 2 — Add your API key

**Option A — `.env` file ✅ recommended for local development**

Create a `.env` file in the project root (already in `.gitignore` — won't be committed) and add the key for your preferred provider:

```
OPENAI_API_KEY=sk-your-key-here
## or
GEMINI_API_KEY=AIzaSy...
## or 
CLAUDE_API_KEY=sk-ant-api03...
```

The Apify SDK loads this automatically when you run `apify run`.

**Option B — directly in `INPUT.json`**

Add it directly to your `storage/key_value_stores/default/INPUT.json` along with your provider selection:

```json
{ 
  "aiProvider": "gemini",
  "aiApiKey": "AIzaSy..." 
}
```

**Option C — No API key (Heuristic Mode)**

Set `"enableAiExtraction": false` in your input. The Actor will completely bypass all AI operations, avoiding API keys and charges altogether. It will use the fast regex-based heuristic extractor — highly scalable but less accurate.

***

#### Step 3 — Set up your local input

Create `storage/key_value_stores/default/INPUT.json` (omit `aiApiKey` if you used Option A):

```json
{
  "startUrls": [
    { "url": "https://www.globaltenders.com/nigeria-tenders" },
    { "url": "https://etenders.com.ng/" }
  ],
  "smeProfile": {
    "industry": "Construction",
    "location": "Lagos",
    "capacity": 20000000,
    "certifications": ["CAC", "Tax Clearance"],
    "services": ["Building and Construction Services", "Renovation"]
  },
  "maxItems": 5,
  "enableAiExtraction": true,
  "aiProvider": "gemini"
}
```

#### Step 4 — Run locally

```bash
apify run
```

#### Step 5 — Deploy to Apify

```bash
apify login   # enter your Apify API token when prompted
apify push    # builds and deploys to the Cloud
```

***

### Integrations

Use the Apify API to trigger BidWisely from your backend:

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

const client = new ApifyClient({ token: 'YOUR_APIFY_TOKEN' });

const run = await client.actor('YOUR_USERNAME/bidwisely-procurement-matcher').call({
  startUrls: [{ url: 'https://www.globaltenders.com/nigeria-tenders' }],
  openAiApiKey: process.env.OPENAI_API_KEY,
  smeProfile: { industry: 'IT Services', location: 'Abuja', capacity: 50000000 },
  maxItems: 20,
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
const summary = await client.keyValueStore(run.defaultKeyValueStoreId).getRecord('AGENT_SUMMARY');

console.log(`Found ${items.length} matched opportunities`);
console.log('AI Briefing:', summary.value.briefing);
```

***

*Built for the Apify Hackathon. BidWisely — because every Nigerian SME deserves a fair shot at public contracts.*

# Actor input Schema

## `startUrls` (type: `array`):

Procurement listing URLs to crawl. Add as many portals as needed.

## `aiProvider` (type: `string`):

Select the AI provider to use for extraction (OpenAI, Gemini, Claude).

## `aiApiKey` (type: `string`):

Your API key for the selected AI provider. Required when 'Enable AI Extraction' is true.

## `aiModel` (type: `string`):

Override the default model (e.g., gpt-4o, gemini-1.5-pro, claude-3-5-sonnet). Leave blank for sensible defaults.

## `smeProfile` (type: `object`):

Your business profile. The matching engine uses this to score every opportunity.

## `maxItems` (type: `integer`):

Maximum number of individual tender pages to extract and match. Keep low for demos.

## `maxRequestsPerCrawl` (type: `integer`):

Hard cap on total HTTP requests. Prevents runaway crawls.

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

If set, only process tender pages containing at least one of these keywords (case-insensitive).

## `enableAiExtraction` (type: `boolean`):

Use GPT-4o-mini to extract structured fields. When disabled, falls back to a fast heuristic extractor (no API charges).

## `proxyConfiguration` (type: `object`):

Select proxies to be used. Apify Proxy is recommended to bypass blocks on sites like globaltenders.com.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.tender.ng/"
    },
    {
      "url": "https://etenders.com.ng/"
    },
    {
      "url": "https://www.globaltenders.com/nigeria-tenders"
    },
    {
      "url": "https://bpp.gov.ng"
    },
    {
      "url": "https://nocopo.bpp.gov.ng/"
    },
    {
      "url": "https://lagosppa.gov.ng/"
    },
    {
      "url": "https://www.nigeriatenders.com/"
    },
    {
      "url": "https://procurement.ngojobsite.com/"
    },
    {
      "url": "https://publicprocurement.org/"
    },
    {
      "url": "https://ebid.com.ng/"
    }
  ],
  "aiProvider": "openai",
  "smeProfile": {
    "industry": "Construction",
    "location": "Lagos",
    "capacity": 20000000,
    "certifications": [
      "CAC",
      "Tax Clearance"
    ],
    "services": [
      "Building and Construction Services",
      "Renovation and Maintenance",
      "Sale and Installation of Construction Aluminum roofing and ceiling materials"
    ],
    "experience": [
      "Construction Projects",
      "Government Contracts"
    ]
  },
  "maxItems": 10,
  "maxRequestsPerCrawl": 200,
  "keywords": [],
  "enableAiExtraction": true,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `matchedOpportunities` (type: `string`):

No description

## `agentSummary` (type: `string`):

No description

# 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 = {
    "startUrls": [
        {
            "url": "https://www.tender.ng/"
        },
        {
            "url": "https://etenders.com.ng/"
        },
        {
            "url": "https://www.globaltenders.com/nigeria-tenders"
        },
        {
            "url": "https://bpp.gov.ng"
        },
        {
            "url": "https://nocopo.bpp.gov.ng/"
        },
        {
            "url": "https://lagosppa.gov.ng/"
        },
        {
            "url": "https://www.nigeriatenders.com/"
        },
        {
            "url": "https://procurement.ngojobsite.com/"
        },
        {
            "url": "https://publicprocurement.org/"
        },
        {
            "url": "https://ebid.com.ng/"
        }
    ],
    "smeProfile": {
        "industry": "Construction",
        "location": "Lagos",
        "capacity": 20000000,
        "certifications": [
            "CAC",
            "Tax Clearance"
        ],
        "services": [
            "Building and Construction Services",
            "Renovation and Maintenance",
            "Sale and Installation of Construction Aluminum roofing and ceiling materials"
        ],
        "experience": [
            "Construction Projects",
            "Government Contracts"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("preciousvictory/bidwisely-procurement-matcher").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 = {
    "startUrls": [
        { "url": "https://www.tender.ng/" },
        { "url": "https://etenders.com.ng/" },
        { "url": "https://www.globaltenders.com/nigeria-tenders" },
        { "url": "https://bpp.gov.ng" },
        { "url": "https://nocopo.bpp.gov.ng/" },
        { "url": "https://lagosppa.gov.ng/" },
        { "url": "https://www.nigeriatenders.com/" },
        { "url": "https://procurement.ngojobsite.com/" },
        { "url": "https://publicprocurement.org/" },
        { "url": "https://ebid.com.ng/" },
    ],
    "smeProfile": {
        "industry": "Construction",
        "location": "Lagos",
        "capacity": 20000000,
        "certifications": [
            "CAC",
            "Tax Clearance",
        ],
        "services": [
            "Building and Construction Services",
            "Renovation and Maintenance",
            "Sale and Installation of Construction Aluminum roofing and ceiling materials",
        ],
        "experience": [
            "Construction Projects",
            "Government Contracts",
        ],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("preciousvictory/bidwisely-procurement-matcher").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 '{
  "startUrls": [
    {
      "url": "https://www.tender.ng/"
    },
    {
      "url": "https://etenders.com.ng/"
    },
    {
      "url": "https://www.globaltenders.com/nigeria-tenders"
    },
    {
      "url": "https://bpp.gov.ng"
    },
    {
      "url": "https://nocopo.bpp.gov.ng/"
    },
    {
      "url": "https://lagosppa.gov.ng/"
    },
    {
      "url": "https://www.nigeriatenders.com/"
    },
    {
      "url": "https://procurement.ngojobsite.com/"
    },
    {
      "url": "https://publicprocurement.org/"
    },
    {
      "url": "https://ebid.com.ng/"
    }
  ],
  "smeProfile": {
    "industry": "Construction",
    "location": "Lagos",
    "capacity": 20000000,
    "certifications": [
      "CAC",
      "Tax Clearance"
    ],
    "services": [
      "Building and Construction Services",
      "Renovation and Maintenance",
      "Sale and Installation of Construction Aluminum roofing and ceiling materials"
    ],
    "experience": [
      "Construction Projects",
      "Government Contracts"
    ]
  }
}' |
apify call preciousvictory/bidwisely-procurement-matcher --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,preciousvictory/bidwisely-procurement-matcher"
        }
    }
}
```

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/W3gxEglal64zEHIzz/builds/IluqtVzvy0fKTx2LK/openapi.json
