# Detik Authorized News Evidence Normalizer (`zinin/detik-news-search`) Actor

Normalize buyer-supplied Detik records you are authorized to process into one deterministic evidence report. Deduplicate source identities, preserve provenance, bind input and report digests, expose review gaps, and reconcile current-run delivery—without fetching Detik.

- **URL**: https://apify.com/zinin/detik-news-search.md
- **Developed by:** [Tim Zinin](https://apify.com/zinin) (community)
- **Categories:** News, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.85 / 1,000 authorized evidence reports

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#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

## Detik Authorized News Evidence Normalizer

Turn buyer-supplied, authorized Detik records into one deterministic evidence report with canonical links, deduplication, SHA-256 bindings, explicit review gaps, and current-run delivery reconciliation. The Actor makes no source request.

This product is for teams that already have a permitted Detik export, feed, archive, or other lawful copy of the records they submit. It is not a scraper, search client, monitoring bot, or way to obtain publisher content. You provide the records and an explicit authorization assertion; the Actor validates their closed shape, normalizes source identity, removes duplicates, and packages the result for review. It never treats a buyer assertion as independent publisher verification.

`INPUT → closed validation → canonicalization → deduplication → evidence report → human review`

![Detik Authorized News Evidence Normalizer: licensed records to one review-ready report](https://raw.githubusercontent.com/TimmyZinin/apify-actor-assets/ffccc895a1108f819b9c339ed3e8853cd2bc2b50/commercial115/detik-news-search/readme-hero.webp)

The Dataset contains settlement-neutral business evidence. KVS `OUTPUT` is the only authority for whether the current run delivered a report, whether the named result event settled paid, free, anomalous, or unknown, and whether retry is safe. A new run always buys another Actor start and may duplicate delivery, so ambiguous post-push outcomes must be reconciled instead of blindly retried.

### What you get

The Actor gives you one compact report for 1–25 authorized records:

- normalized, bounded record fields with credential-free Detik HTTPS links;
- first-seen deduplication by article ID, or by canonical URL when no article ID exists;
- requested, unique, and duplicate counts that preserve the size of the submitted batch;
- a deterministic `inputDigest` over normalized input and `reportDigest` over the report body;
- the buyer’s explicit authorization basis, provider label, and non-secret reference;
- a zero-network evidence boundary: the Actor did not fetch a search page, article, image, API, DNS result, or third-party model;
- buyer-asserted freshness based on each record’s supplied `observedAt` value;
- a bounded confidence explanation that covers format and provenance assertions only;
- explicit known gaps and a next review action;
- `safeToAutomate:false`, because normalization is not publisher verification or an editorial decision;
- settlement-neutral billing intent in the Dataset report;
- a current-run KVS receipt with delivery, named-event counter, paid/free/anomalous/unknown, replay, failure, and recovery truth.

The report is deterministic. If two runs receive the same normalized input, their Dataset report bytes and digests are the same. Run identity, delivery state, settlement, and terminal storage state are deliberately excluded from that stable report and recorded separately in `OUTPUT`.

#### Why one report instead of one row per article?

The paid unit is one **authorized evidence report**, not one scraped article. A report can contain up to 25 records. This keeps a batch auditable: deduplication, authorization, confidence, gaps, and digest bindings live together. It also lets a buyer compare Dataset content with one named `result-found` event rather than reverse-engineering many per-row charge attempts.

The report’s `billing` object describes eligibility and intent before settlement. It never says the row was paid. Current-run settlement is available only in KVS `OUTPUT`, after the linked Dataset operation and the post-push named-event counter have been reconciled.

### Who uses it

#### Newsroom data operations

Normalize an internally licensed export before it enters an archive, index, or review queue. The output preserves the supplied headline, optional excerpt, source URL, source identifiers, publication context, and observation time without reaching back to the publisher.

#### Media-intelligence vendors with a feed agreement

Package records obtained under a commercial feed or publisher agreement into a stable report. The buyer remains responsible for the agreement’s scope, attribution, retention, territorial, and downstream-use terms.

#### Research and compliance teams

Bind an authorized batch to deterministic hashes and surface what is still unknown. The Actor can help prove which normalized bytes entered a workflow; it cannot prove that those bytes were authentic, complete, current, or lawfully obtained.

#### Developers and automation engineers

Use the report as a bounded handoff between a licensed ingestion system and a review workflow. The closed input rejects silent extra fields, malformed timestamps, foreign source links, credentials in URLs, duplicate record IDs, control characters, and oversized strings.

#### AI agents with a human approval step

An agent can prepare or validate the input, call the Actor, and present the evidence report to a reviewer. The Actor explicitly returns `safeToAutomate:false`; it is not a grant to publish, summarize, score people, infer sentiment, or make a consequential decision.

This Actor is not intended for people who need to discover Detik articles from a keyword. It does not search Detik. It is also not intended for buyers who lack permission to process the supplied records.

### How to run

#### Console

1. Open the Actor Input tab.
2. Keep `schemaVersion` at `2.0`.
3. Set an authorization basis: `publisher_license`, `licensed_feed`, or `buyer_owned_export`.
4. Provide a human-readable provider label and a non-secret authorization reference.
5. Set `confirmed:true` only when you have the required authority.
6. Paste 1–25 closed records.
7. Set a run charge cap that covers the automatic start event plus one report event.
8. Start the run once.
9. Read the Dataset report and KVS `OUTPUT` together.

The public example is synthetic. It uses no copied publisher text and triggers no network request. Replace it only with data you may lawfully submit.

#### Minimal API request

```bash
curl -X POST \
  "https://api.apify.com/v2/acts/zinin~detik-news-search/runs?token=$APIFY_TOKEN&maxTotalChargeUsd=0.006" \
  -H "Content-Type: application/json" \
  -d '{
    "schemaVersion":"2.0",
    "authorization":{
      "basis":"buyer_owned_export",
      "provider":"licensed newsroom export",
      "reference":"internal-approval-2026-08",
      "confirmed":true
    },
    "records":[{
      "recordId":"detik-demo-001",
      "query":"Bank Indonesia",
      "title":"Synthetic authorized-news demonstration record",
      "excerpt":"Synthetic fixture only; no publisher content is fetched.",
      "url":"https://news.detik.com/berita/d-9000000/synthetic-authorized-news-demonstration",
      "articleId":"d-9000000",
      "section":"synthetic-demo",
      "publishedAt":"2026-08-12T08:00:00.000Z",
      "publishedText":"synthetic fixture",
      "observedAt":"2026-08-12T09:00:00.000Z"
    }]
  }'
```

Use a secret manager or environment variable for the Apify token. Never put credentials in `authorization.reference`, a record URL, headline, excerpt, query label, or any stored field.

#### Poll and read the receipt

```bash
RUN_ID="<returned-run-id>"
curl "https://api.apify.com/v2/actor-runs/$RUN_ID?token=$APIFY_TOKEN"
curl "https://api.apify.com/v2/actor-runs/$RUN_ID/dataset/items?token=$APIFY_TOKEN&clean=true"
curl "https://api.apify.com/v2/actor-runs/$RUN_ID/key-value-store/records/OUTPUT?token=$APIFY_TOKEN"
```

Wait for the platform run to reach a terminal status. Do not infer success from a Dataset row alone. A returned Dataset write can be free, paid, anomalous, or settlement-unknown; a thrown push can leave Dataset delivery unknown.

### Pricing

The pricing model is PAY\_PER\_EVENT with two events:

- automatic `apify-actor-start` — charged by the platform when a run starts;
- primary `result-found` — one **authorized evidence report** delivered by the linked Dataset operation.

At the FREE tier the current approved prices are `$0.005` for start and `$0.001` for one report. Tier discounts reduce both prices: BRONZE 5%, SILVER 10%, GOLD 15%, PLATINUM 18%, and DIAMOND 20%. The exact active tier visible in the Apify pricing panel is authoritative.

The Actor refuses delivery when the pricing map is not exactly the approved start/result pair, when a default Dataset item event appears, when the named result counter is unavailable or nonzero at run start, when the automatic start charge is not reflected, or when the buyer cap cannot cover one report.

One valid run attempts one named Dataset push. There is no loop of paid row operations. A run with invalid input, missing authorization, missing hosted run identity, pricing failure, or a pre-delivery KVS failure attempts no report push. The automatic start charge may already apply even when no report is delivered.

#### Settlement vocabulary

| State | Dataset write | Named result settlement | Retry |
|---|---:|---:|---|
| `confirmed_paid` | confirmed | exact `0 → 1` | new run charges again; do not use as replay |
| `confirmed_free` | confirmed | exact `0 → 0` | investigate pricing/cap; do not blindly rerun |
| `settlement_anomaly` | confirmed | invalid delta or aggregate | manual reconciliation required |
| `settlement_unknown` | confirmed | post-push counter unreadable | manual reconciliation required |
| `unknown_delivery` | unknown | cannot establish Dataset outcome | inspect Dataset/events; never blind retry |
| `not_attempted` | none | none | input/pricing/output failure occurred before push |

The Dataset report contains `billingEligible` and `billingIntent`. Those are design-time facts, not a payment receipt. Only the current run’s KVS `OUTPUT` has settlement truth.

### Input contract

The input is a closed object. Extra top-level, authorization, or record properties are rejected instead of silently ignored.

```json
{
  "schemaVersion": "2.0",
  "authorization": {
    "basis": "licensed_feed",
    "provider": "Example licensed feed",
    "reference": "agreement-2026-08",
    "confirmed": true
  },
  "records": [
    {
      "recordId": "detik-record-001",
      "query": "Bank Indonesia",
      "title": "Buyer-supplied authorized headline",
      "excerpt": null,
      "url": "https://finance.detik.com/moneter/d-9000001/example",
      "articleId": "d-9000001",
      "section": "detikFinance",
      "publishedAt": "2026-08-12T08:00:00.000Z",
      "publishedText": null,
      "observedAt": "2026-08-12T09:00:00.000Z"
    }
  ]
}
```

#### `schemaVersion`

Required and exactly `2.0`. The earlier public search input is intentionally not an active fallback. A query-only input does not trigger source access; it is rejected.

#### `authorization`

Required closed object:

- `basis`: `publisher_license`, `licensed_feed`, or `buyer_owned_export`;
- `provider`: 1–120 visible NFC characters;
- `reference`: 1–160 visible NFC characters and a non-secret internal reference;
- `confirmed`: exactly `true`.

The Actor checks form and affirmation. It does not contact the provider, read an agreement, verify a licence, or decide whether a use is legal. False confirmation remains the buyer’s responsibility.

#### `records`

Required array with 1–25 items. `recordId` values must be unique. Each record is a closed object:

- `recordId`: 1–96 ASCII identifier characters;
- `query`: 1–200 Unicode characters describing the buyer’s collection label;
- `title`: 1–300 Unicode characters;
- `excerpt`: null or 1–800 Unicode characters;
- `url`: credential-free HTTPS URL on `detik.com` or a subdomain, maximum 2,048 characters;
- `articleId`: null or 1–120 characters;
- `section`: null or 1–120 characters;
- `publishedAt`: null or a real UTC timestamp with milliseconds;
- `publishedText`: null or 1–160 characters;
- `observedAt`: a real UTC timestamp with milliseconds.

All text is required to be NFC, trimmed, bounded, and free of C0/C1 controls, line separators, and BOM. A date-looking string such as `2026-02-31T09:00:00.000Z` is rejected even if it matches a superficial pattern.

URLs are normalized to lowercase host, no fragment, and no common tracking parameters. Credentials are forbidden. The Actor does not open the URL, resolve DNS, follow redirects, or verify an article canonical tag.

#### Deduplication

If `articleId` exists, identity is `detik:<articleId>`. Otherwise identity is a SHA-256 of the normalized source URL. The first record wins. Duplicate source identities are omitted from the records array but counted in `counts.duplicates`. Duplicate `recordId` values are rejected earlier because they make input-row identity ambiguous.

### Happy, partial, and failure output

#### Happy Dataset report

```json
{
  "schemaVersion": "2.0",
  "actor": "detik-news-search",
  "recordType": "detik_authorized_news_evidence_report",
  "records": [{
    "recordId": "detik-record-001",
    "query": "Bank Indonesia",
    "title": "Buyer-supplied authorized headline",
    "excerpt": null,
    "url": "https://finance.detik.com/moneter/d-9000001/example",
    "articleId": "d-9000001",
    "section": "detikFinance",
    "publishedAt": "2026-08-12T08:00:00.000Z",
    "publishedText": null,
    "observedAt": "2026-08-12T09:00:00.000Z",
    "dedupeKey": "detik:id:d-9000001"
  }],
  "counts": { "requested": 1, "unique": 1, "duplicates": 0 },
  "authorization": { "basis": "licensed_feed", "provider": "Example licensed feed", "reference": "agreement-2026-08", "confirmed": true },
  "evidence": { "inputDigest": "sha256:<64 hex>", "sourceBoundary": "buyer_supplied_no_fetch", "externalVerificationPerformed": false, "contentFetchedByActor": false },
  "freshness": { "status": "buyer_asserted", "basis": "record_observed_at", "latestObservedAt": "2026-08-12T09:00:00.000Z" },
  "confidence": { "score": 0.7, "basis": "closed_format_and_buyer_authorization_assertion_only" },
  "gaps": ["external_authenticity_not_verified", "authorization_not_independently_verified", "article_body_not_fetched"],
  "action": { "type": "review_before_downstream_use", "owner": "buyer", "note": "Verify source rights and record accuracy before publication or automated decisions." },
  "safeToAutomate": false,
  "billing": { "billingEligible": true, "billingIntent": "linked_push_result_found", "eventName": "result-found", "unit": "authorized_evidence_report", "settlementSource": "current_run_kvs_output" },
  "inputDigest": "sha256:<64 hex>",
  "reportDigest": "sha256:<64 hex>"
}
```

#### Happy KVS OUTPUT

```json
{
  "schemaVersion": "2.0",
  "actor": "detik-news-search",
  "status": "success",
  "code": null,
  "runId": "<current Apify run ID>",
  "inputDigest": "sha256:<64 hex>",
  "report": { "recordType": "detik_authorized_news_evidence_report", "reportDigest": "sha256:<64 hex>" },
  "counts": { "requested": 1, "unique": 1, "duplicate": 0, "successful": 1, "failed": 0, "delivered": 1, "paid": 1, "free": 0, "anomalous": 0, "unknown": 0, "withheld": 0 },
  "delivery": { "state": "confirmed_paid", "resultEvent": "result-found", "customEventCountBefore": 0, "customEventCountAfter": 1, "customEventCountDelta": 1, "attemptedPushCount": 1, "confirmedDatasetWrites": 1, "unknownDatasetWrites": 0, "paidResultCount": 1, "aggregateChargedCount": 2, "eventChargeLimitReached": false, "lastReceiptId": null },
  "replaySafe": false,
  "errors": [],
  "terminal": { "primaryKvsWrite": "confirmed", "recoveryKvsWrite": "not_attempted", "exit": "requested" }
}
```

The real `report` in `OUTPUT` is the complete Dataset report, not the abbreviated object above. The abbreviated block is shown only to keep this README readable.

#### Acceptance receipt shapes

These two blocks describe the required evidence shape before release; they are not claims that a new canary has already run. After the one authorized canary, the separate campaign release record binds the exact immutable build and run IDs; this reusable contract example remains placeholder-only.

```json
{
  "exampleType": "expected_first_canary_receipt",
  "runId": "<current-run-id>",
  "buildId": "<candidate-build-id>",
  "status": "SUCCEEDED",
  "evidenceAccepted": true,
  "solePostCount": 1,
  "noRetry": true
}
```

```json
{
  "exampleType": "expected_read_only_reconciliation",
  "runId": "<same-current-run-id>",
  "buildId": "<same-candidate-build-id>",
  "status": "SUCCEEDED",
  "evidenceAccepted": true,
  "solePostCount": 0,
  "readOnlyReconciliation": true
}
```

#### Duplicate-only partial business result

Duplicate inputs are not a runtime failure. If three valid input records reduce to two source identities, the report has `requested:3`, `unique:2`, and `duplicates:1`. It still produces one report unit. The gap is visible; no duplicate is silently charged as another result unit.

#### Pre-delivery failure

Invalid input, missing authorization, missing hosted run identity, report construction error, pricing mismatch, budget failure, or inability to persist the prepared receipt produces no Dataset attempt. `replaySafe:true` refers only to result delivery: no result push was attempted. A new run may still incur another automatic start charge.

#### Unknown Dataset delivery

When the linked push throws, runtime cannot safely prove whether the Dataset operation landed. `status` becomes `delivery_unknown`, `delivery.state` becomes `unknown_delivery`, `attemptedPushCount` is one, `unknownDatasetWrites` is one, and `replaySafe` is false. The Actor does not retry.

#### Known Dataset write, unknown settlement

When `pushData` returns but the post-push named counter is unreadable or malformed, Dataset delivery is known: `confirmedDatasetWrites:1`. Settlement is unknown: `status:settlement_unknown`, `counts.unknown:1`, and `replaySafe:false`. This is different from a thrown push.

#### Settlement anomaly

A named counter delta other than zero or one, or an invalid aggregate receipt after an exact `+1`, is a terminal anomaly. The Dataset write is known, but an automated consumer must reconcile the platform events and receipt before any retry or accounting action.

### Field dictionary

#### Report identity

- `schemaVersion`: active closed report version, exactly `2.0`.
- `actor`: stable slug `detik-news-search`.
- `recordType`: `detik_authorized_news_evidence_report`.
- `inputDigest`: SHA-256 over canonical normalized input.
- `reportDigest`: SHA-256 over the report body before `reportDigest` is added.

#### Normalized record

- `recordId`: buyer-supplied unique input row identity.
- `query`: buyer-supplied collection label, not a query executed by this Actor.
- `title`: buyer-supplied headline.
- `excerpt`: optional buyer-supplied licensed excerpt.
- `url`: normalized Detik HTTPS source link; recorded, not fetched.
- `articleId`: optional publisher/source identifier supplied by the buyer.
- `section`: optional buyer-supplied section label.
- `publishedAt`: optional buyer-supplied UTC publication time.
- `publishedText`: optional source display string supplied by the buyer.
- `observedAt`: buyer-supplied observation timestamp.
- `dedupeKey`: deterministic source identity used within this report.

#### Counts

- `requested`: validated input record count before source-identity deduplication.
- `unique`: retained source-identity count.
- `duplicates`: requested minus unique.
- `successful`: normalized unique records in current-run `OUTPUT`.
- `failed`: record-level failures; zero because invalid closed input fails before a report is built.
- `delivered`: confirmed Dataset report writes, including paid/free/anomalous or settlement-unknown states.
- `paid`: exact named paid report count.
- `free`: known Dataset report with zero named result delta.
- `anomalous`: known Dataset report with inconsistent settlement evidence.
- `unknown`: unknown Dataset delivery or unknown settlement count.
- `withheld`: built report not attempted because of a pre-delivery stop.

#### Evidence and review

- `authorization`: the buyer’s assertion, not an independent licence verification.
- `sourceBoundary`: always `buyer_supplied_no_fetch` in version 2.0.
- `externalVerificationPerformed`: always false.
- `contentFetchedByActor`: always false.
- `freshness.status`: always `buyer_asserted`.
- `freshness.latestObservedAt`: maximum submitted observation timestamp.
- `confidence.score`: fixed bounded score for closed format and assertion only; not article accuracy.
- `gaps`: exact unverified dimensions.
- `action`: required buyer review.
- `safeToAutomate`: always false.

#### Billing intent versus settlement

- `billingEligible`: the report is a candidate for the paid unit.
- `billingIntent`: linked named-event push is intended.
- `eventName`: `result-found`.
- `unit`: `authorized_evidence_report`.
- `settlementSource`: current-run KVS `OUTPUT`.

These Dataset fields are intentionally settlement-neutral. Do not convert `billingEligible:true` into “paid.”

### Evidence and boundaries

#### What the digests prove

The digests prove byte relationships inside this contract. They let you detect whether normalized input or report content changed. They can support reproducibility, incident review, and handoff integrity.

They do not prove that a headline was published, that a date was observed at a source, that an article exists, that a provider granted rights, that a record is complete, or that a person or organization approved downstream use. A hash is not a signature, timestamp authority, copyright licence, or authenticity certificate.

#### What zero-network means

The active Actor code has no source HTTP client import and no source-fetch path. The Apify SDK still provides platform communication, but the business runtime does not resolve a Detik hostname, open an article, load an image, follow a source redirect, call an LLM, contact a feed provider, inspect robots, or verify a licence. This makes the report computation deterministic and removes publisher-access, source-redirect, SSRF, proxy, and upstream-rate-limit behavior from the Actor.

Zero-network does not make submitted material lawful. Buyers remain responsible for the records they place into Apify storage and any downstream system.

#### Freshness boundary

`observedAt`, `publishedAt`, and `publishedText` are buyer-supplied. Runtime checks timestamp syntax and calendar reality; it does not compare them with a live page or trusted clock. `freshness.status:buyer_asserted` is literal.

#### Completeness boundary

The report covers only the submitted array. It does not claim to represent every matching article, a complete publisher archive, all sections, all languages, a time window, or the current state of Detik. There is no pagination and no hidden source coverage claim.

#### Decision boundary

The Actor does not assess relevance, sentiment, importance, bias, safety, legality, accuracy, originality, copyright status, or commercial value. It does not recommend publication. The fixed review action exists to prevent a normalization report from being mistaken for a decision.

### Decision routing

The report is designed to enter a human-controlled route:

1. **Validate receipt:** require the current Apify run ID, `status:success`, Dataset length one, exact named counter `0 → 1`, and matching input/report digests.
2. **Review authorization:** confirm that the stored reference maps to an active agreement, export right, or buyer-owned dataset with the intended downstream scope.
3. **Review content:** sample or verify records against the licensed system of record outside this Actor.
4. **Review minimization:** remove fields not needed by the downstream purpose, especially excerpts or personal data embedded in text.
5. **Choose disposition:** approve, correct, retain for audit, or discard.
6. **Route only approved data:** publish, index, analyze, archive, or send to another system according to the applicable rights and retention policy.

Suggested routing states:

- `REVIEW`: default for every report;
- `APPROVE`: a human verified rights, purpose, and content for the intended use;
- `CORRECT`: a human found a field or identity issue and will submit a corrected new run;
- `HOLD`: settlement, authorization, or provenance is unresolved;
- `DISCARD`: data is unnecessary, unauthorized, malformed, expired, or outside purpose.

Never treat the Actor’s fixed confidence score as a routing threshold for publication. It describes contract evidence, not editorial confidence.

### Commercial playbooks

#### Licensed feed intake

Take a small batch from a licensed feed, map it to the closed contract, normalize it, and store the report with the feed’s internal authorization reference. Review duplicates and timestamps, then route approved records to the intended index.

#### Newsroom archive migration

Export records from a system your organization controls, run bounded batches, compare input/report digests, and use the duplicate count to identify repeated source identities. Keep migration reconciliation outside the Actor; the report is one auditable batch artifact.

#### Evidence handoff between teams

The ingestion team supplies authorized records and a reference. The Actor produces a stable report. The reviewing team reads the report and current-run receipt, verifies the reference, and approves a downstream action. The digest gives both teams a common content identity without claiming truth.

#### Regression fixture for a licensed pipeline

Use synthetic or licensed records as a repeatable fixture. Identical normalized input produces an identical report digest. If a mapping change alters the digest, review the contract difference before promoting the downstream pipeline.

#### Incident reconciliation

If the run is `delivery_unknown` or `settlement_unknown`, stop automation. Compare the current run’s Dataset items, named event counters, platform status, and KVS receipt. Do not issue a new run merely to see whether it works; a second run can create another report and another charge.

### Integration recipes

#### JavaScript

```js
const run = await fetch('https://api.apify.com/v2/acts/zinin~detik-news-search/runs?maxTotalChargeUsd=0.006', {
  method: 'POST',
  headers: { Authorization: `Bearer ${process.env.APIFY_TOKEN}`, 'Content-Type': 'application/json' },
  body: JSON.stringify(input),
}).then((response) => response.json());

const runId = run.data.id;
// Poll the run to terminal, then load Dataset and KVS OUTPUT for the same runId.
```

Keep the token out of the URL when your client supports an Authorization header. Do not log the input if it contains licensed text or personal data.

#### Python

```python
import os, requests

response = requests.post(
    'https://api.apify.com/v2/acts/zinin~detik-news-search/runs',
    params={'maxTotalChargeUsd': '0.006'},
    headers={'Authorization': f"Bearer {os.environ['APIFY_TOKEN']}"},
    json=input_payload,
    timeout=30,
)
response.raise_for_status()
run_id = response.json()['data']['id']
```

#### Apify schedules

Schedules are appropriate only when an upstream licensed system prepares fresh input. Reusing a static input creates a new paid report on every run. Make the upstream workflow decide whether a new authorized batch exists before it starts this Actor.

#### Webhooks

Use a run-finished webhook to wake a reconciler, not to assume delivery. The reconciler should load `OUTPUT`, require the same run ID, verify status and counts, and route unknown/anomalous states to manual review.

#### Dataset-to-warehouse

Persist `reportDigest`, `inputDigest`, authorization reference, source identity, and the Apify run ID as separate columns. Do not flatten away `gaps`, `safeToAutomate`, or settlement source. Store the KVS receipt or a verified receipt projection beside the report.

![Detik Authorized News Evidence Normalizer: zero-network evidence and settlement workflow](https://raw.githubusercontent.com/TimmyZinin/apify-actor-assets/ffccc895a1108f819b9c339ed3e8853cd2bc2b50/commercial115/detik-news-search/readme-workflow.webp)

### Operating guide

#### Before every run

- Confirm the batch is authorized for this processing and intended downstream use.
- Remove credentials, signed URLs, secrets, unnecessary contact data, and irrelevant text.
- Ensure all Detik links are credential-free HTTPS.
- Use unique stable `recordId` values.
- Use real UTC timestamps with milliseconds.
- Keep the batch at or below 25 records.
- Set a cap that covers start plus one result at your active tier.
- Record the intended authorization reference outside Apify as well.

#### After every run

- Confirm the platform run ID equals `OUTPUT.runId`.
- Require a terminal platform status compatible with the receipt.
- Confirm Dataset length and `confirmedDatasetWrites` agree.
- Confirm `inputDigest` and `reportDigest` match across Dataset and OUTPUT.
- For success, require named result counter before zero, after one, delta one.
- Review authorization, records, duplicates, gaps, and the required action.
- Apply your retention schedule to INPUT, Dataset, OUTPUT, and logs.

#### Unknown-state procedure

1. Do not start another run.
2. Capture the current run ID, Dataset ID, KVS ID, platform counters, and logs.
3. If the push threw, determine whether the report exists in the Dataset.
4. If the push returned but the counter was unreadable, confirm named settlement from platform event data.
5. Compare report and input digests.
6. Record a manual disposition.
7. Start a corrective new run only when a human accepts the duplicate-delivery and new-start-charge risk.

#### Retention and deletion

Apify INPUT, Dataset, and KVS records are persistent storage. Configure retention appropriate to the licence and purpose. Delete test records and expired evidence. If a person’s data appears in a title or excerpt, apply the relevant privacy request and correction process. Do not assume a source URL makes unrestricted storage or redistribution appropriate.

#### Version policy

Version `2.0` is an intentional product pivot from source search to authorized normalization. Query-only legacy inputs are rejected and never trigger a hidden fallback. Future additive output fields require a versioned contract. Breaking input, digest, record identity, paid unit, or receipt changes require another explicit schema version and migration note.

### FAQ

#### Does this Actor search Detik?

No. It makes no source request. The `query` field is a buyer-supplied collection label preserved for context.

#### Does it verify that a Detik article exists?

No. It validates that the supplied URL is credential-free Detik HTTPS and canonicalizes tracking. It does not open the URL.

#### Does `confirmed:true` prove I have a licence?

No. It is your assertion. The Actor does not read contracts or contact a provider.

#### Why is `safeToAutomate` false?

Because normalized buyer input is not independently verified evidence for publication, legal conclusions, reputational scoring, or consequential decisions.

#### Can I submit a private feed URL?

No. The active record URL must be a Detik HTTPS link and is stored. Never submit credentials or signed URLs. This Actor does not ingest a feed URL.

#### Can I submit more than 25 records?

Split the authorized export into intentional batches. Each run produces and may charge one report, so account for start and result events per batch.

#### Why reject duplicate record IDs instead of deduplicating them?

`recordId` is input-row identity. Reusing it makes evidence mapping ambiguous. Source identity duplicates are handled separately and counted.

#### Why can two different record IDs produce one output record?

They can refer to the same `articleId` or canonical Detik URL. First source identity wins and the duplicate count remains visible.

#### Does `reportDigest` include the run ID?

No. That would make identical business input non-deterministic. Current run binding lives in KVS `OUTPUT`.

#### Is a successful Dataset write always paid?

No. A returned write can settle paid, free, anomalous, or unknown. Read the current-run KVS receipt.

#### Can I retry a failed run?

Pre-delivery failures make no report push, but a new run still buys another start. Any post-attempt state has `replaySafe:false`; reconcile before deciding.

#### Does the Actor store my data?

Yes. INPUT, Dataset, and KVS are Apify storages. Configure retention and deletion. The Actor does not send records to another provider.

#### Does the Actor call an LLM?

No. There is no model inference, prompt, token usage, semantic classification, or generated summary.

#### Does it preserve image URLs?

No. Version 2.0 deliberately omits images to reduce copied-content and tracking surface. Submit only the closed record fields.

#### Can it verify sentiment, relevance, or misinformation?

No. It normalizes fields and evidence boundaries only.

#### Is this legal advice?

No. Rights and privacy obligations depend on your agreement, jurisdiction, purpose, and downstream use. Obtain appropriate advice when needed.

#### What should a public demo contain?

Synthetic text, synthetic identifiers, and a non-fetching Detik-shaped URL. Do not copy live publisher content into a demo merely to prove the Actor works.

### Sources and rights

Version 2.0 does not retrieve content from Detik or any other source. It processes only data placed into Actor INPUT by the buyer. The buyer must have authority to submit, store, transform, and use the data and must comply with publisher, feed-provider, copyright, database, contract, privacy, confidentiality, attribution, and retention requirements.

Public availability of a webpage is not, by itself, a commercial redistribution licence. A robots rule is not a downstream licence. A buyer must not use this Actor to bypass access controls, obtain content they could not lawfully process, manufacture a false authorization trail, or redistribute protected material outside the scope of an agreement.

The authorization reference should point to a real internal approval, provider agreement, export entitlement, or buyer-owned dataset record. Keep the actual agreement and sensitive evidence in an appropriate system; do not paste credentials, signed URLs, personal contact details, or confidential contract text into Actor input.

Submitted titles and excerpts can contain personal data, allegations, sensitive topics, or copyrighted expression. Minimize fields to the downstream purpose, use human review, respect correction and deletion requests, avoid harassment and unlawful profiling, and do not use this normalization report as the sole basis for employment, credit, healthcare, legal, political, or other consequential decisions.

The Actor’s digests establish deterministic byte identity inside the contract. They do not establish ownership, authenticity, publication, accuracy, completeness, consent, or rights. `authorization.confirmed:true` is a buyer assertion. `freshness.status:buyer_asserted` and `externalVerificationPerformed:false` state the limits directly.

#### Ringkasan batas penggunaan

Actor ini tidak mencari atau mengambil halaman Detik. Input harus berasal dari ekspor, feed, atau arsip yang boleh Anda proses. Konfirmasi otorisasi adalah pernyataan pembeli, bukan verifikasi independen. Periksa hak penggunaan, akurasi, kebutuhan data, dan masa penyimpanan sebelum meneruskan hasil. Jangan mengulangi run ketika status pengiriman atau settlement belum jelas.

The operational rule is simple: **authorized input, bounded normalization, explicit gaps, human review, and no blind retry**.

# Actor input Schema

## `schemaVersion` (type: `string`):

Closed zero-network report contract.

## `authorization` (type: `object`):

Your explicit authority for submitting and processing these records. Do not place credentials or private URLs in the reference.

## `records` (type: `array`):

Closed buyer-supplied records. Every string is bounded; URLs must be credential-free Detik HTTPS links; timestamps must be real UTC values with milliseconds.

## Actor input object example

```json
{
  "schemaVersion": "2.0",
  "authorization": {
    "basis": "buyer_owned_export",
    "provider": "licensed newsroom export",
    "reference": "internal-approval-2026-08",
    "confirmed": true
  },
  "records": [
    {
      "recordId": "detik-bank-indonesia-001",
      "query": "Bank Indonesia",
      "title": "Bank Indonesia policy update",
      "excerpt": "Buyer-supplied licensed excerpt for normalization.",
      "url": "https://finance.detik.com/moneter/d-9000001/bank-indonesia-policy-update",
      "articleId": "d-9000001",
      "section": "detikFinance",
      "publishedAt": "2026-08-12T08:00:00.000Z",
      "publishedText": "12 Agustus 2026",
      "observedAt": "2026-08-12T09:00:00.000Z"
    }
  ]
}
```

# Actor output Schema

## `OUTPUT` (type: `string`):

Run-bound report, exact work counts, Dataset delivery, named result-event settlement, failure state, replay safety, and KVS terminal state.

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

API URL for the settlement-neutral report row.

# 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 = {
    "schemaVersion": "2.0",
    "authorization": {
        "basis": "buyer_owned_export",
        "provider": "licensed newsroom export",
        "reference": "internal-approval-2026-08",
        "confirmed": true
    },
    "records": [
        {
            "recordId": "detik-bank-indonesia-001",
            "query": "Bank Indonesia",
            "title": "Bank Indonesia policy update",
            "excerpt": "Buyer-supplied licensed excerpt for normalization.",
            "url": "https://finance.detik.com/moneter/d-9000001/bank-indonesia-policy-update",
            "articleId": "d-9000001",
            "section": "detikFinance",
            "publishedAt": "2026-08-12T08:00:00.000Z",
            "publishedText": "12 Agustus 2026",
            "observedAt": "2026-08-12T09:00:00.000Z"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("zinin/detik-news-search").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 = {
    "schemaVersion": "2.0",
    "authorization": {
        "basis": "buyer_owned_export",
        "provider": "licensed newsroom export",
        "reference": "internal-approval-2026-08",
        "confirmed": True,
    },
    "records": [{
            "recordId": "detik-bank-indonesia-001",
            "query": "Bank Indonesia",
            "title": "Bank Indonesia policy update",
            "excerpt": "Buyer-supplied licensed excerpt for normalization.",
            "url": "https://finance.detik.com/moneter/d-9000001/bank-indonesia-policy-update",
            "articleId": "d-9000001",
            "section": "detikFinance",
            "publishedAt": "2026-08-12T08:00:00.000Z",
            "publishedText": "12 Agustus 2026",
            "observedAt": "2026-08-12T09:00:00.000Z",
        }],
}

# Run the Actor and wait for it to finish
run = client.actor("zinin/detik-news-search").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 '{
  "schemaVersion": "2.0",
  "authorization": {
    "basis": "buyer_owned_export",
    "provider": "licensed newsroom export",
    "reference": "internal-approval-2026-08",
    "confirmed": true
  },
  "records": [
    {
      "recordId": "detik-bank-indonesia-001",
      "query": "Bank Indonesia",
      "title": "Bank Indonesia policy update",
      "excerpt": "Buyer-supplied licensed excerpt for normalization.",
      "url": "https://finance.detik.com/moneter/d-9000001/bank-indonesia-policy-update",
      "articleId": "d-9000001",
      "section": "detikFinance",
      "publishedAt": "2026-08-12T08:00:00.000Z",
      "publishedText": "12 Agustus 2026",
      "observedAt": "2026-08-12T09:00:00.000Z"
    }
  ]
}' |
apify call zinin/detik-news-search --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,zinin/detik-news-search"
        }
    }
}

```

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/VRswz3eNRon70V5eb/builds/jb8eQKVYIljazdl24/openapi.json
