Import formats
Skautik reads the formats the industry already uses, so in most cases you send what you are already sending to a listing portal and change nothing else.
At a glance
| Format | Where it is used | Delivery | Identifier |
|---|---|---|---|
| OpenImmo | Germany, Austria, Switzerland | SFTP drop, or upload the ZIP to the API | verwaltung_techn / objektnr_extern |
| RESO Web API | United States, Canada | We pull from your endpoint on a schedule | ListingKey |
| RETS | United States, legacy | We pull from your RETS server on a schedule | The MLS unique ID field |
| BLM | United Kingdom | SFTP drop, or upload to the API | AGENT_REF |
| Kyero XML | Spain, Portugal, and international listings | We fetch your feed URL on a schedule | id |
| CSV | Anywhere | Upload to the API, SFTP drop, or a fetched URL | external_id column |
| Native JSON | Anywhere | Upload to the API, or POST records individually | external_id |
OpenImmo
The German-language industry standard, and the format almost every German-speaking CRM can already produce.
Region Germany, Austria, Switzerland
Encoding XML 1.2.7, UTF-8, inside a ZIP
Delivery SFTP drop, or upload the ZIP to the API
Record identifier verwaltung_techn / objektnr_extern
A transfer is a ZIP containing one XML document and the images it references by filename. The XML holds one anbieter block per provider and one immobilie block per property. We read the 1.2.x family and accept the older 1.1 documents that some systems still emit.
How removal works
Explicit. Each property carries an aktion element whose aktionart is CHANGE or DELETE, so a transfer says what to remove rather than leaving it to be inferred.
Images
Images travel inside the ZIP and are referenced by filename in anhang elements. Ordering follows document order, and the first image becomes the primary one unless one is flagged.
Example
<?xml version="1.0" encoding="UTF-8"?>
<openimmo>
<uebertragung art="OFFLINE" umfang="TEILZUGRIFF"
modus="NEW" version="1.2.7"
sendersoftware="YourCRM" />
<anbieter>
<anbieternr>AG-4471</anbieternr>
<immobilie>
<objektkategorie>
<nutzungsart WOHNEN="true" />
<vermarktungsart KAUF="true" />
<objektart><wohnung wohnungtyp="DACHGESCHOSS" /></objektart>
</objektkategorie>
<geo>
<plz>10999</plz>
<ort>Berlin</ort>
<strasse>Oranienstrasse</strasse>
</geo>
<preise><kaufpreis>429000</kaufpreis></preise>
<flaechen>
<wohnflaeche>74</wohnflaeche>
<anzahl_zimmer>3</anzahl_zimmer>
</flaechen>
<freitexte>
<objekttitel>Dachgeschosswohnung mit Balkon</objekttitel>
</freitexte>
<anhaenge>
<anhang location="INTERN" gruppe="BILD">
<anhangtitel>Wohnzimmer</anhangtitel>
<daten><pfad>wohnzimmer.jpg</pfad></daten>
</anhang>
</anhaenge>
<verwaltung_techn>
<objektnr_extern>AG-4471-0812</objektnr_extern>
<aktion aktionart="CHANGE" />
</verwaltung_techn>
</immobilie>
</anbieter>
</openimmo>Worth knowing
- anzahl_zimmer is a room count under German convention and usually includes the living room. We map it to rooms, and do not infer bedrooms from it.
- A transfer marked TEILZUGRIFF is a partial update and never implies that absent properties should be withdrawn. Only VOLLZUGRIFF is treated as a full replacement.
- Prices are gross or net depending on the fields present. We read the price fields as supplied rather than deriving one from the other.
RESO Web API
The modern MLS standard. If your data comes from an MLS with a certified Web API, this is the route with the least mapping.
Region United States, Canada
Encoding OData 4.0 over JSON, Data Dictionary 1.7 or later
Delivery We pull from your endpoint on a schedule
Record identifier ListingKey
You supply the service root and credentials, and we replicate the Property resource, following Media and OpenHouse where they are exposed. Because the Data Dictionary already standardises field names and enumerations, mapping is largely automatic.
How removal works
By status rather than by removal. A listing whose StandardStatus leaves the active set is withdrawn on our side; records are not deleted, so history survives.
Images
Read from the Media resource and fetched by URL. MediaModificationTimestamp lets us re-fetch only what changed rather than every image on every run.
Example
GET /Property?$filter=ModificationTimestamp gt 2026-08-11T04:00:00Z
&$orderby=ModificationTimestamp
&$expand=Media
&$top=200
{
"@odata.context": "…/$metadata#Property",
"value": [
{
"ListingKey": "MLS-88213004",
"StandardStatus": "Active",
"ListPrice": 429000,
"LivingArea": 796,
"LivingAreaUnits": "Square Feet",
"BedroomsTotal": 2,
"City": "Austin",
"StateOrProvince": "TX",
"ModificationTimestamp": "2026-08-11T09:14:22Z"
}
]
}Worth knowing
- Replication is incremental on ModificationTimestamp. We overlap the window between runs, because a record committed a moment after a cutoff would otherwise be missed forever.
- LivingAreaUnits is respected rather than assumed. A feed in square feet is converted once on import, not silently treated as metric.
- Local fields outside the Data Dictionary are preserved but not mapped, since their meaning is specific to one MLS.
RETS
Supported for feeds that have not yet moved to the Web API, and treated as a migration path rather than a destination.
Region United States, legacy
Encoding RETS 1.7.2, DMQL2 queries
Delivery We pull from your RETS server on a schedule
Record identifier The MLS unique ID field
RETS predates the Data Dictionary, so field names are per-MLS and require an explicit mapping we build with you once. Everything else about the import behaves the same.
How removal works
By status field, as with the Web API. Some servers expose deleted records only through a separate query, which we use where it exists.
Images
Fetched through GetObject, one call per listing, which makes RETS imports slower than an equivalent Web API pull.
Worth knowing
- RETS is being retired across the industry. Move to the Web API when your MLS offers it: the mapping work disappears and imports get substantially faster.
- Field names, enumerations, and even date formats vary per MLS, so a RETS connector is configured individually rather than automatically.
BLM
The UK bulk-load format. Almost every UK agency CRM can emit it, which usually makes it the shortest path to getting UK stock in.
Region United Kingdom
Encoding Delimited text, sectioned, usually with a ZIP of images
Delivery SFTP drop, or upload to the API
Record identifier AGENT_REF
A .blm file is split into #HEADER#, #DEFINITION#, and #DATA# sections. The definition line names the columns, so the format is self-describing and column order does not have to be agreed in advance.
How removal works
By flag. PUBLISHED_FLAG set to 0 withdraws a property. A record simply vanishing from a later file does not withdraw it, because a truncated export would otherwise wipe an agency's stock.
Images
Referenced by filename in MEDIA_IMAGE_00 through MEDIA_IMAGE_NN columns, with the files supplied alongside.
Example
#HEADER#
Version : 3
EOF : '^'
EOR : '~'
#DEFINITION#
AGENT_REF^ADDRESS_1^TOWN^POSTCODE1^POSTCODE2^PRICE^
PUBLISHED_FLAG^BEDROOMS^PROP_SUB_ID^MEDIA_IMAGE_00~
#DATA#
BR-10024^12 Oranien Street^London^SE1^4TX^650000^
1^2^1^BR-10024-01.jpg~
#END#Worth knowing
- The header declares its own field and record separators. We read them from the file rather than assuming the conventional caret and tilde.
- PROP_SUB_ID encodes property type as a number whose meaning is fixed by the specification, not by the sender.
- Prices are whole pounds. A file that has been through a spreadsheet and acquired decimal places is rejected rather than rounded.
Kyero XML
A small, readable schema widely used for international residential stock, and easy to emit from a system that has no property-specific export.
Region Spain, Portugal, and international listings
Encoding XML feed, version 3
Delivery We fetch your feed URL on a schedule
Record identifier id
One document holds every property, with prices, a type, location components, and per-language descriptions. Its simplicity is the point: a full feed is straightforward to generate and to verify by eye.
How removal works
Implicit. The feed is a complete statement of current stock, so anything absent from a successful fetch is withdrawn. This is the one format where absence means removal.
Images
Referenced by URL in image elements, each with an id that fixes display order.
Example
<?xml version="1.0" encoding="UTF-8"?>
<root>
<kyero><feed_version>3</feed_version></kyero>
<property>
<id>4471-0812</id>
<date>2026-08-11 09:14:22</date>
<ref>AG-4471-0812</ref>
<price>429000</price>
<currency>EUR</currency>
<price_freq>sale</price_freq>
<type>Apartment</type>
<town>Marbella</town>
<province>Malaga</province>
<country>Spain</country>
<beds>2</beds>
<baths>1</baths>
<surface_area><built>74</built></surface_area>
<images>
<image id="1"><url>https://example.com/1.jpg</url></image>
</images>
</property>
</root>Worth knowing
- Because absence means withdrawal, a failed or truncated fetch could wipe your stock. We refuse a feed that has shrunk by more than a configurable share, and alert instead.
- price_freq distinguishes a sale price from a rental period. Omitting it makes the price ambiguous, and we reject rather than guess.
CSV
The fallback when nothing else fits. Every system can produce a spreadsheet, and a mapping turns your column names into ours.
Region Anywhere
Encoding RFC 4180, UTF-8, comma separated
Delivery Upload to the API, SFTP drop, or a fetched URL
Record identifier external_id column
Supply a header row and map it once; the mapping is stored on the import source and reused. Only external_id and a handful of core columns are required, and any column you do not have can simply be absent.
How removal works
By column. A status column set to withdrawn removes a property. Absence never withdraws anything, because a partial export is far too easy to produce by accident.
Images
One or more image URL columns, or filenames if you also supply a ZIP.
Example
external_id,title,transaction_type,property_type,price,currency,
living_area,bedrooms,street,postal_code,city,country,status,image_urls
AG-4471-0812,Top-floor apartment,sale,apartment,42900000,EUR,
74,2,Oranienstrasse,10999,Berlin,DE,active,"https://…/1.jpg|https://…/2.jpg"Worth knowing
- Money is an integer in minor units, the same as everywhere else in the API. 42900000 with EUR is 429,000.00. Getting this wrong by a factor of a hundred is the most common CSV mistake, so a dry run reports the highest and lowest prices it read for you to sanity check.
- Multi-valued columns use a pipe, because a comma inside a CSV field is a quoting problem waiting to happen.
- A file whose header does not match its stored mapping is rejected in full rather than partially applied.
Native JSON
Our own schema, with no mapping layer at all. The right choice when you control the exporting system.
Region Anywhere
Encoding NDJSON or a JSON array, matching the property schema
Delivery Upload to the API, or POST records individually
Record identifier external_id
Records match the property object the API returns, with external_id added so we can match yours to ours. NDJSON is preferred for anything large, since it streams rather than requiring the whole document in memory.
How removal works
By status field, or by calling DELETE on the property directly.
Images
Image URLs in an images array, fetched after the record is accepted.
Worth knowing
- Sending individual POSTs is fine for a handful of records and wrong for thousands: use an import so the whole set is validated and applied together.
Something else entirely
If your system exports a format not listed here, send us a sample rather than writing a converter. Property exchange formats are a small, well-known set, and adding one is usually a day of work on our side against weeks on yours.
Start with bulk import for how the lifecycle works, whichever format you end up using.