How to Sync Active Listings to Your Property Portal via the HTTP Connector
Build a nightly Spojit workflow that pulls active listings from your MySQL database, reshapes each record into your portal's schema, and upserts it to the portal's REST API using the http connector, even though no native portal tile exists.
What This Integration Does
Real estate teams usually keep the master record of every property in their own listings database, then have to re-key that same data into one or more public property portals. That double entry is slow and drifts out of sync the moment a price changes or a listing sells. This tutorial keeps your portal current automatically: each night Spojit reads every active listing from MySQL, maps it to the exact field names your portal expects, and pushes it to the portal's REST API. Because most property portals do not have a dedicated Spojit connector, you reach the portal honestly through the http connector, which can call any external REST endpoint.
The workflow runs on a Schedule trigger (for example every night at 1am in your timezone). On each run it queries the database for current listings, iterates them one at a time with a Loop node, transforms each row into the portal's listing shape, and sends an http-put upsert keyed on a stable listing id. Using an idempotent PUT means a re-run never creates duplicates: a listing that already exists is updated in place, and a new one is created. The workflow leaves no partial state of its own, so if a run fails you can simply run it again and the next pass reconciles everything.
Prerequisites
- A mysql connection in Spojit pointing at the database that holds your listings table (set up under Connections -> Add connection). See the MySQL connector article for connection details.
- The name and column layout of your listings table, plus a way to identify "active" listings (for example a
statuscolumn). - Your property portal's REST API base URL, the upsert endpoint path, and an API key or token. Confirm whether the portal upserts via
PUT /listings/{externalId}or a similar route. - A field mapping from your database columns to the portal's expected JSON field names (price, bedrooms, address, photos, and so on).
- An http connection if your portal requires a stored credential, or you can pass the token directly in a request header. No connection is needed for the built-in http, json, and csv utility connectors.
Step 1: Start the workflow on a Schedule trigger
Create a new workflow and add a Trigger node, then set Trigger Type to Schedule. Enter a 5-field cron expression and an IANA timezone so the sync runs overnight when portal traffic is low. For a 1am daily run in Sydney use:
0 1 * * *
Australia/Sydney
A single Schedule trigger can hold more than one schedule, so you could add a second entry such as 0 13 * * * for a midday refresh. The trigger output is simply {{ trigger.scheduledAt }}, which you can log or include in a notification.
Step 2: Pull active listings with the mysql connector
Add a Connector node in Direct mode, choose the mysql connector, and select the execute-query tool. In Direct mode you map exactly one tool with no AI cost, which is ideal for a predictable read. Put your SELECT in the query field and use ? placeholders bound through params rather than string concatenation:
SELECT
listing_ref,
address_line,
suburb,
postcode,
price,
bedrooms,
bathrooms,
car_spaces,
property_type,
description,
photo_urls,
updated_at
FROM listings
WHERE status = ?
ORDER BY updated_at DESC
Set params to ["active"]. Name the output variable something like listings so later nodes can read {{ listings.rows }}. If your table is large, add a LIMIT and an offset and run the workflow in batches, or filter to rows changed since the last run with a WHERE updated_at >= ? clause.
Step 3: Loop over each listing
Add a Loop node set to ForEach and point it at the array of rows, {{ listings.rows }}. The Loop runs its body once per record and exposes the current row as a loop item, for example {{ item }}. Everything in the next two steps lives inside the Loop body so each listing is shaped and sent individually. Keeping the upsert inside the Loop (rather than sending all listings in one request) makes each call independently retryable and keeps payloads small.
Step 4: Shape each row into the portal schema with a Transform node
Add a Transform node inside the Loop body to convert the raw database row into the JSON object your portal expects. Map your column names to the portal's field names and coerce types so prices are numbers and photo lists are arrays. A Transform that produces the portal body looks like this:
{
"externalId": "{{ item.listing_ref }}",
"headline": "{{ item.property_type }} in {{ item.suburb }}",
"price": {{ item.price }},
"address": {
"line1": "{{ item.address_line }}",
"suburb": "{{ item.suburb }}",
"postcode": "{{ item.postcode }}"
},
"features": {
"bedrooms": {{ item.bedrooms }},
"bathrooms": {{ item.bathrooms }},
"carSpaces": {{ item.car_spaces }}
},
"description": "{{ item.description }}",
"status": "active"
}
If your photo_urls column is stored as a delimited string rather than a real array, add a Connector node on the json connector or the csv connector before the Transform to split and parse it. For example, the csv connector's parse tool turns a comma-delimited cell into structured rows, and the json connector's parse tool turns a JSON-encoded string column into a real object you can reference. Name the Transform output portalListing.
Step 5: Upsert each listing with the http connector
Still inside the Loop body, add a Connector node in Direct mode on the http connector and select the http-put tool. PUT to a path that includes the listing's stable id so the call is idempotent: existing listings are updated and new ones are created. Configure the request like this:
URL: https://api.yourportal.com/v1/listings/{{ item.listing_ref }}
Headers:
Authorization: Bearer YOUR_PORTAL_TOKEN
Content-Type: application/json
Body: {{ portalListing }}
If your portal upserts by POSTing to a collection endpoint instead, use http-post against https://api.yourportal.com/v1/listings and include externalId in the body so the portal can match on it. Name the response variable upsertResult so you can inspect the status downstream. For the full pattern of calling a system that has no native tile, see the tutorial on connecting to any REST API with HTTP Requests.
Step 6: Report the result after the Loop
After the Loop completes, add a Send Email node to confirm the nightly sync ran. Set Recipients to your operations inbox, give it a templated Subject such as Portal sync complete: {{ trigger.scheduledAt }}, and include the listing count in the body, for example Synced {{ listings.rowCount }} active listings. The Send Email node uses Spojit's built-in mail service, so no extra connection is required, though external recipients must be on the org allowlist under Settings -> General -> Email recipients. To send from your own domain instead, swap in the resend or smtp connector.
Tips
- Filter the SQL to only rows changed since the last run with a
WHERE updated_at >= ?clause to keep nightly payloads small and stay well under portal rate limits. - If your portal enforces a request quota, add a short Loop body delay or batch the listings so you do not exceed its per-minute cap.
- Use Miraxa, the intelligent layer across your automation, to scaffold this quickly: try a prompt like "Add a Loop over
{{ listings.rows }}with a Transform node and anhttp-putConnector node on the http connector," then fine-tune the field mapping in the properties panel. - Keep the upsert in Direct mode rather than Agent mode so the call stays deterministic and free of AI cost.
Common Pitfalls
- Building the URL with the wrong key duplicates records. Use a stable identifier such as
{{ item.listing_ref }}in thehttp-putpath, not an internal row id that can change. - Forgetting the
Content-Type: application/jsonheader causes some portals to reject the body or treat it as a string. - Building SQL by concatenating values invites injection and breaks on quotes in addresses. Always use
?placeholders bound throughparams. - Cron uses the IANA timezone you set, not the database server's clock. Confirm the timezone so a "nightly" run does not fire mid-afternoon during daylight saving shifts.
- If the portal returns errors only for some listings, the Loop continues by default. Check
{{ upsertResult }}per iteration and branch on failures with a Condition node if you need to capture them.
Testing
Before enabling the schedule, validate on a small scope. Temporarily add LIMIT 3 to the SELECT in Step 2 and use the Run button to execute the workflow manually. Open the execution log and inspect the listings output, the shaped portalListing body from the Transform, and the upsertResult from each http-put call. Confirm those three listings appear correctly in your portal, then run the workflow a second time and verify the same listings are updated in place rather than duplicated. Once the small batch round-trips cleanly, remove the LIMIT and turn the Schedule trigger on.