Skip to the documentation
Realtor FlowTool Kit

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.

Paste one lineCall the API

On your page
<!-- Once, anywhere on the page -->
<script src="https://api.realtorflow.ca/v1/kit.js"
        data-key="rf_pub_..." async></script>

<!-- Where the survey goes -->
<div data-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.
ToolWhat it doesPasted lineAPI
SurveysA short quiz that scores each person and files them as a hot, warm or nurture lead. Write it yourself, or describe it and have it drafted.YesYes
Video articlesA YouTube video and its transcript in. A headline, an article, questions and answers, ready HTML and search markup out.Yes
ListingsYour homes for sale and for rent: a searchable grid and listing pages with photos, an enquiry form and search markup.YesYes
BookingsShort-stay requests on listings that take bookings: dates, guests and an estimate, then the broker confirms. No payment is taken.YesYes
Articles and translationA 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.Yes
AI visibility and trafficThe files AI assistants read about the business, built for your site, and a count of the visits that AI assistants send you.YesYes
NewsletterA sign-up box that adds people to the account's mailing list, with their consent kept word for word. Campaigns are sent from the dashboard.YesYes
LeadsAn enquiry from any form lands in the account's lead inbox and the alert email goes out.YesYes
MediaA 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.

  1. 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.

  2. 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.

    HTML
    <script src="https://api.realtorflow.ca/v1/kit.js" data-key="rf_pub_..." async></script>
  3. Put a tag where the survey goes

    The name is the survey's short name. The Developers page writes this tag for each of your surveys.

    HTML
    <div data-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.

  1. Make a test key

    In the dashboard, open Developers, choose New key and pick Test key. It is shown once: copy it then.

  2. Check that it works

    curl https://api.realtorflow.ca/v1/account \
      -H "Authorization: Bearer $RF_KEY"
    Answer · 200
    {
      "object": "account",
      "name": "Harbour Realty",
      "mode": "test",
      "key": "Local testing",
      "language": "en",
      "tools": {
        "videos": true,
        "surveys": true,
        "leads": true,
        "listings": true,
        "bookings": true,
        "newsletter": true,
        "ai_visibility": true,
        "ai_traffic": true,
        "articles": true,
        "translation": true,
        "media": true
      },
      "usage": {
        "resets_at": "2026-12-01T00:00:00.000Z",
        "units": [
          {
            "unit": "video_article",
            "label": "Video articles",
            "used": 3,
            "in_progress": 1,
            "limit": 25,
            "left": 21
          },
          {
            "unit": "survey_draft",
            "label": "Surveys drafted by AI",
            "used": 2,
            "in_progress": 0,
            "limit": 50,
            "left": 48
          }
        ]
      }
    }
  3. Build against the test key

    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.

On a web page

The pasted line

HTML
<script src="https://api.realtorflow.ca/v1/kit.js" data-key="rf_pub_..." async></script>

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
<div data-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
<div data-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.

On a web page

Count visits from AI assistants

HTML
<script src="https://api.realtorflow.ca/v1/kit.js" data-key="rf_pub_..."
        data-count="on" async></script>

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
<div data-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.

AttributeWhat it does
data-rf-titleA heading above the box.
data-rf-textA line under the heading: what they will get and how often.
data-rf-consentYour 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-buttonThe 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 -->
<div data-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.
AttributeWhat it does
data-rf-listingsEmpty for all listings, or names separated by commas to show only those.
data-rf-typesale or rent: start the grid on one of them.
data-rf-limitHow 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.

  1. 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.

  2. 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_...' );
  3. 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.

ShortcodeWhat it shows
[realtorflow_listings]The listings grid. Options: type="sale" or "rent", limit="12", filters="off", only="name-one,name-two".
[realtorflow_listing name="..."]One listing on a page of its own.
[realtorflow_survey name="..."]A lead survey.
[realtorflow_form]A contact form. Options: title, button.
[realtorflow_newsletter]Newsletter sign-up. Options: title, text, consent, button, name="on".
[realtorflow_booking name="..."]A booking request for a rental.

What it does beyond the pasted line

  • 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: 0 10px 30px rgba(0, 0, 0, .08);
}
PropertyWhat it setsWhen left out
--rf-accentButtons, the chosen answer, the progress barThe account's accent colour
--rf-on-accentText on the accent colourBlack or white, whichever reads better
--rf-bgThe card's backgroundTransparent
--rf-textText colourThe page's
--rf-fontFontThe page's
--rf-heading-fontFont of the headingsThe page's
--rf-title-sizeSize of the headings1.5em
--rf-radiusRounding of the card12px
--rf-widthThe card's greatest width640px
--rf-marginSpace around the card. 0 auto centres it0: it lines up with the text around it
--rf-paddingSpace inside the card20 to 32 pixels, by screen width
--rf-borderThe card's borderA thin grey line
--rf-field-bgBackground of the form fieldsTransparent
--rf-card-widthListings: the smallest width of a card in the grid250px
--rf-gapListings: the space between cards20px

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.

EventWhenevent.detail
rf:readyA survey, form or listings tag has drawn itself{ survey }, { form }, { listings }, { listing }, { booking } or { newsletter }
rf:listingA listing was opened{ listing }
rf:subscribeSomeone signed up for the newsletter{ newsletter: true }
rf:bookingA booking request was sent{ listing, check_in, check_out }
rf:startA visitor pressed the start button{ survey }
rf:completeA visitor finished a survey and is now a lead{ survey }
rf:leadA contact form or a listing enquiry was sent{ form }, or { form: "listing", listing }
rf:errorSomething 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:

KindStarts withWhere it goesWhat it does
Website keyrf_pub_In the pasted line, in a pageShows listings, booking requests, surveys, forms and the newsletter sign-up on the websites listed on it. Nothing else.
Test keyrf_test_On your server, while you buildEvery call answers. Nothing is saved, sent or counted.
Live keyrf_live_On your serverThe 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:

CallWith a test key
Reading the account, usage, surveys and listingsThe real answers from the account.
Adding, changing or deleting a listingChecked and answered as if done. Nothing is saved and no photo is fetched.
Adding a sign-up or unsubscribing someoneChecked and answered as if done. The list does not change.
Booking terms, requests and status changesChecked like the real thing, nights already held included. Nothing is saved and no email goes out.
AI visibility files and AI trafficThe real answers: building the files changes nothing.
Write an article, translateA fixed sample article; each text back marked [fr] (or the language asked for). No AI runs.
Send a videoA sample video with an id that starts test_. Its status is writing for about four seconds, then ready with a fixed sample article. No AI runs.
List videosAlways an empty list.
Draft a surveyOne of the ready-made surveys, not one written from your words. No AI runs.
Creating, changing or deleting a surveyChecked and answered as if done. Nothing is saved.
Send a finished surveyScored for real against the survey, draft or live, so you can build against true results. No lead is saved and nobody is written to.
Send a leadChecked and answered. No lead is saved and no email goes out.
List and get leadsThe account's real leads: reading changes nothing.
Change a lead, stop its follow-upsThe lead as it would be afterwards, marked. Nothing is saved and no follow-up is stopped.
Upload a photoA 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:

