# MarketPulse Nigeria — AI Pricing & Market Saturation Agent (`brendy/market-pulse-nigeria`) Actor

Ask in plain language what a product sells for in Nigeria. An AI agent plans the searches, pulls live listings from Jumia and TikTok, and returns a price band, common price points, a market saturation score, and a plain-language pricing verdict.

- **URL**: https://apify.com/brendy/market-pulse-nigeria.md
- **Developed by:** [Rukome Okoro](https://apify.com/brendy) (community)
- **Stats:** 1 total users, 0 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $7.00 / 1,000 results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
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

**MarketPulse Nigeria** tells a young Nigerian intending to start a small business the current cost of a product, and how saturated the market already is. By entering an item in the search field — e.g. *"women's shoes"*, *"okrika bale"*, *"zobo"* — the Actor returns a price band, the prices sellers cluster on, a market saturation score, and a concrete range to price within. This solution is for any Nigerian trying to start a small business from the comfort of their home.

It reads live listings from [Jumia](https://www.jumia.com.ng) (formal retail) and [Jiji](https://jiji.ng) (classifieds and secondhand), and returns both a structured dataset and a ready-to-read report. It runs on the Apify platform, so it can be scheduled, called from an API, or left running as a web dashboard.

### What does MarketPulse Nigeria do?

A young Nigerian intending to start a small business in women jewellery would usually ask around, guess, and they either leave money on the table or price themselves out of a sale. Formal market data for African small business simply does not exist at this level.

MarketPlus Actor closes that gap. Given a plain-language description of a product, it:

1. **Rewrites your words into terms the marketplaces can answer.** This is not cosmetic. Searching Jumia for *"bag of rice"* returns **storage containers** — the words match the packaging, not the contents. Searching *"rice"* returns rice. *"okrika bale"* returns nothing on Jumia at all, because it is listed there as *"thrift bale"*. Without this step the results are confidently wrong.
2. **Collects live listings from both sides of the market** — formal retail on Jumia, and the informal trade on Jiji where traders, thrift sellers and secondhand goods actually change hands.
3. **Computes a price band** — lowest, highest — after removing listings priced so far from the middle that they are clearly a different product or pack size.
4. **Finds the prices sellers cluster on**, which is often more useful than an average. For women's shoes on Jumia, 87% of listings sit between ₦7,000 and ₦9,000.
5. **Compares retail against the informal market.** Rice retails around ₦27,000 on Jumia and around ₦65,000 on Jiji, because Jiji is mostly full 50kg bags from traders. That gap is your margin.
6. **Scores market saturation** from 0–100, and shows the three inputs behind the number so you can judge it yourself.

### Why use MarketPulse Nigeria?

- **Price with evidence instead of guesswork.** A concrete price range, drawn from listings that exist today.
- **See how crowded a market is before you enter it.** Fifty sellers on a commodity with tightly clustered prices is a very different business to fourteen sellers with a wide spread.
- **See wholesale and retail side by side.** The gap between what a thing costs on Jiji and what it sells for on Jumia is the single most actionable number for someone buying to resell.
- **Spot the traps.** Bulk and single units sit side by side in search results. The report flags this rather than quietly averaging a 50kg sack with a single sachet.
- **Build on it.** Every listing is pushed to a dataset you can export as JSON, CSV or Excel, or pull through the Apify API.

### How to use MarketPulse Nigeria

1. Open the **Input** tab.
2. Type what you are pricing in **What are you pricing?** — everyday wording is fine.
3. Optionally set your **location**, which filters Jiji listings to your state, and raise **Maximum listings** for a wider sample.
4. Click **Start**.
5. Read the report on the run's **Output** tab, or browse every listing in the **Dataset** tab.

For repeat checks, set a **Schedule** and the Actor will track the same product over time.

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `query` **(required)** | string | — | What you're pricing, in plain language |
| `location` | string | — | Where you sell, e.g. "Mile 12, Lagos". Filters Jiji listings to that state; Jumia listings are national |
| `country` | enum | `ng` | Marketplace country: `ng`, `ke`, `gh`, `eg` (Jiji covers `ng`, `ke`, `gh` only) |
| `platforms` | array | `["jumia","jiji"]` | Sources to use: `jumia`, `jiji`, `tiktok` |
| `maxResults` | integer | `60` | Upper bound on listings collected (10–200) |
| `agentMode` | boolean | `true` | Reserved for the AI planner — see Roadmap |
| `useProxy` | boolean | `true` | Routes Jumia requests through Apify residential proxy. **Required on the platform** — Jumia blocks datacenter IPs |
| `proxyGroups` | array | `["RESIDENTIAL"]` | Which Apify Proxy groups to use |
| `llmApiKey` | secret | — | Optional Anthropic API key — see Roadmap |

```json
{
  "query": "okrika bale",
  "location": "Mile 12, Lagos",
  "country": "ng",
  "platforms": ["jumia", "jiji"],
  "maxResults": 60
}
```

### Output

Every listing becomes one dataset row. Download as JSON, HTML, CSV or Excel.

```json
{
  "platform": "jumia",
  "title": "Big Bull Rice 50Kg",
  "price": 75499.0,
  "currency": "NGN",
  "url": "https://www.jumia.com.ng/big-bull-rice-50kg-399018372.html",
  "seller_id": "479970",
  "brand": "Big Bull",
  "categories": ["Grocery", "Dried Beans, Grains & Rice", "Rice & Grains"],
  "rating": 4.1,
  "rating_count": 124,
  "is_sponsored": false,
  "region": null,
  "is_verified_seller": false,
  "source_query": "rice",
  "scraped_at": "2026-09-25T10:03:26Z"
}
```

The full report — price band, common price points, saturation score and verdict — is saved to the key-value store under `OUTPUT`, and to a shared store named `market-pulse-reports` so scheduled runs and the dashboard see the same cache.

#### Data fields

| Field | Description |
|---|---|
| `platform` | Source marketplace — `jumia`, `jiji` or `tiktok` |
| `title` | Product name as listed |
| `price` / `currency` | Numeric price and ISO currency code |
| `url` | Direct link to the listing |
| `seller_id` | Seller identifier, used for the saturation score |
| `brand` | Brand, where the marketplace reports one |
| `categories` | Full category path |
| `rating` / `rating_count` | Average rating and number of reviews (Jumia) |
| `region` | Seller's state (Jiji) |
| `is_verified_seller` | Whether the marketplace has verified the seller (Jiji) |
| `is_sponsored` | Whether the listing was a paid placement |
| `source_query` | Which rewritten search term matched it |
| `scraped_at` | UTC timestamp of collection |

### Web dashboard (Standby mode)

The Actor also runs as an HTTP server, so a non-technical user never has to touch the Apify Console. Requests authenticate with `Authorization: Bearer <APIFY_TOKEN>` or `?token=<APIFY_TOKEN>`.

| Endpoint | Method | Description |
|---|---|---|
| `/` | GET | The dashboard. Also answers the platform readiness probe. |
| `/api/market?q=…` | GET | A market report. Serves from cache when one exists. Params: `q` (required), `country`, `platforms`, `refresh`. |
| `/api/queries` | GET | Every cached report, newest first. |

```bash
curl "https://brendy--market-pulse-nigeria.apify.actor/api/market?q=okrika+bale&platforms=jumia,jiji" \
  -H "Authorization: Bearer $APIFY_TOKEN"
```

Open `https://brendy--market-pulse-nigeria.apify.actor/?token=$APIFY_TOKEN` in a browser for the dashboard itself.

### Technologies

| Layer | What is used |
|---|---|
| Runtime | Python 3.14 on `apify/actor-python:3.14`, packaged as a Docker image |
| Platform | Apify Actors, Datasets, Key-Value Stores, **Standby mode**, Schedules, **Residential Proxy**, pay-per-event billing |
| SDK / tooling | Apify SDK for Python 4.0.2, Apify CLI |
| Collection | **curl\_cffi 0.13.0** for browser TLS impersonation; Actor-to-Actor composition via `piotrv1001/jiji-listings-scraper` |
| Parsing | No browser and no HTML parser — Jumia's result set is read from the `window.__STORE__` JSON blob embedded in its pages |
| Analysis | Pydantic 2.12.3 for typed contracts; Python `statistics` for quantiles; `collections.Counter` for price clustering |
| Web layer | FastAPI 0.121.1 + Uvicorn 0.38.0, OpenAPI 3.0.3 schema, single-file vanilla HTML/CSS/JS dashboard with light and dark themes |
| AI (inactive) | Anthropic SDK 0.75.0 targeting Claude Opus 5, using structured outputs and the tool runner — see Roadmap |

Jumia needs no browser and no HTML parsing, so its compute cost is minimal; Jiji is billed by the upstream Store Actor at roughly $0.002 per listing. A 60-listing report across both sources takes about 20–40 seconds.

### Roadmap — the AI agent layer

**This is planned, not yet enabled.** The Actor ships fully functional without it, and turns it on automatically once an Anthropic API key is configured (as `ANTHROPIC_API_KEY` on the Actor, or per-run via the `llmApiKey` input). The code for all three stages is in `market_pulse/agent.py`; what follows is what changes when a key is present.

**1. Query planning becomes general instead of rule-based.** Today an explicit rewriter handles the common failures — it translates Nigerian market vocabulary (`okrika` → thrift, `tokunbo` → used, `derica` → measuring cup), strips packaging words that would match the packaging itself, and drops over-specific size qualifiers. It works, but it only knows the terms written into it. The planner generalises to products and phrasings nobody enumerated.

**2. `agentMode` becomes a real tool-calling loop.** Rather than planning once, the agent would run searches as tools and re-query when results come back thin — so a query returning nothing triggers a second, broader attempt automatically instead of returning an empty report.

**3. TikTok social commerce becomes available.** This is the part that cannot be done any other way. Social sellers do not publish a price field; the number lives in the caption — *"Available 💰35k DM to order"*. Reading it out requires a language model. The source is written and wired (`market_pulse/sources/tiktok.py`, calling the maintained `clockworks/free-tiktok-scraper` Actor), and returns nothing rather than guessing while no key is set.

**4. The verdict gets written rather than composed.** Today's verdict is assembled from the statistics by template — accurate, and honest about its limits, but flat. A model reading the same numbers can say which specific listings are dragging the range and what that means for this seller.

Until then, every number shown is computed deterministically from the raw listings and is reproducible from the dataset. That property is deliberate and will not change: **the statistics are never produced by a language model**, only interpreted by one.

# Actor input Schema

## `query` (type: `string`):

Describe the product the way you'd say it out loud. Informal phrasing is fine — an AI agent translates it into searches the marketplaces can actually answer. Examples: 'bag of rice', 'women's shoes', 'okrika bale'.

## `location` (type: `string`):

Where you sell, e.g. 'Mile 12, Lagos'. Used to filter Jiji listings to your state, and as context for the pricing verdict. Jumia listings are national.

## `country` (type: `string`):

Which Jumia storefront to search. Only Nigeria is verified end to end; the others are supported but untested.

## `platforms` (type: `array`):

Which marketplaces to pull listings from. Jumia is formal retail; Jiji is classifieds and secondhand, where traders and thrift sellers list. Running both shows the gap between retail and wholesale pricing, which is usually the most useful number. TikTok needs an AI key — see the README roadmap.

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

Upper bound on listings collected across all sources. Higher values cost more and take longer.

## `agentMode` (type: `boolean`):

When on, the AI agent runs searches as tools and re-queries when results come back thin — so 'okrika bale' (which returns nothing on Jumia) becomes 'thrift bale' automatically. When off, it plans once and runs a fixed pipeline.

## `useProxy` (type: `boolean`):

Required on the Apify platform — Jumia blocks datacenter IPs, so listings are fetched through a residential proxy in the marketplace country. Leave on unless you are running locally.

## `llmApiKey` (type: `string`):

Bring your own Anthropic API key. Leave empty to use the key configured on this Actor.

## `proxyGroups` (type: `array`):

Apify Proxy groups to route marketplace requests through. Jumia blocks datacenter IPs, so a residential or unblocker group is required when running on the platform.

## `diagnoseProxy` (type: `boolean`):

Run a connectivity probe across proxy groups and TLS profiles, report which combinations reach the marketplace, then exit without producing a report.

## Actor input object example

```json
{
  "query": "bag of rice",
  "location": "Mile 12, Lagos",
  "country": "ng",
  "platforms": [
    "jumia",
    "jiji"
  ],
  "maxResults": 60,
  "agentMode": true,
  "useProxy": true,
  "proxyGroups": [
    "RESIDENTIAL"
  ],
  "diagnoseProxy": false
}
```

# Actor output Schema

## `listings` (type: `string`):

No description

## `report` (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 = {
    "query": "bag of rice"
};

// Run the Actor and wait for it to finish
const run = await client.actor("brendy/market-pulse-nigeria").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 = { "query": "bag of rice" }

# Run the Actor and wait for it to finish
run = client.actor("brendy/market-pulse-nigeria").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 '{
  "query": "bag of rice"
}' |
apify call brendy/market-pulse-nigeria --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,brendy/market-pulse-nigeria"
        }
    }
}
```

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/ApQKEKpGgQqmpbEpS/builds/JQfycLaxCCdw0pzcf/openapi.json
