# Tunisia Public Tenders (TUNEPS) (`a-g-d/tn-tuneps`) Actor

Public tender notices from Tunisia's national e-procurement system TUNEPS (tuneps.tn) as JSON: notice number, title in French and Arabic, buyer, category, procedure, governorate, guarantee, deadlines and opening times, status and links.

- **URL**: https://apify.com/a-g-d/tn-tuneps.md
- **Developed by:** [Aloever Dulay](https://apify.com/a-g-d) (community)
- **Categories:** Lead generation, Other
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 tender notices

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

## Tunisia Public Tenders (TUNEPS)

Public tender notices from Tunisia's national e-procurement system,
[TUNEPS](https://www.tuneps.tn), as clean JSON with English keys: notices published on
TUNEPS, newest first, with the notice number, title in French (and Arabic when it
differs), public buyer, category, procedure, sector, governorate, financing, evaluation
method, bid guarantee, the publication, submission, deadline and opening times, a status
(open, closed, opened) and a link to the notice.

### Who needs this

- **Suppliers and contractors** bidding for Tunisian public contracts who want every new
  tender in their field each morning instead of browsing the TUNEPS portal by hand.
- **Tender-alert services and bid consultants** building feeds, email digests or
  WhatsApp/Telegram alerts for Tunisia or the Maghreb.
- **Foreign exporters and development-finance teams** tracking calls financed by the World
  Bank, AfDB, AFD, EIB, JICA or KfW.
- **Sales and market-intelligence teams** watching which buyers are procuring what, and
  when.
- **Journalists, researchers and civil society** studying public procurement: who buys
  what, how, and under which procedures.
- **AI agents and automations** that need a stable schema instead of a JavaScript portal
  that has no public API.

### Input

| Field                | Type    | Default  | Description                                                                                                                                                         |
| -------------------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `keyword`            | string  | `""`     | Only tenders whose title contains this text, e.g. `ordinateur`, `travaux`, `véhicules`. Case-insensitive.                                                           |
| `buyer`              | string  | `""`     | Only tenders from buyers whose name contains this text, e.g. `Municipalité de Sfax`, `Hôpital`, `Office National`. Case-insensitive.                                |
| `category`           | string  | all      | `works`, `goods`, `services` or `studies`.                                                                                                                          |
| `procedure`          | string  | all      | `open`, `prequalification`, `twoStage` or `contest`.                                                                                                                |
| `governorate`        | string  | all      | Where the contract is carried out, as TUNEPS spells it: `TUNIS`, `SFAX`, `SIDI BOUZID`, … or `Autres`.                                                              |
| `publishedSince`     | string  | `""`     | Only tenders published after this point: a relative span (`24 hours`, `7 days`, `2 weeks`), a date (`2026-09-20`, midnight Tunisian time) or an ISO-8601 timestamp. |
| `openOnly`           | boolean | `true`   | Keep tenders whose submission deadline has not passed. Turn it off to include closed tenders too.                                                                   |
| `maxItems`           | integer | `100`    | Stop after this many tenders (1-50,000), newest first. See "How long a run takes" below.                                                                            |
| `proxyConfiguration` | object  | no proxy | Optional Apify Proxy. Not needed normally; use it only if runs fail with connection errors.                                                                         |

Examples:

```json
{ "publishedSince": "24 hours", "category": "works", "maxItems": 150 }
```

```json
{ "keyword": "informatique", "governorate": "SFAX", "openOnly": true }
```

```json
{ "buyer": "Municipalité de Sfax", "openOnly": false, "publishedSince": "30 days" }
```

#### How long a run takes

TUNEPS lists only a notice's headline, so the actor opens every notice to complete it, one
request at a time with a pause of at least one second in between. That makes a run cost
about **1.5 seconds per tender**, whatever the filters:

| `maxItems`       | Approximate time  | Run timeout needed |
| ---------------- | ----------------- | ------------------ |
| 20 (the prefill) | about 30 seconds  | default (300 s)    |
| 100 (default)    | about 2.5 minutes | default (300 s)    |
| 500              | about 13 minutes  | at least 900 s     |
| 1,000            | about 25 minutes  | at least 1,800 s   |

With the default 300-second timeout a run stores about 150 tenders at most; it then stops
cleanly and keeps them. For bigger runs, raise the timeout in the run options. For a daily
feed, a narrow `publishedSince` (such as `24 hours`) with a generous `maxItems` gets
every new notice without re-reading old ones.

### Output

One dataset item per tender notice, each with `recordType: "tender"`. Keys are English;
text is kept exactly as published (French or Arabic), trimmed and with HTML entities
decoded. Times are ISO-8601 in Tunisian time (`+01:00`) and `fetchedAt` is UTC. The Output
tab on this page documents every field. The dataset has a "Tenders" table view.

A real tender notice (captured 2026-09-26):

```json
{
  "recordType": "tender",
  "tenderNo": "20260902090",
  "modificationSeq": "00",
  "noticeType": "Nouvel enregistrement",
  "referenceNo": "33/2026",
  "title": "Acquisition de viandes rouges et viandes de volailles, oeufs, lait et dérivés, ustensiles à usage unique, fruits et légumes et produits alimentaires",
  "titleAr": null,
  "buyer": "Hôpital Mongi Slim à la Marsa",
  "buyerCode": "1000000170",
  "buyerDepartment": "APPROVISIONNEMENT",
  "category": "Fourniture de biens",
  "categoryEn": "Goods & Equipments",
  "procedure": "Appel d'offres ouvert",
  "procedureEn": "Open Bidding",
  "sector": "Nourriture",
  "sectorEn": "Food",
  "governorate": "TUNIS",
  "financing": "Budget",
  "evaluationMethod": "Moins disant",
  "priceType": "Marché à prix unitaire",
  "submissionMode": "En ligne",
  "scope": "Appel d'offres national (part dinar)",
  "consortiumAllowed": false,
  "isFrameworkAgreement": true,
  "guarantee": "Engagement personnel et solidaire",
  "bidValidityDays": 120,
  "documentPrice": 0,
  "documentPriceCurrency": "TND",
  "publishedAt": "2026-09-26T13:10:39+01:00",
  "submissionStartAt": "2026-09-28T08:00:00+01:00",
  "prequalificationDeadlineAt": null,
  "deadlineAt": "2026-10-13T10:00:00+01:00",
  "openingAt": "2026-10-13T13:30:00+01:00",
  "openingResultsPublishedAt": null,
  "status": "open",
  "submissionPlace": "HOPITAL MONGI SLIM LA MARSA",
  "detailUrl": "https://www.tuneps.tn/portail/offres/details/136077/20260902090",
  "sourceUrl": "https://www.tuneps.tn/api2/portail/bid/master/136077",
  "fetchedAt": "2026-09-26T15:37:19.735Z"
}
```

#### About tender status

TUNEPS does not publish a status field. The actor derives `status` when it fetches the
notice: `open` while the submission deadline is ahead, `closed` once it has passed, and
`opened` once TUNEPS has published the result of the opening of offers
(`openingResultsPublishedAt`). An addendum published as its own notice has
`noticeType: "Additif"` and a `modificationSeq` above `00`.

### Pricing

Pay per event, charged only for tenders actually stored in your dataset:

- **`tender-notice`**: one per tender notice stored.

The actor defines no start event of its own. It checks your spending limit before
fetching each batch, stores the batch before it is charged, and stops cleanly when the
limit is reached, so you never pay for data you cannot see.

### Data source and politeness

The data comes from **TUNEPS** (Tunisia On-line E-Procurement System), the national
e-procurement system managed by the online public procurement unit set up by decree
2014-1039: the public tender list of its portal and each notice's public page. No login
is needed.

The actor:

- sends **one request at a time with at least 1 second between requests**, with an honest
  User-Agent that names the actor;
- verifies every TLS certificate (TUNEPS serves an incomplete certificate chain; the actor
  completes it with the missing public intermediate certificate instead of turning
  verification off);
- retries connection errors, timeouts and server errors with exponential backoff and
  jitter inside a fixed budget, and stops with a clear error if the site keeps refusing;
- **never solves or gets around a CAPTCHA**: if the site ever answers with a
  human-verification page, the run stops at once with an error saying so, keeping what it
  has stored; try again later;
- stops shortly before the run's timeout and keeps what it has stored;
- never logs in, never downloads tender documents, and reads only pages anyone can open.

TUNEPS publishes no robots.txt; its terms of use govern registered buyers and suppliers.

### Data and privacy

The output is procurement metadata exactly as the Tunisian government publishes it. The
actor leaves out the names and user ids of the buyers' staff that TUNEPS notices carry. It
does not enrich, profile or combine records with other sources.

### Limitations

- **Tender notices are the ones TUNEPS lists publicly** (about 60,000), newest publication
  first. With `openOnly` (the default), only tenders whose deadline is ahead.
- **No award results**: who won and for how much is not part of this actor.
- **One request per tender**: see "How long a run takes" above.
- **The TUNEPS notice page is a JavaScript application**: `detailUrl` opens in a browser,
  not in tools that do not run JavaScript.
- **Text is as published**: titles are sometimes in Arabic in the French field, and some
  titles and names contain the portal's own typos.
- The list is read newest first while TUNEPS keeps publishing; a notice that moves between
  pages during a run is stored once.

# Changelog

This Actor's version history is a separate document: https://apify.com/a-g-d/tn-tuneps/changelog.md

# Actor input Schema

## `keyword` (type: `string`):

Only tenders whose title contains this text, e.g. "ordinateur", "travaux" or "véhicules". Case-insensitive; titles are mostly French, some Arabic. Leave empty for all tenders.

## `buyer` (type: `string`):

Only tenders from public buyers whose name contains this text, e.g. "Municipalité de Sfax", "Hôpital" or "Office National". Case-insensitive, matched by TUNEPS. Leave empty for all buyers.

## `category` (type: `string`):

Works, goods, services or studies. Leave empty for all.

## `procedure` (type: `string`):

The award procedure. Leave empty for all.

## `governorate` (type: `string`):

The governorate where the contract is carried out. Leave empty for all.

## `publishedSince` (type: `string`):

Only tenders published after this point: a relative span like "24 hours" or "7 days", a date like "2026-09-20" (midnight Tunisian time) or an ISO-8601 timestamp. Leave empty for no lower bound.

## `openOnly` (type: `boolean`):

Keep tenders whose submission deadline has not passed. Turn off to include closed tenders (TUNEPS keeps about 60,000 notices).

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

Stop after storing this many tenders (newest first). Each tender needs its own request at a polite pace, about 1.5 seconds apiece: 100 tenders take about 2.5 minutes; raise the run timeout above 300 seconds for more than about 150.

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

Optional. The actor talks to tuneps.tn directly; use Apify Proxy only if runs fail with connection errors.

## Actor input object example

```json
{
  "keyword": "",
  "buyer": "",
  "publishedSince": "",
  "openOnly": true,
  "maxItems": 20,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `tenders` (type: `string`):

One item per published TUNEPS notice matching the input filters, newest publication first: notice number, title (French, plus Arabic when different), buyer, category, procedure, sector, governorate, financing, evaluation method, bid guarantee, publication, submission, deadline and opening times in Tunisian time (+01:00), status and a link to the notice.

# 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 = {
    "maxItems": 20,
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("a-g-d/tn-tuneps").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 = {
    "maxItems": 20,
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("a-g-d/tn-tuneps").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 '{
  "maxItems": 20,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call a-g-d/tn-tuneps --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,a-g-d/tn-tuneps"
        }
    }
}
```

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/3zDQDNNmmXHMA7ynF/builds/lyThupNlgEnSo7xHh/openapi.json