CallA minute, per key
Any call120
Send a finished survey, send a lead, add a sign-up, unsubscribe60 each
Send a video, create or change a survey, add or change a listing, booking terms, requests and status changes30 each
Draft a survey, write an article, translate20 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.

curl https://api.realtorflow.ca/v1/account \
  -H "Authorization: Bearer $RF_KEY"
Answer · 200
{
  "object": "account",
  "name": "Harbour Realty",
  "mode": "live",
  "key": "Website server",
  "language": "en",
  "tools": {
    "videos": true,
    "surveys": true,
    "leads": true,
    "listings": true,
    "bookings": true,
    "newsletter": true,
    "ai_visibility": true,
    "ai_traffic": true,
    "articles": true,
    "translation": true,
    "media": true
  },
  "usage": {
    "resets_at": "2026-12-01T00:00:00.000Z",
    "units": [
      {
        "unit": "video_article",
        "label": "Video articles",
        "used": 3,
        "in_progress": 1,
        "limit": 25,
        "left": 21
      },
      {
        "unit": "survey_draft",
        "label": "Surveys drafted by AI",
        "used": 2,
        "in_progress": 0,
        "limit": 50,
        "left": 48
      }
    ]
  }
}

In the answer

namestring
The account's business name.
modestring
live or test: the kind of key that made the call.
keystring
The name the key was given on the Developers page.
languagestring
The account's language. Articles and drafted surveys are written in it.
toolsobject
Which tools are switched on for the account.
usageobject
The same as the month's count.

Can be refused with no_key bad_key key_revoked not_switched_on

The month's count

GET/v1/usage

What has been counted this calendar month (UTC) and what is left.

curl https://api.realtorflow.ca/v1/usage \
  -H "Authorization: Bearer $RF_KEY"
Answer · 200
{
  "object": "usage",
  "resets_at": "2026-12-01T00:00:00.000Z",
  "units": [
    {
      "unit": "video_article",
      "label": "Video articles",
      "used": 3,
      "in_progress": 1,
      "limit": 25,
      "left": 21
    },
    {
      "unit": "survey_draft",
      "label": "Surveys drafted by AI",
      "used": 2,
      "in_progress": 0,
      "limit": 50,
      "left": 48
    }
  ]
}

In the answer

resets_atstring
When the count starts again.
units[].unitstring
video_article or survey_draft.
units[].usednumber
Work that came back and was counted.
units[].in_progressnumber
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).

  1. Send the video

    Send a video with its transcript. The answer is 202 with the status writing.

  2. Ask until it is ready

    Get the video every few seconds until status is ready.

  3. 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.

    HTML
    <head>
      <title>{search.title}</title>
      <meta name="description" content="{search.description}">
      <script type="application/ld+json">{json_ld}</script>
    </head>
    <body>
      <h1>{headline}</h1>
      {embed_html}
      {html}
    </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."
  }'
Answer · 202
{
  "id": "5d0c1c0e-8f0a-4a57-9f0e-2b7f6a1d3c11",
  "object": "video",
  "status": "writing",
  "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": null,
  "summary": null,
  "created_at": "2026-11-12T15:04:05.000Z",
  "updated_at": "2026-11-12T15:04:05.000Z"
}

Can be refused with bad_youtube_url no_transcript transcript_too_short transcript_too_long bad_page_url limit_reached ai_off tool_off

Get a video

GET/v1/videos/{id}

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.
curl https://api.realtorflow.ca/v1/videos/5d0c1c0e-8f0a-4a57-9f0e-2b7f6a1d3c11 \
  -H "Authorization: Bearer $RF_KEY"
Answer · 200
{
  "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.

Can be refused with not_found

List videos

GET/v1/videos

The account's videos, newest first, in the short form (no article). A test key always gets an empty list.

In the address

limitnumber
How many to a page: 1 to 100. The default is 20.
pagenumber
Which page, from 1.
curl "https://api.realtorflow.ca/v1/videos?limit=10" \
  -H "Authorization: Bearer $RF_KEY"
Answer · 200
{
  "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.

curl -X DELETE https://api.realtorflow.ca/v1/videos/5d0c1c0e-8f0a-4a57-9f0e-2b7f6a1d3c11 \
  -H "Authorization: Bearer $RF_KEY"
Answer · 200
{
  "id": "5d0c1c0e-8f0a-4a57-9f0e-2b7f6a1d3c11",
  "object": "video",
  "deleted": true
}

Can be refused with not_found not_yours_to_delete

Reference

Surveys

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
    }
  ]
}
FieldTypeWhat it is
textstringThe question. Up to 200 characters.
helpstringAn optional line under the question.
typestringsingle (one answer, the default) or multi (several can be chosen).
optionsarray2 to 8 answers, each a label (up to 120 characters) and its points (0 to 100).
idstringGiven 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."
  }'
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.

Can be refused with no_prompt draft_failed limit_reached try_later ai_off tool_off

Create a survey

POST/v1/surveys

Saves a survey, written by you or as a draft handed back. It is a draft until its status is live. An account holds up to 50 surveys.

Send

titlestringrequired
The survey's name, shown at its top. Up to 140 characters.
introstring
A sentence or two under the title. Up to 600 characters.
questionsarrayrequired
Up to 12 questions. See the shape of a question.
resultsobject
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"
  }'
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"
}

Can be refused with bad_survey too_many_surveys tool_off

List surveys

GET/v1/surveys

Every survey on the account, each in full with its count of starts and finished responses.

curl https://api.realtorflow.ca/v1/surveys \
  -H "Authorization: Bearer $RF_KEY"
Answer · 200
{
  "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.

curl https://api.realtorflow.ca/v1/surveys/ready-to-buy \
  -H "Authorization: Bearer $RF_KEY"
Answer · 200
{
  "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"
}

In the answer

slugstring
The short name used in the tag and in addresses.
statusstring
draft, live or archived.
questionsarray
See the shape of a question. Every question and answer has an id.
resultsobject
The three result messages.
formobject
The wording of the contact step and whether a phone number is asked for.
max_scorenumber
What the strongest answers add up to. It should be 100; when it is not, warnings says so.
bandsobject
Where hot and warm begin: 80 and 50.
statsobject
How many people started, how many finished, and how the finished ones were sorted.
embed_htmlstring
The tag that shows this survey on a page that has the pasted line.

Can be refused with not_found

Change a survey

PATCH/v1/surveys/{id}

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.
questionsarray
Up to 12 questions. See the shape of a question.
resultsobject
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.
curl -X PATCH https://api.realtorflow.ca/v1/surveys/ready-to-buy \
  -H "Authorization: Bearer $RF_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "archived"
  }'
Answer · 200
{
  "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"
}

Can be refused with bad_survey not_found

Delete a survey

DELETE/v1/surveys/{id}

The leads it brought in stay in the inbox.

curl -X DELETE https://api.realtorflow.ca/v1/surveys/ready-to-buy \
  -H "Authorization: Bearer $RF_KEY"
Answer · 200
{
  "id": "0f6f4f0a-3c58-4b0e-9b7e-6f6c2f2a7d55",
  "object": "survey",
  "deleted": true
}

