The canonical structure for portals/<portal>.yaml. scout-recon writes these. Scripts and agents read them. Bump schema_version if you change the shape.
Required Fields (profile is unusable without these)
schema_version: 1
portal: <slug> # kleinanzeigen, immoscout24, etc.
display_name: <human-readable>
base_url: https://...
last_verified: <YYYY-MM-DD>
revisions: <int>
legal:
preferred_access: [scrape|api|rss|email_alerts]
ban_risk: low|medium|high
rendering:
search_page: ssr|csr|hybrid_ssr|hybrid_csr
detail_page: ssr|csr|hybrid_ssr|hybrid_csr
strategy:
recommended_order: [readability|hydration_blob|jina|playwright_stealth|chrome]
query_grammar:
search_url_template: <string with {placeholders}>
district_map:
"<District Name>": <encoded value>
extraction:
detail:
primary_source: hydration_blob|jsonld|dom
fields:
cold_rent: {path: ..., format: ..., required: true, presence_rate: 0.0}
sqm: {path: ..., format: ..., required: false, presence_rate: 0.0}
rooms: {path: ..., format: ..., required: false, presence_rate: 0.0}
district: {path: ..., format: ..., required: true, presence_rate: 0.0}
listing_id: {path: ..., format: ..., required: true, presence_rate: 0.0}
posted_at: {path: ..., format: ..., required: false, presence_rate: 0.0}
expected_field_count: <int>
Full Schema (all fields)
See portals/kleinanzeigen.yaml for a complete filled example. Key sections:
legal: robots_allows_listings, tos_automation, preferred_access, ban_risk, notes
discovery: rss, api, email_alerts, sitemap, newest_first_param, update_frequency, poll_interval_s
rendering: page types, hydration_blob selector, jsonld_present, ajax_endpoints
anti_bot: provider, cold_request_works, rate_limit_estimate_rpm, challenges_seen
strategy: recommended_order, tier_confidence (0-1 float per tier), per_tier_cooldown_s
query_grammar: search_url_template, district_encoding, district_map, rent/sqm/rooms params
extraction: search_results card selector + fields; detail primary_source + all fields
quirks: array of {description, impact: blocking|annoying|informational, workaround}
scam_signals: portal-specific patterns fed to scam-judge
evidence: directory path and file list
Formats Reference
| Format |
Meaning |
Example input |
Parsed value |
eur_de |
German euro string |
"1.350 €" |
1350 |
sqm_de |
German sqm string |
"65 m²" |
65 |
float_de |
German float |
"2,5" |
2.5 |
url_last_segment |
Last path segment |
".../789-101" |
"789" |
de_date_relative |
German relative date |
"vor 2 Stunden" |
requires fetched_at reference |
url_list |
List of src attrs |
multiple img tags |
["url1", "url2"] |
Versioning
When changing the schema shape:
- Bump
schema_version
- Add a
revision_notes entry with the change description
- Update any parsing code that reads specific paths
- Do NOT silently rename fields — old profiles break if consumers expect old names
1---2name: portal-profile-schema3description: This skill should be used when reading or writing portal YAML profiles in portals/<portal>.yaml. Defines the canonical structure that scout-recon writes and scraper/query-builder reads. Reference before creating or validating any portal profile.4---56The canonical structure for `portals/<portal>.yaml`. scout-recon writes these. Scripts and agents read them. Bump `schema_version` if you change the shape.78## Required Fields (profile is unusable without these)910```yaml11schema_version: 112portal: <slug> # kleinanzeigen, immoscout24, etc.13display_name: <human-readable>14base_url: https://...15last_verified: <YYYY-MM-DD>16revisions: <int>1718legal:19 preferred_access: [scrape|api|rss|email_alerts]20 ban_risk: low|medium|high2122rendering:23 search_page: ssr|csr|hybrid_ssr|hybrid_csr24 detail_page: ssr|csr|hybrid_ssr|hybrid_csr2526strategy:27 recommended_order: [readability|hydration_blob|jina|playwright_stealth|chrome]2829query_grammar:30 search_url_template: <string with {placeholders}>31 district_map:32 "<District Name>": <encoded value>3334extraction:35 detail:36 primary_source: hydration_blob|jsonld|dom37 fields:38 cold_rent: {path: ..., format: ..., required: true, presence_rate: 0.0}39 sqm: {path: ..., format: ..., required: false, presence_rate: 0.0}40 rooms: {path: ..., format: ..., required: false, presence_rate: 0.0}41 district: {path: ..., format: ..., required: true, presence_rate: 0.0}42 listing_id: {path: ..., format: ..., required: true, presence_rate: 0.0}43 posted_at: {path: ..., format: ..., required: false, presence_rate: 0.0}44 expected_field_count: <int>45```4647## Full Schema (all fields)4849See `portals/kleinanzeigen.yaml` for a complete filled example. Key sections:5051- `legal`: robots_allows_listings, tos_automation, preferred_access, ban_risk, notes52- `discovery`: rss, api, email_alerts, sitemap, newest_first_param, update_frequency, poll_interval_s53- `rendering`: page types, hydration_blob selector, jsonld_present, ajax_endpoints54- `anti_bot`: provider, cold_request_works, rate_limit_estimate_rpm, challenges_seen55- `strategy`: recommended_order, tier_confidence (0-1 float per tier), per_tier_cooldown_s56- `query_grammar`: search_url_template, district_encoding, district_map, rent/sqm/rooms params57- `extraction`: search_results card selector + fields; detail primary_source + all fields58- `quirks`: array of {description, impact: blocking|annoying|informational, workaround}59- `scam_signals`: portal-specific patterns fed to scam-judge60- `evidence`: directory path and file list6162## Formats Reference6364| Format | Meaning | Example input | Parsed value |65|--------|---------|--------------|--------------|66| `eur_de` | German euro string | "1.350 €" | 1350 |67| `sqm_de` | German sqm string | "65 m²" | 65 |68| `float_de` | German float | "2,5" | 2.5 |69| `url_last_segment` | Last path segment | ".../789-101" | "789" |70| `de_date_relative` | German relative date | "vor 2 Stunden" | requires fetched_at reference |71| `url_list` | List of src attrs | multiple img tags | ["url1", "url2"] |7273## Versioning7475When changing the schema shape:761. Bump `schema_version`772. Add a `revision_notes` entry with the change description783. Update any parsing code that reads specific paths794. Do NOT silently rename fields — old profiles break if consumers expect old names