# Turkey EKAP Tenders Scraper | Turkish Public Tenders (`automatic/turkey-ekap-tenders-scraper`) Actor

Search Turkish public tenders on EKAP by keyword, province, tender type, status or IKN. One row per tender with procuring entity, tender date, status and documents; add details for announcement text, OKAS codes, lots, estimated value and contract result. Daily new-only feed.

- **URL**: https://apify.com/automatic/turkey-ekap-tenders-scraper.md
- **Developed by:** [Philip Kirkbride](https://apify.com/automatic) (community)
- **Categories:** Lead generation
- **Features:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 80.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 tenders

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

## Turkey EKAP Tenders Scraper

Turkish public tenders are announced on **EKAP**, the national electronic
procurement platform run by the Public Procurement Authority (Kamu İhale
Kurumu). Every central and local authority publishes its invitations there,
which makes EKAP the single place to follow Turkish public buying — roughly
two million tender records, growing every day.

This actor searches EKAP for you and saves one clean row per tender, ready for
a spreadsheet, a database or an alerting workflow.

### What you can do with it

- **Bid teams** follow the tenders they can supply: a keyword in the title, a
  province, a tender type, and the nearest tender date first.
- **Tender-alert services** run it on a schedule with *Only new or changed since
  the last run* and forward new tenders to email, Slack, a CRM or a spreadsheet.
- **Market research and aggregators** sweep a province, a type or a status, with
  tender details on, and compare authorities, estimated values and contract
  results.

### Quick start

Open tenders with the nearest tender date first — the Console prefill, 20 rows:

```json
{"maxItems": 20}
```

Works tenders in Ankara, nearest first:

```json
{"keyword": "", "province": "Ankara", "tenderType": "works", "maxItems": 50}
```

Cybersecurity service tenders with the announcement text, lots and contract
result:

```json
{
  "keyword": "siber güvenlik",
  "keywordScope": "all",
  "status": ["announced"],
  "includeDetails": true,
  "maxItems": 50
}
```

One tender by its IKN, with details:

```json
{"ikn": "2026/271215", "includeDetails": true}
```

The 20-row Console sample costs about $0.025 and each 50-row example about
$0.055, with or without tender details — see [Price](#price).

### What you get

Each row carries the tender's own identity and the facts EKAP shows on the
search result:

- **IKN** — the tender's stable registration number, for example
  `2026/271215`; use it to identify and de-duplicate a tender
- **Source record id** — EKAP's own handle for the tender in this response;
  it can change between reads, so the IKN is the stable identity
- **Title** — as published, in Turkish
- **Procuring entity** — the authority running the tender
- **Province** — where the tender belongs
- **Tender type** — goods, works, service or consultancy
- **Procedure** and **status** — for example "Contract Signed", "Result
  Announcement Published" or "Tender Canceled"; the procedure also gets an
  English label when EKAP's own procedure code is one of the fixed ones
- **Tender date and time** — the bid opening, in Turkish local time
- **Documents** — how many tender documents EKAP lists, with their ids and
  dates

Ask for **tender details** and each row also gets the announcement text, the
place where the work happens, the tender venue, the district and parent
authority, the OKAS item codes, and — when EKAP has published the result — the
lots, the estimated value, the winning contractor and the contract value.

Everything is delivered exactly as EKAP shows it, including Turkish
characters. Nothing is translated, guessed or filled in: a field EKAP leaves
empty is `null` in the row.

### How to search

All filters are optional. An empty input follows open tenders whose tender
date is still ahead (from the moment the run starts), nearest first.

| Filter | What it does |
| --- | --- |
| Keyword + where to look | Words in the tender title (default), the announcement text, the specification, or everywhere |
| Tender number (IKN) | One exact tender |
| Province | A Turkish province name, for example `Ankara` or `İstanbul` |
| Tender type | Goods, works, service or consultancy |
| Status | Any combination of preliminary announcement, open for participation, offers evaluated, evaluation completed, result published, contract signed, canceled, canceled before the announcement, approval not given. Open tenders are selected by default; choose All statuses to search every status |
| Announcement date, tender date | Date ranges |
| Order | Nearest tender date first (default), newest announcement first, or latest tender date first |
| Maximum tenders | How many rows to save (50 by default; the Console prefills 20). With tender details on, a run stops at 2,000 tenders |

### Good to know

- Turkish tender dates are in Turkey time (UTC+03:00 all year); saved dates
  say so explicitly.
- EKAP does not publish an estimated value for most tenders; it appears in
  detail mode only when the authority has published one, together with the
  currency it printed (`TRY` in practice).
- A search that matches nothing finishes successfully with zero rows and a
  plain message.
- Tender dates and statuses change while a tender is live. Run the actor again
  to refresh them.

Use `recordKey` (`ekap:<ikn>`) to identify and update a tender across runs.
The underlying `ikn` is EKAP's stable tender registration number.
`sourceRecordId` is the source's per-response detail handle and can change;
it is retained for provenance and for the actor's detail requests.

### Use it as a daily feed

Turn on **Only new or changed since the last run** (`onlyNewSinceLastRun`) and run the
Actor on a schedule with the same input. It then returns only tenders this search has not
delivered before, or whose watched fields changed since the last run, so there is nothing
to deduplicate on your side.

- Every returned row carries `changeType` (`new` or `changed`) and `changedFields`.
- The first run has no saved history, so it returns everything and reports
  `feed.firstRun` in the run's OUTPUT.
- The history lives in the named key-value store `turkey-ekap-tenders-scraper-feed-state`, one record per
  search. Leave `stateKey` empty to name the record from the input, so different searches
  keep separate histories; set a name to share one or to start a fresh feed.
- An entry leaves the history once its deadline is more than 90 days past and it has not been delivered in the last 90 days, so the history stays bounded.
- The watched fields are the title, the tender date, the status, the estimated value and the documents.

With the flag off (the default) the Actor returns every match, exactly as before.

### Output example

One row per tender, with the detail fields filled in when tender details are
requested (`announcementText` is shortened here; the actor delivers the first
4,000 characters):

```json
{
  "recordKey": "ekap:2026/1369336",
  "sourceRecordId": "599f5dec2df18ec8b400b4a126e68df71bf2b25ac05ac7208b0e460f9d3c38a4",
  "ikn": "2026/1369336",
  "title": "Masaüstü Bilgisayar Alımı",
  "procuringEntity": "EMNİYET GENEL MÜDÜRLÜĞÜ DESTEK HİZMETLERİ DAİRE BAŞKANLIĞI",
  "province": "ANKARA",
  "tenderType": "Mal",
  "tenderTypeCode": "1",
  "tenderTypeLabel": "goods",
  "procedure": "TENDER_SEARCH.MAIN.PAGEITEM.TENDER_TYPE TENDER_SEARCH.ENUMERATIONS.SEARCH_METHOD.OPEN",
  "procedureLabel": "Open procedure",
  "procedureCode": "1",
  "statusCode": "15",
  "status": "Result Announcement Published",
  "tenderDate": "2026-09-17T14:00:00+03:00",
  "eTender": true,
  "documentCount": 0,
  "documents": [],
  "publishedAt": "2026-08-18T00:00:00+03:00",
  "announcementTitle": "MASAÜSTÜ BİLGİSAYAR SATIN ALINACAKTIR",
  "announcementText": "MASAÜSTÜ BİLGİSAYAR SATIN ALINACAKTIR Masaüstü Bilgisayar Alımı mal alımı 4734 sayılı Kamu İhale Kanununun 19 uncu maddesine göre açık ihale usulü ile ihale edilecektir…",
  "announcementTextTruncated": true,
  "workLocation": "Emniyet Genel Müdürlüğü Trafik Başkanlığı Deposu Ankara adresine tek parti halinde teslim edilecektir.",
  "tenderPlace": "Emniyet Genel Müdürlüğü Destek Hizmetleri Daire Başkanlığı 4 Nolu Satın Alma Şube Müdürlüğü Ayrancı Mah. Dikmen Cad. No:6 O Blok 2. Kat Oda No: 6 Çankaya / ANKARA",
  "topAuthority": "İÇİŞLERİ BAKANLIĞI",
  "subAuthority": "EMNİYET GENEL MÜDÜRLÜĞÜ",
  "district": "ÇANKAYA",
  "okasCodes": [{"code": "30213300", "name": "Masaüstü bilgisayar"}],
  "lots": [
    {
      "name": "Masaüstü Bilgisayar Alımı",
      "highestBidText": "32.210.000,00 TRY",
      "lowestBidText": "24.445.000,00 TRY",
      "estimatedValueText": "336.916.050,00 TRY",
      "estimatedValue": 336916050.0
    }
  ],
  "contract": {
    "contractor": "LN BİLGİSAYAR VE DANIŞMANLIK HİZMETLERİ SANAYİ VE TİCARET ANONİM ŞİRKETİ",
    "contractDate": "2026-11-02",
    "valueText": "24,445,000.00 TRY",
    "value": 24445000.0,
    "highestBidText": "32,210,000.00 TRY",
    "lowestBidText": "24,445,000.00 TRY",
    "estimatedValueText": "336.916.050,00 TRY",
    "estimatedValue": 336916050.0,
    "currency": "TRY"
  },
  "estimatedValue": 336916050.0,
  "estimatedValueText": "336.916.050,00 TRY",
  "currency": "TRY",
  "observedAt": "2026-11-04T22:39:51.823637+03:00",
  "detailsParsed": true,
  "detailsError": null
}
```

Amounts keep EKAP's own text (`valueText`) beside a number (`value`), because
EKAP prints the same amount in both `1,486,307.76 TRY` and `14.608.282,00 TRY`
style.

### Price

You pay for what you get: **$0.001 per saved tender**, plus a run-start charge
of $0.005 per GB of memory — the default 512 MB run is one start charge, so
$0.005. Tender details are included in the same price; they only make the run
longer (about one extra request per tender).

| Run | Tenders | Cost |
|---|---:|---:|
| Console sample (20 open tenders) | 20 | $0.025 |
| One province and type for a month | 50 | $0.055 |
| A province sweep with details | 200 | $0.205 |
| Every open tender nationwide | 1,000 | $1.005 |

The start charge applies even when a search saves nothing, and a tender that is
not saved is not charged. Platform usage is included in the price.

### Questions people ask

**Which tenders are still open?** Keep the default statuses, "Preliminary
announcement published" and "Tender announcement published (open for
participation)", and the default order, tender date soonest first.

**What does a run cost?** $0.001 for each saved tender plus one run-start charge
of $0.005 for the default 512 MB run: the 20-row Console sample costs $0.025,
200 tenders cost $0.205 and 1,000 cost $1.005. Tenders that are not saved are
not charged.

**Can I run it on a schedule?** Yes. Save the input, add a schedule in the Apify
Console — daily is usual for tender alerts — and choose where the results should
go (email, webhook, Google Sheets or your own server). Turn on *Only new or
changed since the last run* so the feed carries no duplicates.

**How long does a run with details take?** Details add about one request per
tender, so a 50-tender detailed run takes a few minutes. For a large sweep,
leave details off and read the tenders you care about by IKN afterwards.

**Can I follow one authority?** Search a keyword in the title, or use the
province filter together with a keyword for the authority's typical subject.

**Do I need an EKAP account?** No. Only public search results are read.

**Are tender documents downloaded?** No. The row tells you how many documents
EKAP lists and when they were published; the documents themselves stay on
EKAP.

EKAP is the official public procurement platform of the Republic of Türkiye.
This actor reads its public search results.

# Actor input Schema

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

Words to look for, as typed on EKAP. Turkish characters are fine. Leave empty to search without a keyword; the other filters still apply, so the default lists open tenders with the nearest tender date first.

## `keywordScope` (type: `string`):

Which part of a tender the keyword is searched in. EKAP's own search page offers the same choice.

## `ikn` (type: `string`):

One exact tender registration number, for example 2026/271215. Overrides nothing else; leave empty to search.

## `province` (type: `string`):

Turkish province name, for example Ankara or Istanbul, or an EKAP province id.

## `tenderType` (type: `string`):

Goods (Mal), works (Yapım), service (Hizmet) or consultancy (Danışmanlık).

## `status` (type: `array`):

Where the tender stands on EKAP. The default follows open tenders: preliminary announcements and tenders open for participation. Select All statuses (or leave the list empty) to search every status.

## `announcementFrom` (type: `string`):

Earliest announcement date, YYYY-MM-DD.

## `announcementTo` (type: `string`):

Latest announcement date, YYYY-MM-DD.

## `tenderDateFrom` (type: `string`):

Earliest tender (bid opening) date, YYYY-MM-DD. When ordered by tender date, searches start at the run start (Turkey time) by default so the nearest tenders still ahead come first; set this to look earlier.

## `tenderDateTo` (type: `string`):

Latest tender (bid opening) date, YYYY-MM-DD.

## `sort` (type: `string`):

Nearest tender date first (the default), newest announcement first, or tender date latest first.

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

How many tenders to save, from 1 to 5000. With tender details on, the run stops at 2000 tenders.

## `includeDetails` (type: `boolean`):

Open each tender for the announcement text, work location, OKAS codes, lots, estimated value and contract result. Slower: one extra request per tender.

## `onlyNewSinceLastRun` (type: `boolean`):

Return only tenders this search has not delivered before, or whose title, deadline, status, value or documents changed since the last run. Keep the same input and schedule to get a clean daily feed.

## `stateKey` (type: `string`):

Optional name for the saved feed state. Leave empty to derive it from the input, so different searches keep separate histories. Set a new name to start the feed over.

## Actor input object example

```json
{
  "keyword": "",
  "keywordScope": "title",
  "tenderType": "all",
  "status": [
    "preliminaryAnnouncement",
    "announced"
  ],
  "sort": "tenderDateAsc",
  "maxItems": 20,
  "includeDetails": false,
  "onlyNewSinceLastRun": false,
  "stateKey": ""
}
```

# Actor output Schema

## `results` (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 = {
    "keyword": "",
    "keywordScope": "title",
    "tenderType": "all",
    "status": [
        "preliminaryAnnouncement",
        "announced"
    ],
    "sort": "tenderDateAsc",
    "maxItems": 20,
    "includeDetails": false
};

// Run the Actor and wait for it to finish
const run = await client.actor("automatic/turkey-ekap-tenders-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 = {
    "keyword": "",
    "keywordScope": "title",
    "tenderType": "all",
    "status": [
        "preliminaryAnnouncement",
        "announced",
    ],
    "sort": "tenderDateAsc",
    "maxItems": 20,
    "includeDetails": False,
}

# Run the Actor and wait for it to finish
run = client.actor("automatic/turkey-ekap-tenders-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 '{
  "keyword": "",
  "keywordScope": "title",
  "tenderType": "all",
  "status": [
    "preliminaryAnnouncement",
    "announced"
  ],
  "sort": "tenderDateAsc",
  "maxItems": 20,
  "includeDetails": false
}' |
apify call automatic/turkey-ekap-tenders-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,automatic/turkey-ekap-tenders-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/KOHYDzPOq1CtYpHYO/builds/RnzdjmJOPVZAW5Xh4/openapi.json