Can be refused with not_found

Send a finished survey

POST/v1/surveys/{id}/responses

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.
curl -X POST https://api.realtorflow.ca/v1/surveys/ready-to-buy/responses \
  -H "Authorization: Bearer $RF_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "answers": {
      "r53jk9om": "y179w03u",
      "wunt43tg": "i5psby4o",
      "k2m8xq4d": "q3f7j2nb"
    },
    "name": "Dana Whitfield",
    "email": "dana@example.com",
    "phone": "902-555-0142",
    "page_url": "https://www.example.com/ready-to-buy"
  }'
Answer · 201
{
  "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.
resultobject
The message for this person: heading and text.

Can be refused with bad_response survey_not_live not_found

Reference

Media

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"
  }'
Answer · 201
{
  "id": "a3d9e6f1-2c4b-4e8a-b7d5-9f1c3e2a6b48",
  "object": "media",
  "filename": "harbour-at-dusk.jpg",
  "width": 2000,
  "height": 1200,
  "bytes": 1843921,
  "alt": "The harbour at dusk, seen from the condo balcony",
  "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"
}

Can be refused with no_photo bad_photo bad_upload file_too_large bad_alt media_full

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.

In the address

limitnumber
Up to 200. The default is 50.
pagenumber
Which page of results, from 1.
curl https://api.realtorflow.ca/v1/media \
  -H "Authorization: Bearer $RF_KEY"
Answer · 200
{
  "object": "list",
  "data": [
    {
      "id": "a3d9e6f1-2c4b-4e8a-b7d5-9f1c3e2a6b48",
      "object": "media",
      "filename": "harbour-at-dusk.jpg",
      "width": 2000,
      "height": 1200,
      "bytes": 1843921,
      "alt": "The harbour at dusk, seen from the condo balcony",
      "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"
    }
  ],
  "page": 1,
  "has_more": false
}

Can be refused with

Get a photo

GET/v1/media/{id}

One photo by its id, with used_in: the listings, pages, articles and other places on the account that show it.

curl https://api.realtorflow.ca/v1/media/a3d9e6f1-2c4b-4e8a-b7d5-9f1c3e2a6b48 \
  -H "Authorization: Bearer $RF_KEY"
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, seen from the condo balcony",
  "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",
  "used_in": [
    {
      "kind": "listing",
      "id": "9cb5d6ac-fba8-4538-898b-c3309569fa5d",
      "name": "Waterfront condo"
    }
  ]
}

Can be refused with not_found

Change a photo's alt text

PATCH/v1/media/{id}

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"
  }'
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"
}

Can be refused with nothing_to_change bad_alt not_found

Delete a photo

DELETE/v1/media/{id}

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.

curl -X DELETE https://api.realtorflow.ca/v1/media/a3d9e6f1-2c4b-4e8a-b7d5-9f1c3e2a6b48 \
  -H "Authorization: Bearer $RF_KEY"
Answer · 200
{
  "id": "a3d9e6f1-2c4b-4e8a-b7d5-9f1c3e2a6b48",
  "object": "media",
  "deleted": true
}

Can be refused with media_in_use not_found

Reference

Listings

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.

In the address

statusstring
Only listings with this status.
listing_typestring
sale or rent.
limitnumber
Up to 100. The default is 20.
pagenumber
Which page of results, from 1.
curl "https://api.realtorflow.ca/v1/listings?status=active&limit=20" \
  -H "Authorization: Bearer $RF_KEY"
Answer · 200
{
  "object": "list",
  "data": [
    {
      "id": "9cb5d6ac-fba8-4538-898b-c3309569fa5d",
      "object": "listing",
      "slug": "brick-duplex-near-the-river",
      "status": "active",
      "title": "Brick duplex near the river",
      "listing_type": "sale",
      "property_type": "Duplex",
      "price": 899000,
      "currency": "CAD",
      "bedrooms": 5,
      "bathrooms": 2,
      "area": 2100,
      "area_unit": "sqft",
      "city": "Halifax",
      "region": "NS",
      "is_featured": false,
      "photos": [
        {
          "url": "https://api.realtorflow.ca/media/3b8f6a2e-7c1d-4e5a-9f20-6d1c8b4a7e13/a41c9e7b-2f6d-4b8e-91c3-5e7a0d2f6b84/1600.webp",
          "width": 1600,
          "height": 1067,
          "alt": "Front of the duplex",
          "sizes": [
            {
              "width": 480,
              "url": "https://api.realtorflow.ca/media/3b8f6a2e-7c1d-4e5a-9f20-6d1c8b4a7e13/a41c9e7b-2f6d-4b8e-91c3-5e7a0d2f6b84/480.webp"
            },
            {
              "width": 960,
              "url": "https://api.realtorflow.ca/media/3b8f6a2e-7c1d-4e5a-9f20-6d1c8b4a7e13/a41c9e7b-2f6d-4b8e-91c3-5e7a0d2f6b84/960.webp"
            },
            {
              "width": 1600,
              "url": "https://api.realtorflow.ca/media/3b8f6a2e-7c1d-4e5a-9f20-6d1c8b4a7e13/a41c9e7b-2f6d-4b8e-91c3-5e7a0d2f6b84/1600.webp"
            }
          ]
        }
      ],
      "created_at": "2026-11-13T14:34:59.409Z",
      "updated_at": "2026-11-13T14:34:59.409Z"
    }
  ],
  "page": 1,
  "has_more": false
}

Can be refused with tool_off

Get a listing

GET/v1/listings/{id}

One listing in full, by its id or its slug, with its search markup. Put json_ld in a <script type="application/ld+json"> on the listing's page.

In the address

page_urlstring
The address of the page the listing is on, so the markup carries it.
curl "https://api.realtorflow.ca/v1/listings/brick-duplex-near-the-river?page_url=https://www.example.com/homes/brick-duplex" \
  -H "Authorization: Bearer $RF_KEY"
