Imports
Move inventory in from whatever system already holds it. A one-off import handles a migration; an import source keeps a CRM and Skautik in step indefinitely.
/v1/api/importsimports:writeStart an import
Upload a file, or point us at one, and process it.
Accepts a multipart upload or a URL we fetch. Validation runs first and the whole file is rejected if it cannot be parsed; individual records that fail are reported without stopping the rest, unless you ask for all-or-nothing.
Query parameters
| Name | Type | Description |
|---|---|---|
formatrequired | enum | Format of the file being sent. blm | csv | json | kyero | openimmo example: csv |
mode | enum | Incremental updates what the file contains and leaves the rest alone. A full sync also withdraws anything absent from it, which is why it is not the default. incremental | full_sync default: incrementalexample: incremental |
source_id | string | Import source this transfer belongs to, when it comes from a standing connector rather than a one-off upload. example: src_immoscout |
dry_run | boolean | Parse and report what would change without writing anything. Worth doing once with a new mapping. example: true |
filename | string | Original filename, recorded on the run so a failure can be traced back to the file that caused it. example: transfer-2026-08-12.zip |
confirm_shrink | boolean | Allow a full sync that would withdraw a large share of the source's portfolio. Without it, a delivery carrying far fewer records than the source currently holds has its withdrawals held back and the run is reported as partial, on the assumption that the export was truncated. Set this when the reduction is real. example: true |
Body fields
| Name | Type | Description |
|---|---|---|
file | binary | The payload. Mutually exclusive with url. example: listings.csv |
Headers
| Name | Type | Description |
|---|---|---|
Idempotency-Key | string | Prevents a retried upload from being processed twice. example: a7f3c9e2-4b1d-4e8a-9c2f-1d3b5a7c9e01 |
Request body
curl -sS -X POST "https://api.skautik.com/v1/api/imports" \
-H "Authorization: Bearer $SKAUTIK_API_KEY" \
-H "Idempotency-Key: 9c1f-…" \
-F "format=openimmo" \
-F "mode=incremental" \
-F "file=@transfer-2026-08-12.zip"Response
{
"data": {
"id": "imp_5a91c0",
"status": "validating",
"format": "openimmo",
"mode": "incremental",
"dry_run": false,
"created_at": "2026-08-12T10:04:00Z"
}
}Notable responses
- 202
- Accepted for processing. Poll or await import.completed.
- 415
- The payload is not the declared format.
- 422
- The file parsed but no record in it was usable.
https://api.skautik.com/v1/api/imports
POST /v1/api/imports?format=csv&mode=incremental&source_id=src_immoscout&dry_run=true HTTP/1.1
Host: api.skautik.com
Authorization: Bearer sk_live_…
Content-Type: application/json
{
"file": "listings.csv"
}/v1/api/imports/{import_id}imports:writeRetrieve an import
Progress, counts, and a summary of what changed.
Path parameters
| Name | Type | Description |
|---|---|---|
import_idrequired | string | Import identifier. example: imp_3d9b7e2145 |
Response
{
"data": {
"id": "imp_5a91c0",
"status": "completed",
"format": "openimmo",
"mode": "incremental",
"counts": {
"read": 214,
"created": 12,
"updated": 196,
"withdrawn": 3,
"unchanged": 0,
"failed": 3
},
"started_at": "2026-08-12T10:04:02Z",
"finished_at": "2026-08-12T10:05:41Z"
}
}https://api.skautik.com/v1/api/imports/{import_id}
GET /v1/api/imports/imp_3d9b7e2145 HTTP/1.1
Host: api.skautik.com
Authorization: Bearer sk_live_…/v1/api/imports/{import_id}/recordsimports:writeList import records
Per-record outcome, including why any failed.
Filter to failed to get the working list for a fix. Each entry names the source identifier and the line or element it came from, so a problem can be found in your export rather than guessed at.
Path parameters
| Name | Type | Description |
|---|---|---|
import_idrequired | string | Import identifier. example: imp_3d9b7e2145 |
Response
{
"data": [
{
"external_id": "AG-4471-0812",
"property_id": "prop_8f2a41c9d0",
"outcome": "updated",
"changed": ["price", "status"]
},
{
"external_id": "AG-4471-0904",
"property_id": null,
"outcome": "failed",
"location": "immobilie[17]",
"errors": [
{ "field": "preise.kaufpreis", "code": "missing",
"detail": "A sale listing needs a price." }
]
}
],
"meta": { "has_more": false, "limit": 50 }
}https://api.skautik.com/v1/api/imports/{import_id}/records
GET /v1/api/imports/imp_3d9b7e2145/records HTTP/1.1
Host: api.skautik.com
Authorization: Bearer sk_live_…/v1/api/imports/formatsimports:writeList accepted formats and columns
What an import may carry: the formats that work today, every CSV column, and the limits.
Served from the same tables the parser reads, so it cannot fall behind what is actually accepted. Worth calling once when you build the integration rather than transcribing the column list from this page.
Response
{
"data": {
"formats": ["blm", "csv", "json", "kyero", "openimmo"],
"csv_columns": [
"address_city", "address_country", "address_district", "address_number",
"address_postal_code", "address_province", "address_street",
"attic_sqm", "balcony_count", "balcony_terrace_sqm", "bathrooms",
"bedrooms", "cellar_sqm", "commission_amount", "commission_note",
"commission_payer", "commission_percent", "condition",
"construction_phase", "construction_type", "currency", "deposit",
"description", "description_fittings", "description_location",
"description_other", "energy_certificate_issued_at",
"energy_certificate_type", "energy_certificate_valid_until",
"energy_co2_emissions", "energy_consumption_kwh", "energy_demand_kwh",
"energy_includes_hot_water", "energy_label", "energy_primary_carrier",
"external_id", "floor", "flooring", "floors_in_building", "garden_sqm",
"half_bathrooms", "has_air_conditioning", "has_alarm_system",
"has_balcony", "has_cellar", "has_fireplace", "has_fitted_kitchen",
"has_garden", "has_guest_toilet", "has_lift", "has_pool", "has_sauna",
"has_solar_panels", "has_terrace", "heating_costs", "heating_type",
"is_barrier_free", "is_furnished", "is_leasehold",
"is_monument_protected", "kitchen_type", "latitude", "living_area_sqm",
"location_precision", "longitude", "office_area_sqm", "other_area_sqm",
"parking_included", "parking_space_count", "parking_type",
"plot_area_sqm", "price", "price_on_request", "price_period",
"property_subtype", "publish_address", "retail_area_sqm",
"service_charges", "status", "storage_area_sqm", "terrace_count",
"title", "total_area_sqm", "total_rent", "total_rooms",
"transaction_type", "type", "usable_area_sqm", "window_glazing",
"year_built", "year_renovated"
],
"limits": {
"max_records": 10000,
"max_bytes": 33554432,
"max_archive_records": 25000,
"max_archive_bytes": 536870912,
"required_field": "external_id"
},
"modes": ["incremental", "full_sync"]
}
}https://api.skautik.com/v1/api/imports/formats
GET /v1/api/imports/formats HTTP/1.1
Host: api.skautik.com
Authorization: Bearer sk_live_…/v1/api/importsimports:writeList imports
Import history for your organisation.
https://api.skautik.com/v1/api/imports
GET /v1/api/imports HTTP/1.1
Host: api.skautik.com
Authorization: Bearer sk_live_…/v1/api/import-sourcesimports:writeList import sources
Every standing connector your organisation has configured.
Credentials are never returned. A fetched source often holds the login for your own server in its URL, so the URL comes back with the password removed.
Response
{
"data": [
{
"id": "src_1d77a0",
"name": "Head office CRM",
"format": "csv",
"delivery": {
"type": "fetch_url",
"url": "https://partner:***@feeds.example.com/stock.csv"
},
"schedule": "0 */4 * * *",
"deletion_policy": "explicit_only",
"active": true,
"last_delivery_at": "2026-08-13T12:00:04Z",
"next_expected_at": "2026-08-13T16:00:00Z",
"created_at": "2026-08-12T10:04:00Z"
}
]
}https://api.skautik.com/v1/api/import-sources
GET /v1/api/import-sources HTTP/1.1
Host: api.skautik.com
Authorization: Bearer sk_live_…/v1/api/import-sourcesimports:writeCreate an import source
A standing connector that imports on a schedule.
Configure once and stop thinking about it. A source holds the format, the delivery method, the field mapping, and the deletion policy, and every run against it produces an import you can inspect.
Body fields
| Name | Type | Description |
|---|---|---|
namerequired | string | Your own label for the connector. example: Nightly listing sync |
formatrequired | enum | Format this source delivers. Only csv and json parse today; the others are named in the schema and are refused here rather than accepted into a connector that would fail every run. csv | json example: csv |
deliveryrequired | object | How data arrives. fetch_url pulls from a URL you give us on a schedule; api_push means you send us each delivery. SFTP drops are named in the schema and are not running yet, so asking for one is refused rather than answered with credentials for a host that would never accept them. example: {"url": "https://example.com/hooks/skautik", "compression": "gzip"} |
schedule | string | How often to pull, for fetched sources. Cron expression in UTC. Ignored for drops and pushes, which run on arrival. example: 15 3 * * * |
mapping | object | Field mapping, for formats that need one. Omit for OpenImmo and RESO, which are already standardised. example: {"Objektnummer": "external_id", "Kaufpreis": "price", "Wohnflaeche": "living_area"} |
deletion_policy | enum | explicit_only honours deletion markers alone. absence_withdraws also withdraws anything missing from a full delivery, and should only be used where the format is a complete statement of stock. explicit_only | absence_withdraws default: explicit_onlyexample: explicit_only |
Request body
{
"name": "Head office CRM",
"format": "csv",
"delivery": {
"type": "fetch_url",
"url": "https://feeds.example.com/stock.csv"
},
"schedule": "0 */4 * * *",
"deletion_policy": "explicit_only"
}Response
{
"data": {
"id": "src_1d77a0",
"name": "Head office CRM",
"format": "csv",
"delivery": {
"type": "fetch_url",
"url": "https://feeds.example.com/stock.csv"
},
"schedule": "0 */4 * * *",
"deletion_policy": "explicit_only",
"active": true,
"next_expected_at": "2026-08-12T12:00:00Z",
"created_at": "2026-08-12T10:04:00Z"
}
}https://api.skautik.com/v1/api/import-sources
POST /v1/api/import-sources HTTP/1.1
Host: api.skautik.com
Authorization: Bearer sk_live_…
Content-Type: application/json
{
"name": "Head office CRM",
"format": "csv",
"delivery": {
"type": "fetch_url",
"url": "https://feeds.example.com/stock.csv"
},
"schedule": "0 */4 * * *",
"deletion_policy": "explicit_only"
}/v1/api/import-sources/{source_id}imports:writeRetrieve an import source
Configuration and recent run health.
Path parameters
| Name | Type | Description |
|---|---|---|
source_idrequired | string | Source identifier. example: src_immoscout |
https://api.skautik.com/v1/api/import-sources/{source_id}
GET /v1/api/import-sources/src_immoscout HTTP/1.1
Host: api.skautik.com
Authorization: Bearer sk_live_…/v1/api/import-sources/{source_id}imports:writeUpdate an import source
Change the mapping, schedule, or deletion policy.
Path parameters
| Name | Type | Description |
|---|---|---|
source_idrequired | string | Source identifier. example: src_immoscout |
Request body
{
"schedule": "15 3 * * *",
"active": true,
"mapping": {
"Objektnummer": "external_id",
"Kaufpreis": "price"
}
}https://api.skautik.com/v1/api/import-sources/{source_id}
PATCH /v1/api/import-sources/src_immoscout HTTP/1.1
Host: api.skautik.com
Authorization: Bearer sk_live_…
Content-Type: application/json
{
"schedule": "15 3 * * *",
"active": true,
"mapping": {
"Objektnummer": "external_id",
"Kaufpreis": "price"
}
}/v1/api/import-sources/{source_id}imports:writeDelete an import source
Stop importing from this connector.
Deleting a source stops future runs. It does not withdraw the properties it created, which stay under your organisation until you withdraw them yourself.
Path parameters
| Name | Type | Description |
|---|---|---|
source_idrequired | string | Source identifier. example: src_immoscout |
https://api.skautik.com/v1/api/import-sources/{source_id}
DELETE /v1/api/import-sources/src_immoscout HTTP/1.1
Host: api.skautik.com
Authorization: Bearer sk_live_…