# KONEPS Live Tender Matcher (`byungwoongyoo/koneps-tender-feed`) Actor

Unofficial, unaffiliated tool that fetches, filters, and normalizes live Korean public procurement notices from the official PPS KONEPS Open API.

- **URL**: https://apify.com/byungwoongyoo/koneps-tender-feed.md
- **Developed by:** [Byungwoong Yoo](https://apify.com/byungwoongyoo) (community)
- **Categories:** Automation, Business, Developer tools
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 koneps tender 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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

### KONEPS Live Tender Matcher — Apify Actor

Fetch Korean public procurement notices from the **official Public Procurement Service (PPS) KONEPS API**, filter them for a buyer's use case, and return flat JSON records that export cleanly to CSV.

This is an independent, unofficial product. It is not affiliated with, endorsed by, or operated by PPS, KONEPS, or the Korean government. The original KONEPS notice is always authoritative.

The Actor does not use a paid external API. Each user supplies their own free `data.go.kr` service key.

**Current Store price:** $1.00 per 1,000 successfully returned tender records, plus the standard $0.00005 Actor-start event. Apify platform usage is included. Filtered-out notices and failed output writes do not trigger the `tender-result` event.

### 30-second fit check

Use this Actor when you need a spreadsheet-ready shortlist of public Korean tenders and want to answer three questions quickly:

1. Which notices match my keywords or region?
2. What changed, and when is each bid due?
3. Where is the authoritative original notice?

Start from the configured public example: **[나라장터 AI·자동화 용역 찾기](https://apify.com/byungwoongyoo/koneps-tender-feed/examples/find-korean-service-tenders)**. Replace the secret placeholder with your own free `data.go.kr` key, review the date range, and run it on Apify. The example returns at most 10 records and sets `maxSpendUsd` to **$0.01**. It will not submit a bid or decide whether you are eligible.

Each returned record has a flat shape like the following. This is an illustrative field shape, not a live procurement notice:

```json
{
  "bidNoticeNo": "<official notice number>",
  "title": "<official notice title>",
  "noticeAgency": "<issuing agency>",
  "budgetKrw": 0,
  "deadlineAt": "<official timestamp>",
  "status": "<normalized notice state>",
  "sourceUrl": "<original KONEPS notice URL>"
}
```

**Cost boundary:** returned records cost $0.001 each, the start event costs $0.00005, and Apify platform usage is included. The example's finite $0.01 cap is enforced before output; a smaller platform run cap wins if you set one.

### 한국어 빠른 안내

#### 나라장터 입찰 공고 API를 CSV로 받을 수 있나요?

네. 이 Actor는 조달청 나라장터 입찰 공고 Open API의 서비스·물품·공사·외자·기타 공고를 조회하고, 공고 번호·기관·예산·마감일·변경 상태·원문 URL을 평면 JSON으로 정규화합니다. 결과는 Apify에서 CSV, Excel, JSON으로 내보낼 수 있습니다.

#### 한국 공공조달 공고 데이터를 어떻게 필터링하나요?

공고 등록일 또는 변경일 범위를 지정하고, 선택적으로 키워드와 지역을 입력합니다. 같은 공고는 공식 `bidNtceNo + bidNtceOrd`를 기준으로 중복 제거합니다. 누락된 값은 추측하지 않고 `null`로 반환하며, 입찰 전에는 반드시 원문 공고를 확인해야 합니다.

### What it does

- Queries a Korean date range by notice registration time or change time.
- Uses the matching official operation for services, goods, construction, foreign procurement, and other notices.
- Applies optional keyword and region filters.
- Normalizes notice number, title, agencies, budget, bid deadline, change state, and original KONEPS URL.
- Deduplicates by `bidNtceNo + bidNtceOrd` (with a deterministic fallback only when the official ID is absent).
- Pushes one flat dataset item per unique result for JSON, CSV, Excel, API, Make, or Zapier use.
- Can charge exactly one `tender-result` event for each successfully pushed result under Apify pay-per-event pricing.

### Official source and verified fields

Source: [PPS KONEPS Bid Public Notice API, data.go.kr dataset 15129394](https://www.data.go.kr/data/15129394/openapi.do).

The official API is free, real-time, JSON/XML, automatically approved for development and production, and has a 1,000-call development allowance at the time of implementation. Check the source page for the current allowance and terms.

The implementation uses these official list operations:

| Input type | Official operation |
|---|---|
| `service` | `getBidPblancListInfoServc` |
| `goods` | `getBidPblancListInfoThng` |
| `construction` | `getBidPblancListInfoCnstwk` |
| `foreign` | `getBidPblancListInfoFrgcpt` |
| `other` | `getBidPblancListInfoEtc` |

The official reference document defines `inqryDiv=1` as registration-time lookup and `inqryDiv=3` as change-time lookup. Both use `inqryBgnDt` and `inqryEndDt` in `YYYYMMDDHHMM` format.

Core output mappings are defensive:

- ID/title: `bidNtceNo`, `bidNtceOrd`, `bidNtceNm`, `refNo`
- parties: `ntceInsttNm`, `dminsttNm`
- dates: `bidNtceDt`, `bidBeginDt`, `bidClseDt`, `opengDt`
- budget: `asignBdgtAmt`, then `bdgtAmt`, then `presmptPrce`
- change status: `chgDt`, `chgNtceRsn`, `ntceKindNm`, `reNtceYn`
- links: `bidNtceDtlUrl`, `bidNtceUrl`, `stdNtceDocUrl`

If PPS omits or changes a non-required field, the Actor returns `null` rather than inventing a value. Always read the original notice before bidding.

### Service key setup

1. Create or sign in to a `data.go.kr` account.
2. Apply for API **15129394**.
3. Paste either the encoded or decoding service key into `serviceKey` in the Actor input.

`serviceKey` is marked `isSecret: true`. Apify encrypts it at rest in saved Actor inputs and decrypts it only for the run. The Actor never logs, returns, or writes the key to output. Do not commit a real key to Git, examples, `.env`, or local `storage`.

### Input example

See [`examples/sample_input.json`](./examples/sample_input.json). Replace the placeholder with your own key and choose a current date range. The inclusive date range is limited to 31 days and page/result caps are validated before any API request.

### Output

Every dataset record is deliberately flat. Important fields include:

- `bidNoticeNo`, `bidNoticeOrder`, `title`, `businessTypeKo`
- `noticeAgency`, `demandAgency`
- `budgetKrw`, `budgetField`, and the three source budget fields
- `deadlineAt`, `status`, `isChanged`, `changeAt`, `changeReason`
- `region`, `matchedKeywords`, `sourceUrl`, `documentUrl`

An `OUTPUT` key-value-store record summarizes fetched, matched, deduplicated, and pushed counts without storing credentials.

### Pay-per-event pricing and spend controls

The live Store configuration is:

- `tender-result`: **$0.001 per successfully returned record** (shown as $1.00 / 1,000)
- `apify-actor-start`: **$0.00005**
- primary event: `tender-result`
- platform usage: included (`Pay per event + usage` is off)
- minimum selectable max cost per run: **$0.01**

Code and Console configuration must remain aligned:

1. Deploy the Actor.
2. In **Apify Console → Monetization**, select **Pay per event**.
3. Define a custom event named exactly **`tender-result`** at the disclosed per-result price.
4. Remove the synthetic **`apify-default-dataset-item`** event before saving the pricing setup. Leaving it enabled would charge each pushed record twice: once for that synthetic event and once for `tender-result`.
5. Keep the synthetic **`apify-actor-start`** event at its recommended default price. Do not charge it manually in code.
6. Enable **Pay per event + usage** only if the intended commercial setup is that Actor users, rather than the developer margin, cover compute/storage/network usage. This lowers pricing transparency and makes the Actor ineligible for agentic payments, so leave it off for the normal Store configuration after validating unit economics.
7. Set a sensible minimum maximum cost in the monetization configuration.
8. Recommend that API callers also pass the run option `maxTotalChargeUsd`. The Actor requires the finite input `maxSpendUsd`; when both caps are present, the lower one wins.

Billing is fail-closed:

- Under active PPE pricing, the Actor refuses to run if `tender-result` has no positive configured event price.
- It also refuses to run if `apify-default-dataset-item` is still configured, preventing accidental double charging before any result is fetched or written.
- The input cap is converted to a maximum number of result events before output starts.
- If `maxTotalChargeUsd` is supplied as a run option, the lower of that platform cap and `maxSpendUsd` wins; otherwise the finite input cap applies.
- Results use `Actor.pushData(record, 'tender-result')`; the Actor increments its output count only when `chargedCount === 1`.
- It stops immediately when Apify reports the event charge limit has been reached.
- In a free/local build, records are pushed without a billable event.

Apify's current monetization documentation: [Pay-per-event](https://docs.apify.com/actors/publishing/monetize/pay-per-event) and [SDK charging limits](https://docs.apify.com/sdk/js/docs/concepts/pay-per-event).

### Local checks

```bash
npm install
npm test
npm run check
```

For a local Actor run, place a private input at `storage/key_value_stores/default/INPUT.json` (never commit it), then:

```bash
npm start
```

To emulate PPE locally, Apify documents `ACTOR_TEST_PAY_PER_EVENT=true`. Because this Actor also verifies that `tender-result` is present in the pricing configuration, complete the definitive PPE smoke test on a beta build after defining the event in Apify Console.

### Deployment

With the Apify CLI installed and authenticated:

```bash
apify push
```

Release checks:

- Validate `.actor/input_schema.json` and `.actor/dataset_schema.json` in Apify Console/CLI.
- Confirm `serviceKey` renders as a secret field.
- Confirm the Console still uses the disclosed `tender-result` price and has no default-dataset event.
- Run one low-limit paid test only with a legitimate user-supplied `data.go.kr` key.
- Confirm the Store README clearly states that users bring their own free public-data key and may pay Apify platform/event charges.
- Export the dataset as both JSON and CSV and inspect nulls, amounts, timestamps, and original links.

### Limitations

- This Actor discovers public notices; it does not decide eligibility, prepare a bid, sign declarations, or submit to KONEPS.
- Region is a client-side match over public region and agency fields because fields vary by work category.
- A missing deadline is excluded when `onlyOpen=true`.
- API limits, schemas, and procurement rules can change. The original notice is authoritative.

# Actor input Schema

## `serviceKey` (type: `string`):

Your own decoding or encoded service key for public data API 15129394. It is encrypted by Apify and is never logged or returned.

## `startDate` (type: `string`):

First Korean calendar date to query, inclusive (YYYY-MM-DD).

## `endDate` (type: `string`):

Last Korean calendar date to query, inclusive (YYYY-MM-DD). Maximum range: 31 days.

## `dateField` (type: `string`):

Use official API query division 1 for registration time or 3 for change time.

## `businessTypes` (type: `array`):

KONEPS work categories. Each category uses its matching official operation.

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

Optional case-insensitive filters applied to title, reference number, agencies and public product/task fields.

## `keywordMatch` (type: `string`):

Match any keyword or require all keywords.

## `regions` (type: `array`):

Optional case-insensitive region filters applied to official region and agency fields.

## `onlyOpen` (type: `boolean`):

When enabled, discard records with a missing or already-passed bid deadline.

## `maxPages` (type: `integer`):

Hard page limit for each selected operation. Keeps calls within your data.go.kr allowance.

## `pageSize` (type: `integer`):

Official numOfRows parameter.

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

Absolute output cap before the spending cap is applied.

## `maxSpendUsd` (type: `number`):

Actor-level fail-closed PPE cap. If the run option maxTotalChargeUsd is also provided, the lower cap wins.

## `requestTimeoutSeconds` (type: `integer`):

Timeout for each public API request.

## Actor input object example

```json
{
  "dateField": "registeredAt",
  "businessTypes": [
    "service",
    "goods",
    "construction"
  ],
  "keywords": [],
  "keywordMatch": "any",
  "regions": [],
  "onlyOpen": true,
  "maxPages": 3,
  "pageSize": 100,
  "maxResults": 500,
  "maxSpendUsd": 1,
  "requestTimeoutSeconds": 30
}
```

# Actor output Schema

## `results` (type: `string`):

Normalized tender notices in the run's default dataset.

## `runSummary` (type: `string`):

Counts, billing limit information, and completion time for this run.

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("byungwoongyoo/koneps-tender-feed").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("byungwoongyoo/koneps-tender-feed").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 '{}' |
apify call byungwoongyoo/koneps-tender-feed --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,byungwoongyoo/koneps-tender-feed"
        }
    }
}

```

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/creRgIRCz9gRWxrOk/builds/2p4ApPxdXZbPIaWMY/openapi.json