Answer · 200
{
  "id": "9cb5d6ac-fba8-4538-898b-c3309569fa5d",
  "object": "listing",
  "slug": "brick-duplex-near-the-river",
  "status": "active",
  "title": "Brick duplex near the river",
  "listing_type": "sale",
  "property_type": "Duplex",
  "price": 899000,
  "currency": "CAD",
  "bedrooms": 5,
  "bathrooms": 2,
  "area": 2100,
  "area_unit": "sqft",
  "city": "Halifax",
  "region": "NS",
  "is_featured": false,
  "photos": [
    {
      "url": "https://api.realtorflow.ca/media/3b8f6a2e-7c1d-4e5a-9f20-6d1c8b4a7e13/a41c9e7b-2f6d-4b8e-91c3-5e7a0d2f6b84/1600.webp",
      "width": 1600,
      "height": 1067,
      "alt": "Front of the duplex",
      "sizes": [
        {
          "width": 480,
          "url": "https://api.realtorflow.ca/media/3b8f6a2e-7c1d-4e5a-9f20-6d1c8b4a7e13/a41c9e7b-2f6d-4b8e-91c3-5e7a0d2f6b84/480.webp"
        },
        {
          "width": 960,
          "url": "https://api.realtorflow.ca/media/3b8f6a2e-7c1d-4e5a-9f20-6d1c8b4a7e13/a41c9e7b-2f6d-4b8e-91c3-5e7a0d2f6b84/960.webp"
        },
        {
          "width": 1600,
          "url": "https://api.realtorflow.ca/media/3b8f6a2e-7c1d-4e5a-9f20-6d1c8b4a7e13/a41c9e7b-2f6d-4b8e-91c3-5e7a0d2f6b84/1600.webp"
        }
      ]
    }
  ],
  "created_at": "2026-11-13T14:34:59.409Z",
  "updated_at": "2026-11-13T14:34:59.409Z",
  "description": "Two full units on a quiet street, each with its own entrance.\n\nA detached garage and a private terrace.",
  "lot_size": null,
  "year_built": 1928,
  "address": "1234 Example Street",
  "country": "CA",
  "mls_number": null,
  "features": [
    "Garage",
    "Terrace"
  ],
  "meta_title": null,
  "meta_description": null,
  "json_ld": {
    "@context": "https://schema.org",
    "@type": "RealEstateListing",
    "name": "Brick duplex near the river",
    "url": "https://www.example.com/homes/brick-duplex",
    "mainEntityOfPage": "https://www.example.com/homes/brick-duplex",
    "datePosted": "2026-11-13T14:34:59.409Z",
    "dateModified": "2026-11-13T14:34:59.409Z",
    "mainEntity": {
      "@type": "Accommodation",
      "name": "Brick duplex near the river",
      "address": {
        "@type": "PostalAddress",
        "streetAddress": "1234 Example Street",
        "addressLocality": "Halifax",
        "addressRegion": "NS",
        "addressCountry": "CA"
      },
      "numberOfBedrooms": 5,
      "numberOfBathroomsTotal": 2,
      "floorSize": {
        "@type": "QuantitativeValue",
        "value": 2100,
        "unitCode": "FTK"
      },
      "yearBuilt": 1928,
      "...": "..."
    },
    "provider": {
      "@type": "RealEstateAgent",
      "name": "Harbour Realty"
    },
    "image": [
      "https://api.realtorflow.ca/media/3b8f6a2e-7c1d-4e5a-9f20-6d1c8b4a7e13/a41c9e7b-2f6d-4b8e-91c3-5e7a0d2f6b84/1600.webp"
    ],
    "offers": {
      "@type": "Offer",
      "price": 899000,
      "priceCurrency": "CAD"
    }
  }
}

In the answer

statusstring
active (shown), pending, sold, draft or archived (never shown).
listing_typestring
sale or rent. For a rental, price is per month.
property_typestring
House, Condo, Townhouse, Duplex, Triplex, Multiplex, Cottage, Land, Commercial or Other.
pricenumber
In currency. null means price on request.
areanumber
Living area in area_unit: sqft or m2, as the account is set.
photosarray
Each photo stored at several widths: url is the largest, sizes lists them all for a srcset. The addresses work on any website.
json_ldobject
The listing's search markup (RealEstateListing) for the page it is on. Ask with page_url to have that address in it.

Can be refused with not_found tool_off

Add a listing

POST/v1/listings

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"
      }
    ]
  }'
Answer · 201
{
  "id": "9cb5d6ac-fba8-4538-898b-c3309569fa5d",
  "object": "listing",
  "slug": "brick-duplex-near-the-river",
  "status": "active",
  "title": "Brick duplex near the river",
  "listing_type": "sale",
  "property_type": "Duplex",
  "price": 899000,
  "currency": "CAD",
  "bedrooms": 5,
  "bathrooms": 2,
  "area": 2100,
  "area_unit": "sqft",
  "city": "Halifax",
  "region": "NS",
  "is_featured": false,
  "photos": [
    {
      "url": "https://api.realtorflow.ca/media/3b8f6a2e-7c1d-4e5a-9f20-6d1c8b4a7e13/a41c9e7b-2f6d-4b8e-91c3-5e7a0d2f6b84/1600.webp",
      "width": 1600,
      "height": 1067,
      "alt": "Front of the duplex",
      "sizes": [
        {
          "width": 480,
          "url": "https://api.realtorflow.ca/media/3b8f6a2e-7c1d-4e5a-9f20-6d1c8b4a7e13/a41c9e7b-2f6d-4b8e-91c3-5e7a0d2f6b84/480.webp"
        },
        {
          "width": 960,
          "url": "https://api.realtorflow.ca/media/3b8f6a2e-7c1d-4e5a-9f20-6d1c8b4a7e13/a41c9e7b-2f6d-4b8e-91c3-5e7a0d2f6b84/960.webp"
        },
        {
          "width": 1600,
          "url": "https://api.realtorflow.ca/media/3b8f6a2e-7c1d-4e5a-9f20-6d1c8b4a7e13/a41c9e7b-2f6d-4b8e-91c3-5e7a0d2f6b84/1600.webp"
        }
      ]
    }
  ],
  "created_at": "2026-11-13T14:34:59.409Z",
  "updated_at": "2026-11-13T14:34:59.409Z",
  "description": "Two full units on a quiet street, each with its own entrance.\n\nA detached garage and a private terrace.",
  "lot_size": null,
  "year_built": 1928,
  "address": "1234 Example Street",
  "country": "CA",
  "mls_number": null,
  "features": [
    "Garage",
    "Terrace"
  ],
  "meta_title": null,
  "meta_description": null,
  "json_ld": {
    "@context": "https://schema.org",
    "@type": "RealEstateListing",
    "name": "Brick duplex near the river",
    "datePosted": "2026-11-13T14:34:59.409Z",
    "dateModified": "2026-11-13T14:34:59.409Z",
    "mainEntity": {
      "@type": "Accommodation",
      "name": "Brick duplex near the river",
      "address": {
        "@type": "PostalAddress",
        "streetAddress": "1234 Example Street",
        "addressLocality": "Halifax",
        "addressRegion": "NS",
        "addressCountry": "CA"
      },
      "numberOfBedrooms": 5,
      "numberOfBathroomsTotal": 2,
      "floorSize": {
        "@type": "QuantitativeValue",
        "value": 2100,
        "unitCode": "FTK"
      },
      "yearBuilt": 1928,
      "...": "..."
    },
    "provider": {
      "@type": "RealEstateAgent",
      "name": "Harbour Realty"
    },
    "image": [
      "https://api.realtorflow.ca/media/3b8f6a2e-7c1d-4e5a-9f20-6d1c8b4a7e13/a41c9e7b-2f6d-4b8e-91c3-5e7a0d2f6b84/1600.webp"
    ],
    "offers": {
      "@type": "Offer",
      "price": 899000,
      "priceCurrency": "CAD"
    }
  }
}

Can be refused with bad_listing bad_photo too_many_photos too_many_listings tool_off

Change a listing

PATCH/v1/listings/{id}

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.
mls_numberstring
Shown only when sent.
curl -X PATCH https://api.realtorflow.ca/v1/listings/brick-duplex-near-the-river \
  -H "Authorization: Bearer $RF_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "price": 879000,
    "status": "pending"
  }'
