Skip to main content
Seeki.eu

Agency Import API

Push listings into Seeki via API.

Bulk-import real-estate listings from your CRM or property management system. One endpoint, per-item status, automatic TTL.

Get an API key

Access to the Agency Import API requires an active Seeki agency subscription. Each subscription comes with one API key scoped to your agency account.

Subscribe on the For Agents page to get started.

Key management UI (view, rotate, revoke keys) is coming in a future dashboard update. For now, your API key is emailed when your subscription is activated. Contact info@seeki.eu if you need a new key.

Authentication

Every request must include your API key as a Bearer token in the Authorization header.

HTTP header
Authorization: Bearer seeki_live_your_key_here

Keys starting with seeki_live_ are production keys. Staging keys start with seeki_test_. They are not interchangeable across environments.

Your first import

Send a POST request to /v1/listings with a JSON body containing a listings array. Each element represents one property.

curl
curl -X POST https://ingest.seeki.eu/v1/listings \
  -H "Authorization: Bearer seeki_live_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "listings": [
      {
        "external_id": "crm-listing-00123",
        "listing_type": "SELL",
        "real_estate_type": "APARTMENT",
        "price": 320000,
        "currency": "EUR",
        "address": {
          "street": "Wenceslas Square 1",
          "city": "Prague",
          "country_code": "CZ",
          "postal_code": "11000"
        },
        "name": "Sunny 2-bedroom apartment, Wenceslas Square",
        "floorage": 65
      }
    ]
  }'

A successful batch returns HTTP 200 with accepted and rejected arrays:

200 OK
{
  "accepted": [
    { "external_id": "crm-listing-00123", "import_item_id": "uuid-…" }
  ],
  "rejected": []
}

Request shape

The top-level body is { listings: [...] }. Required fields per listing:

FieldRequired?Notes
external_idRequiredYour CRM's unique ID. Used for idempotent re-imports and deduplication.
listing_typeRequiredSELL or RENT.
real_estate_typeRequiredAPARTMENT, HOUSE, LAND, COMMERCIAL, or OTHER.
priceRequiredNumeric. Use currency field for the ISO 4217 code (defaults to EUR).
address OR coordinatesRequiredProvide either an address object (street, city, country_code) or coordinates ({ lat, lng }). At least one must be present.
currencyOptionalISO 4217 code, e.g. EUR, CZK, PLN. Defaults to EUR.
name, descriptionOptionalListing title and free-text description.
floorage, floor, roomsOptionalProperty size in m², floor number, and room count.
imagesOptionalArray of public image URLs. The import worker fetches and caches them.
contactOptionalAgent name, email, and phone for inquiry routing.

See the full schema with all optional fields in the interactive API reference.

Response shape & errors

The /v1/listings endpoint always returns HTTP 200 OK as long as the request itself is well-formed (valid JSON, correct auth header, within size limits). Individual listing failures are reported inside the rejected array — not as HTTP error codes.

Each rejected item includes external_id, error_code, and a human-readable message. Batch-level errors (bad auth, oversized payload) return a non-200 HTTP status with a top-level error_code.

Error codes

Batch-level codes return HTTP errors. Per-item codes appear in the rejected array of a 200 response.

error_codeHTTP / scopeMeaning
MISSING_AUTHORIZATION401No Authorization header was sent.
INVALID_KEY401The key was not found, has been revoked, or has expired.
SUBSCRIPTION_INACTIVE403The agency's subscription is not active or past_due.
PAYLOAD_TOO_LARGE413Request body exceeds the 25 MB limit.
TOO_MANY_ITEMS413The listings array contains more than 5,000 items.
VALIDATION_FAILEDper-itemOne or more required fields are missing or have an invalid value. Check the message field for details.
GEOCODE_ADDRESS_NOT_FOUNDper-itemThe provided address could not be resolved to coordinates. Verify street, city, and country_code.
GEOCODE_COORDINATES_INVALIDper-itemThe provided lat/lng are outside valid ranges (lat ±90, lng ±180).
INFRA_QUEUE_REJECTEDper-itemThe workflow queue rejected the item, usually due to a transient overload. Retry after a short delay.
INFRA_LOSTper-itemThe workflow was accepted but its result was never recorded. Use the retry endpoint.
INTERNAL_ERRORper-itemAn unexpected server error occurred. Retrying may succeed; contact support if it persists.

Idempotency / re-imports

Re-posting a listing with the same external_id updates the existing record — it does not create a duplicate. The listing's TTL is refreshed on every successful import.

Internally, each import item maps to a Cloudflare Workflow with the ID:

Workflow ID pattern
agency-{agency_id}-{external_id}

Listing expiration

Every imported listing has a TTL of 60 days from the most recent successful import. Once the TTL expires, the listing is automatically unpublished by a daily cron job.

To keep a listing live indefinitely, re-import it (even with identical data) before the 60-day window closes. Implementing a nightly re-sync of your active listings is the recommended pattern.

Status + retry

Use GET /v1/import-items/{import_item_id}/status to poll the processing state of a single item. The import_item_id is returned in the accepted array of the original POST response.

GET /import-items/{id}/status
curl https://ingest.seeki.eu/v1/import-items/{import_item_id}/status \
  -H "Authorization: Bearer seeki_live_your_key_here"
Response
{
  "import_item_id": "uuid-…",
  "external_id": "crm-listing-00123",
  "status": "completed",
  "listing_id": "uuid-listing"
}

If an item ends up in an INFRA_LOST or INFRA_QUEUE_REJECTED state, re-queue it without resubmitting the full batch:

POST /import-items/{id}/retry
curl -X POST https://ingest.seeki.eu/v1/import-items/{import_item_id}/retry \
  -H "Authorization: Bearer seeki_live_your_key_here"

Limits

  • Body size: 25 MB maximum per request.
  • Items per request: 5,000 maximum. Split larger batches.
  • No QPS limit is enforced today. Very large bursts may be rate-limited automatically; spread high-volume syncs over multiple requests.

Going live

There are two separate environments. API keys are environment-scoped — a production key will not work on staging, and vice versa.

EnvironmentBase URL
Staginghttps://ingest.seeki.store/v1
Productionhttps://ingest.seeki.eu/v1

All endpoints (/listings, /import-items/{id}/status, /import-items/{id}/retry) are identical across both environments.

Ready to integrate?

Open the interactive reference to explore all fields, try requests in the browser, and download the JSON Schema for your validator.

We use cookies to improve your experience on our site. Privacy Policy