# Licensed Baku Property Export Normalizer (`zinin/tap-az-baku`) Actor

Normalize authorized or licensed Baku property exports into provenance-linked review rows with stable identity, freshness, confidence gaps, human actions, and current-run settlement. Zero portal requests, verification, valuation, or automated property decisions.

- **URL**: https://apify.com/zinin/tap-az-baku.md
- **Developed by:** [Tim Zinin](https://apify.com/zinin) (community)
- **Categories:** Real estate
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.70 / 1,000 normalized baku property records

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

## Licensed Baku Property Export Normalizer

Turn property records you are allowed to use into clean, provenance-linked evidence rows for Baku real-estate review. The Actor accepts buyer-owned, owner- or agent-authorized, licensed-feed, or otherwise authorized structured exports. It does **not** visit Tap.az, crawl a property portal, open listing URLs, log in, use a browser, or verify that a listing is active.

![Licensed Baku Property Export Normalizer: authorized listings to evidence-led review](https://raw.githubusercontent.com/TimmyZinin/apify-actor-assets/50a6654c304b6d3861d544f20cf601b7f2bc43db/commercial115/tap-az-baku/readme-hero.webp)

The historical Store slug remains `tap-az-baku` for API continuity. The current product is an independent zero-network property-export normalizer. It is not affiliated with Tap.az and does not grant access to or rights in Tap.az content. Historical search fields remain accepted, but they now return a free migration diagnostic instead of contacting the old source.

### What you get

For every accepted unique licensed property record, the Actor delivers one Dataset row with:

- the established property fields: URL, title, price, currency, deal type, property type, rooms, area, location, coordinates, posting date, description, images, floor, and processing time;
- a stable listing identity derived from the supplied source name and source listing ID;
- deterministic input, request, and row digests;
- source name, source URL, licence or authorization statement, retrieval time, and changes made;
- freshness based only on the buyer-supplied source retrieval timestamp;
- evidence confidence with a basis and concrete gaps;
- a human-review outcome, priority, action, and `safeToAutomate: false`;
- settlement-neutral Dataset billing metadata;
- authoritative current-run delivery and PAY\_PER\_EVENT reconciliation in KVS `OUTPUT`.

The Actor answers “did this supplied record satisfy the normalization contract?” It does not answer “is this property available?”, “is the price current?”, “is the seller genuine?”, “is this a good investment?”, or “may I republish these photos?”.

### Who uses it

#### Property operations teams

Normalize authorized exports from owners, agents, internal systems, data partners, or licensed feeds before human review and downstream routing.

#### Real-estate agencies and portfolio teams

Create a consistent evidence layer across mixed exports while preserving source identity, licence context, timestamps, and transformations.

#### Data engineers

Consume stable IDs, digests, closed schemas, explicit diagnostics, and a current-run settlement receipt instead of relying on process exit or Dataset presence alone.

#### Governance and compliance reviewers

Inspect source-rights context, licence text, image and description provenance, retrieval time, confidence gaps, and the recommended review action.

#### Market analysts

Organize authorized listing evidence by price, type, area, location, and freshness without implying live market coverage or investment advice.

### What it does not do

- It does not request Tap.az or another property website.
- It does not fetch the recorded listing, source, or image URLs.
- It does not use a browser, proxy, session cookie, API key, or login.
- It does not bypass robots instructions, access controls, paywalls, or rate limits.
- It does not verify ownership, agency authority, title, permits, zoning, valuation, availability, price, dimensions, coordinates, images, or descriptions.
- It does not detect scams, legal defects, investment quality, or expected returns.
- It does not contact sellers, agents, owners, tenants, or buyers.
- It does not identify or profile natural persons.
- It does not create a cross-run monitoring baseline.
- It does not make a purchase, rental, credit, legal, or investment decision.
- It does not prove settlement from the Dataset row; current-run KVS `OUTPUT` is authoritative.

### How it works

![Licensed Baku Property Export Normalizer: rights, schema, identity, provenance, delivery, and review](https://raw.githubusercontent.com/TimmyZinin/apify-actor-assets/50a6654c304b6d3861d544f20cf601b7f2bc43db/commercial115/tap-az-baku/readme-workflow.webp)

1. **Rights confirmation** — select a closed source context and confirm commercial processing rights.
2. **Schema validation** — reject unknown fields, bad types, controls, malformed timestamps, bad coordinates, impossible prices, and unsafe URL syntax.
3. **Identity deduplication** — suppress duplicate `(sourceName, sourceListingId)` identities before paid delivery.
4. **Evidence construction** — derive stable IDs, digests, freshness, confidence gaps, provenance, and a human-review boundary.
5. **Linked delivery** — deliver each eligible row through one `pushData(row, 'result-found')` operation.
6. **Settlement verification** — require the named counter to move by exactly `+1` and require the linked aggregate receipt to match.
7. **Terminal receipt** — persist exact work, delivery, payment, diagnostic, withheld, unknown, partial, budget, fatal, and replay state to KVS `OUTPUT`.

The whole pipeline performs zero source network requests.

### How to run

Use the modern contract for new integrations:

```json
{
  "schemaVersion": "2.0",
  "authorization": "I confirm I may process and commercially use these property records.",
  "sourceContext": "owner_or_agent_authorized_export",
  "batchName": "baku-property-review",
  "freshnessHours": 168,
  "listings": [
    {
      "sourceListingId": "demo-baku-001",
      "title": "Two-bedroom apartment",
      "price": 285000,
      "currency": "AZN",
      "deal_type": "sale",
      "property_type": "Apartment",
      "rooms": 3,
      "rooms_raw": "2 otaq",
      "area_sqm": 95,
      "location": "Bakı, Azərbaycan",
      "floor_raw": "8-ci mərtəbə",
      "lat": null,
      "lng": null,
      "posted_date": "2026-08-12T09:00:00.000Z",
      "description": "Buyer-supplied property summary.",
      "images": [],
      "url": "https://properties.example.com/listings/demo-baku-001",
      "sourceName": "Example authorized property export",
      "sourceUrl": "https://properties.example.com/exports/baku",
      "sourceLicense": "Owner-authorized export for internal property review.",
      "sourceRetrievedAt": "2026-08-12T10:00:00.000Z",
      "changesMade": "Selected fields and normalized whitespace."
    }
  ]
}
```

The domains, listing, price, and organization in the example are fictional. Replace them with data and provenance you are authorized to process.

### Input contract

The root object is closed. Unknown fields fail before Dataset delivery. Choose exactly one mode.

#### Modern authorized-export mode

Required root fields:

| Field | Type | Meaning |
|---|---|---|
| `schemaVersion` | string | Must equal `2.0`. |
| `authorization` | closed string | Exact rights confirmation. |
| `sourceContext` | enum | Buyer-asserted rights context. |
| `listings` | array | One to 100 structured records. |

Optional root fields:

| Field | Bounds | Default | Meaning |
|---|---:|---:|---|
| `batchName` | 1–80 nonblank characters | `licensed-property-evidence-review` | Review-batch label. |
| `freshnessHours` | 1–8,760 integer | `168` | Freshness threshold applied to source retrieval time. |

Source contexts:

- `buyer_owned_property_export`
- `owner_or_agent_authorized_export`
- `licensed_property_feed`
- `other_authorized_property_export`

The selection is a buyer assertion. The Actor does not read a contract or make a legal determination.

#### Listing record fields

| Field | Required | Contract |
|---|---:|---|
| `sourceListingId` | yes | Nonblank durable source identifier, maximum 200 characters. |
| `title` | yes | Supplied title, 1–300 characters. |
| `price` | yes | Finite number above zero and at most `1e15`. |
| `currency` | yes | Supplied currency label, 1–10 characters. No exchange-rate validation. |
| `deal_type` | yes | `sale` or `rent`. |
| `property_type` | yes | Supplied type label, maximum 100 characters. |
| `rooms` | no | Nullable finite number from 0 to 1,000. |
| `rooms_raw` | no | Nullable source label such as `2 otaq`, maximum 100 characters. |
| `area_sqm` | no | Nullable finite number from 0 to 100,000,000. Unit is buyer-asserted. |
| `location` | no | Nullable supplied location, maximum 300 characters. |
| `floor_raw` | no | Nullable supplied floor label, maximum 100 characters. |
| `lat` | no | Nullable latitude from -90 through 90. |
| `lng` | no | Nullable longitude from -180 through 180. |
| `posted_date` | no | Nullable calendar-valid UTC timestamp ending in `Z`. |
| `description` | no | Nullable buyer-supplied text, maximum 500 characters. Minimize personal data. |
| `images` | yes | Zero to five HTTPS URLs. URLs are recorded and never fetched. |
| `url` | yes | HTTPS listing reference without credentials, custom port, or fragment. Never fetched. |
| `sourceName` | yes | Source or export name, maximum 200 characters. |
| `sourceUrl` | yes | HTTPS provenance reference, never fetched. |
| `sourceLicense` | yes | Licence or authorization statement, maximum 700 characters. |
| `sourceRetrievedAt` | yes | Calendar-valid UTC timestamp supplied by the buyer/provider. |
| `changesMade` | yes | Selection, normalization, truncation, or transformation disclosure, maximum 500 characters. |

The URL parser lowercases hostnames. It rejects credentials, explicit ports, fragments, and non-HTTPS protocols. This is syntax validation only; the Actor does not resolve or trust the host.

#### Legacy migration mode

Historical inputs continue to parse:

```json
{
  "category": "menziller",
  "max_items": 5,
  "max_pages": 1,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "AZ"
  }
}
```

No Tap.az or other source request is made. The normalized legacy scope is digested into the receipt, and one free diagnostic explains how to submit an authorized `listings` export. No `result-found` event is intentionally emitted.

Legacy bounds are preserved:

- `category`: bounded nonblank historical string
- `max_items`: integer 1–2000
- `max_pages`: integer 1–20
- `proxyConfiguration`: closed historical object; unknown keys are rejected and values are only normalized and digested

These values no longer control a crawl; they exist for migration compatibility.

### Happy, partial, and failure output

The release gate uses these concise acceptance wrappers. They bind the current run and immutable build; they are verification evidence, not extra Dataset or `OUTPUT` fields.

```json
{
  "runId": "example-current-run",
  "buildId": "candidate-build-id",
  "status": "SUCCEEDED",
  "evidenceAccepted": true,
  "artifact": "Dataset row"
}
```

```json
{
  "runId": "example-current-run",
  "buildId": "candidate-build-id",
  "status": "SUCCEEDED",
  "evidenceAccepted": true,
  "artifact": "KVS OUTPUT"
}
```

#### Happy output

A valid unique record produces one `licensed_property_record`. The checked example is generated from `examples/input.json`. Key fields look like this:

```json
{
  "schemaVersion": "2.0.0",
  "recordType": "licensed_property_record",
  "stableId": "licensed-property:…",
  "title": "Two-bedroom apartment",
  "price": 285000,
  "currency": "AZN",
  "deal_type": "sale",
  "property_type": "Apartment",
  "found": true,
  "partial": false,
  "sourceListingId": "demo-baku-001",
  "freshness": {
    "status": "fresh",
    "basis": "buyer_supplied_source_retrieval_time"
  },
  "change": {
    "status": "not_measured",
    "basis": "single_authorized_export_record"
  },
  "decision": {
    "outcome": "review_required",
    "priority": "normal",
    "safeToAutomate": false
  },
  "billing": {
    "billingEligible": true,
    "billingIntent": "linked_push_result_found",
    "eventName": "result-found",
    "settlementSource": "current_run_kvs_output"
  }
}
```

The Dataset row states billing eligibility and intent, not payment. Read current-run KVS `OUTPUT` to determine paid, free, withheld, unknown, or anomalous settlement.

#### Partial output

`PARTIAL` means the run delivered diagnostics, suppressed duplicates, rejected invalid records, stopped at the budget, or completed less than all eligible work without a fatal state.

Examples:

- one valid and one malformed record can produce one paid listing plus one free invalid-record diagnostic;
- repeated source identity produces one paid candidate plus a free duplicate diagnostic;
- legacy scope produces a free migration diagnostic and no paid listing;
- a buyer budget may preserve confirmed rows while counting remaining rows as withheld.

Diagnostics have `recordType: property_export_advisory`, `found: false`, `partial: true`, null property facts, empty images, explicit `failureDiagnostics`, and `billingEligible: false`.

#### Failure output

Fatal conditions include:

- missing or invalid hosted run ID;
- missing current Dataset ID;
- invalid input root or mode;
- pricing map, tier, spend, or initial named-counter mismatch;
- pre-push counter failure;
- unknown linked-push outcome;
- unreadable or contradictory post-push settlement;
- free diagnostic write failure;
- primary and bounded recovery KVS failures;
- platform exit failure.

A thrown linked push is `unknown_delivery`; it is not automatically retried or labeled withheld. A returned push with unreadable settlement is `settlement_unknown`; the Dataset write may be known even when payment is not. A contradictory counter/receipt is `settlement_anomaly`. These are distinct states.

### Field dictionary

| Field | Meaning now |
|---|---|
| `url` | Recorded HTTPS listing URL; never fetched. |
| `title` | Supplied listing title. |
| `price` | Supplied numeric price; not appraised or verified. |
| `currency` | Supplied currency label; no FX conversion. |
| `deal_type` | Supplied `sale` or `rent`. |
| `property_type` | Supplied property type. |
| `rooms` | Supplied normalized number or null. |
| `area_sqm` | Supplied area or null. |
| `location` | Supplied location or null. |
| `lat`, `lng` | Supplied coordinates or null. |
| `posted_date` | Supplied UTC timestamp or null. |
| `description` | Supplied bounded description or null. |
| `images` | Up to five recorded HTTPS URLs; never fetched. |
| `source_portal` | Compatibility alias for `sourceName`; not a verified portal identity. |
| `scraped_at` | Compatibility alias for `observedAt`; the current runtime does not scrape. |
| `partial` | False on accepted property rows, true on diagnostics. |
| `partial_reason` | Null on property rows, diagnostic summary on advisories. |
| `rooms_raw` | Supplied room label. |
| `floor_raw` | Supplied floor label. |
| `found` | True only for an accepted normalized property record. |
| `error`, `note` | Null on accepted records; diagnostic summary on advisories. |

### Evidence and boundaries

#### Identity

- `schemaVersion`: `2.0.0`
- `recordType`: paid candidate or free advisory
- `stableId`: deterministic source-name plus source-listing identity
- `sourceListingId`: supplied source identity
- `rowDigest`: digest of the complete row excluding itself

#### Provenance

- `sourceName`
- `sourceUrl`
- `sourceLicense`
- `sourceRetrievedAt`
- `changesMade`
- `evidence.entityId`
- `evidence.inputDigest`
- `evidence.requestDigest`
- `evidence.sourceContext`
- `evidence.networkRequestsMade: 0`
- `evidence.sourceFetchedByActor: false`

Provenance values are preserved buyer assertions. Their presence improves traceability but does not prove authenticity or legal sufficiency.

#### Freshness

- `status`: `fresh`, `stale`, or `unknown`
- `observedAt`: processing time
- `sourceRetrievedAt`: supplied retrieval time
- `ageSeconds`: nonnegative difference; a future supplied time clamps to zero
- `thresholdSeconds`: `freshnessHours × 3,600`
- `basis`: buyer-supplied retrieval time or no delivered record

Freshness does not prove listing activity.

#### Change

`change.status` is always `not_measured`. The Actor is stateless and does not compare with earlier runs. Use stable IDs and row digests in your own licensed storage if version tracking is required.

#### Confidence

The confidence score reflects deterministic contract completeness. Its permanent gaps include unverified listing activity, price, property facts, source terms, and identity. Missing location, posting time, rooms, area, or stale source evidence adds more gaps. The score is not an accuracy probability.

#### Decision

Accepted property records use `review_required`. Diagnostics use `input_action_required`. Every row has `safeToAutomate: false`. Review should cover source rights, availability, price, property facts, images, personal data, and the intended downstream action.

#### Billing

Dataset billing fields are settlement-neutral:

- `billingEligible`
- `billingIntent`
- `eventName`
- `unit`
- `settlementSource: current_run_kvs_output`

The unit is **one delivered normalized licensed property record**.

### KVS OUTPUT

KVS `OUTPUT` is bound to the current run and current default Dataset.

#### Input partition

Modern input must satisfy:

```text
requestedCount = uniqueCount + duplicateCount + invalidCount
```

Legacy mode has `legacyScopeCount: 1` and zero modern counts.

#### Work partition

The receipt exposes:

- `attemptedCount`
- `successfulCount`
- `failedCount`
- `deliveredRowCount`
- `paidRowCount`
- `freeRowCount`
- `withheldRowCount`
- `unknownDeliveryCount`
- `unknownSettlementCount`
- `normalizedPropertyCount`
- `partial`
- `budgetStopped`
- `fatalError`
- `replaySafe`
- `safeToAutomate`

The runtime semantic validator reconstructs the exact partition. Nonnegative numbers alone are not enough.

#### Paid delivery proof

For each paid record:

1. named `result-found` counter before the push equals already confirmed paid rows;
2. one linked push is attempted;
3. named counter after equals before plus one;
4. named delta is exactly one;
5. aggregate linked receipt is exactly two events;
6. Dataset delivery count and paid count advance together.

The start event is automatic and separate. A final paid operation may report `eventChargeLimitReached: true`; that row remains delivered and paid, while later work is withheld.

#### Replay safety

`replaySafe` is true only before any Dataset push attempt in the current run. After a free or paid push attempt it is false. A new run always incurs a new automatic start event and can redeliver/recharge the same supplied records; stable IDs do not make Actor billing idempotent.

### Pricing

The Actor uses PAY\_PER\_EVENT:

- automatic Actor start event;
- `result-found` for one delivered normalized licensed property record.

Free diagnostic rows do not intentionally emit `result-found`. Unknown or anomalous operations require reconciliation and are never promised free.

Base FREE-tier prices before subscription discounts:

- Actor start: **$0.005**
- normalized property record: **$0.002**

| Delivered records | Result subtotal | Start | Base total |
|---:|---:|---:|---:|
| 1 | $0.002 | $0.005 | $0.007 |
| 10 | $0.020 | $0.005 | $0.025 |
| 50 | $0.100 | $0.005 | $0.105 |
| 100 | $0.200 | $0.005 | $0.205 |

The live pricing panel and current-run receipt are authoritative. Higher tiers receive the configured discounts. No proxy, browser, source API, or LLM variable cost is used by this runtime.

### Baku and Azerbaijan context

The product retains a Baku-oriented Store identity because existing integrations and buyers use that scope. The modern contract does not infer geography from a URL or title. Supply location and coordinates only from your authorized source.

Azerbaijani property labels such as `satılır`, `kirayə`, `mənzil`, `2 otaq`, and floor descriptions may be preserved as source text. The Actor does not translate, reinterpret, standardize legal categories, or convert Azerbaijani manat.

Do not treat a district name, map point, or description as cadastral evidence. Do not infer title, zoning, earthquake compliance, occupancy, tenant status, or municipal permission from a normalized listing row.

### Azərbaycan dilində qısa izah

Bu Actor Tap.az saytına və ya başqa əmlak portalına sorğu göndərmir. O, yalnız sizin qanuni şəkildə istifadə etməyə icazəniz olan strukturlaşdırılmış əmlak məlumatlarını normallaşdırır. Hər ödənişli sətir mənbə identifikatorunu, təqdim edilmiş faktları, mənbə və lisenziya qeydlərini, yenilik göstəricisini, boşluqları, insan yoxlaması üçün növbəti addımı və cari icranın KVS hesablaşma istinadını saxlayır.

Məlumatı göndərməzdən əvvəl şəxsi əlaqə məlumatlarını, məxfi URL parametrlərini və lazımsız şəxsi məlumatları çıxarın. Nəticə mülkiyyət hüququnu, elanın aktivliyini, qiyməti, hüquqi statusu və ya investisiya uyğunluğunu təsdiqləmir; insan yoxlaması tələb olunur. Köhnə Tap.az giriş sahələri yalnız miqrasiya diaqnostikası yaradır və heç bir portal sorğusu və ya `result-found` ödənişi yaratmır.

### Sources and rights

You must hold rights that cover:

1. obtaining the export;
2. processing it in your Apify workspace;
3. transforming fields and storing digests;
4. commercial use where applicable;
5. sharing with intended recipients;
6. retaining descriptions and images;
7. required attribution and change indication;
8. deletion or correction obligations.

Public visibility is not a redistribution licence. Robots allowance is not a commercial licence. Buyer authorization cannot replace owner, agent, portal, database, photographer, or other rightsholder permission when such permission is required.

The current Actor does not request Tap.az. If you independently possess a Tap.az export, use it only if written rights cover the intended processing and downstream use. The Actor does not review or extend those rights.

### Privacy and responsible use

Property exports can contain seller names, agent contacts, phone numbers, email addresses, tenant information, free-text personal data, occupied-home details, precise coordinates, or image URLs.

Before submission:

- remove direct personal contacts unless strictly necessary and lawfully processed;
- remove people, documents, license plates, access codes, or sensitive interior details from images where required;
- avoid exact coordinates when district-level location is sufficient;
- minimize descriptions;
- do not put secrets or session tokens in URLs;
- establish purpose, access controls, retention, deletion, and correction workflows;
- honor applicable data-protection, real-estate, advertising, consumer, database, and copyright rules;
- prohibit harassment, discrimination, unlawful profiling, tenant targeting, occupancy inference, and unsafe site visits;
- treat hashes and stable IDs as pseudonymous linkage, not anonymization.

Apify persists run input and storage according to workspace settings. Do not submit secrets or unnecessary personal information.

### Decision routing

#### Fresh and complete supplied evidence

Route with normal priority to a person who can verify availability, price, facts, source rights, and intended use.

#### Stale export

Route with high priority for a refresh from the authorized provider. Do not assume that a listing remains active.

#### Missing location, rooms, area, or posting time

Keep review-required. Ask the source owner for missing evidence instead of inventing values.

#### Duplicate source identity

Correct the upstream export. The Actor suppresses duplicate paid work but does not select a winning version.

#### Invalid record

Inspect the schema, fix the source mapping, and start a new run only after accounting for the separate start charge.

#### Unknown delivery or settlement

Do not blind-retry. Reconcile the exact platform run, Dataset ID, KVS ID, named-event counters, and `OUTPUT` first.

### Commercial playbooks

#### Agency inventory review

1. Export only listings the agency may process and share.
2. Preserve agent/source identity and authorization evidence outside the Actor.
3. Supply listing IDs, facts, timestamps, URLs, licence, and changes.
4. Normalize a single row first.
5. Reconcile Dataset and KVS.
6. Verify availability and facts with the responsible agent.
7. Route approved records to the intended channel.

#### Licensed feed onboarding

1. Archive the licence and data dictionary.
2. Record permitted fields, territories, recipients, attribution, and retention.
3. Map source IDs without changing their meaning.
4. Keep photos and descriptions only when licensed.
5. Use `licensed_property_feed`.
6. Review gaps and stale evidence.
7. Scale only after one-row acceptance.

#### Internal portfolio normalization

1. Use `buyer_owned_property_export` for records controlled by your organization.
2. Keep internal property IDs stable.
3. State how prices and areas were transformed.
4. Avoid presenting internal estimates as portal facts.
5. Retain review-required decisions.

#### Migration from historical Tap.az scope

1. Existing flat fields continue to parse.
2. Observe the free migration diagnostic.
3. Obtain an authorized structured export.
4. Map it to `listings`.
5. Add licence and change disclosure.
6. Run one bounded canary.
7. Remove assumptions about portal crawling from downstream code.

### Integration recipes

#### Apify API

Start with the modern JSON input. Poll the exact run. Then read:

```text
GET /v2/datasets/{defaultDatasetId}/items
GET /v2/key-value-stores/{defaultKeyValueStoreId}/records/OUTPUT
```

Require `OUTPUT.runId` and `resultsUrl` to bind to the same current run before accepting rows.

#### Webhook

Treat a run-finished webhook as notification only. Fetch and validate `OUTPUT`, inspect failure and unknown counts, then fetch Dataset rows. Do not interpret webhook delivery as property-record settlement.

#### Warehouse

Use `stableId` as the natural identity and retain `rowDigest`, input digest, request digest, source retrieval time, observed time, source context, licence, and changes made.

Same stable ID plus same digest means the same normalized version. Same stable ID plus a different digest means review a changed supplied record. It does not prove a portal edit or live market change.

#### Map or GIS workflow

Coordinates are supplied and unverified. Validate datum, precision, authorization, and privacy before mapping. Never expose precise occupied-home coordinates merely because they were present in the export.

#### CRM or property-management system

Do not blind-import. Verify target schema, permitted fields, identity mapping, source rights, availability, and record ownership. Preserve the `safeToAutomate: false` control.

#### Spreadsheet review

The Dataset overview exposes price, currency, type, rooms, area, location, URL, freshness, confidence, action, evidence, failure, and billing intent. Nested JSON can be lossy in CSV; keep the original JSON receipt for audit.

### Operating guide

#### Before a run

- Confirm source rights and image rights.
- Remove unnecessary personal data.
- Validate durable source listing IDs.
- Normalize timestamps to UTC.
- Decide freshness threshold.
- Set a bounded maximum charge.
- Start with one record.
- Decide who performs human review.

#### After a run

1. Check platform terminal status.
2. Read current-run KVS `OUTPUT`.
3. Bind run and Dataset identities.
4. Inspect input partitions.
5. Inspect paid, free, withheld, and unknown partitions.
6. Require exact named `+1` per paid row.
7. Review partial and fatal state.
8. Review each Dataset decision and evidence block.
9. Verify listing facts and rights outside the Actor.
10. Do not retry an unknown operation automatically.

#### Monitoring

Alert on:

- failed status;
- any unknown delivery or settlement;
- fatal error;
- unexpected withheld work;
- rising invalid or duplicate counts;
- stale records;
- changed licence or transformation disclosure;
- Dataset count mismatch;
- nonzero network request claim;
- any downstream system attempting to override `safeToAutomate: false`.

### Reliability model

The runtime fails closed around identity, pricing, and settlement:

- hosted run ID required;
- current Dataset ID required;
- exact two-event pricing map required;
- exact tier prices required;
- visible start spend required;
- initial result counter zero for bounded canary;
- pre-push counter equals confirmed paid count;
- one linked push per row;
- named delta exactly one;
- aggregate receipt exactly two;
- no blind retry;
- exact current-run KVS receipt;
- one bounded KVS recovery write;
- failed terminal state when the truth cannot be proven.

JSON Schema is the structural stage. Runtime `validateRow` and `validateOutput` are mandatory semantic stages for exact digest, evidence, decision, work, delivery, source, and terminal relations.

### FAQ

#### Does this Actor scrape Tap.az?

No. The current runtime contains no source-fetch path and makes zero external source requests.

#### Why keep the old slug?

To preserve Actor identity and API integration continuity while replacing the source-incompatible product behavior.

#### Is it affiliated with Tap.az?

No. It is independent and does not imply endorsement, access, permission, or verification.

#### Can I process a Tap.az export?

Only when you possess rights covering the export, processing, commercial use, fields, images, retention, and downstream recipients. The Actor does not grant or verify them.

#### Does it verify active listings?

No. It does not fetch the listing URL. Active status is always an explicit confidence gap.

#### Does it verify the price or currency?

No. It validates numeric and text shape only. No appraisal, FX lookup, or source check occurs.

#### Does it provide investment advice?

No. It normalizes supplied evidence and requires human review.

#### Are images downloaded?

No. Up to five HTTPS references are recorded. The Actor does not fetch, inspect, resize, license, or moderate them.

#### Is an HTTPS URL trusted?

No. Syntax validation is not source verification or network safety proof.

#### Are duplicates billed?

Valid duplicate source identities are suppressed before paid work. A free diagnostic reports them.

#### Are invalid records billed?

Invalid records are excluded from paid work. A free diagnostic can report them when the Dataset write channel is confirmed free.

#### Does a Dataset row prove payment?

No. It contains settlement-neutral intent. Read the current-run KVS receipt.

#### What happens at the charge limit?

The Actor stops before unaffordable work. Already confirmed rows remain; remaining eligible work is withheld.

#### Can I retry a failed push?

Not blindly. A thrown push may have an unknown outcome. Reconcile manually first.

#### Is the run replay-safe?

Only before any push attempt. A new run can redeliver and recharge the same records and always has a separate start event.

#### Does it track listing changes?

No. `change.status` is `not_measured`. Compare stable IDs and row digests in your own licensed system.

#### Does freshness prove availability?

No. It measures age of the supplied retrieval timestamp.

#### Is confidence an accuracy score?

No. It is deterministic contract completeness with explicit gaps.

#### Why is automation marked unsafe?

Because rights, availability, price, facts, ownership, images, privacy, and downstream policy are not independently verified.

#### Can I submit 1,500 records as before?

The modern contract is intentionally bounded at 100 records per run for exact review and settlement. Split larger authorized exports into independently reconciled runs. Legacy `max_items` remains accepted only for migration.

#### Are Azerbaijani labels translated?

No. Supplied text is normalized for whitespace but not translated or legally interpreted.

#### Does `source_portal` mean portal-verified?

No. It is a compatibility alias for the supplied source name.

#### Why is the field called `scraped_at`?

Backward compatibility. It equals processing `observedAt`; the current runtime does not scrape.

#### Can it contact agents?

No. No outbound message, webhook, email, or call is made.

#### Can it identify a seller from images or descriptions?

No. Do not use this product for identity inference or personal profiling.

#### What audit evidence should I retain?

Keep the original authorized export, written rights evidence, exact Actor input, build and run IDs, Dataset and KVS IDs, current-run `OUTPUT`, row digests, and your review decision.

#### Where is the final truth?

Dataset rows contain normalized evidence. KVS `OUTPUT` contains current-run work and settlement truth. External source and legal truth must come from your authorized provider and review process.

### Support checklist

Provide non-secret:

- build ID and run ID;
- platform terminal status;
- Dataset and KVS IDs;
- `OUTPUT` status and failure code;
- input counters;
- delivered, paid, free, withheld, and unknown counters;
- stable ID and row digest;
- modern versus legacy mode;
- expected and observed behavior.

Do not publish access tokens, confidential licence text, owner contacts, tenant details, access instructions, or sensitive property information in support requests.

### Final boundary

This Actor is a normalization, evidence, review, and settlement layer for property exports you already have the right to use. It is not a crawler, portal licence, valuation service, investment product, title search, legal opinion, property inspector, identity verifier, or automated decision system. Source rights, privacy, copyright, listing verification, physical safety, review, retention, and downstream use remain the buyer’s responsibility.

# Actor input Schema

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

Closed public input contract version.

## `authorization` (type: `string`):

Required buyer attestation of processing and commercial-use rights; recorded but not independently verified by the Actor.

## `sourceContext` (type: `string`):

Buyer-declared rights context for the supplied structured export; the Actor records but does not independently verify it.

## `batchName` (type: `string`):

Stable buyer label returned with this review batch.

## `freshnessHours` (type: `integer`):

Age threshold used to label buyer-supplied evidence as fresh, stale, or unknown.

## `listings` (type: `array`):

Buyer-owned, owner- or agent-authorized, or otherwise licensed property records with provenance and transformation disclosure.

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

Legacy Tap.az `category` field retained only for migration; no source request is made.

## `max_items` (type: `integer`):

Legacy Tap.az `max_items` field retained only for migration; no source request is made. Historical integer bounds: 1 through 2000.

## `max_pages` (type: `integer`):

Legacy Tap.az `max_pages` field retained only for migration; no source request is made. Historical integer bounds: 1 through 20.

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

Legacy Tap.az `proxyConfiguration` field retained only for migration; no source request is made. Historical object values are closed, normalized, and digested; they never trigger a network request.

## Actor input object example

```json
{
  "schemaVersion": "2.0",
  "authorization": "I confirm I may process and commercially use these property records.",
  "sourceContext": "owner_or_agent_authorized_export",
  "batchName": "baku-property-review",
  "freshnessHours": 168,
  "listings": [
    {
      "sourceListingId": "demo-baku-001",
      "title": "Two-bedroom apartment",
      "price": 285000,
      "currency": "AZN",
      "deal_type": "sale",
      "property_type": "Apartment",
      "rooms": 2,
      "rooms_raw": "2 otaq",
      "area_sqm": 75,
      "location": "Bakı, Azərbaycan",
      "floor_raw": "8-ci mərtəbə",
      "lat": null,
      "lng": null,
      "posted_date": "2026-08-12T09:00:00.000Z",
      "description": "Buyer-supplied property summary.",
      "images": [],
      "url": "https://properties.example.com/listings/demo-baku-001",
      "sourceName": "Example authorized property export",
      "sourceUrl": "https://properties.example.com/exports/baku",
      "sourceLicense": "Owner-authorized export for internal property review.",
      "sourceRetrievedAt": "2026-08-12T10:00:00.000Z",
      "changesMade": "Selected fields and normalized whitespace."
    }
  ]
}
```

# Actor output Schema

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

No description

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

No description

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

// Run the Actor and wait for it to finish
const run = await client.actor("zinin/tap-az-baku").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("zinin/tap-az-baku").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 zinin/tap-az-baku --silent --output-dataset

```

## MCP server setup

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

```

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/7zsPIYrMtCiPub9AU/builds/5mCk212hdg4FICbwS/openapi.json