Answer · 200
{
  "id": "9cb5d6ac-fba8-4538-898b-c3309569fa5d",
  "object": "listing",
  "slug": "brick-duplex-near-the-river",
  "status": "pending",
  "title": "Brick duplex near the river",
  "listing_type": "sale",
  "property_type": "Duplex",
  "price": 879000,
  "currency": "CAD",
  "bedrooms": 5,
  "bathrooms": 2,
  "area": 2100,
  "area_unit": "sqft",
  "city": "Halifax",
  "region": "NS",
  "is_featured": false,
  "photos": [
    {
      "url": "https://api.realtorflow.ca/media/3b8f6a2e-7c1d-4e5a-9f20-6d1c8b4a7e13/a41c9e7b-2f6d-4b8e-91c3-5e7a0d2f6b84/1600.webp",
      "width": 1600,
      "height": 1067,
      "alt": "Front of the duplex",
      "sizes": [
        {
          "width": 480,
          "url": "https://api.realtorflow.ca/media/3b8f6a2e-7c1d-4e5a-9f20-6d1c8b4a7e13/a41c9e7b-2f6d-4b8e-91c3-5e7a0d2f6b84/480.webp"
        },
        {
          "width": 960,
          "url": "https://api.realtorflow.ca/media/3b8f6a2e-7c1d-4e5a-9f20-6d1c8b4a7e13/a41c9e7b-2f6d-4b8e-91c3-5e7a0d2f6b84/960.webp"
        },
        {
          "width": 1600,
          "url": "https://api.realtorflow.ca/media/3b8f6a2e-7c1d-4e5a-9f20-6d1c8b4a7e13/a41c9e7b-2f6d-4b8e-91c3-5e7a0d2f6b84/1600.webp"
        }
      ]
    }
  ],
  "created_at": "2026-11-13T14:34:59.409Z",
  "updated_at": "2026-11-13T14:34:59.409Z",
  "description": "Two full units on a quiet street, each with its own entrance.\n\nA detached garage and a private terrace.",
  "lot_size": null,
  "year_built": 1928,
  "address": "1234 Example Street",
  "country": "CA",
  "mls_number": null,
  "features": [
    "Garage",
    "Terrace"
  ],
  "meta_title": null,
  "meta_description": null,
  "json_ld": "..."
}

Can be refused with not_found bad_listing bad_photo too_many_photos tool_off

Delete a listing

DELETE/v1/listings/{id}

The listing and its photos are removed. Leads that asked about it stay in the inbox.

curl -X DELETE https://api.realtorflow.ca/v1/listings/brick-duplex-near-the-river \
  -H "Authorization: Bearer $RF_KEY"
Answer · 200
{
  "id": "9cb5d6ac-fba8-4538-898b-c3309569fa5d",
  "object": "listing",
  "deleted": true
}

Can be refused with not_found tool_off

Reference

Articles and translation

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"
  }'
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.

Can be refused with draft_too_short draft_too_long limit_reached try_later ai_off draft_failed tool_off

Translate

POST/v1/translate

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"
    ]
  }'
Answer · 200
{
  "object": "translation",
  "from": "en",
  "to": "fr",
  "items": [
    {
      "text": "Three bedrooms near the harbour, listed at $649,000.",
      "translation": "Trois chambres près du port, à 649 000 $.",
      "problem": null
    },
    {
      "text": "Book a visit",
      "translation": "Planifier une visite",
      "problem": null
    }
  ],
  "translated": 2,
  "failed": 0
}

Can be refused with no_text text_too_long bad_language limit_reached try_later ai_off translation_failed tool_off

Reference

AI visibility and traffic

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."
      }
    ]
  }'
Answer · 200
{
  "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.

Can be refused with bad_origin bad_listing_url bad_pages tool_off

AI traffic

GET/v1/analytics

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.

In the address

daysnumber
7, 30 (the default) or 90.
curl "https://api.realtorflow.ca/v1/analytics?days=30" \
  -H "Authorization: Bearer $RF_KEY"
Answer · 200
{
  "object": "analytics",
  "days": 30,
  "tz": "America/Toronto",
  "keep_days": 180,
  "totals": {
    "views": 1840,
    "visits": 1210,
    "visitors": 960,
    "ai": 64
  },
  "by_day": [
    {
      "day": "2026-11-13",
      "views": 71,
      "visits": 48,
      "ai": 3,
      "visitors": 40
    },
    {
      "...": "..."
    }
  ],
  "sources": {
    "ai": 64,
    "search": 610,
    "social": 140,
    "direct": 330,
    "other": 66
  },
  "pages": [
    {
      "path": "/homes/waterfront-condo",
      "views": 220,
      "ai": 11
    }
  ],
  "ai_referrers": [
    {
      "name": "ChatGPT",
      "n": 41
    },
    {
      "name": "Perplexity",
      "n": 15
    },
    {
      "name": "Gemini",
      "n": 8
    }
  ],
  "ai_pages": [
    {
      "path": "/neighbourhoods/verdun",
      "n": 9
    }
  ],
  "referrers": [],
  "campaigns": [],
  "crawlers": [],
  "crawled": [],
  "leads": {
    "on": true,
    "total": 22,
    "by": [
      {
        "utm_source": "chatgpt.com",
        "n": 2,
        "ai": true
      }
    ]
  }
}

In the answer

totals.ainumber
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.

Can be refused with tool_off

Report AI crawler visits

POST/v1/crawls

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.
curl -X POST https://api.realtorflow.ca/v1/crawls \
  -H "Authorization: Bearer $RF_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "hits": [
      {
        "user_agent": "Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; GPTBot/1.2; +https://openai.com/gptbot)",
        "path": "/homes/waterfront-condo",
        "at": "2026-11-13T18:02:11Z"
      },
      {
        "user_agent": "Mozilla/5.0 (compatible; ClaudeBot/1.0; +claudebot@anthropic.com)",
        "path": "/llms.txt",
        "at": "2026-11-13T18:04:40Z"
      }
    ]
  }'
Answer · 200
{
  "object": "crawls",
  "counted": 2,
  "dropped": 0,
  "crawlers": {
    "GPTBot": 1,
    "ClaudeBot": 1
  }
}

Can be refused with bad_crawls tool_off

Reference

Bookings

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.
curl -X PUT https://api.realtorflow.ca/v1/listings/beach-cottage/booking \
  -H "Authorization: Bearer $RF_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "nightly_rate": 180,
    "min_nights": 2,
    "max_guests": 4
  }'
Answer · 200
{
  "object": "booking_terms",
  "listing": "beach-cottage",
  "nightly_rate": 180,
  "currency": "CAD",
  "min_nights": 2,
  "max_guests": 4,
  "taking_bookings": true
}

Can be refused with bad_terms not_found tool_off

Check availability

GET/v1/listings/{id}/availability

The terms and the nights already held by confirmed or paid bookings, for a calendar on your own page. Only dates: never who booked.

curl https://api.realtorflow.ca/v1/listings/beach-cottage/availability \
  -H "Authorization: Bearer $RF_KEY"
