Exports API
Exports generate a CSV of a large historical range in the background, so you don’t have to page through thousands of rows yourself. The flow is always three steps: create the export, poll it until it is finished, then download the file from the URL the export gives you.
The only export type available today is rtb_inbounds — see RTB Inbounds. Calls have no async export yet; page through the Calls API directly, as the API Code Example does.
Export object
{
"id": 501,
"export_count": 168,
"export_progress": 168,
"export_percentage": 100.0,
"format": "csv",
"file_upload_url": "/v2/reports/rtb_inbounds/501/download",
"in_progress": false,
"errors": {}
}Fields
| Field | Type | Description |
|---|---|---|
id |
integer | The export’s ID. Use it to poll the export. |
format |
string | csv. |
in_progress |
boolean | true while generating; false once finished — or failed, so check errors. |
export_count |
integer | null | null while generating. Once finished, the number of rows in the export. |
export_progress |
integer | 0 while generating. Once finished, the same as export_count. |
export_percentage |
number | 0.0 while generating; 100.0 once finished. |
file_upload_url |
string | null | Path to download the finished file from, relative to https://api.retreaver.com — not a full URL. null until the export is done. |
errors |
object | Populated if the export request itself was invalid, e.g. a bad params filter. |
Create an export
curl -X POST "https://api.retreaver.com/api/v5/exports/rtb_inbounds.json?api_key=[api_key]" \
-H "Content-Type: application/json" \
-d '{
"export": {
"format": "csv",
"params": { "campaign_id": [123], "created_at": "last_7d" }
}
}'The above command returns JSON structured like this:
{
"id": 501,
"export_count": null,
"export_progress": 0,
"export_percentage": 0.0,
"format": "csv",
"file_upload_url": null,
"in_progress": true,
"errors": {}
}HTTP Request
curl -X POST "https://api.retreaver.com/api/v5/exports/rtb_inbounds.json?api_key=[api_key]" \
-H "Content-Type: application/json"Body Parameters
All parameters must be nested under an export key.
| Parameter | Type | Required | Description |
|---|---|---|---|
format |
string | Only csv is supported, and it is the default. |
|
params |
object | required | The filter to export. Takes the same keys as the RTB Inbounds query parameters (campaign_id, affiliate_id, target_id, caller_state, status, and so on, as plain values or arrays), plus either created_at (a relative window like last_7d) or created_at_start / created_at_end. The window is capped at 2 months, as on the live log. |
Check an export’s status
curl "https://api.retreaver.com/api/v5/exports/rtb_inbounds/501.json?api_key=[api_key]"The above command returns an Export object.
Poll until in_progress is false. While an export is generating, export_count stays null and export_progress / export_percentage stay at 0 — there is no partial progress to report.
HTTP Request
curl "https://api.retreaver.com/api/v5/exports/rtb_inbounds/:id.json?api_key=[api_key]"Download the export
curl -L -o rtb_inbounds.csv.gz \
"https://api.retreaver.com/v2/reports/rtb_inbounds/501/download?api_key=[api_key]"
gunzip rtb_inbounds.csv.gzOnce in_progress is false, prepend https://api.retreaver.com to the export’s file_upload_url. Don’t build the path yourself — only prepend the host, since the path is not under /api/v5/. It authenticates the same way (api_key) and redirects to the actual file.
Note
The file is always gzip-compressed, whatever the export’s format. Decompress it before reading — gunzip, or Zlib::GzipReader or your language’s equivalent, as in the scripts below.
Script Example: Downloading an RTB Inbounds export
Creates an export, polls it, downloads it, and decompresses it to a date-ranged CSV in the current directory, named rtb_inbounds_<start>-<end>.csv (e.g. rtb_inbounds_20260908-20260915.csv). It is the scripted version of the three steps above.
# How to run:
# 1. Save this script to a file, e.g. download_rtb_inbounds.rb
# 2. Edit API_KEY below
# 3. Run: ruby download_rtb_inbounds.rb
# 4. You'll end up with one result file in the current directory, named
# rtb_inbounds_<start>-<end>.csv (e.g. rtb_inbounds_20260908-20260915.csv)
require 'net/http'
require 'uri'
require 'json'
require 'time'
require 'zlib'
require 'stringio'
API_KEY = "your-api-key" # <- Change to your api key https://retreaver.com/user/edit/api_access
BASE_URL = "https://api.retreaver.com"
# The download itself is a redirect (302) to the actual file, not the file itself —
# Net::HTTP doesn't follow redirects on its own.
def http_get_following_redirects(uri, limit = 5)
raise "too many redirects" if limit == 0
response = Net::HTTP.get_response(uri)
response.is_a?(Net::HTTPRedirection) ? http_get_following_redirects(URI.parse(response['location']), limit - 1) : response
end
# Create + poll + download an RTB Inbounds export, saving the decompressed CSV to `path`.
def fetch_rtb_inbounds_csv(created_at_start:, created_at_end:, path:)
puts "Creating RTB Inbounds export..."
create_uri = URI.parse("#{BASE_URL}/api/v5/exports/rtb_inbounds.json?api_key=#{API_KEY}")
create_body = { export: { format: "csv", params: { created_at_start: created_at_start, created_at_end: created_at_end } } }
export = JSON.parse(Net::HTTP.post(create_uri, create_body.to_json, "Content-Type" => "application/json").body)
puts " export id=#{export['id']} created, polling until it's done..."
show_uri = URI.parse("#{BASE_URL}/api/v5/exports/rtb_inbounds/#{export['id']}.json?api_key=#{API_KEY}")
loop do
sleep 2
export = JSON.parse(Net::HTTP.get_response(show_uri).body)
# export_progress/export_count don't report partial progress — poll on in_progress itself.
puts " in_progress=#{export['in_progress']}..."
break unless export['in_progress']
end
# file_upload_url is a path (e.g. "/v2/reports/rtb_inbounds/501/download"), not a full URL — prepend BASE_URL.
puts "Downloading export from #{export['file_upload_url']}..."
download_uri = URI.parse("#{BASE_URL}#{export['file_upload_url']}?api_key=#{API_KEY}")
response = http_get_following_redirects(download_uri)
puts " downloaded #{response.body.bytesize} gzip-compressed bytes."
# The export is always written gzip-compressed — decompress before use.
csv_text = Zlib::GzipReader.new(StringIO.new(response.body)).read
File.write(path, csv_text)
puts " saved #{csv_text.bytesize} bytes (decompressed) to #{path}"
path
end
created_at_start = (Time.now - 7 * 24 * 3600).utc
created_at_end = Time.now.utc
date_range = "#{created_at_start.strftime('%Y%m%d')}-#{created_at_end.strftime('%Y%m%d')}"
puts "About to export RTB Inbounds created between #{created_at_start.iso8601} and #{created_at_end.iso8601}"
puts " using api_key=#{API_KEY}"
print "Press Enter to continue, or Ctrl+C to cancel... "
STDIN.gets
rtb_inbounds_csv_path = fetch_rtb_inbounds_csv(
created_at_start: created_at_start.iso8601,
created_at_end: created_at_end.iso8601,
path: "rtb_inbounds_#{date_range}.csv",
)
puts "\nDone! RTB Inbounds saved to #{File.expand_path(rtb_inbounds_csv_path)}"Example: merging Calls and RTB Inbounds by call_uuid
RTB Inbounds rows carry the call_uuid of the Call each reservation belongs to, so you can enrich an export with Call data, or the reverse, by joining on it. This script fetches neither dataset; it joins two files you already have:
- a Calls JSON file, e.g.
calls_20260915_180831.jsonfrom the Ruby API Code Example. - an RTB Inbounds CSV, e.g.
rtb_inbounds_20260908-20260915.csvfrom Downloading an RTB Inbounds export above.
Fetch both for the same time window, or the unmatched rows on each side will mostly be an artifact of the windows not lining up.
The join is a full outer join on call_uuid. Every RTB Inbounds row is kept even when it has no matching Call (a rejected or no-target ping never creates one), and every Call is kept even when no RTB Inbounds row references it (it wasn’t reserved through RTB). The unmatched side is left blank rather than the row dropped. Columns are ordered RTB Inbounds first, then Call.
# How to run:
# ruby merge_calls_with_rtb_inbounds.rb <calls.json> <rtb_inbounds.csv>
#
# calls.json — from the API Code Example in the Introduction
# rtb_inbounds.csv — from the Script Example: Downloading an RTB Inbounds export, above
require 'json'
require 'csv'
calls_path, rtb_inbounds_csv_path = ARGV
if calls_path.nil? || rtb_inbounds_csv_path.nil?
abort "Usage: ruby #{$PROGRAM_NAME} <calls.json> <rtb_inbounds.csv>"
end
# Each element of the Calls API response is root-wrapped ({"call": {...}}) — unwrap it
# so every entry is a plain call hash.
all_calls = JSON.parse(File.read(calls_path)).map { |entry| entry["call"] || entry }
rtb_inbounds = CSV.read(rtb_inbounds_csv_path, headers: true)
calls_by_uuid = all_calls.each_with_object({}) { |call, h| h[call["uuid"]] = call }
# A Call field can be an array/hash (downstream_call_uuids, target_group, ...) — flatten those to a
# JSON string so they round-trip through a single CSV cell.
def csv_value(value)
value.is_a?(Array) || value.is_a?(Hash) ? value.to_json : value
end
# RTB Inbounds columns first, then one column per key found across all_calls' own JSON keys.
rtb_headers = rtb_inbounds.headers || []
call_headers = all_calls.flat_map(&:keys).uniq
matched_call_uuids = {}
written = 0
puts "Merging #{rtb_inbounds.length} RTB Inbounds rows with #{all_calls.length} calls by call_uuid..."
CSV.open("calls_with_rtb_inbounds.csv", "w") do |out|
out << rtb_headers + call_headers
rtb_inbounds.each do |row|
call = calls_by_uuid[row["Call UUID"]]
matched_call_uuids[row["Call UUID"]] = true if call
out << rtb_headers.map { |h| row[h] } + call_headers.map { |h| call && csv_value(call[h]) }
written += 1
end
all_calls.each do |call|
next if matched_call_uuids[call["uuid"]]
out << rtb_headers.map { nil } + call_headers.map { |h| csv_value(call[h]) }
written += 1
end
end
puts "Wrote #{written} rows (#{rtb_inbounds.length} from RTB Inbounds, #{written - rtb_inbounds.length} Calls with no RTB Inbounds row) to calls_with_rtb_inbounds.csv."Note
The RTB Inbounds CSV headers are human-readable labels (Call UUID, Status), not the JSON field names (call_uuid, status) the RTB Inbounds endpoint returns. Calls have no such relabeling: each Call’s own JSON keys (uuid, status, …) become its columns as-is.
The merged calls_with_rtb_inbounds.csv is the input to the Control/Treatment payout reduction script.
Help us improve this article or request new support guides.