Asynchronous operations

Some operations, such as exporting a campaign, can take a while and so the results will not be available immediately. In this case the API method usually offer two methods to get the results - receiving a callback or polling for a deferred result.

Callback URLs are normally preferred, as they avoid the need for polling. However, for some serverless workflows or data import scripts, the deferred results approach avoids having to run a server to receive the result.

Receiving callbacks

If the API method supports callbacks then it will be indicated in the description. In this case you supply a callback_url parameter when making the API call. When the result are ready, this URL will receive a POST request with the result.

Polling for deferred results

Some API methods also support deferred results. This will be indicated in the description.

In this case, you will receive a deferred_result_id in response to your request. You can then poll the /api/deferred_results/${deferred_result_id} endpoint until the result is available.

The deferred result endpoint will return a JSON payload containing a status key which can have the following values: running, ready, failed. If the result is ready then it will be contained in the result key.

Note that a deferred result will only be available to the original API user which requested it.

Examples

Exporting an audience, waiting for the result, then reading the results

The following is a Ruby script demonstrating how to export an audience, download the result, and print the CSV.

This is a useful example for integrations, as you can build complex search filters within Movement, such as:

  1. Recent joiners
  2. Recently subscribed/unsubscribed
  3. Voting in a ballot/campaign

And then export on demand an updated list of people matching the criteria.

From here you can use the API to perform other queries on each member if you need.

#!/usr/bin/env ruby

require 'net/http'
require 'json'
require 'uri'
require 'zip'
require 'tempfile'

BASE_URL = 'https://{yoursubdomain}.yourmovement.org'
SEARCH_ID = 1
HEADERS = {'Authorization' => 'Bearer test', 'accept' => 'application/json', 'content-type' => 'application/json'}

# 1. Export audience
uri = URI("#{BASE_URL}/api/searches/#{SEARCH_ID}/export")
response = Net::HTTP.post(uri, '', HEADERS)
raise "Export failed: #{response.code}" unless response.code == '200'

data = JSON.parse(response.body)
password = data['password']
deferred_id = data['deferred_result_id']

# 2. Poll for results
result = nil
60.times do
  uri = URI("#{BASE_URL}/api/deferred_results/#{deferred_id}")
  req = Net::HTTP::Get.new(uri, {'Authorization' => 'Bearer test', 'accept' => 'application/json'})
  response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
  data = JSON.parse(response.body)

  case data['status']
  when 'ready' then result = data['result']; break
  when 'failed' then raise "Export failed"
  when 'running' then sleep 5
  end
end
raise "Timeout" unless result

# 3. Download ZIP
zip_response = Net::HTTP.get_response(URI(result))
temp_file = Tempfile.new(['export', '.zip'])
temp_file.binmode.write(zip_response.body)
temp_file.close

# 4. Extract CSV with password
require 'fileutils'
extract_dir = Dir.mktmpdir
system("unzip -P '#{password}' '#{temp_file.path}' -d '#{extract_dir}'", out: File::NULL, err: File::NULL)
csv_file = Dir.glob("#{extract_dir}/*.csv").first
csv_content = File.read(csv_file)
FileUtils.rm_rf(extract_dir)

# 5. Print CSV
puts csv_content

temp_file.unlink