Answer · 200
{
  "object": "availability",
  "listing": "beach-cottage",
  "taking_bookings": true,
  "nightly_rate": 180,
  "currency": "CAD",
  "min_nights": 2,
  "max_guests": 4,
  "max_nights": 90,
  "booked": [
    {
      "check_in": "2026-12-26",
      "check_out": "2027-01-02"
    }
  ]
}

Can be refused with not_found tool_off

Send a booking request

POST/v1/bookings

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."
  }'
Answer · 201
{
  "id": "4e2a9c71-8b3d-4f6e-a0c5-7d19e2b6f348",
  "object": "booking",
  "status": "enquiry",
  "listing": {
    "id": "9cb5d6ac-fba8-4538-898b-c3309569fa5d",
    "slug": "beach-cottage",
    "title": "Beach cottage"
  },
  "check_in": "2026-12-18",
  "check_out": "2026-12-21",
  "nights": 3,
  "guests": 2,
  "name": "Dana Whitfield",
  "email": "dana@example.com",
  "phone": "902-555-0142",
  "message": "Arriving late on the first night.",
  "nightly_rate": 180,
  "currency": "CAD",
  "estimate": 540,
  "notes": null,
  "overlaps_confirmed": false,
  "created_at": "2026-11-13T17:20:05.000Z",
  "updated_at": "2026-11-13T17:20:05.000Z"
}

In the answer

statusstring
enquiry, confirmed, paid, completed or cancelled.
estimatenumber
Nights times the nightly rate: an estimate, not a price.
overlaps_confirmedboolean
true when an enquiry asks for nights another booking already holds.

Can be refused with bad_booking not_bookable dates_taken tool_off

List bookings

GET/v1/bookings

The account's bookings, newest first.

In the address

statusstring
Only bookings with this status.
limitnumber
Up to 200. The default is 50.
pagenumber
Which page of results, from 1.
curl "https://api.realtorflow.ca/v1/bookings?status=enquiry" \
  -H "Authorization: Bearer $RF_KEY"
Answer · 200
{
  "object": "list",
  "data": [
    {
      "id": "4e2a9c71-8b3d-4f6e-a0c5-7d19e2b6f348",
      "object": "booking",
      "status": "enquiry",
      "listing": {
        "id": "9cb5d6ac-fba8-4538-898b-c3309569fa5d",
        "slug": "beach-cottage",
        "title": "Beach cottage"
      },
      "check_in": "2026-12-18",
      "check_out": "2026-12-21",
      "nights": 3,
      "guests": 2,
      "name": "Dana Whitfield",
      "email": "dana@example.com",
      "phone": "902-555-0142",
      "message": "Arriving late on the first night.",
      "nightly_rate": 180,
      "currency": "CAD",
      "estimate": 540,
      "notes": null,
      "overlaps_confirmed": false,
      "created_at": "2026-11-13T17:20:05.000Z",
      "updated_at": "2026-11-13T17:20:05.000Z"
    }
  ],
  "page": 1,
  "has_more": false
}

Can be refused with bad_status tool_off

Get a booking

GET/v1/bookings/{id}

One booking by its id.

curl https://api.realtorflow.ca/v1/bookings/4e2a9c71-8b3d-4f6e-a0c5-7d19e2b6f348 \
  -H "Authorization: Bearer $RF_KEY"
Answer · 200
{
  "id": "4e2a9c71-8b3d-4f6e-a0c5-7d19e2b6f348",
  "object": "booking",
  "status": "enquiry",
  "listing": {
    "id": "9cb5d6ac-fba8-4538-898b-c3309569fa5d",
    "slug": "beach-cottage",
    "title": "Beach cottage"
  },
  "check_in": "2026-12-18",
  "check_out": "2026-12-21",
  "nights": 3,
  "guests": 2,
  "name": "Dana Whitfield",
  "email": "dana@example.com",
  "phone": "902-555-0142",
  "message": "Arriving late on the first night.",
  "nightly_rate": 180,
  "currency": "CAD",
  "estimate": 540,
  "notes": null,
  "overlaps_confirmed": false,
  "created_at": "2026-11-13T17:20:05.000Z",
  "updated_at": "2026-11-13T17:20:05.000Z"
}

Can be refused with not_found tool_off

Change a booking's status

PATCH/v1/bookings/{id}

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.

Send

statusstring
The new status.
notesstring
Up to 5,000 characters.
curl -X PATCH https://api.realtorflow.ca/v1/bookings/4e2a9c71-8b3d-4f6e-a0c5-7d19e2b6f348 \
  -H "Authorization: Bearer $RF_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "confirmed",
    "notes": "Deposit by e-transfer."
  }'
Answer · 200
{
  "id": "4e2a9c71-8b3d-4f6e-a0c5-7d19e2b6f348",
  "object": "booking",
  "status": "confirmed",
  "listing": {
    "id": "9cb5d6ac-fba8-4538-898b-c3309569fa5d",
    "slug": "beach-cottage",
    "title": "Beach cottage"
  },
  "check_in": "2026-12-18",
  "check_out": "2026-12-21",
  "nights": 3,
  "guests": 2,
  "name": "Dana Whitfield",
  "email": "dana@example.com",
  "phone": "902-555-0142",
  "message": "Arriving late on the first night.",
  "nightly_rate": 180,
  "currency": "CAD",
  "estimate": 540,
  "notes": "Deposit by e-transfer.",
  "overlaps_confirmed": false,
  "created_at": "2026-11-13T17:20:05.000Z",
  "updated_at": "2026-11-13T17:20:05.000Z"
}

Can be refused with bad_status dates_taken not_found tool_off

Reference

Newsletter

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"
  }'
Answer · 201
{
  "object": "subscriber",
  "email": "dana@example.com",
  "name": "Dana Whitfield",
  "status": "subscribed",
  "consent_text": "Yes, send me the monthly market note from Harbour Realty.",
  "consent_at": "2026-11-13T16:02:11.000Z",
  "page_url": "https://www.example.com/newsletter",
  "created_at": "2026-11-13T16:02:11.000Z",
  "updated_at": "2026-11-13T16:02:11.000Z"
}

In the answer

statusstring
subscribed, or blocked for an address that bounced or complained.
alreadyboolean
true when the address was already on the list (the answer is then 200).

Can be refused with bad_subscriber no_consent tool_off

List sign-ups

GET/v1/subscribers

The mailing list, newest first, with each person's consent and when it was given.

In the address

statusstring
subscribed, unsubscribed or bounced.
limitnumber
Up to 200. The default is 50.
pagenumber
Which page of results, from 1.
curl "https://api.realtorflow.ca/v1/subscribers?status=subscribed&limit=50" \
  -H "Authorization: Bearer $RF_KEY"
Answer · 200
{
  "object": "list",
  "data": [
    {
      "object": "subscriber",
      "email": "dana@example.com",
      "name": "Dana Whitfield",
      "status": "subscribed",
      "consent_text": "Yes, send me the monthly market note from Harbour Realty.",
      "consent_at": "2026-11-13T16:02:11.000Z",
      "page_url": "https://www.example.com/newsletter",
      "created_at": "2026-11-13T16:02:11.000Z",
      "updated_at": "2026-11-13T16:02:11.000Z"
    }
  ],
  "page": 1,
  "has_more": false
}

