# TenderHer Procurement Intelligence (`bitbychelle/tenderher-procurement-intelligence`) Actor

Evidence-backed procurement intelligence for African SMEs.

- **URL**: https://apify.com/bitbychelle/tenderher-procurement-intelligence.md
- **Developed by:** [Racheal Oluwayemisi Olayode](https://apify.com/bitbychelle) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 tender intelligence results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

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

TenderHer finds **real public tenders** across Nigerian procurement portals, reads the actual tender documents, pulls out the requirements with evidence, and tells you how ready your business is to bid — then serves it all as a live JSON API.

Instead of scrolling through portals and guessing which tenders fit, you get a shortlist with a MATCH / POTENTIAL_GAP / UNKNOWN verdict per requirement and a bid-readiness checklist. Every extracted requirement carries the exact text and source URL it came from, so you can verify it yourself.

**The Standby API is live and read-only — the Actor stays ready so you can call it:**

The Actor exposes a live, read-only API through Apify Standby mode. Direct requests to an Apify Standby endpoint may require Apify authentication, so do not assume the URL is unauthenticated; see the authentication note in the API section below.

```text
https://bitbychelle--tenderher-procurement-intelligence.apify.actor/api/health
```

It is a working, read-only frontend for the data — useful for prototypes and wiring up your own dashboard.

### Table of Contents

- [What does TenderHer do?](#what-does-tenderher-do)
- [Why use TenderHer?](#why-use-tenderher)
- [How to use TenderHer](#how-to-use-tenderher)
- [Input](#input)
- [Live API reference](#live-api-reference)
- [Output](#output)
- [Data table](#data-table)
- [Evidence-First Intelligence](#evidence-first-intelligence)
- [Requirement Assessment](#requirement-assessment)
- [Bid-readiness Checklist](#bid-readiness-checklist)
- [How it works](#how-it-works)
- [Technology](#technology)
- [Procurement Sources](#procurement-sources)
- [Pricing and cost](#pricing-and-cost)
- [Tips and advanced options](#tips-and-advanced-options)
- [FAQ, disclaimers, and support](#faq-disclaimers-and-support)
- [Source Code](#source-code)
- [For developers](#for-developers)
- [Limitations](#limitations)

### What does TenderHer do?

TenderHer discovers procurement opportunities from three real sources: **NOCOPO** (the federal portal), **Ebonyi State e-Procurement**, and **Jigawa State Due Process**. For each tender it downloads the tender document when one exists, extracts the text from PDFs, identifies the requirements (eligibility, documents, financial, technical, experience, submission), and produces an optional assessment against your business profile plus a bid-readiness checklist.

Because everything runs on the Apify platform, you get API access, scheduling, integrations, proxy rotation, and run monitoring out of the box — and Apify's own dataset export gives you the results as JSON, HTML, CSV, or Excel.

### Why use TenderHer?

- **Stop reading 200-page PDFs by hand.** Requirements are extracted automatically and each one is linked back to its source document and text.
- **Know your bid fit before you invest time.** A business profile turns requirements into MATCH / POTENTIAL_GAP / UNKNOWN so weak spots surface early.
- **One feed instead of three portals.** NOCOPO, Ebonyi, and Jigawa in a single, consistent schema.
- **Evidence-backed, not guessed.** No requirement appears without the text and URL that supports it.
- **Front-end friendly.** The standby API means a dashboard can load tender data without scraping anything itself.

### How to use TenderHer

**Option A — browse the live API (fastest)**

1. Open the [live API](https://bitbychelle--tenderher-procurement-intelligence.apify.actor/api/tenders) in a browser.
2. Confirm it is alive with `GET /api/health`, which returns `{"status":"ok"}`.
3. Pull a page of tenders with `GET /api/tenders?limit=25`.
4. Filter with `?source=`, `?status=`, or page with `?offset=`.
5. Open one tender with `GET /api/tenders/:id` to read its requirements, assessments, and checklist.

**Option B — run a fresh crawl**

1. Open the Actor in the Apify Console and go to **Input**.
2. Pick your `sources` and, optionally, fill in `businessProfile` to get assessments.
3. Click **Start run**.
4. Watch results stream into the **Output** tab, then export the dataset or call the API.

### Input

All fields are optional except where noted; the defaults produce a working run.

| Field | Default | Description |
| --- | --- | --- |
| `sources` | `["nocopo-bpp-ng"]` | Which portals to crawl: `nocopo-bpp-ng`, `ebonyi-egp`, `jigawa-dp`. |
| `sourceUrl` | NOCOPO listing URL | Override the NOCOPO listing page. |
| `maxTenders` | `15` | Maximum tenders to collect per selected source. |
| `maxRequestsPerCrawl` | `50` | Global cap on requests for the run. |
| `ebonyiOnlyOpenTenders` | `true` | For Ebonyi, keep only open tenders. |
| `businessProfile` | none | Optional. `industry`, `location`, `services`, `businessSize`, `experienceYears`, `certifications`, `annualTurnover`. Omit it and every assessment is `UNKNOWN` — nothing is invented. |

```json
{
    "sources": ["jigawa-dp", "ebonyi-egp"],
    "maxTenders": 10,
    "businessProfile": {
        "industry": "Technology",
        "location": "Lagos, Nigeria",
        "services": ["software development", "cloud services"],
        "businessSize": "SME",
        "experienceYears": 4,
        "certifications": [],
        "annualTurnover": 20000000
    }
}
```

### Live API reference

Base URL:

```text
https://bitbychelle--tenderher-procurement-intelligence.apify.actor
```

| Method | Path | Returns |
| --- | --- | --- |
| `GET` | `/api/health` | `{ "status": "ok" }` |
| `GET` | `/api/tenders` | `{ "data": [...], "total": number }` |
| `GET` | `/api/tenders/:id` | `{ "data": {...} }`, or `404` with `{ "error": "Tender not found" }` |

Query parameters for `/api/tenders`: `limit` (default `25`, max `100`), `offset`, `status`, `source`.

`:id` accepts a bare `sourceTenderId` or a composite `source:sourceTenderId` when ids could overlap across sources. URL-encode the `/` in ids.

```bash
## Health check
curl "https://bitbychelle--tenderher-procurement-intelligence.apify.actor/api/health"

## First 5 tenders
curl "https://bitbychelle--tenderher-procurement-intelligence.apify.actor/api/tenders?limit=5"

## Only Jigawa tenders that are still open
curl "https://bitbychelle--tenderher-procurement-intelligence.apify.actor/api/tenders?source=jigawa-dp&status=open"

## One tender with requirements, assessments, and checklist
curl "https://bitbychelle--tenderher-procurement-intelligence.apify.actor/api/tenders/jigawa-dp%3AJG%2FMOC%2F2026%2F002"
```

CORS is enabled for all origins, so a browser frontend can call the API directly. **Authentication:** direct requests to an Apify Standby endpoint may require Apify authentication, so the API is not guaranteed to be publicly unauthenticated. When you call it from your own code, send your token in an `Authorization: Bearer YOUR_TOKEN` header and keep the token in a server-side environment variable — never put it in frontend code or in a URL query string. To try the endpoints without handling a token at all, open the **Endpoints** tab of the Actor in the Apify Console.

### Output

Each item is one normalized tender. Simplified:

```json
{
    "source": "jigawa-dp",
    "country": "Nigeria",
    "sourceTenderId": "JG/MOC/2026/002",
    "title": "CONSULTANCY SERVICES FOR THE CONSTRUCTION OF MODERN MARKET",
    "buyer": "Jigawa State Government",
    "status": "Open",
    "deadline": "2026-08-14T00:00:00.000Z",
    "tenderUrl": "https://dueprocess.jigawastate.gov.ng/tenders/2",
    "documentUrls": ["https://dueprocess.jigawastate.gov.ng/storage/tenders/Y9gEA8SjnBItDsQOLZqaInVzr10ck5ix9KLIe0UZ.pdf"],
    "requirementsStatus": "EXTRACTED",
    "requirements": [
        {
            "requirement": "The consultant must have at least 5 years of relevant experience",
            "type": "experience",
            "evidence": {
                "text": "The consultant must have at least 5 years of relevant experience",
                "sourceUrl": "https://dueprocess.jigawastate.gov.ng/storage/tenders/Y9gEA8SjnBItDsQOLZqaInVzr10ck5ix9KLIe0UZ.pdf"
            }
        }
    ],
    "businessProfileProvided": true,
    "assessments": [
        {
            "requirement": "The consultant must have at least 5 years of relevant experience",
            "assessment": "POTENTIAL_GAP",
            "reason": "the business profile lists 4 years of experience, which is below the 5-year requirement"
        }
    ],
    "readinessChecklist": [
        {
            "item": "The consultant must have at least 5 years of relevant experience",
            "status": "NEEDS_REVIEW"
        }
    ]
}
```

Each tender is stored as one record in an Apify Dataset. Apify's built-in dataset export lets you download that dataset in various formats such as JSON, HTML, CSV, or Excel, from the Console or the Apify API — TenderHer itself does not implement separate exporters.

### Data table

| Field | Description |
| --- | --- |
| `source` / `sourceTenderId` | Portal and the tender's own identifier. |
| `country` | Always `Nigeria` for the current sources. |
| `title` | Tender title as published. |
| `buyer` / `procurementType` / `category` | Buying entity, procurement method, and category. |
| `referenceNumber` | Published reference or lot number. |
| `publicationDate` / `deadline` / `openingDate` | Key dates. |
| `location` | Bid location or place of execution. |
| `status` | Tender status, e.g. `Open`. |
| `estimatedValue` / `currency` / `bidSecurity` | Financial terms. |
| `tenderUrl` / `documentUrls` / `documentText` | Source page, documents found, and extracted PDF text. |
| `requirements` / `requirementsStatus` | Extracted requirements with evidence; `EXTRACTED` or `NONE`. |
| `businessProfileProvided` | Whether a profile was supplied for this run. |
| `assessments` | `MATCH`, `POTENTIAL_GAP`, or `UNKNOWN` per requirement, with a reason. |
| `readinessChecklist` | Actionable checklist for preparing a bid. |
| `evidence` | Text and source URL backing each requirement. |

### How it works

1. **Read the input** — the run picks the selected `sources`, the crawl limits, and an optional `businessProfile`; unknown source ids are ignored with a warning, and the run stops early if none of the selected ids is supported.
2. **Crawl the portals** — a Cheerio crawler reads the listing pages over plain HTTP. No headless browser is used.
3. **Normalize each tender** — every listing becomes one record with the portal id, title, buyer, reference, dates, budget terms, status, document URLs, and the source page URL.
4. **Read the documents** — when a tender has a PDF, it is downloaded and its text is extracted inside the Actor.
5. **Extract requirements** — eligibility, documentary, financial, technical, experience, and submission requirements are extracted, each with the exact source text and document URL.
6. **Assess and build a checklist** — if a `businessProfile` was supplied, every requirement gets a `MATCH`, `POTENTIAL_GAP`, or `UNKNOWN` verdict with a reason, plus a bid-readiness checklist.
7. **Publish the data** — each record is pushed to the run's Apify Dataset. In Standby mode the same records are served over the read-only HTTP API below.

### Technology

- **Runtime:** Node.js 24 on the `apify/actor-node:24` Docker image (Apify Actor runtime), running JavaScript with ES modules. No web framework.
- **Scraping:** Crawlee `@crawlee/cheerio` (`CheerioCrawler`) — plain HTTP requests with concurrency limits and retries, no headless browser.
- **Apify Actor SDK:** the `apify` package for the Actor lifecycle, Datasets, and `apify/log` (credential-censored logging). Runs in Standby mode with an HTTP readiness probe.
- **Storage:** Apify Datasets hold one normalized tender record per item; the output schema links to the run dataset.
- **API:** Node's built-in `node:http` server — the read-only endpoints add zero runtime dependencies, enable CORS, and read the dataset per request.
- **Documents:** custom, in-house PDF text extraction.
- **Requirement extraction:** deterministic, rule-based logic written in this repository (`src/requirements.js`), operating on the extracted document text.
- **Business-profile matching:** deterministic rule-based logic (`src/matching.js`) producing `MATCH` / `POTENTIAL_GAP` / `UNKNOWN` — the same tender and profile always yield the same assessment.
- **Schemas:** JSON Schemas for the Actor definition, input, output, and dataset views.
- **Quality:** Vitest 5 (134 tests, including real captured HTML/PDF fixtures), ESLint with `@apify/eslint-config`, Prettier, and a GitHub Actions workflow that runs the tests on every push.
- **Not used:** no OCR, no machine learning, no LLM, and no external AI provider. TenderHer has exactly two runtime dependencies (`apify` and `@crawlee/cheerio`); all extraction, assessment, and matching logic is local, deterministic code, and no tender document is sent to a third-party model or service.

### Source Code

TenderHer is open source and the Actor source code is available on GitHub:

**GitHub:** https://github.com/Ifeoluwayemisi/tenderherr

The repository contains the Actor implementation, procurement source adapters, document extraction, requirement extraction, business-profile matching, API, tests, and Apify configuration.

### Pricing and cost

Pricing for this Actor is shown on its **Pricing** tab. What you spend depends on which side you use:

- **Browsing the live API** costs only the platform compute for standby runs that serve your requests.
- **Running a crawl** costs compute proportional to the work: `maxRequestsPerCrawl` and `maxTenders` are the main levers, and `businessProfile` adds no extra scraping cost.

Keep runs small while testing, then raise the limits for a scheduled production run. The Apify Console shows estimated cost before you start.

### Tips and advanced options

- **Raise `maxTenders` for breadth, not `maxRequestsPerCrawl` for speed.** More requests per tender means deeper detail but higher cost.
- **Always send a `businessProfile`** if you care about assessments — without one every verdict is `UNKNOWN` by design.
- **Combine sources** in one run (`["nocopo-bpp-ng", "jigawa-dp"]`) for a single unified feed.
- **Filter server-side in your frontend.** Use `source` and `status` rather than downloading everything and filtering in the browser.
- **Schedule runs** in the Console to keep the dataset fresh automatically.

### FAQ, disclaimers, and support

**Does this replace reading the tender document?** No, and it should not. TenderHer tells you what it found and where it found it so you can decide what matters. Always verify against the official document before bidding.

**Where does the data come from?** Publicly accessible procurement portals: NOCOPO, Ebonyi State e-Procurement, and Jigawa State Due Process. TenderHer does not bypass paywalls, logins, or access controls.

**How accurate is the extraction?** Requirements are extracted from the real tender document text with the source text preserved, but PDF layout quirks can affect extraction. Treat results as a fast first pass, not legal or procurement advice.

**Is my business profile stored?** It is used only for that run's assessments and is not written into the output records beyond the `businessProfileProvided` flag.

**Something looks wrong or missing.** Use the **Issues** tab on the Actor page to report it. Custom scrapers, extra sources, and tailored output schemas are also possible on request.

***

#### For developers

<details>
<summary>Project structure and local development</summary>

```text
.actor/            # Actor config, input/output/dataset schemas
src/
├── main.js        # Entry point: input parsing, router assembly, API + crawl
├── api.js         # Read-only HTTP API (node:http, no extra dependencies)
├── routes.js      # Crawlee routers and document download
├── requirements.js# Requirement extraction from document text
├── matching.js    # Assessments and bid-readiness checklist
└── sources/       # nocopo, ocds, ebonyi, jigawa, pdf
test/              # 134 tests (vitest)
```

```bash
npm install
npm test
npm run lint
node src/main.js   # local run
```

Server-side environment variables: `APIFY_TOKEN` (injected by Apify), `TENDERHER_DATASET_ID` (named dataset shared by crawl runs and the standby API), and `ACTOR_WEB_SERVER_PORT` (listen port, default `4321`).

A standby run serves the API and never crawls. A standard run from the Console, the API, or a schedule crawls and exits as usual.

</details>

# Actor input Schema

## `sources` (type: `array`):

Procurement sources to crawl. NOCOPO yields broad federal discovery; Ebonyi State e-Procurement yields document-rich state tenders with notice PDF text; Jigawa State Due Process Bureau yields open tenders with downloadable tender documents.

## `sourceUrl` (type: `string`):

NOCOPO (Nigeria Bureau of Public Procurement) grid page to discover tenders from.

## `maxTenders` (type: `integer`):

Maximum number of tender records to collect across all pages.

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

Maximum number of requests that can be made by this crawler.

## `ebonyiOnlyOpenTenders` (type: `boolean`):

Only collect Ebonyi tenders whose deadline is still in the future. Disable to collect the most recently advertised Ebonyi tenders regardless of deadline, which is useful when verifying the notice PDF pipeline.

## `businessProfile` (type: `object`):

Optional business profile used to assess extracted requirements as MATCH, POTENTIAL_GAP, or UNKNOWN. Leave empty to keep every assessment UNKNOWN. Registration, tax status, and legal eligibility are never inferred.

## Actor input object example

```json
{
  "sources": [
    "nocopo-bpp-ng"
  ],
  "sourceUrl": "https://nocopo.bpp.gov.ng/noc/frmcitizendashboard3.aspx",
  "maxTenders": 15,
  "maxRequestsPerCrawl": 50,
  "ebonyiOnlyOpenTenders": true
}
```

# Actor output Schema

## `tenders` (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 = {
    "sources": [
        "nocopo-bpp-ng"
    ],
    "sourceUrl": "https://nocopo.bpp.gov.ng/noc/frmcitizendashboard3.aspx",
    "ebonyiOnlyOpenTenders": true
};

// Run the Actor and wait for it to finish
const run = await client.actor("bitbychelle/tenderher-procurement-intelligence").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 = {
    "sources": ["nocopo-bpp-ng"],
    "sourceUrl": "https://nocopo.bpp.gov.ng/noc/frmcitizendashboard3.aspx",
    "ebonyiOnlyOpenTenders": True,
}

# Run the Actor and wait for it to finish
run = client.actor("bitbychelle/tenderher-procurement-intelligence").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 '{
  "sources": [
    "nocopo-bpp-ng"
  ],
  "sourceUrl": "https://nocopo.bpp.gov.ng/noc/frmcitizendashboard3.aspx",
  "ebonyiOnlyOpenTenders": true
}' |
apify call bitbychelle/tenderher-procurement-intelligence --silent --output-dataset

```

## MCP server setup

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

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/Zi2iyTBZudYOihymP/builds/x6cibiuhq6JTcVzFE/openapi.json
