Realtor Flow Tool Kit · for real estate web designers and developers
Listings, lead surveys, video articles and lead capture for any real estate website
Keep the website you have. Paste one line to put your listings, a lead-scoring survey or a contact form on any page, or call the API from your own code. The work runs on Realtor Flow.
<!-- Once, anywhere on the page --><scriptsrc="https://api.realtorflow.ca/v1/kit.js"data-key="rf_pub_..." async></script><!-- Where the survey goes --><divdata-rf-survey="ready-to-buy"></div>
When would you like to move?
Within 3 months
In 3 to 6 months
In 6 to 12 months
Just looking for now
What a visitor sees. An illustration: yours takes your site's font and colours.
Start here
What the tool kit is
The Realtor Flow Tool Kit puts the working parts of a real estate website on a site you already have. The site stays where it is, on WordPress, on a site builder or on your own code. The tools run on Realtor Flow and are reached in two ways:
A line you paste into a page. It draws your listings, a survey, a contact form or a newsletter sign-up wherever you put a tag. There is no code to write.
An API your own code calls with a key. It answers in JSON.
A rough draft becomes a clear article that search engines and AI assistants can quote; text is translated into English, French, Spanish, German or Chinese. AI writes them, so they count.
A photo library: upload once, sized for every screen and served for good, ready for pages and listings.
Yes
On WordPress, the plugin does both for you: the tools go on a page as a block or a shortcode, and listings are written into the page itself so search engines and AI assistants read them.
More of the platform's tools are on their way into the tool kit. This page lists only what works today.
Start here
Quick start: paste one line
For designers. A survey on a page in about five minutes, with no code.
Make a website key
In the Realtor Flow dashboard, open Developers and choose Make a website key. Type the address of the website it will be used on. The line to paste appears on the same page with your key already in it.
Paste the line
Put it on the page once, anywhere. In the site's footer is a good place: it is then on every page.
The name is the survey's short name. The Developers page writes this tag for each of your surveys.
HTML
<divdata-rf-survey="ready-to-buy"></div>
Open the page. The survey draws itself in the page's own font and colours. Each person who finishes appears in the account's lead inbox, scored and marked hot, warm or nurture.
A survey has to be live to show on a page. A draft shows a short note in its place that says so.
Start here
Quick start: call the API
For developers. A first call in a minute, with a key that cannot break or use up anything.
Make a test key
In the dashboard, open Developers, choose New key and pick Test key. It is shown once: copy it then.
Every call answers in the same shape as the real thing. Nothing is saved, nothing is sent and nothing is counted. When your page looks right, change to the live key. See Test mode.
One copy per page is enough; a second copy does nothing. async keeps it from holding up the page. It has no dependencies and sets no cookies.
Add data-count="on" to it to count visits, including the ones AI assistants send you.
The key in it is a website key (it starts rf_pub_). A website key is made to be seen in a page's source, so it is tied down in two ways:
It only works from a browser, on the websites listed on the key (up to 10). www. is ignored, so a key for example.com also works on www.example.com. Any other subdomain is listed on its own. Add localhost to try it on your own machine.
It only opens what a visitor needs: reading a live survey's questions (never its points), sending a finished survey, sending a contact form, signing up for the newsletter and showing the listings visitors may see (active and pending ones, and a sold one on its own page). It cannot read leads, drafts or anything else on the account.
Pages that draw themselves after loading (page builders, single-page apps) are covered: new tags are picked up as they appear. To ask for it by hand, call RealtorFlow.mount().
When the key, the website or the survey's name is not right, the tag shows a short note that says what to fix, and the same sentence is written to the browser's console.
On a web page
A survey on a page
HTML
<divdata-rf-survey="ready-to-buy"></div>
The visitor sees the title and intro with a start button, then one question at a time with a progress bar, then a short contact step (name, email and, if the survey asks for it, a phone number), then the result that fits their score.
What happens on our side when they finish:
The answers are scored. 80 points or more is hot, 50 or more is warm, anything less is nurture.
A lead is added to the account's inbox with the score, every answer, the page it came from and the utm_source, utm_medium and utm_campaign of that page's address.
The account's alert email goes out, and any follow-up the account has set up starts.
The visitor is shown the result message only. Points and scores never reach the browser.
To make a survey, use the dashboard (Surveys & Quizzes, where one can be built by describing it in a chat) or the Surveys calls.
On a web page
A contact form on a page
HTML
<divdata-rf-form="contact"data-rf-title="Ask about this home"data-rf-button="Send my question"></div>
A form with name, email, phone (optional) and message. data-rf-title and data-rf-button are optional. What is sent lands in the account's lead inbox with the page it came from, and the account's alert email goes out.
With data-count="on", the pasted line counts each page opened, the way Realtor Flow sites are counted: no cookie, and the visitor's address is never kept (a visitor is told apart for one day only, by a code that cannot be turned back into an address). Pages that change their address without reloading are counted too.
Each visit is sorted by where it came from: AI assistants (ChatGPT, Perplexity, Gemini, Copilot, Claude and others, from the link in their answer or their tag in the address), search engines, social networks, other sites or direct. The figures are in the dashboard's AI-Traffic Analytics and through the API.
AI crawlers (the programs that read pages for an assistant) do not run scripts, so a pasted line cannot count them. Realtor Flow sites count them on the server, and so does the WordPress plugin.
On a web page
Booking requests on a page
HTML
<divdata-rf-booking="beach-cottage"></div>
A "Request to book" card for a listing that takes bookings: the nightly rate, the shortest stay and the most guests, the nights already booked, check-in and check-out, guests, a running estimate and the guest's details. In the listings grid, the same card appears on every listing that takes bookings, above the enquiry form.
Booked nights are refused before anything is sent, and again on our side, in the account's language.
The request lands in the dashboard's Bookings & Reservations as an enquiry. The broker gets an alert and the guest a receipt that says it is a request, not a booking.
No payment is taken. The estimate is nights times the nightly rate, and the card says so.
A listing takes bookings once it has booking terms, set in the dashboard or with the API.
On a web page
A newsletter sign-up on a page
HTML
<div data-rf-newsletter
data-rf-title="The monthly market note"data-rf-text="Prices, new listings and open houses, once a month."></div>
An email field, a box the visitor ticks themselves and a button. The box is never ticked for them, and nothing is sent until it is.
The words beside the box are kept with the address, word for word, as the record of their consent: the account's standard line ("I agree to receive emails from Harbour Realty. I can unsubscribe at any time."), or your own with data-rf-consent. Every newsletter sent from the account carries an unsubscribe link.
Attribute
What it does
data-rf-title
A heading above the box.
data-rf-text
A line under the heading: what they will get and how often.
data-rf-consent
Your own words beside the box, instead of the standard line. Say who will write and that they can unsubscribe.
data-rf-name="on"
Also ask for their name (optional for them).
data-rf-button
The button's words. The default is "Subscribe".
The answer is the same whether the address is new, already on the list or blocked, so the box cannot be used to find out who subscribes.
On a web page
Listings on a page
HTML
<!-- A grid of the account's homes, with filters --><div data-rf-listings></div><!-- One listing, for a page of its own --><divdata-rf-listing="brick-duplex-near-the-river"></div>
The grid shows the active and pending listings, featured ones first, with filters for sale or rent, the type of home and the number of bedrooms. A card opens the listing in place: its photos, price, facts, description, features and an enquiry form. A sold listing leaves the grid but its own page still opens.
The open listing gets its own address (?rf_listing= and its name), so it can be shared, bookmarked and reached with the back button.
While a listing is open, its search markup (RealEstateListing) is in the page's head, with the page's address.
An enquiry from a listing lands in the lead inbox filed with that listing.
Prices, rooms and labels are written in the account's language, with its currency and unit of area.
Attribute
What it does
data-rf-listings
Empty for all listings, or names separated by commas to show only those.
data-rf-type
sale or rent: start the grid on one of them.
data-rf-limit
How many cards at a time, up to 60. The default is 12; a button loads more.
data-rf-filters="off"
Hide the filters.
Listings are added in the dashboard (Listings & Property Engine) or with the Listings calls.
On a web page
WordPress plugin
Every tool on a WordPress site, without pasting anything, and readable by search engines and AI assistants.
[Download the plugin](https://api.realtorflow.ca/v1/wordpress/realtorflow-toolkit.zip) (version 1.0.0, WordPress 6.0 or later, PHP 7.4 or later). In WordPress: Plugins > Add New > Upload Plugin, then activate it.
Make two keys
On the Developers page of the Realtor Flow dashboard: a website key with the WordPress site's address on it, and a live server key.
Paste them
In WordPress, Settings > Realtor Flow. The page says which account it reached and which tools are on. The server key stays on the server and is never put in a page; to keep it out of the database too, put it in wp-config.php instead:
define( 'RF_TOOLKIT_SERVER_KEY', 'rf_live_...' );
Put the tools on pages
In the block editor, add the Realtor Flow block and choose what it shows. Or use a shortcode wherever shortcodes work.
Shortcode
What it shows
[realtorflow_listings]
The listings grid. Options: type="sale" or "rent", limit="12", filters="off", only="name-one,name-two".
Listings in the page itself. The grid and each listing are written into the HTML on the server, with photos, prices, facts, descriptions and their search markup, so crawlers that do not run scripts still read them. Visitors see the same listings drawn by the pasted line.
A page for each listing. A listing opened on the listings page (?rf_listing= and its name) gets its own title, canonical address and RealEstateListing markup, including alongside Yoast SEO and Rank Math.
AI search files./llms.txt (the business, its published pages and its listings), the AI crawler lines added to WordPress's robots.txt, and the business's RealEstateAgent markup on the home page. Each can be turned off, for example when an SEO plugin already describes the business.
AI crawlers counted. Visits from GPTBot, ClaudeBot, PerplexityBot and the others are noted as pages are drawn and sent every few minutes, with Report AI crawler visits. They show in the dashboard's AI traffic.
Page views and AI-assistant visits counted as with data-count="on": no cookie, no address kept.
Listings and the AI files are kept for a few minutes so pages stay fast. Settings > Realtor Flow has a button to read them fresh.
On a web page
Make it match the site
Surveys and forms take the page's font and text colour as they are. The accent colour comes from the account. Everything else is set with CSS custom properties, on the tag or on any element around it:
CSS
[data-rf-survey], [data-rf-form] {
--rf-accent: #0b6e4f;
--rf-radius: 4px;
--rf-bg: #ffffff;
--rf-width: 560px;
}
/* The card itself can be reached too. */
[data-rf-survey]::part(card) {
box-shadow: 010px30px rgba(0, 0, 0, .08);
}
Property
What it sets
When left out
--rf-accent
Buttons, the chosen answer, the progress bar
The account's accent colour
--rf-on-accent
Text on the accent colour
Black or white, whichever reads better
--rf-bg
The card's background
Transparent
--rf-text
Text colour
The page's
--rf-font
Font
The page's
--rf-heading-font
Font of the headings
The page's
--rf-title-size
Size of the headings
1.5em
--rf-radius
Rounding of the card
12px
--rf-width
The card's greatest width
640px
--rf-margin
Space around the card. 0 auto centres it
0: it lines up with the text around it
--rf-padding
Space inside the card
20 to 32 pixels, by screen width
--rf-border
The card's border
A thin grey line
--rf-field-bg
Background of the form fields
Transparent
--rf-card-width
Listings: the smallest width of a card in the grid
250px
--rf-gap
Listings: the space between cards
20px
Listings have no frame of their own: they fill the space they are given. Give them one with --rf-border and --rf-padding.
Each survey, form or listing grid draws itself inside its own shadow root, so the page's styles do not break it and it breaks none of the page's.
On a web page
Events
Each tag announces what happens in it. The events bubble, so one listener on document hears them all. Use them for analytics or to show something of your own afterwards.
Event
When
event.detail
rf:ready
A survey, form or listings tag has drawn itself
{ survey }, { form }, { listings }, { listing }, { booking } or { newsletter }
rf:listing
A listing was opened
{ listing }
rf:subscribe
Someone signed up for the newsletter
{ newsletter: true }
rf:booking
A booking request was sent
{ listing, check_in, check_out }
rf:start
A visitor pressed the start button
{ survey }
rf:complete
A visitor finished a survey and is now a lead
{ survey }
rf:lead
A contact form or a listing enquiry was sent
{ form }, or { form: "listing", listing }
rf:error
Something could not be shown or sent
{ code, message }
JavaScript
document.addEventListener('rf:complete', (event) => {
// For example, count it in Google Analytics.
gtag('event', 'generate_lead', { survey: event.detail.survey });
});
The API
Keys
Keys are made on the Developers page of the dashboard. There are three kinds:
Shows listings, booking requests, surveys, forms and the newsletter sign-up on the websites listed on it. Nothing else.
Test key
rf_test_
On your server, while you build
Every call answers. Nothing is saved, sent or counted.
Live key
rf_live_
On your server
The real thing.
A live or test key is shown once, when it is made. Only a fingerprint of it is kept, so a lost key cannot be shown again: revoke it and make a new one.
An account can have up to 10 keys in use. Give each one a name that says where it is used, and revoke the ones that are not.
Never put a live or test key in a page or in an app's front end. Anyone can read it there. The API does not answer pages on other websites, apart from the pasted line's own calls.
The tool kit is switched on for each account by Realtor Flow. If the Developers page says it is not on yet, write to jarvis@realtorflow.ca.
The API
Test mode
A test key runs the same checks on what you send as a live key, and refuses the same mistakes with the same codes. The difference is what happens next:
Call
With a test key
Reading the account, usage, surveys and listings
The real answers from the account.
Adding, changing or deleting a listing
Checked and answered as if done. Nothing is saved and no photo is fetched.
Adding a sign-up or unsubscribing someone
Checked and answered as if done. The list does not change.
Booking terms, requests and status changes
Checked like the real thing, nights already held included. Nothing is saved and no email goes out.
AI visibility files and AI traffic
The real answers: building the files changes nothing.
A file is read to check it is a photo and the sample has its real size; a web address is not fetched. Nothing is stored. Reading the library is real; changing alt text and deleting answer as if done but change nothing.
A sample answer carries "test": true, and every answer has the header X-RF-Mode set to test or live.
The API
Calls and answers
Base address
https://api.realtorflow.ca/v1
Send the key in a header: Authorization: Bearer <key>.
Send JSON with Content-Type: application/json. The most a call can carry is 2 MB.
Answers are JSON. Times are in UTC, written like 2026-11-12T15:04:05.000Z.
A list comes back as { "object": "list", "data": [...], "page": 1, "has_more": false }.
A refusal always has the same shape: a sentence a person can read and a short code a program can act on, with the HTTP status that fits. Branch on code. The sentence is written to be shown to a person as it is.
400
{
"error": "A lead needs a \"name\" and a valid \"email\".",
"code": "bad_lead"
}
The examples on this page read the key from RF_KEY. The PHP ones use WordPress's own functions; outside WordPress, any HTTP library does the same.
The API
Limits and usage
How fast
Each key can make 120 calls a minute. Calls that start work have a lower ceiling of their own:
Call
A minute, per key
Any call
120
Send a finished survey, send a lead, add a sign-up, unsubscribe
60 each
Send a video, create or change a survey, add or change a listing, booking terms, requests and status changes
30 each
Draft a survey, write an article, translate
20 each
Every answer says where the key stands in the headers RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset (seconds until the count starts again). Past the ceiling the answer is 429 with the code slow_down.
The pasted line is counted for each visitor instead: 120 calls a minute, 10 finished surveys, 6 contact forms, 10 booking requests, 6 newsletter sign-ups and 120 counted page views in ten minutes. A real visitor never reaches these.
How much
Work that is written by AI is counted against a monthly allowance: one for each video article, each survey draft, each article written from a draft and each translation call. Everything else (reading, saving surveys and listings, taking responses, taking leads) is not counted.
Months are calendar months in UTC. The month's count says what is used and what is left.
Work is counted when it comes back. Work that fails is not counted.
The same video sent twice is counted once.
When the allowance is used up, the answer is 429 with the code limit_reached, and nothing is started.
Allowances are set for each account. For a larger one, write to jarvis@realtorflow.ca.
Reference
Account
The account
GET/v1/account
Who the key belongs to, which tools are switched on and how much of the month's allowance is left. The call to make first: it says whether the key works.
Work under way. It holds a place until it comes back.
units[].limitnumber
The account's allowance for the month.
units[].leftnumber
What can still be asked for.
Reference
Videos
A YouTube video becomes a page that search engines and AI assistants can read: an article, questions and answers, the transcript and the markup that ties them to the video.
The article is written from what is said in the video. The title and description you send, and the business details on the account, are read for context. Nothing is looked up and nothing is added from elsewhere; quotations are kept only when the transcript holds them word for word.
The tool kit does not fetch the transcript from YouTube: you send it. A channel's owner can download a video's captions as a .srt or .vtt file in YouTube Studio, and that file can be sent as it is. The article is written in the account's language, from the first hour or so of speech (60,000 characters).
Send the video
Send a video with its transcript. The answer is 202 with the status writing.
Ask until it is ready
Get the video every few seconds until status is ready.
Put it on your page
Use headline as the heading, search.title and search.description in the page's head, then embed_html, html and json_ld in the body.
The HTML carries no styles of its own. Dress it with these class names: rf-video-article (the whole thing), rf-summary, rf-intro, rf-points, rf-section, rf-quote, rf-closing, rf-faq, rf-transcript, rf-time (the time links in the transcript) and rf-video (the player).
Send a video
POST/v1/videos
Send the address of a YouTube video and the words spoken in it. The answer comes back at once with the status writing; the article follows, usually in under two minutes.
Sending the same video again returns the same answer and is not counted twice. To have the article written again, send rewrite: true: that is counted as a new article.
Send
youtube_urlstringrequired
The address of the video on YouTube.
transcriptstringrequired
The words spoken in the video: plain text, or the contents of a captions file (.srt or .vtt) as it is. At least 400 characters of speech. Not needed when the account already holds a transcript for this video.
titlestring
The video's title. Up to 200 characters.
descriptionstring
The video's description on YouTube. It is read for context.
page_urlstring
The full address of the page the article will be on. It goes into the markup, so send it if you know it.
durationnumber
The video's length in seconds.
published_atstring
When the video was published, as a date.
rewriteboolean
true to have an article that already exists written again.
curl-X POST https://api.realtorflow.ca/v1/videos \
-H"Authorization: Bearer $RF_KEY" \
-H"Content-Type: application/json" \
-d'{
"youtube_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"title": "Harbour district walk-through",
"page_url": "https://www.example.com/videos/before-you-make-an-offer",
"transcript": "Welcome to the harbour district. Today we walk through what to look at before you make an offer on a home here. First, look at the roof and ask how old it is. Second, read the condo minutes for the last two years. Third, ask what the seller is leaving behind. The best time to visit is the early morning, when the street is quiet. Thank you for watching, and write to us with your questions about buying in the harbour district."
}'
const res = await fetch('https://api.realtorflow.ca/v1/videos', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.RF_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
youtube_url: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
title: 'Harbour district walk-through',
page_url: 'https://www.example.com/videos/before-you-make-an-offer',
transcript: 'Welcome to the harbour district. Today we walk through what to look at before you make an offer on a home here. First, look at the roof and ask how old it is. Second, read the condo minutes for the last two years. Third, ask what the seller is leaving behind. The best time to visit is the early morning, when the street is quiet. Thank you for watching, and write to us with your questions about buying in the harbour district.',
}),
});
const video = await res.json();
$response = wp_remote_post( 'https://api.realtorflow.ca/v1/videos', array(
'headers' => array(
'Authorization' => 'Bearer ' . RF_KEY,
'Content-Type' => 'application/json',
),
'body' => wp_json_encode( array(
'youtube_url' => 'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
'title' => 'Harbour district walk-through',
'page_url' => 'https://www.example.com/videos/before-you-make-an-offer',
'transcript' => 'Welcome to the harbour district. Today we walk through what to look at before you make an offer on a home here. First, look at the roof and ask how old it is. Second, read the condo minutes for the last two years. Third, ask what the seller is leaving behind. The best time to visit is the early morning, when the street is quiet. Thank you for watching, and write to us with your questions about buying in the harbour district.',
) ),
) );
$video = json_decode( wp_remote_retrieve_body( $response ), true );
Ask until status is ready. Every few seconds is enough. Until then the answer has the short form shown under Send a video; the article and its markup are added when it is ready. A video that is failed carries a note that says why; send it again to retry, and nothing was counted.
In the address
transcriptstring
Send 0 to leave the transcript out of html. It stays in transcript and in the markup.
{
"id": "5d0c1c0e-8f0a-4a57-9f0e-2b7f6a1d3c11",
"object": "video",
"status": "ready",
"youtube_id": "dQw4w9WgXcQ",
"youtube_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"title": "Harbour district walk-through",
"page_url": "https://www.example.com/videos/before-you-make-an-offer",
"headline": "What to Check Before You Make an Offer in the Harbour District",
"summary": "Three things to look at before you make an offer on a home in the harbour district: the roof, the condo minutes and what the seller leaves behind.",
"created_at": "2026-11-12T15:04:05.000Z",
"updated_at": "2026-11-12T15:05:12.000Z",
"search": {
"title": "Before You Make an Offer in the Harbour District",
"description": "The roof, the condo minutes and what stays with the home: what to check before you make an offer in the harbour district."
},
"topic": null,
"tags": [
"harbour district",
"making an offer",
"buying a home"
],
"article": {
"intro": "Before you make an offer on a home in the harbour district, three checks are worth the time.",
"points": [
"Ask how old the roof is.",
"Read the condo minutes for the last two years.",
"Ask what the seller is leaving behind."
],
"sections": [
{
"heading": "What should you look at first?",
"text": "Start with the roof and ask how old it is. ..."
}
],
"quotes": [
{
"text": "The best time to visit is the early morning, when the street is quiet.",
"who": ""
}
],
"faq": [
{
"question": "When is the best time to visit?",
"answer": "The early morning, when the street is quiet."
}
],
"closing": "Write to us with your questions about buying in the harbour district."
},
"check": {
"numbers": [],
"quotes": []
},
"html": "<div class=\"rf-video-article\">\n<p class=\"rf-summary\">Three things to look at ...</p>\n...\n</div>",
"embed_html": "<div class=\"rf-video\" style=\"...\"><iframe src=\"https://www.youtube-nocookie.com/embed/dQw4w9WgXcQ\" ...></iframe></div>",
"json_ld": [
{
"@context": "https://schema.org",
"@type": "VideoObject",
"name": "What to Check Before You Make an Offer in the Harbour District",
"...": "..."
},
{
"@context": "https://schema.org",
"@type": "FAQPage",
"...": "..."
}
],
"transcript": [
{
"t": 0,
"h": "",
"text": "Welcome to the harbour district. Today we walk through ..."
}
]
}
In the answer
idstring
The video's id on this account.
statusstring
writing, ready or failed. (new for a video that has no article and none on the way.)
youtube_idstring
The video's id on YouTube.
titlestring
The title you sent.
page_urlstring or null
The address you said the article will be on.
headlinestring or null
The headline written for the article. Use it as the page's <h1>.
summarystring or null
Two or three sentences on what the video covers.
search.titlestring
Written for the page's <title>.
search.descriptionstring
Written for the page's meta description.
topicstring or null
The topic it was filed under, when the account has set topics.
tagsarray
A few subject tags.
articleobject
The article in parts: intro, points (the takeaways), sections (each a heading and text), quotes (each text and who), faq (each question and answer) and closing.
checkobject
numbers and quotes in the article that the transcript does not hold word for word. Empty lists mean there is nothing to look at. Read these before you publish.
htmlstring
The whole article as plain HTML: no styles, no scripts, class names that start with rf-.
embed_htmlstring
The YouTube player, set to load no YouTube cookies until a visitor presses play.
json_ldarray
The markup search engines and AI assistants read: a VideoObject and, when the article has questions, a FAQPage.
transcriptarray
The transcript in paragraphs: t (seconds from the start, when known), h (a heading, when there is one) and text.
{
"object": "list",
"data": [
{
"id": "5d0c1c0e-8f0a-4a57-9f0e-2b7f6a1d3c11",
"object": "video",
"status": "ready",
"youtube_id": "dQw4w9WgXcQ",
"youtube_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"title": "Harbour district walk-through",
"page_url": "https://www.example.com/videos/before-you-make-an-offer",
"headline": "What to Check Before You Make an Offer in the Harbour District",
"summary": "Three things to look at before you make an offer on a home in the harbour district: the roof, the condo minutes and what the seller leaves behind.",
"created_at": "2026-11-12T15:04:05.000Z",
"updated_at": "2026-11-12T15:04:05.000Z"
}
],
"page": 1,
"has_more": false
}
Delete a video
DELETE/v1/videos/{id}
Removes a video that was added through the tool kit, with its article. A video the account added in its own dashboard is removed there.
A short quiz that tells an agent who is ready now. Every person who finishes becomes a lead with a score.
The shape of a question
JSON
{
"text": "When would you like to move?",
"type": "single",
"options": [
{
"label": "Within 3 months",
"points": 35
},
{
"label": "In 3 to 6 months",
"points": 25
},
{
"label": "In 6 to 12 months",
"points": 10
},
{
"label": "Just looking for now",
"points": 0
}
]
}
Field
Type
What it is
text
string
The question. Up to 200 characters.
help
string
An optional line under the question.
type
string
single (one answer, the default) or multi (several can be chosen).
options
array
2 to 8 answers, each a label (up to 120 characters) and its points (0 to 100).
id
string
Given to every question and answer when the survey is saved. Send them back when you change a survey.
How a survey is scored
A single question gives the points of the answer chosen. A multi question gives the points of every answer chosen.
The total decides the lead's temperature: hot at 80 or more, warm at 50 or more, otherwise nurture.
So the strongest answers should add up to 100. A drafted survey always does. When yours does not, the survey says so in warnings.
Two more fields come back on every question and answer, maps and value. They match answers to listings on sites hosted by Realtor Flow. Leave them out.
Draft a survey from a description
POST/v1/surveys/draft
Say who the survey is for and what you want to find out. A complete survey comes back: questions, answers, points that add up to 100 and the three result messages. The call answers when the draft is written, which can take up to a minute.
Nothing is saved. Look the draft over, then send its survey to Create a survey. To change a draft, send it back as survey with a new prompt ("make it shorter", "add a question about schools"). To carry a longer conversation, send messages instead of prompt: a list of { "role": "user" | "assistant", "text": "..." } that ends with one from the user.
Each draft that comes back is counted as one survey draft. A draft that fails is not counted.
Send
promptstringrequired
What you want, in your own words. Up to 2,000 characters. Left out only when messages is sent.
surveyobject
A draft to change, as it came back from this call.
messagesarray
The whole conversation so far, instead of prompt. Up to 24 messages.
curl-X POST https://api.realtorflow.ca/v1/surveys/draft \
-H"Authorization: Bearer $RF_KEY" \
-H"Content-Type: application/json" \
-d'{
"prompt": "A survey for first-time buyers in Halifax. I want to know how soon they plan to buy and where they are with financing."
}'
const res = await fetch('https://api.realtorflow.ca/v1/surveys/draft', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.RF_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
prompt: 'A survey for first-time buyers in Halifax. I want to know how soon they plan to buy and where they are with financing.',
}),
});
const draft = await res.json();
$response = wp_remote_post( 'https://api.realtorflow.ca/v1/surveys/draft', array(
'timeout' => 90,
'headers' => array(
'Authorization' => 'Bearer ' . RF_KEY,
'Content-Type' => 'application/json',
),
'body' => wp_json_encode( array(
'prompt' => 'A survey for first-time buyers in Halifax. I want to know how soon they plan to buy and where they are with financing.',
) ),
) );
$draft = json_decode( wp_remote_retrieve_body( $response ), true );
Answer · 200
{
"object": "survey_draft",
"reply": "Here is a three-question survey for first-time buyers in Halifax. Timing and financing carry most of the points.",
"survey": {
"title": "Are you ready to buy in Halifax?",
"intro": "Three quick questions about your timing and your financing.",
"questions": [
{
"text": "When would you like to move?",
"type": "single",
"options": [
{
"label": "Within 3 months",
"points": 35
},
{
"label": "In 3 to 6 months",
"points": 25
},
{
"label": "In 6 to 12 months",
"points": 10
},
{
"label": "Just looking for now",
"points": 0
}
]
},
{
"...": "two more questions"
}
],
"results": {
"hot": {
"heading": "You are ready to buy",
"text": "Your timing and financing are in place. I will be in touch shortly to line up viewings."
},
"warm": {
"heading": "You are getting close",
"text": "A step or two is still open, usually financing or timing. I will follow up with what to do next."
},
"nurture": {
"heading": "You are at the start",
"text": "There is no rush. Reach out whenever you want to talk it through."
}
}
},
"problem": ""
}
In the answer
replystring
A sentence or two for the person: what was written or changed.
surveyobject
The draft: title, intro, questions and results.
problemstring
What stops the draft from going live, in words. Empty when it is complete.
What a person reads at the end: hot, warm and nurture, each a heading and text.
formobject
The contact step: button_label, contact_heading, contact_text, ask_phone and require_phone.
statusstring
draft (the default), live or archived. A live survey has to be complete.
slugstring
The short name used in the tag and in addresses. Made from the title when left out.
curl-X POST https://api.realtorflow.ca/v1/surveys \
-H"Authorization: Bearer $RF_KEY" \
-H"Content-Type: application/json" \
-d'{
"title": "Are you ready to buy in Halifax?",
"intro": "Three quick questions about your timing and your financing.",
"questions": [
{
"text": "When would you like to move?",
"type": "single",
"options": [
{
"label": "Within 3 months",
"points": 35
},
{
"label": "In 3 to 6 months",
"points": 25
},
{
"label": "In 6 to 12 months",
"points": 10
},
{
"label": "Just looking for now",
"points": 0
}
]
},
{
"text": "Where are you with financing?",
"type": "single",
"options": [
{
"label": "Pre-approved for a mortgage",
"points": 35
},
{
"label": "Paying cash",
"points": 35
},
{
"label": "Talking to a lender",
"points": 20
},
{
"label": "Not started",
"points": 0
}
]
},
{
"text": "Do you have a home to sell first?",
"type": "single",
"options": [
{
"label": "No",
"points": 30
},
{
"label": "Yes, and it is on the market",
"points": 20
},
{
"label": "Yes, not listed yet",
"points": 5
}
]
}
],
"results": {
"hot": {
"heading": "You are ready to buy",
"text": "Your timing and financing are in place. I will be in touch shortly to line up viewings."
},
"warm": {
"heading": "You are getting close",
"text": "A step or two is still open, usually financing or timing. I will follow up with what to do next."
},
"nurture": {
"heading": "You are at the start",
"text": "There is no rush. Reach out whenever you want to talk it through."
}
},
"status": "live"
}'
const res = await fetch('https://api.realtorflow.ca/v1/surveys', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.RF_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
title: 'Are you ready to buy in Halifax?',
intro: 'Three quick questions about your timing and your financing.',
questions: [
{
text: 'When would you like to move?',
type: 'single',
options: [
{
label: 'Within 3 months',
points: 35,
},
{
label: 'In 3 to 6 months',
points: 25,
},
{
label: 'In 6 to 12 months',
points: 10,
},
{
label: 'Just looking for now',
points: 0,
},
],
},
{
text: 'Where are you with financing?',
type: 'single',
options: [
{
label: 'Pre-approved for a mortgage',
points: 35,
},
{
label: 'Paying cash',
points: 35,
},
{
label: 'Talking to a lender',
points: 20,
},
{
label: 'Not started',
points: 0,
},
],
},
{
text: 'Do you have a home to sell first?',
type: 'single',
options: [
{
label: 'No',
points: 30,
},
{
label: 'Yes, and it is on the market',
points: 20,
},
{
label: 'Yes, not listed yet',
points: 5,
},
],
},
],
results: {
hot: {
heading: 'You are ready to buy',
text: 'Your timing and financing are in place. I will be in touch shortly to line up viewings.',
},
warm: {
heading: 'You are getting close',
text: 'A step or two is still open, usually financing or timing. I will follow up with what to do next.',
},
nurture: {
heading: 'You are at the start',
text: 'There is no rush. Reach out whenever you want to talk it through.',
},
},
status: 'live',
}),
});
const survey = await res.json();
$response = wp_remote_post( 'https://api.realtorflow.ca/v1/surveys', array(
'headers' => array(
'Authorization' => 'Bearer ' . RF_KEY,
'Content-Type' => 'application/json',
),
'body' => wp_json_encode( array(
'title' => 'Are you ready to buy in Halifax?',
'intro' => 'Three quick questions about your timing and your financing.',
'questions' => array(
array(
'text' => 'When would you like to move?',
'type' => 'single',
'options' => array(
array(
'label' => 'Within 3 months',
'points' => 35,
),
array(
'label' => 'In 3 to 6 months',
'points' => 25,
),
array(
'label' => 'In 6 to 12 months',
'points' => 10,
),
array(
'label' => 'Just looking for now',
'points' => 0,
),
),
),
array(
'text' => 'Where are you with financing?',
'type' => 'single',
'options' => array(
array(
'label' => 'Pre-approved for a mortgage',
'points' => 35,
),
array(
'label' => 'Paying cash',
'points' => 35,
),
array(
'label' => 'Talking to a lender',
'points' => 20,
),
array(
'label' => 'Not started',
'points' => 0,
),
),
),
array(
'text' => 'Do you have a home to sell first?',
'type' => 'single',
'options' => array(
array(
'label' => 'No',
'points' => 30,
),
array(
'label' => 'Yes, and it is on the market',
'points' => 20,
),
array(
'label' => 'Yes, not listed yet',
'points' => 5,
),
),
),
),
'results' => array(
'hot' => array(
'heading' => 'You are ready to buy',
'text' => 'Your timing and financing are in place. I will be in touch shortly to line up viewings.',
),
'warm' => array(
'heading' => 'You are getting close',
'text' => 'A step or two is still open, usually financing or timing. I will follow up with what to do next.',
),
'nurture' => array(
'heading' => 'You are at the start',
'text' => 'There is no rush. Reach out whenever you want to talk it through.',
),
),
'status' => 'live',
) ),
) );
$survey = json_decode( wp_remote_retrieve_body( $response ), true );
Answer · 201
{
"id": "0f6f4f0a-3c58-4b0e-9b7e-6f6c2f2a7d55",
"object": "survey",
"slug": "ready-to-buy",
"status": "live",
"title": "Are you ready to buy in Halifax?",
"intro": "Three quick questions about your timing and your financing.",
"questions": [
{
"id": "r53jk9om",
"text": "When would you like to move?",
"help": "",
"type": "single",
"maps": "",
"options": [
{
"id": "y179w03u",
"label": "Within 3 months",
"points": 35,
"value": ""
},
{
"id": "711toz20",
"label": "In 3 to 6 months",
"points": 25,
"value": ""
},
{
"id": "p0d3n7ra",
"label": "In 6 to 12 months",
"points": 10,
"value": ""
},
{
"id": "b6t1w9ze",
"label": "Just looking for now",
"points": 0,
"value": ""
}
]
},
{
"...": "two more questions"
}
],
"results": {
"hot": {
"heading": "You are ready to buy",
"text": "Your timing and financing are in place. I will be in touch shortly to line up viewings."
},
"warm": {
"heading": "You are getting close",
"text": "A step or two is still open, usually financing or timing. I will follow up with what to do next."
},
"nurture": {
"heading": "You are at the start",
"text": "There is no rush. Reach out whenever you want to talk it through."
}
},
"form": {
"button_label": "Start",
"contact_heading": "Almost done. Where can I reach you?",
"contact_text": "Enter your details to see your result.",
"ask_phone": true,
"require_phone": false
},
"max_score": 100,
"bands": {
"hot": 80,
"warm": 50
},
"stats": {
"starts": 41,
"responses": 28,
"hot": 9,
"warm": 12,
"nurture": 7
},
"embed_html": "<div data-rf-survey=\"ready-to-buy\"></div>",
"created_at": "2026-11-12T15:04:05.000Z",
"updated_at": "2026-11-12T15:04:05.000Z"
}
{
"object": "list",
"data": [
{
"id": "0f6f4f0a-3c58-4b0e-9b7e-6f6c2f2a7d55",
"object": "survey",
"slug": "ready-to-buy",
"status": "live",
"title": "Are you ready to buy in Halifax?",
"intro": "Three quick questions about your timing and your financing.",
"questions": [
{
"id": "r53jk9om",
"text": "When would you like to move?",
"help": "",
"type": "single",
"maps": "",
"options": [
{
"id": "y179w03u",
"label": "Within 3 months",
"points": 35,
"value": ""
},
{
"id": "711toz20",
"label": "In 3 to 6 months",
"points": 25,
"value": ""
},
{
"id": "p0d3n7ra",
"label": "In 6 to 12 months",
"points": 10,
"value": ""
},
{
"id": "b6t1w9ze",
"label": "Just looking for now",
"points": 0,
"value": ""
}
]
},
{
"...": "two more questions"
}
],
"results": {
"hot": {
"heading": "You are ready to buy",
"text": "Your timing and financing are in place. I will be in touch shortly to line up viewings."
},
"warm": {
"heading": "You are getting close",
"text": "A step or two is still open, usually financing or timing. I will follow up with what to do next."
},
"nurture": {
"heading": "You are at the start",
"text": "There is no rush. Reach out whenever you want to talk it through."
}
},
"form": {
"button_label": "Start",
"contact_heading": "Almost done. Where can I reach you?",
"contact_text": "Enter your details to see your result.",
"ask_phone": true,
"require_phone": false
},
"max_score": 100,
"bands": {
"hot": 80,
"warm": 50
},
"stats": {
"starts": 41,
"responses": 28,
"hot": 9,
"warm": 12,
"nurture": 7
},
"embed_html": "<div data-rf-survey=\"ready-to-buy\"></div>",
"created_at": "2026-11-12T15:04:05.000Z",
"updated_at": "2026-11-12T15:04:05.000Z"
}
],
"page": 1,
"has_more": false
}
Get a survey
GET/v1/surveys/{id}
By its id or by its slug. This is the owner's view, points included: call it from your server, never from a page.
{
"id": "0f6f4f0a-3c58-4b0e-9b7e-6f6c2f2a7d55",
"object": "survey",
"slug": "ready-to-buy",
"status": "live",
"title": "Are you ready to buy in Halifax?",
"intro": "Three quick questions about your timing and your financing.",
"questions": [
{
"id": "r53jk9om",
"text": "When would you like to move?",
"help": "",
"type": "single",
"maps": "",
"options": [
{
"id": "y179w03u",
"label": "Within 3 months",
"points": 35,
"value": ""
},
{
"id": "711toz20",
"label": "In 3 to 6 months",
"points": 25,
"value": ""
},
{
"id": "p0d3n7ra",
"label": "In 6 to 12 months",
"points": 10,
"value": ""
},
{
"id": "b6t1w9ze",
"label": "Just looking for now",
"points": 0,
"value": ""
}
]
},
{
"...": "two more questions"
}
],
"results": {
"hot": {
"heading": "You are ready to buy",
"text": "Your timing and financing are in place. I will be in touch shortly to line up viewings."
},
"warm": {
"heading": "You are getting close",
"text": "A step or two is still open, usually financing or timing. I will follow up with what to do next."
},
"nurture": {
"heading": "You are at the start",
"text": "There is no rush. Reach out whenever you want to talk it through."
}
},
"form": {
"button_label": "Start",
"contact_heading": "Almost done. Where can I reach you?",
"contact_text": "Enter your details to see your result.",
"ask_phone": true,
"require_phone": false
},
"max_score": 100,
"bands": {
"hot": 80,
"warm": 50
},
"stats": {
"starts": 41,
"responses": 28,
"hot": 9,
"warm": 12,
"nurture": 7
},
"embed_html": "<div data-rf-survey=\"ready-to-buy\"></div>",
"created_at": "2026-11-12T15:04:05.000Z",
"updated_at": "2026-11-12T15:04:05.000Z"
}
Send only what changes. questions replaces the whole list: keep the id of each question and answer you keep, so the answers already collected still line up.
Send
titlestring
The survey's name, shown at its top. Up to 140 characters.
introstring
A sentence or two under the title. Up to 600 characters.
{
"id": "0f6f4f0a-3c58-4b0e-9b7e-6f6c2f2a7d55",
"object": "survey",
"slug": "ready-to-buy",
"status": "archived",
"title": "Are you ready to buy in Halifax?",
"intro": "Three quick questions about your timing and your financing.",
"questions": [
{
"id": "r53jk9om",
"text": "When would you like to move?",
"help": "",
"type": "single",
"maps": "",
"options": [
{
"id": "y179w03u",
"label": "Within 3 months",
"points": 35,
"value": ""
},
{
"id": "711toz20",
"label": "In 3 to 6 months",
"points": 25,
"value": ""
},
{
"id": "p0d3n7ra",
"label": "In 6 to 12 months",
"points": 10,
"value": ""
},
{
"id": "b6t1w9ze",
"label": "Just looking for now",
"points": 0,
"value": ""
}
]
},
{
"...": "two more questions"
}
],
"results": {
"hot": {
"heading": "You are ready to buy",
"text": "Your timing and financing are in place. I will be in touch shortly to line up viewings."
},
"warm": {
"heading": "You are getting close",
"text": "A step or two is still open, usually financing or timing. I will follow up with what to do next."
},
"nurture": {
"heading": "You are at the start",
"text": "There is no rush. Reach out whenever you want to talk it through."
}
},
"form": {
"button_label": "Start",
"contact_heading": "Almost done. Where can I reach you?",
"contact_text": "Enter your details to see your result.",
"ask_phone": true,
"require_phone": false
},
"max_score": 100,
"bands": {
"hot": 80,
"warm": 50
},
"stats": {
"starts": 41,
"responses": 28,
"hot": 9,
"warm": 12,
"nurture": 7
},
"embed_html": "<div data-rf-survey=\"ready-to-buy\"></div>",
"created_at": "2026-11-12T15:04:05.000Z",
"updated_at": "2026-11-12T15:04:05.000Z"
}
For a site that draws the questions itself instead of using the pasted line. Send what the person chose and how to reach them. The score is worked out on our side, the lead lands in the account's inbox with its answers, the alert email goes out and any follow-up the account has set up starts.
Show the person result. Keep score and temperature on your server: they are for the agent, not for the visitor.
Send
answersobjectrequired
Each question's id with the id of the answer chosen, or a list of ids where several can be chosen.
namestringrequired
The person's name.
emailstringrequired
A valid email address.
phonestring
Required when the survey's form has require_phone set.
page_urlstring
The page the survey was taken on.
utm_sourcestring
Also utm_medium and utm_campaign: where the visitor came from, kept with the lead.
{
"object": "survey_response",
"lead_id": "7c1d7f52-2a3e-4d8a-8a55-0d7c3f1e9b20",
"score": 100,
"temperature": "hot",
"result": {
"heading": "You are ready to buy",
"text": "Your timing and financing are in place. I will be in touch shortly to line up viewings.",
"button": null
},
"created_at": "2026-11-12T15:09:41.000Z"
}
In the answer
lead_idstring
The lead this response became.
scorenumber
The points of the answers chosen.
temperaturestring
hot at 80 points or more, warm at 50 or more, otherwise nurture.
The account's photo library: upload a photo once and it is sized for every screen and served from Realtor Flow, ready for any page and for listings.
The library is the one in the dashboard: photos uploaded there are read here, and photos uploaded here can be picked there.
Upload a photo
POST/v1/media
Adds a photo to the account's media library: send the file itself as a form upload, or a web address to fetch it from. It is turned into WebP at several widths (480, 960, 1600 and 2400 pixels, never wider than the original) and served from Realtor Flow for good, so the addresses work on any website.
The answer has the addresses of every size, a srcset, and html: an <img> ready to paste, with the width and height set so the page does not jump while it loads. Write alt as a short description of what the photo shows: it is read out to people who cannot see it and read by search engines.
JPEG, PNG, WebP, AVIF, HEIC, GIF or TIFF; up to 20 MB as a file, 15 MB from a web address. Nothing here uses AI, so photos are not counted against the month's allowance.
Send
filefile
The photo, as a multipart/form-data upload with this field name. Send this or url.
urlstring
A web address to fetch the photo from (JSON body). Send this or file.
altstring
What the photo shows, in a short sentence. Up to 300 characters.
curl-X POST https://api.realtorflow.ca/v1/media \
-H"Authorization: Bearer $RF_KEY" \
-H"Content-Type: application/json" \
-d'{
"url": "https://www.example.com/photos/harbour-at-dusk.jpg",
"alt": "The harbour at dusk, seen from the condo balcony"
}'
const res = await fetch('https://api.realtorflow.ca/v1/media', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.RF_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
url: 'https://www.example.com/photos/harbour-at-dusk.jpg',
alt: 'The harbour at dusk, seen from the condo balcony',
}),
});
const media = await res.json();
$response = wp_remote_post( 'https://api.realtorflow.ca/v1/media', array(
'headers' => array(
'Authorization' => 'Bearer ' . RF_KEY,
'Content-Type' => 'application/json',
),
'body' => wp_json_encode( array(
'url' => 'https://www.example.com/photos/harbour-at-dusk.jpg',
'alt' => 'The harbour at dusk, seen from the condo balcony',
) ),
) );
$media = json_decode( wp_remote_retrieve_body( $response ), true );
To send the file itself, use a form upload instead of JSON:
curl
curl-X POST https://api.realtorflow.ca/v1/media \
-H"Authorization: Bearer $RF_KEY" \
-F"file=@harbour-at-dusk.jpg" \
-F"alt=The harbour at dusk, seen from the condo balcony"
JavaScript (Node 18 or later)
import { readFile } from'node:fs/promises';
const form = new FormData();
form.append('file', new Blob([await readFile('harbour-at-dusk.jpg')]), 'harbour-at-dusk.jpg');
form.append('alt', 'The harbour at dusk, seen from the condo balcony');
const res = await fetch('https://api.realtorflow.ca/v1/media', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.RF_KEY}` },
body: form,
});
const media = await res.json();
To put a library photo on a listing, send { "media": "<id>" } in the listing's photos.
List photos
GET/v1/media
The account's media library, newest first: photos uploaded here, in the dashboard, or fetched for listings.
Changes the words that describe the photo. A listing keeps the alt text it was given when the photo was added to it.
Send
altstringrequired
Up to 300 characters. An empty string clears it.
curl-X PATCH https://api.realtorflow.ca/v1/media/a3d9e6f1-2c4b-4e8a-b7d5-9f1c3e2a6b48 \
-H"Authorization: Bearer $RF_KEY" \
-H"Content-Type: application/json" \
-d'{
"alt": "The harbour at dusk from the balcony of unit 1204"
}'
const res = await fetch('https://api.realtorflow.ca/v1/media/a3d9e6f1-2c4b-4e8a-b7d5-9f1c3e2a6b48', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.RF_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
alt: 'The harbour at dusk from the balcony of unit 1204',
}),
});
const media = await res.json();
$response = wp_remote_request( 'https://api.realtorflow.ca/v1/media/a3d9e6f1-2c4b-4e8a-b7d5-9f1c3e2a6b48', array(
'method' => 'PATCH',
'headers' => array(
'Authorization' => 'Bearer ' . RF_KEY,
'Content-Type' => 'application/json',
),
'body' => wp_json_encode( array(
'alt' => 'The harbour at dusk from the balcony of unit 1204',
) ),
) );
$media = json_decode( wp_remote_retrieve_body( $response ), true );
Answer · 200
{
"id": "a3d9e6f1-2c4b-4e8a-b7d5-9f1c3e2a6b48",
"object": "media",
"filename": "harbour-at-dusk.jpg",
"width": 2000,
"height": 1200,
"bytes": 1843921,
"alt": "The harbour at dusk from the balcony of unit 1204",
"url": "https://api.realtorflow.ca/media/4f0e8a2c-6b1d-4c3e-9a7f-2d5b8c1e0f63/a3d9e6f1-2c4b-4e8a-b7d5-9f1c3e2a6b48/2000.webp",
"sizes": [
{
"width": 480,
"url": "https://api.realtorflow.ca/media/4f0e8a2c-6b1d-4c3e-9a7f-2d5b8c1e0f63/a3d9e6f1-2c4b-4e8a-b7d5-9f1c3e2a6b48/480.webp"
},
{
"width": 960,
"url": "https://api.realtorflow.ca/media/4f0e8a2c-6b1d-4c3e-9a7f-2d5b8c1e0f63/a3d9e6f1-2c4b-4e8a-b7d5-9f1c3e2a6b48/960.webp"
},
{
"width": 1600,
"url": "https://api.realtorflow.ca/media/4f0e8a2c-6b1d-4c3e-9a7f-2d5b8c1e0f63/a3d9e6f1-2c4b-4e8a-b7d5-9f1c3e2a6b48/1600.webp"
},
{
"width": 2000,
"url": "https://api.realtorflow.ca/media/4f0e8a2c-6b1d-4c3e-9a7f-2d5b8c1e0f63/a3d9e6f1-2c4b-4e8a-b7d5-9f1c3e2a6b48/2000.webp"
}
],
"srcset": "https://api.realtorflow.ca/media/4f0e8a2c-6b1d-4c3e-9a7f-2d5b8c1e0f63/a3d9e6f1-2c4b-4e8a-b7d5-9f1c3e2a6b48/480.webp 480w, https://api.realtorflow.ca/media/4f0e8a2c-6b1d-4c3e-9a7f-2d5b8c1e0f63/a3d9e6f1-2c4b-4e8a-b7d5-9f1c3e2a6b48/960.webp 960w, https://api.realtorflow.ca/media/4f0e8a2c-6b1d-4c3e-9a7f-2d5b8c1e0f63/a3d9e6f1-2c4b-4e8a-b7d5-9f1c3e2a6b48/1600.webp 1600w, https://api.realtorflow.ca/media/4f0e8a2c-6b1d-4c3e-9a7f-2d5b8c1e0f63/a3d9e6f1-2c4b-4e8a-b7d5-9f1c3e2a6b48/2000.webp 2000w",
"html": "<img src=\"https://api.realtorflow.ca/media/4f0e8a2c-6b1d-4c3e-9a7f-2d5b8c1e0f63/a3d9e6f1-2c4b-4e8a-b7d5-9f1c3e2a6b48/960.webp\" srcset=\"https://api.realtorflow.ca/media/4f0e8a2c-6b1d-4c3e-9a7f-2d5b8c1e0f63/a3d9e6f1-2c4b-4e8a-b7d5-9f1c3e2a6b48/480.webp 480w, https://api.realtorflow.ca/media/4f0e8a2c-6b1d-4c3e-9a7f-2d5b8c1e0f63/a3d9e6f1-2c4b-4e8a-b7d5-9f1c3e2a6b48/960.webp 960w, https://api.realtorflow.ca/media/4f0e8a2c-6b1d-4c3e-9a7f-2d5b8c1e0f63/a3d9e6f1-2c4b-4e8a-b7d5-9f1c3e2a6b48/1600.webp 1600w, https://api.realtorflow.ca/media/4f0e8a2c-6b1d-4c3e-9a7f-2d5b8c1e0f63/a3d9e6f1-2c4b-4e8a-b7d5-9f1c3e2a6b48/2000.webp 2000w\" sizes=\"(max-width: 960px) 100vw, 960px\" width=\"2000\" height=\"1200\" alt=\"The harbour at dusk, seen from the condo balcony\" loading=\"lazy\" decoding=\"async\">",
"created_at": "2026-11-13T19:42:10.000Z"
}
Removes the photo and every size of it; its addresses stop working. Refused while anything on the account still shows it, so no page is left with a missing photo.
The account's homes for sale and for rent, the same ones a Realtor Flow site shows. Keep them here from your own system, and show them with the pasted line or with your own pages.
A listing added here also appears in the account's dashboard, and one added in the dashboard is read here.
List listings
GET/v1/listings
The account's listings, featured ones first, then the newest. Each comes in the short form, with its first photo.
Photos are sent as web addresses. Each one is fetched once, stored and sized like a photo uploaded in the dashboard, so it no longer depends on where it came from. A call with many photos can take some seconds.
Nothing here uses AI, so listings are not counted against the month's allowance.
Send
titlestringrequired
Up to 200 characters.
descriptionstring
Plain text. A blank line starts a new paragraph. Up to 12,000 characters.
listing_typestring
sale (the default) or rent.
property_typestring
One of the types above, in any capitals.
pricenumber
Leave it out for price on request.
currencystring
CAD (the default), USD or EUR.
bedroomsnumber
Also bathrooms, area, lot_size and year_built.
addressstring
Also city, region and country.
featuresarray
Short lines such as "Garage". Up to 40.
photosarray
Up to 30 photos: web addresses, { "url", "alt" }, or { "media", "alt" } for a photo already in the media library. A web address is fetched once and stored (JPEG, PNG or WebP, up to 15 MB). A library photo is used as it is and stays in the library if the listing drops it later.
statusstring
active (the default here), pending, sold, draft or archived.
mls_numberstring
Shown only when sent.
curl-X POST https://api.realtorflow.ca/v1/listings \
-H"Authorization: Bearer $RF_KEY" \
-H"Content-Type: application/json" \
-d'{
"title": "Brick duplex near the river",
"listing_type": "sale",
"property_type": "Duplex",
"price": 899000,
"bedrooms": 5,
"bathrooms": 2,
"area": 2100,
"address": "1234 Example Street",
"city": "Halifax",
"region": "NS",
"features": [
"Garage",
"Terrace"
],
"photos": [
{
"url": "https://www.example.com/photos/duplex-front.jpg",
"alt": "Front of the duplex"
}
]
}'
Send only what changes. photos replaces the whole set; the photos it no longer uses are removed. Without photos, the listing keeps the ones it has. Marking a home sold is { "status": "sold" }.
Send
titlestring
Up to 200 characters.
descriptionstring
Plain text. A blank line starts a new paragraph. Up to 12,000 characters.
listing_typestring
sale (the default) or rent.
property_typestring
One of the types above, in any capitals.
pricenumber
Leave it out for price on request.
currencystring
CAD (the default), USD or EUR.
bedroomsnumber
Also bathrooms, area, lot_size and year_built.
addressstring
Also city, region and country.
featuresarray
Short lines such as "Garage". Up to 40.
photosarray
Up to 30 photos: web addresses, { "url", "alt" }, or { "media", "alt" } for a photo already in the media library. A web address is fetched once and stored (JPEG, PNG or WebP, up to 15 MB). A library photo is used as it is and stays in the library if the listing drops it later.
statusstring
active (the default here), pending, sold, draft or archived.
Writing for your own pages, with the same rules Realtor Flow sites follow: only the facts you gave, every figure checked.
Write an article from a draft
POST/v1/articles/draft
Send a rough draft: notes, bullet points, a transcript, an old blog post. A clear article comes back: a headline, a short answer up top, key points, sections with question headings, common questions and a closing line, plus a search title and description, ready HTML and its markup.
Only what is in the draft and in the account's business details is used. No figure, price, rule, name or quotation is added; a thin draft makes a short article. gaps lists what a reader would expect that the draft does not give, for the writer to add. check lists any figure or quotation in the article that is not in the draft word for word: read those before publishing.
Nothing is published or saved. The call answers when the article is written, which can take up to a minute. One article is counted against the month's allowance; a draft that fails is not counted.
Send
sourcestringrequired
The draft. 200 to 20,000 characters, in any of the five languages; the article is written in the account's language.
titlestring
A working title, kept when it is a good one.
page_urlstring
Where the article will be, so the markup carries its address.
curl-X POST https://api.realtorflow.ca/v1/articles/draft \
-H"Authorization: Bearer $RF_KEY" \
-H"Content-Type: application/json" \
-d'{
"title": "What to check before an offer",
"source": "Before you make an offer in the harbour district, look at the roof and ask how old it is. Read the condo minutes for the last two years, because they show what repairs are coming. Ask what the seller is leaving behind, such as appliances and window coverings.",
"page_url": "https://www.example.com/blog/before-an-offer"
}'
const res = await fetch('https://api.realtorflow.ca/v1/articles/draft', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.RF_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
title: 'What to check before an offer',
source: 'Before you make an offer in the harbour district, look at the roof and ask how old it is. Read the condo minutes for the last two years, because they show what repairs are coming. Ask what the seller is leaving behind, such as appliances and window coverings.',
page_url: 'https://www.example.com/blog/before-an-offer',
}),
});
const article_draft = await res.json();
$response = wp_remote_post( 'https://api.realtorflow.ca/v1/articles/draft', array(
'timeout' => 90,
'headers' => array(
'Authorization' => 'Bearer ' . RF_KEY,
'Content-Type' => 'application/json',
),
'body' => wp_json_encode( array(
'title' => 'What to check before an offer',
'source' => 'Before you make an offer in the harbour district, look at the roof and ask how old it is. Read the condo minutes for the last two years, because they show what repairs are coming. Ask what the seller is leaving behind, such as appliances and window coverings.',
'page_url' => 'https://www.example.com/blog/before-an-offer',
) ),
) );
$article_draft = json_decode( wp_remote_retrieve_body( $response ), true );
Answer · 200
{
"object": "article_draft",
"title": "What to check before you make an offer in the harbour district",
"excerpt": "Three things to check before an offer: the roof, the condo minutes and what the seller is leaving behind.",
"meta_title": "What to check before an offer in the harbour district",
"meta_description": "Before an offer in the harbour district, check the roof, read two years of condo minutes and ask what the seller is leaving behind.",
"body": {
"intro": "...",
"points": [
"..."
],
"sections": [
{
"heading": "How old is the roof?",
"text": "..."
}
],
"faq": [
{
"question": "...",
"answer": "..."
}
],
"closing": "...",
"button_label": "",
"button_link": ""
},
"html": "<div class=\"rf-article\">\n<div class=\"rf-intro\"><p>...</p></div>\n<div class=\"rf-points\"><h2>In short</h2><ul><li>...</li></ul></div>\n<section class=\"rf-section\"><h2>How old is the roof?</h2><p>...</p></section>\n...\n</div>",
"json_ld": [
{
"@context": "https://schema.org",
"@type": "Article",
"headline": "...",
"url": "https://www.example.com/blog/before-an-offer",
"...": "..."
},
{
"@context": "https://schema.org",
"@type": "FAQPage",
"...": "..."
}
],
"gaps": [
"The draft gives no prices for the area."
],
"check": {
"numbers": [],
"quotes": []
}
}
In the answer
htmlstring
The article, ready to put in a page, with no styles of its own. Class names: rf-article, rf-intro, rf-points, rf-section, rf-faq, rf-closing.
bodyobject
The same article in parts, to lay out yourself.
gapsarray
Notes for the writer only.
checkobject
numbers and quotes in the article that are not in the draft word for word.
Text from your site in English, French, Spanish, German or Chinese, translated as Realtor Flow sites translate theirs: faithfully, in a real estate agent's words, with the business's names kept as they are.
Every line is checked before it is handed back: figures, prices, phone numbers, email and web addresses, {markers} and the business's name must come through exactly, and the length must be reasonable. A line that fails comes back with translation set to null and the reason in problem, so it can be kept in the original language.
One translation is counted for each call, up to 20,000 characters; a call where nothing came back translated is not counted.
Send
tostringrequired
en, fr, es, de or zh.
textstringrequired
One text. Or texts: a list of up to 200, answered in the same order.
fromstring
The language of the text. The default is the account's.
curl-X POST https://api.realtorflow.ca/v1/translate \
-H"Authorization: Bearer $RF_KEY" \
-H"Content-Type: application/json" \
-d'{
"to": "fr",
"texts": [
"Three bedrooms near the harbour, listed at $649,000.",
"Book a visit"
]
}'
const res = await fetch('https://api.realtorflow.ca/v1/translate', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.RF_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
to: 'fr',
texts: [
'Three bedrooms near the harbour, listed at $649,000.',
'Book a visit',
],
}),
});
const translation = await res.json();
Being found and named by AI assistants: the files they read about the business, and how many visits they send.
Build the AI visibility files
POST/v1/visibility
What AI assistants read about the business, built for your website's own address: a plain-text guide (llms.txt), the business as markup (RealEstateAgent) and the lines for robots.txt that welcome AI crawlers, following the account's choice about crawlers that collect pages to train models.
Everything comes from what the account already says about itself (its name, area, licence and contact details, set in the dashboard) and from the pages and listings you name. Nothing is looked up or added. Publish llms_txt at /llms.txt, put json_ld in a <script type="application/ld+json"> on every page, and add robots_txt to your robots.txt (its sitemap line assumes /sitemap.xml).
Building them changes nothing, so it is not counted. Build them again when the listings or the details change.
Send
originstringrequired
The website's address, https://www.example.com. Only the address is kept, not a path.
listing_urlstring
How the website addresses a listing, with {slug}: https://www.example.com/homes/{slug}. Without it, listings are left out of the guide.
pagesarray
The website's main pages, up to 50: { "title", "url", "description" }.
curl-X POST https://api.realtorflow.ca/v1/visibility \
-H"Authorization: Bearer $RF_KEY" \
-H"Content-Type: application/json" \
-d'{
"origin": "https://www.example.com",
"listing_url": "https://www.example.com/homes/{slug}",
"pages": [
{
"title": "Selling your home",
"url": "https://www.example.com/sell",
"description": "How I price and sell homes in Halifax."
}
]
}'
const res = await fetch('https://api.realtorflow.ca/v1/visibility', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.RF_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
origin: 'https://www.example.com',
listing_url: 'https://www.example.com/homes/{slug}',
pages: [
{
title: 'Selling your home',
url: 'https://www.example.com/sell',
description: 'How I price and sell homes in Halifax.',
},
],
}),
});
const visibility = await res.json();
{
"object": "visibility",
"origin": "https://www.example.com",
"llms_txt": "# Harbour Realty\n\n> The website of Harbour Realty. Area served: Halifax and Dartmouth. 12 homes for sale or rent are listed.\n\nLicensed with the Nova Scotia Real Estate Commission\n\nContact:\n\n- Phone: 902-555-0142\n- Email: hello@harbour.example\n\n## Pages\n\n- [Selling your home](https://www.example.com/sell): How I price and sell homes in Halifax.\n\n## Listings\n\n- [Waterfront condo](https://www.example.com/homes/waterfront-condo): Condo. For sale. Halifax, NS. Beds: 2. $649,000\n...",
"listings_in_llms_txt": 12,
"json_ld": {
"@context": "https://schema.org",
"@type": "RealEstateAgent",
"name": "Harbour Realty",
"url": "https://www.example.com",
"telephone": "902-555-0142",
"email": "hello@harbour.example",
"areaServed": "Halifax and Dartmouth"
},
"robots_choice": "all",
"robots_txt": "# AI crawlers are welcome on this site.\nUser-agent: OAI-SearchBot\nUser-agent: ChatGPT-User\nUser-agent: GPTBot\n...\nAllow: /\n\nUser-agent: *\nAllow: /\n\n# A plain-text guide to this site for AI assistants: https://www.example.com/llms.txt\nSitemap: https://www.example.com/sitemap.xml\n"
}
In the answer
robots_choicestring
all (every AI crawler welcome) or no_training (crawlers that answer questions or build AI search welcome, crawlers that collect pages to train models asked to stay away). Set in the dashboard.
The page views the pasted line counted (with data-count="on") and, for a Realtor Flow site, its own: visits, visitors, where they came from and which AI assistants sent them. The same figures as the dashboard's AI-Traffic Analytics.
Visits that came from an AI assistant: a link in its answer, or its tag in the address.
ai_referrersarray
Those visits by assistant.
crawlersarray
AI crawlers seen on a Realtor Flow site, or reported with Report AI crawler visits (the WordPress plugin does). A pasted line cannot see crawlers, which do not run scripts.
For a website whose own server draws its pages: report the AI crawlers it saw (GPTBot, ClaudeBot, PerplexityBot and the others), so they appear in the account's AI traffic beside its visitors. A pasted line cannot do this, because crawlers do not run scripts. The WordPress plugin calls this for you.
Send every visit you are unsure about: anything that is not a known AI crawler is dropped, so people are never counted here. Send them in batches, every few minutes, rather than one call per page.
Send
hitsarrayrequired
1 to 200 visits, each { "user_agent", "path", "at" }: the visitor's user agent as your server received it, the page's path (starting with /), and when (ISO 8601, within the last two days; now when left out). A single visit can also be sent on its own, without hits.
Short stays on the account's rentals: booking terms on a listing, requests from guests, and the broker's pipeline from enquiry to completed. No payment is taken.
Bookings made here are the ones in the dashboard's Bookings & Reservations, and the other way round.
Take bookings on a listing
PUT/v1/listings/{id}/booking
Gives a listing its booking terms, or changes them. From then on, guests can ask for dates on it. DELETE on the same address stops it taking requests; the bookings already made stay.
Send
nightly_ratenumberrequired
In the listing's currency.
min_nightsnumberrequired
The shortest stay, from 1 to 90.
max_guestsnumberrequired
From 1 to 99.
is_onboolean
false keeps the terms but stops new requests for now.
A request from your own booking form. It is checked as on a Realtor Flow site (the dates, the shortest stay, the guests and the nights already held), saved as an enquiry with its estimate, and the broker's alert and the guest's receipt go out.
A request does not hold the nights. They are held once the broker confirms. No payment is taken anywhere in the tool kit.
Send
listingstringrequired
The listing's id or slug.
check_instringrequired
A date, YYYY-MM-DD. Not in the past, and within two years.
check_outstringrequired
A later date. Up to 90 nights.
guestsnumberrequired
Up to the listing's most guests.
namestringrequired
The guest's name.
emailstringrequired
A valid email address. The receipt goes there.
phonestring
Also message.
curl-X POST https://api.realtorflow.ca/v1/bookings \
-H"Authorization: Bearer $RF_KEY" \
-H"Content-Type: application/json" \
-d'{
"listing": "beach-cottage",
"check_in": "2026-12-18",
"check_out": "2026-12-21",
"guests": 2,
"name": "Dana Whitfield",
"email": "dana@example.com",
"phone": "902-555-0142",
"message": "Arriving late on the first night."
}'
const res = await fetch('https://api.realtorflow.ca/v1/bookings', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.RF_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
listing: 'beach-cottage',
check_in: '2026-12-18',
check_out: '2026-12-21',
guests: 2,
name: 'Dana Whitfield',
email: 'dana@example.com',
phone: '902-555-0142',
message: 'Arriving late on the first night.',
}),
});
const booking = await res.json();
Moves a booking along: enquiry, confirmed, paid (a note that the money reached the broker some other way), completed or cancelled. Confirming, or marking paid, is refused while another booking already holds any of the nights. notes are the broker's own and never reach the guest.
The account's mailing list, for its newsletter. Only people who agreed themselves, each with the words they agreed to.
The list is the one in the dashboard's Managed Email Delivery, where campaigns are written and sent. Sending needs the account's own sending address to be set up there.
Add a sign-up
POST/v1/subscribers
Someone who signed up on your own form, with a box they ticked themselves. They join the account's mailing list, and campaigns sent from the dashboard reach them. Never send people who did not agree.
An address that bounced or complained is not added; the answer says blocked. Someone who had unsubscribed and signs up again is back on the list, with the new consent.
Send
emailstringrequired
Their email address.
consent_textstringrequired
The exact words beside the box they ticked. Kept with the address as the record of their consent. 10 to 300 characters.
namestring
Their name.
page_urlstring
The page they signed up on.
curl-X POST https://api.realtorflow.ca/v1/subscribers \
-H"Authorization: Bearer $RF_KEY" \
-H"Content-Type: application/json" \
-d'{
"email": "dana@example.com",
"name": "Dana Whitfield",
"consent_text": "Yes, send me the monthly market note from Harbour Realty.",
"page_url": "https://www.example.com/newsletter"
}'
For an unsubscribe link or request handled on your side. The address leaves the list and goes on the account's do-not-mail list; any follow-up emails under way for it stop.
Enquiries from any form and finished surveys: send them in, read them back with their scores and answers, move them along, and stop their follow-up emails.
These are the leads in the dashboard's lead inbox, and the other way round. Reading them needs a live or test key, from a server: a website key cannot read leads.
Send a lead
POST/v1/leads
An enquiry from your own form. It lands in the account's lead inbox, the alert email goes out and any follow-up the account has set up starts, exactly as for a site hosted by Realtor Flow.
Send
namestringrequired
The person's name.
emailstringrequired
A valid email address.
phonestring
A phone number.
messagestring
What they wrote. Up to 4,000 characters.
page_urlstring
The page the form is on.
listingstring
For an enquiry about one of the account's listings: its id or slug. The lead is filed with the listing.
utm_sourcestring
Also utm_medium and utm_campaign: where the visitor came from, kept with the lead.
curl-X POST https://api.realtorflow.ca/v1/leads \
-H"Authorization: Bearer $RF_KEY" \
-H"Content-Type: application/json" \
-d'{
"name": "Dana Whitfield",
"email": "dana@example.com",
"phone": "902-555-0142",
"message": "Is the house on Tower Road still available?",
"page_url": "https://www.example.com/contact"
}'
const res = await fetch('https://api.realtorflow.ca/v1/leads', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.RF_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: 'Dana Whitfield',
email: 'dana@example.com',
phone: '902-555-0142',
message: 'Is the house on Tower Road still available?',
page_url: 'https://www.example.com/contact',
}),
});
const lead = await res.json();
Every lead in the account's inbox, newest first, wherever it came from: a Realtor Flow site, the pasted line or your own server. Pull new ones into your own system with since.
A lead from a survey carries its score out of 100, how ready the person is (hot, warm or nurture), every answer as worded when they answered, and the note written for the agent when the survey is set to write one. follow_ups lists the follow-up sequences the lead is going through, with how many emails went out and when the next one is due.
Leads marked as spam are left out unless status asks for them.
In the address
statusstring
new, contacted, qualified, closed or spam. Several, separated by commas.
kindstring
contact (a form), listing (a form about one listing) or quiz (a survey). Several, separated by commas.
temperaturestring
Survey leads only: hot, warm or nurture. Several, separated by commas.
sincestring
Only leads that came in at or after this date and time (ISO 8601).
beforestring
Only leads that came in before this date and time.
Moves a lead along (new, contacted, qualified, closed, spam) and keeps the agent's own notes, the same ones the dashboard shows. Notes never reach the lead.
Closing a lead, or marking it as spam, stops its follow-up emails at once.
Stops every follow-up sequence the lead is going through, for example once the agent has spoken to them. The lead itself is left as it is. stopped says how many were stopped; calling it again stops nothing and is not an error.
curl-X POST https://api.realtorflow.ca/v1/leads/7c1d7f52-2a3e-4d8a-8a55-0d7c3f1e9b20/follow-ups/stop \
-H"Authorization: Bearer $RF_KEY"
const res = await fetch('https://api.realtorflow.ca/v1/leads/7c1d7f52-2a3e-4d8a-8a55-0d7c3f1e9b20/follow-ups/stop', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.RF_KEY}` },
});
const lead = await res.json();
A follow-up's stop_reason says why it stopped: lead_closed, lead_spam, unsubscribed, failed (the email could not be sent three times) or stopped_by_api.
Reference
Errors
Every refusal is { "error": "...", "code": "..." } with the status shown here.
Code
Status
What it means
no_key
401
No key was sent. Send it as Authorization: Bearer <key>.
bad_key
401
The key is not one of ours. Copy it again from the Developers page.
key_revoked
401
The key has been revoked. Make a new one on the Developers page.
paused
403
The account is paused. Write to Realtor Flow.
not_switched_on
403
The tool kit is not switched on for this account. Write to Realtor Flow.
wrong_key
403
The wrong kind of key for this call: a website key was used from a server, or a live or test key was put in a page.
browser_only
403
A website key was used without a browser. It only works from a page.
domain_not_listed
403
The page is on a website that is not listed on the website key. Add it on the Developers page.
tool_off
403
The tool this call belongs to (Videos, Surveys, Leads, Listings, Bookings and so on) is not switched on for the account.
not_found
404
There is nothing with that id, or no such call.
bad_json
400
The body is not valid JSON. Send JSON with Content-Type: application/json.
too_large
413
The body is over 2 MB.
slow_down
429
Too many calls in a minute. Wait and try again; the RateLimit-Reset header says how many seconds.
limit_reached
429
The month's allowance for this kind of work is used up. It starts again on the first of the month (UTC).
try_later
503
The account has reached the day's ceiling for AI work. It starts again the next day.
ai_off
503
AI writing is not available right now. Try again later.
server_error
500
Something went wrong on our side. Try again; if it keeps happening, write to Realtor Flow.
bad_youtube_url
400
youtube_url is missing or is not the address of a YouTube video.
no_transcript
400
No transcript was sent for a video that has none yet.
transcript_too_short
422
The transcript holds under 400 characters of speech, too little to write from.
transcript_too_long
413
The transcript is far beyond two hours of speech.
bad_page_url
400
page_url is not a full address starting with https://.
not_yours_to_delete
409
The video was added in the dashboard, not through the tool kit. Remove it there.
bad_survey
400
The survey sent is not complete. The sentence says which part: a missing title, a question with no wording, fewer than two answers.
too_many_surveys
409
The account already holds 50 surveys. Delete or reuse one.
no_prompt
400
Nothing to draft from. Send a prompt, or messages that end with one from the user.
draft_failed
502
The draft did not come back complete. Nothing was counted. Try again.
survey_not_live
409
Real answers were sent to a survey that is not live. Set its status to live.
bad_response
400
The answers do not belong to this survey, or the name, email or required phone number is missing.
bad_lead
400
A lead needs a name and a valid email.
draft_too_short
400
The draft is under 200 characters. The article only uses what is in the draft, so it needs something to work with.
draft_too_long
413
The draft is over 20,000 characters.
no_text
400
Nothing to translate. Send text, or texts (up to 200).
text_too_long
413
Over 20,000 characters in one call. Split it into several calls.
bad_language
400
to (or from) is not one of en, fr, es, de or zh, or both are the same.
translation_failed
502
The translation did not come back. Nothing was counted. Try again.
bad_crawls
400
hits is empty or holds more than 200 visits.
bad_origin
400
origin is missing or is not a web address starting with https://.
bad_listing_url
400
listing_url is not a full address with {slug} where each listing's short name goes.
bad_pages
400
pages is not a list of { "title", "url", "description" }.
bad_terms
400
The booking terms are not right: a nightly rate above zero, and whole numbers for the shortest stay and the most guests.
bad_booking
400
The request is not right. The sentence says which part, in the account's language: the dates, the length of stay, the number of guests or the contact details.
not_bookable
404
The listing is not taking booking requests: it has no booking terms, or it is not active.
dates_taken
409
Some of those nights are already held by a confirmed or paid booking.
bad_status
400
status is not one this call takes. Bookings: enquiry, confirmed, paid, completed, cancelled. Leads: new, contacted, qualified, closed, spam.
bad_kind
400
kind is one or more of contact, listing and quiz, separated by commas.
bad_temperature
400
temperature is one or more of hot, warm and nurture, separated by commas.
bad_date
400
since or before is not a date, such as 2026-11-01 or 2026-11-01T09:00:00Z.
nothing_to_change
400
The body has nothing to change: for a lead, neither status nor notes; for a photo, no alt.
no_photo
400
No photo was sent: a form upload with the field file, or JSON with url.
bad_upload
400
The form upload is not one photo in the field file.
file_too_large
413
The photo is over 20 MB.
bad_alt
400
alt is not text, or is over 300 characters.
media_full
409
The account's media library holds 5,000 photos, the most it can. Delete some first.
media_in_use
409
The photo is still used by a listing, page, article or something else on the account. The sentence names them. Take it out of those first.
bad_notes
400
notes is not text (send null to clear it).
notes_too_long
413
notes is over 4,000 characters.
bad_subscriber
400
A sign-up needs a valid email.
no_consent
400
No consent: the box was not ticked, or consent_text (the exact words the person agreed to) was not sent.
bad_listing
400
The listing sent is not right. The sentence says which part: a missing title, an unknown property type or status.
bad_photo
400
A photo could not be used: the address does not answer, is not a JPEG, PNG or WebP, is over 15 MB, or is not on the public internet. The sentence names the photo.
too_many_photos
400
A listing can have up to 30 photos.
too_many_listings
409
The account already holds 2,000 listings. Delete or archive old ones.
More
For AI tools and site builders
Building with an AI coding assistant or a site builder? Hand it one of these and it has what it needs to call the tool kit:
No. The website stays where it is. The tool kit adds listings, surveys, contact forms and video articles to it, and the work runs on Realtor Flow.
Do I need to write code?
Not for listings, surveys, contact forms and newsletter sign-ups: paste one line into the page and put a tag where each one should appear. Video articles are reached through the API, so they need a developer or a tool that can call one.
Which websites does the pasted line work on?
Any page where you can add your own HTML with a script tag, on a website address listed on the key. Some site builders allow custom code only on certain plans, or place it inside a frame served from another address; inside such a frame the key will not match the website.
Where do the leads go?
Into the lead inbox of the account's Realtor Flow dashboard, each with its score and every answer. The alert email goes to the addresses the account has set.
Can a visitor see how the answers are scored?
No. The pasted line never receives the points, and the score is not sent to the browser. It stays with the lead in the inbox.
What does testing use up?
Nothing. A test key never reaches the AI, saves nothing, sends no email and is not counted against the allowance.
Where does a video article get its facts?
From what is said in the video. The title, the description and the account's business details are read for context, and nothing is looked up elsewhere. Figures and quotations that the transcript does not hold word for word are listed in "check", so a person can look before publishing.
How do I get a key?
Keys are made on the Developers page of the Realtor Flow dashboard. The tool kit is switched on for each account by Realtor Flow: write to jarvis@realtorflow.ca.