Can be refused with tool_off

Unsubscribe someone

POST/v1/subscribers/unsubscribe

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.

Send

emailstringrequired
The address to take off.
curl -X POST https://api.realtorflow.ca/v1/subscribers/unsubscribe \
  -H "Authorization: Bearer $RF_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "dana@example.com"
  }'
Answer · 200
{
  "object": "subscriber",
  "email": "dana@example.com",
  "status": "unsubscribed"
}

Can be refused with bad_subscriber tool_off

Reference

Leads

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"
  }'
Answer · 201
{
  "id": "7c1d7f52-2a3e-4d8a-8a55-0d7c3f1e9b20",
  "object": "lead",
  "name": "Dana Whitfield",
  "email": "dana@example.com",
  "created_at": "2026-11-12T15:09:41.000Z"
}

Can be refused with bad_lead not_found tool_off

List leads

GET/v1/leads

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.
emailstring
Only leads from this address (any case).
listingstring
Only leads about this listing (its id or slug).
surveystring
Only leads from this survey (its id or slug).
limitnumber
Up to 200. The default is 50.
pagenumber
Which page of results, from 1.
curl "https://api.realtorflow.ca/v1/leads?status=new&kind=quiz,listing" \
  -H "Authorization: Bearer $RF_KEY"
Answer · 200
{
  "object": "list",
  "data": [
    {
      "id": "7c1d7f52-2a3e-4d8a-8a55-0d7c3f1e9b20",
      "object": "lead",
      "kind": "listing",
      "status": "new",
      "name": "Dana Whitfield",
      "email": "dana@example.com",
      "phone": "902-555-0142",
      "message": "Is the house on Tower Road still available?",
      "listing": {
        "id": "9cb5d6ac-fba8-4538-898b-c3309569fa5d",
        "slug": "tower-road-family-home",
        "title": "Tower Road family home"
      },
      "survey": null,
      "brief": null,
      "survey_email": null,
      "follow_ups": [
        {
          "id": "b71f0c2e-5d4a-4c38-9e61-2a8f7d3c1b90",
          "sequence": {
            "id": "e3c9a1d4-7b2f-4e8a-b5c6-1d0f9a8e7c21",
            "name": "New enquiries"
          },
          "status": "active",
          "stop_reason": null,
          "emails_sent": 1,
          "next_email_at": "2026-11-15T15:09:41.000Z",
          "started_at": "2026-11-12T15:09:41.000Z"
        }
      ],
      "page_url": "https://www.example.com/contact",
      "utm": {
        "utm_source": "newsletter"
      },
      "language": null,
      "notes": null,
      "alert_sent": true,
      "created_at": "2026-11-12T15:09:41.000Z",
      "updated_at": "2026-11-12T15:09:41.000Z"
    },
    {
      "id": "2f8e4b61-9c3a-4d7e-8b15-6a0c9e2d4f73",
      "object": "lead",
      "kind": "quiz",
      "status": "new",
      "name": "Hattie Moreau",
      "email": "hattie@example.com",
      "phone": "902-555-0177",
      "message": null,
      "listing": null,
      "survey": {
        "id": "5a7c2e91-3b8d-4f60-a1e4-9d2b6c8f0e35",
        "slug": "ready-to-buy",
        "title": "Ready to buy?",
        "score": 90,
        "temperature": "hot",
        "answers": [
          {
            "q": "When would you like to move?",
            "a": [
              "Within three months"
            ]
          },
          {
            "q": "Have you spoken to a lender?",
            "a": [
              "Yes, I am pre-approved"
            ]
          }
        ]
      },
      "brief": {
        "summary": "Pre-approved and wants to move within three months.",
        "next_step": "Call this week and offer two viewings."
      },
      "survey_email": {
        "subject": "Your result, and a next step",
        "status": "sent",
        "sent_at": "2026-11-11T10:02:13.000Z",
        "note": null
      },
      "follow_ups": [],
      "page_url": "https://www.example.com/quiz",
      "utm": null,
      "language": "en",
      "notes": null,
      "alert_sent": true,
      "created_at": "2026-11-11T10:02:05.000Z",
      "updated_at": "2026-11-11T10:02:13.000Z"
    }
  ],
  "page": 1,
  "has_more": false
}

Can be refused with bad_status bad_kind bad_temperature bad_date not_found tool_off

Get a lead

GET/v1/leads/{id}

One lead by its id, with everything the list gives.

curl https://api.realtorflow.ca/v1/leads/7c1d7f52-2a3e-4d8a-8a55-0d7c3f1e9b20 \
  -H "Authorization: Bearer $RF_KEY"
Answer · 200
{
  "id": "7c1d7f52-2a3e-4d8a-8a55-0d7c3f1e9b20",
  "object": "lead",
  "kind": "listing",
  "status": "new",
  "name": "Dana Whitfield",
  "email": "dana@example.com",
  "phone": "902-555-0142",
  "message": "Is the house on Tower Road still available?",
  "listing": {
    "id": "9cb5d6ac-fba8-4538-898b-c3309569fa5d",
    "slug": "tower-road-family-home",
    "title": "Tower Road family home"
  },
  "survey": null,
  "brief": null,
  "survey_email": null,
  "follow_ups": [
    {
      "id": "b71f0c2e-5d4a-4c38-9e61-2a8f7d3c1b90",
      "sequence": {
        "id": "e3c9a1d4-7b2f-4e8a-b5c6-1d0f9a8e7c21",
        "name": "New enquiries"
      },
      "status": "active",
      "stop_reason": null,
      "emails_sent": 1,
      "next_email_at": "2026-11-15T15:09:41.000Z",
      "started_at": "2026-11-12T15:09:41.000Z"
    }
  ],
  "page_url": "https://www.example.com/contact",
  "utm": {
    "utm_source": "newsletter"
  },
  "language": null,
  "notes": null,
  "alert_sent": true,
  "created_at": "2026-11-12T15:09:41.000Z",
  "updated_at": "2026-11-12T15:09:41.000Z"
}

Can be refused with not_found tool_off

Change a lead's status or notes

PATCH/v1/leads/{id}

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.

Send

statusstring
The new status.
notesstring
Up to 4,000 characters. null clears them.
curl -X PATCH https://api.realtorflow.ca/v1/leads/7c1d7f52-2a3e-4d8a-8a55-0d7c3f1e9b20 \
  -H "Authorization: Bearer $RF_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "contacted",
    "notes": "Called Tuesday. Viewing on Saturday at 10."
  }'
Answer · 200
{
  "id": "7c1d7f52-2a3e-4d8a-8a55-0d7c3f1e9b20",
  "object": "lead",
  "kind": "listing",
  "status": "contacted",
  "name": "Dana Whitfield",
  "email": "dana@example.com",
  "phone": "902-555-0142",
  "message": "Is the house on Tower Road still available?",
  "listing": {
    "id": "9cb5d6ac-fba8-4538-898b-c3309569fa5d",
    "slug": "tower-road-family-home",
    "title": "Tower Road family home"
  },
  "survey": null,
  "brief": null,
  "survey_email": null,
  "follow_ups": [
    {
      "id": "b71f0c2e-5d4a-4c38-9e61-2a8f7d3c1b90",
      "sequence": {
        "id": "e3c9a1d4-7b2f-4e8a-b5c6-1d0f9a8e7c21",
        "name": "New enquiries"
      },
      "status": "active",
      "stop_reason": null,
      "emails_sent": 1,
      "next_email_at": "2026-11-15T15:09:41.000Z",
      "started_at": "2026-11-12T15:09:41.000Z"
    }
  ],
  "page_url": "https://www.example.com/contact",
  "utm": {
    "utm_source": "newsletter"
  },
  "language": null,
  "notes": "Called Tuesday. Viewing on Saturday at 10.",
  "alert_sent": true,
  "created_at": "2026-11-12T15:09:41.000Z",
  "updated_at": "2026-11-12T15:09:41.000Z"
}

Can be refused with nothing_to_change bad_status bad_notes notes_too_long not_found tool_off

Stop a lead's follow-ups

POST/v1/leads/{id}/follow-ups/stop

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"
Answer · 200
{
  "id": "7c1d7f52-2a3e-4d8a-8a55-0d7c3f1e9b20",
  "object": "lead",
  "kind": "listing",
  "status": "new",
  "name": "Dana Whitfield",
  "email": "dana@example.com",
  "phone": "902-555-0142",
  "message": "Is the house on Tower Road still available?",
  "listing": {
    "id": "9cb5d6ac-fba8-4538-898b-c3309569fa5d",
    "slug": "tower-road-family-home",
    "title": "Tower Road family home"
  },
  "survey": null,
  "brief": null,
  "survey_email": null,
  "follow_ups": [
    {
      "id": "b71f0c2e-5d4a-4c38-9e61-2a8f7d3c1b90",
      "sequence": {
        "id": "e3c9a1d4-7b2f-4e8a-b5c6-1d0f9a8e7c21",
        "name": "New enquiries"
      },
      "status": "stopped",
      "stop_reason": "stopped_by_api",
      "emails_sent": 1,
      "next_email_at": null,
      "started_at": "2026-11-12T15:09:41.000Z"
    }
  ],
  "page_url": "https://www.example.com/contact",
  "utm": {
    "utm_source": "newsletter"
  },
  "language": null,
  "notes": null,
  "alert_sent": true,
  "created_at": "2026-11-12T15:09:41.000Z",
  "updated_at": "2026-11-12T15:09:41.000Z",
  "stopped": 1
}

Can be refused with not_found tool_off

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.

CodeStatusWhat it means
no_key401No key was sent. Send it as Authorization: Bearer <key>.
bad_key401The key is not one of ours. Copy it again from the Developers page.
key_revoked401The key has been revoked. Make a new one on the Developers page.
paused403The account is paused. Write to Realtor Flow.
not_switched_on403The tool kit is not switched on for this account. Write to Realtor Flow.
wrong_key403The 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_only403A website key was used without a browser. It only works from a page.
domain_not_listed403The page is on a website that is not listed on the website key. Add it on the Developers page.
tool_off403The tool this call belongs to (Videos, Surveys, Leads, Listings, Bookings and so on) is not switched on for the account.
not_found404There is nothing with that id, or no such call.
bad_json400The body is not valid JSON. Send JSON with Content-Type: application/json.
too_large413The body is over 2 MB.
slow_down429Too many calls in a minute. Wait and try again; the RateLimit-Reset header says how many seconds.
limit_reached429The month's allowance for this kind of work is used up. It starts again on the first of the month (UTC).
try_later503The account has reached the day's ceiling for AI work. It starts again the next day.
ai_off503AI writing is not available right now. Try again later.
server_error500Something went wrong on our side. Try again; if it keeps happening, write to Realtor Flow.
bad_youtube_url400youtube_url is missing or is not the address of a YouTube video.
no_transcript400No transcript was sent for a video that has none yet.
transcript_too_short422The transcript holds under 400 characters of speech, too little to write from.
transcript_too_long413The transcript is far beyond two hours of speech.
bad_page_url400page_url is not a full address starting with https://.
not_yours_to_delete409The video was added in the dashboard, not through the tool kit. Remove it there.
bad_survey400The survey sent is not complete. The sentence says which part: a missing title, a question with no wording, fewer than two answers.
too_many_surveys409The account already holds 50 surveys. Delete or reuse one.
no_prompt400Nothing to draft from. Send a prompt, or messages that end with one from the user.
draft_failed502The draft did not come back complete. Nothing was counted. Try again.
survey_not_live409Real answers were sent to a survey that is not live. Set its status to live.
bad_response400The answers do not belong to this survey, or the name, email or required phone number is missing.
bad_lead400A lead needs a name and a valid email.
draft_too_short400The draft is under 200 characters. The article only uses what is in the draft, so it needs something to work with.
draft_too_long413The draft is over 20,000 characters.
no_text400Nothing to translate. Send text, or texts (up to 200).
text_too_long413Over 20,000 characters in one call. Split it into several calls.
bad_language400to (or from) is not one of en, fr, es, de or zh, or both are the same.
translation_failed502The translation did not come back. Nothing was counted. Try again.
bad_crawls400hits is empty or holds more than 200 visits.
bad_origin400origin is missing or is not a web address starting with https://.
bad_listing_url400listing_url is not a full address with {slug} where each listing's short name goes.
bad_pages400pages is not a list of { "title", "url", "description" }.
bad_terms400The booking terms are not right: a nightly rate above zero, and whole numbers for the shortest stay and the most guests.
bad_booking400The 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_bookable404The listing is not taking booking requests: it has no booking terms, or it is not active.
dates_taken409Some of those nights are already held by a confirmed or paid booking.
bad_status400status is not one this call takes. Bookings: enquiry, confirmed, paid, completed, cancelled. Leads: new, contacted, qualified, closed, spam.
bad_kind400kind is one or more of contact, listing and quiz, separated by commas.
bad_temperature400temperature is one or more of hot, warm and nurture, separated by commas.
bad_date400since or before is not a date, such as 2026-11-01 or 2026-11-01T09:00:00Z.
nothing_to_change400The body has nothing to change: for a lead, neither status nor notes; for a photo, no alt.
no_photo400No photo was sent: a form upload with the field file, or JSON with url.
bad_upload400The form upload is not one photo in the field file.
file_too_large413The photo is over 20 MB.
bad_alt400alt is not text, or is over 300 characters.
media_full409The account's media library holds 5,000 photos, the most it can. Delete some first.
media_in_use409The 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_notes400notes is not text (send null to clear it).
notes_too_long413notes is over 4,000 characters.
bad_subscriber400A sign-up needs a valid email.
bad_listing400The listing sent is not right. The sentence says which part: a missing title, an unknown property type or status.
bad_photo400A 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_photos400A listing can have up to 30 photos.
too_many_listings409The 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:

More

Questions

Do I have to move my website to Realtor Flow?

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.