Reporting API
Liftoff’s Reporting API enables automated access to campaign reports with groupings and metrics of your choice. Discover insights on campaign performance by analyzing funnel metrics: impressions, clicks, installs and in-app events across different groupings (e.g. country, publisher app, ad format, creatives, etc). Perform cohort analysis through the API by specifying the look-back window relative to the install.
NOTE: Python examples assume that you have Requests installed.
Data Refresh Frequency: All reports are continuously updated with a delay of several hours for certain fields. The latest full day’s report will be available by 5am the following day. For example, to retrieve a complete report for 9/12/2024, please request on 9/13/2024 at 5am or later.
Getting Started
- Contact your AM to get API key and API secret emailed to you.
- Explore the various entities associated with your account (i.e. apps, creatives, campaigns, events) via the following endpoints. You will be prompted for a password.
- cURL
- Python
curl --user 'API_KEY:API_SECRET' --url https://data.liftoff.io/api/v1/apps
curl --user 'API_KEY:API_SECRET' --url https://data.liftoff.io/api/v1/creatives
curl --user 'API_KEY:API_SECRET' --url https://data.liftoff.io/api/v1/campaigns
curl --user 'API_KEY:API_SECRET' --url https://data.liftoff.io/api/v1/events
requests.get(
"https://data.liftoff.io/api/v1/apps",
auth=("API_KEY", "API_SECRET"),
)
requests.get(
"https://data.liftoff.io/api/v1/creatives",
auth=("API_KEY", "API_SECRET"),
)
requests.get(
"https://data.liftoff.io/api/v1/campaigns",
auth=("API_KEY", "API_SECRET"),
)
requests.get(
"https://data.liftoff.io/api/v1/events",
auth=("API_KEY", "API_SECRET"),
)
- Create a report request and validate it with the
test=true
query parameter. This ensures that you don’t run into rate limits while testing.
- cURL
- Python
curl --header "Content-Type: application/json" \
--request POST \
--data '{"start_time":"2019-01-01","end_time":"2019-01-03"}' \
--user 'API_KEY:API_SECRET' \
--url https://data.liftoff.io/api/v1/reports?test=true
requests.post(
"https://data.liftoff.io/api/v1/reports",
json={
"start_time": "2019-01-01",
"end_time": "2019-01-03",
},
params={"test": "true"},
auth=("API_KEY", "API_SECRET"),
headers={"Content-Type": "application/json"},
)
- Check that it passes without errors.
{ "message": "Validation passed without errors." }
- Generate the report without the
test
query parameter.
- cURL
- Python
curl --header "Content-Type: application/json" \
--request POST \
--data '{"start_time":"2019-01-01","end_time":"2019-01-03"}' \
--user 'API_KEY:API_SECRET' \
--url https://data.liftoff.io/api/v1/reports
requests.post(
"https://data.liftoff.io/api/v1/reports",
json={
"start_time": "2019-01-01",
"end_time": "2019-01-03",
},
auth=("API_KEY", "API_SECRET"),
headers={"Content-Type": "application/json"},
)
- The report response will contain a report ID if it was successfully created.
{
"id": "abc123",
"created_at": "2020-11-15T00:00:00Z",
"state": "queued",
"parameters": {
"start_time": "2019-01-01",
"end_time": "2019-01-03"
}
}
The status of the report can be polled via the following:
- Recommended: Provide a callback URL with the report request. When the report is completed/failed/cancelled, a message will be sent to the provided callback URL. An OPTIONS request is made to the endpoint before report creation to verify the host is reachable.
- Poll the
/reports/{ID}/status
endpoint once every minute.
- cURL
- Python
curl --user 'API_KEY:API_SECRET' \
--url https://data.liftoff.io/api/v1/reports/{ID}/status
requests.get(
"https://data.liftoff.io/api/v1/reports/{ID}/status",
auth=("API_KEY", "API_SECRET"),
)
{
"id": "abc123",
"created_at": "2020-11-15T00:00:00Z",
"state": "completed",
"parameters": {
"start_time": "2019-01-01",
"end_time": "2019-01-03"
}
}
- When the report is generated, its state will be marked as "completed", and the report data can be obtained via the following:
- cURL
- Python
curl --user 'API_KEY:API_SECRET' \
--url https://data.liftoff.io/api/v1/reports/{ID}/data
requests.get(
"https://data.liftoff.io/api/v1/reports/{ID}/data",
auth=("API_KEY", "API_SECRET"),
)
Authentication
Liftoff’s Reporting API supports HTTP basic authentication.
- cURL
- Python
curl --request GET \
--user 'API_KEY:API_SECRET' \
--url https://data.liftoff.io/api/v1/reports
requests.get(
"https://data.liftoff.io/api/v1/reports",
auth=("API_KEY", "API_SECRET"),
)
API Structure
All API URLs start with https://data.liftoff.io/api/v1
. Different endpoints
can be called by appending to the base url. All available end points are
described in the table below:
Endpoints | HTTP Method | Description |
---|---|---|
/reports | GET | Look up metadata for recently submitted reports. |
/reports | POST | Generate a report. |
/reports/{ID}/status | GET | Get the status of a report. |
/reports/{ID}/data | GET | Download the report in CSV or JSON format. |
/{ENTITY_TYPE} | GET | Get entity details (apps , campaigns , creatives or events ). |
Rate Limits
All valid endpoints are rate-limited and will return the following headers:
x-rate-limit-max
x-rate-limit-remaining
Endpoint | HTTP Method | Hourly Rate Limit |
---|---|---|
/api/v1/reports | GET | 60 |
/api/v1/reports | POST | 30 |
/api/v1/reports?test=true | POST | 60 |
/api/v1/reports/{ID}/status | GET | 1000 |
/api/v1/reports/{ID}/data | GET | 30 |
/api/v1/{ENTITY_TYPE} | GET | 60 |
Errors
The following errors can be returned by the API:
- 400 "BAD REQUEST"
- 403 "ACCESS DENIED"
- 404 "NOT FOUND"
- 429 "TOO MANY REQUESTS"
- 500 "INTERNAL ERROR"
Error Format:
Name | Type | Description |
---|---|---|
error_type | string | Error type (e.g. "BAD REQUEST"). |
message | string | General message on possible error cause. |
errors | array of strings (nullable) | List of issues with request. |
Sample Error:
{
"error_type": "BAD REQUEST",
"message": "Please correct the following issues with the request.",
"errors": [
"start_time should be before end_time.",
"Max allowable date range is one year."
]
}
Endpoints
GET /reports
This endpoint allows you to look up metadata for recently submitted reports.
- cURL
- Python
curl --user 'API_KEY:API_SECRET' --url https://data.liftoff.io/api/v1/reports
requests.get(
"https://data.liftoff.io/api/v1/reports",
auth=("API_KEY", "API_SECRET"),
)
Response Format:
Name | Type | Description |
---|---|---|
id | string | The report ID. |
created_at | timestamp (string) | Timestamp in UTC (e.g. "2021-10-01T00:00:00Z"). |
parameters | object | The JSON body that was provided by the POST request. |
state | string | "queued", "cancelled", "completed" or "failed". |
Sample Response:
[
{
"id": "abc123",
"created_at": "2021-11-15T00:00:00Z",
"state": "completed",
"parameters": {
"start_time": "2021-10-01",
"end_time": "2021-11-01"
}
}
]
POST /reports
This endpoint allows you to generate a report. When the test
query parameter
is set, no report is generated, but the request will still get validated. This
also has a separate rate-limit compared to requests without the query parameter.
- cURL
- Python
curl --header "Content-Type: application/json" \
--request POST \
--data '{"group_by":["apps","campaigns"],"start_time":"2020-10-01","end_time":"2020-11-01"}' \
--user 'API_KEY:API_SECRET' \
--url https://data.liftoff.io/api/v1/reports
requests.post(
"https://data.liftoff.io/api/v1/reports",
json={
"group_by": ["apps", "campaigns"],
"start_time": "2020-10-01",
"end_time": "2020-11-01",
},
auth=("API_KEY", "API_SECRET"),
headers={"Content-Type": "application/json"},
)
Request Format:
Name | Type | Default | Description |
---|---|---|---|
app_ids | array of strings (nullable) | null | Only include specified app IDs or all. |
campaign_ids | array of strings (nullable) | null | Only include specified campaign IDs or all. |
event_ids | array of strings (nullable) | null | All events belonging to the account. |
start_time | timestamp (string) | Required | ISO date and time in UTC or ISO date (with time at start of day for given timezone). |
end_time | timestamp (string) | Required | ISO date and time in UTC or ISO date (with time at start of day for given timezone). |
group_by | array of strings | ["apps", "campaigns"] | Group metrics by one of the available presets. |
cohort_window | int (1 - 90) (nullable) | null | Number of days since install. If specified, events will be counted since the day of install. |
format | string (nullable) | "csv" | "csv" or "json". |
callback_url | string (nullable) | null | A URL that will receive a POST request once the report completed or failed. |
timezone | string (nullable) | "UTC" | A TZ database name to use for date groupings. |
include_repeat_events | boolean (nullable) | true | Count all instances of post-install events will be counted, instead of just first occurrences. |
remove_zero_rows | boolean (nullable) | false | Drop all rows whose numeric metrics are equal to 0. |
use_two_letter_country | boolean (nullable) | false | Use the two letter country code (as opposed to the default three letter code). |
include_skan_installs_with_no_conversion_value | boolean (nullable) | false | Include the number of SKAN installs with no conversion value as an additional metric. |
include_skan_installs_with_conversion_value | boolean (nullable) | false | Include the number of SKAN installs with a conversion value as an additional metric. |
video_play_milestones | boolean (nullable) | false | Include 4 video play milestone metrics for VAST impressions: video_starts, video_plays_at_25_percent, video_plays_at_50_percent, video_plays_at_75_percent, and video_completes. Only available for reports with a group_by of creative or ad_format . |
demand_product | string (nullable) | null | Return only data for a specific demand product. Can be "accelerate", or "direct". If not provided returns all data according to API user's permissions. |
include_demand_product | boolean (nullable) | false | Include column for demand product. Only available for reports with a group_by of campaigns . |
Available Group By Presets:
["apps", "campaigns"]
["apps", "campaigns", "ad_format"]
["apps", "campaigns", "publisher"]
["apps", "campaigns", "country"]
["apps", "campaigns", "country", "publisher"]
["apps", "campaigns", "creatives"]
["apps", "campaigns", "creatives", "country"]
["apps", "campaigns", "creatives", "publisher"]
["apps", "campaigns", "creatives", "country", "publisher"]
Sample Request Body:
{
"app_ids": ["abc456", "abc123"],
"cohort_window": 1,
"group_by": ["apps", "campaigns", "creatives"]
}
Sample Response:
{
"id": "abc123",
"created_at": "2021-11-15T00:00:00Z",
"state": "queued",
"parameters": {
"group_by": ["apps", "campaigns"],
"start_time": "2021-10-01",
"end_time": "2021-11-01"
}
}
GET /reports/{ID}/status
This endpoint allows you to fetch the status of a single report. Similar to
/reports
endpoint but with a higher rate limit.
- cURL
- Python
curl --user 'API_KEY:API_SECRET' https://data.liftoff.io/api/v1/reports/{ID}/status
requests.get(
"https://data.liftoff.io/api/v1/reports/{ID}/status",
auth=("API_KEY", "API_SECRET"),
)
Sample Response:
{
"id": "abc123",
"created_at": "2021-11-15T00:00:00Z",
"state": "failed",
"parameters": {
"group_by": ["apps", "campaigns"],
"start_time": "2021-10-01",
"end_time": "2021-11-01"
}
}
GET /reports/{ID}/data
This endpoint allows you to download the completed report. The report can either be JSON/CSV depending on the format provided at report creation.
- cURL
- Python
curl --user 'API_KEY:API_SECRET' \
--url https://data.liftoff.io/api/v1/reports/{ID}/data
requests.get(
"https://data.liftoff.io/api/v1/reports/{ID}/data",
auth=("API_KEY", "API_SECRET"),
)
Report Row Format:
Name | Type | Description |
---|---|---|
date | string | Date (YYYY-MM-dd) for the report timezone. |
app_id | string | ID of the app that is being advertised. |
campaign_id | string | ID of the advertising campaign. |
creative_id | string | ID of the creative. |
country_code | string (nullable) | 2 letter country-code of countries in which the ad is being shown. |
publisher_app_store_id | string (nullable) | Bundle IDs of the publisher app. |
publisher_name | string (nullable) | Display name of the publisher app. |
ad_format | string (nullable) | Format of the ad unit. |
video_starts | int | Number of times a video started playing for VAST impressions. Only included if video_play_milestones is set. |
video_plays_at_25_percent | int | Number of times a video played up to 25% for VAST impressions. Only included if video_play_milestones is set. |
video_plays_at_50_percent | int | Number of times a video played up to 50% for VAST impressions. Only included if video_play_milestones is set. |
video_plays_at_75_percent | int | Number of times a video played up to 75% for VAST impressions. Only included if video_play_milestones is set. |
video_completes | int | Number of times a video finished playing for VAST impressions. Only included if video_play_milestones is set. |
spend | float | Amount of budget that was spent in that time period. |
impressions | int | Number of ad impressions shown. |
clicks | int | Number of clicks on the ads. |
installs | int | Number of installs resulting from the ads. |
<event_name> | int | Number of events resulting from the ads. Note that an event has to be specified. |
cpm | float | Cost per thousand impressions. Cost is defined as ad spend. |
cpc | float | Cost per click. Cost is defined as ad spend. |
ctr | float | Click through rate. Cost is defined as ad spend. |
cpi | float | Cost per install. Cost is defined as ad spend. |
cpa | float | Cost per action. Cost is defined as ad spend. Action refers to in-app events. |
skan-installs-with-no-conversion-value | int (nullable) | Number of SKAN installs without a conversion value. Only included if include_skan_installs_with_no_conversion_value is set. |
skan-installs-with-conversion-value | int (nullable) | Number of SKAN installs with a conversion value. Only included if include_skan_installs_with_conversion_value is set. |
Sample Response:
{
"columns": ["date", "app_id", "campaign_id", "spend", "impressions", ...],
"rows": [
["2019-02-08", "abc123", "zxy123", 123.12, 12, ...],
]
}
GET /apps
This endpoint allows you to fetch app details.
- cURL
- Python
curl --user 'API_KEY:API_SECRET' \
--url https://data.liftoff.io/api/v1/apps
requests.get(
"https://data.liftoff.io/api/v1/apps",
auth=("API_KEY", "API_SECRET"),
)
Response Format:
Name | Type | Description |
---|---|---|
id | string | ID of the app that is being advertised. |
name | string | App title. |
app_store_id | string | Apple/Play store app ID. |
bundle_id | string | App bundle/package ID. |
title | string | App title (as appears in creatives). |
platform | string | "iOS" or "Android". |
optimization_event | object (nullable) | Event selected for ML optimization. |
state | string | "enabled" or "paused". |
Sample Response:
[
{
"id": "abc123",
"name": "Sample",
"app_store_id": "io.liftoff.sample",
"bundle_id": "io.liftoff.sample",
"title": "Sample",
"platform": "Android",
"optimization_event": {
"id": "xyz123",
"name": "Tutorial"
},
"state": "enabled"
}
]
GET /campaigns
This endpoint allows you to fetch campaign details.
- cURL
- Python
curl --user 'API_KEY:API_SECRET' \
--url https://data.liftoff.io/api/v1/campaigns
requests.get(
"https://data.liftoff.io/api/v1/campaigns",
auth=("API_KEY", "API_SECRET"),
)
Response Format:
Name | Type | Description |
---|---|---|
id | string | ID of the advertising campaign. |
app_id | string | ID of the app. |
name | string | Name of the advertising campaign. |
campaign_type | string | "user-acquisition" or "reengagement". |
tracker_type | string | "MMP", "SKAN" or "NONE". |
min_os_version | string (nullable) | Minimum OS version of the campaign. |
max_os_version | string (nullable) | Maximum OS version of the campaign. |
state | string | "enabled" or "paused". |
state_last_changed_at | timestamp (string) | Timestamp in UTC (e.g. "2021-10-01T00:00:00Z"). |
demand_product | string | "accelerate" or "direct". |
Sample Response:
[
{
"id": "fff123",
"app_id": "abc123",
"name": "App - iOS - JP",
"campaign_type": "user-acquisition",
"tracker_type": "SKAN",
"min_os_version": "14.5",
"max_os_version": null,
"state": "enabled",
"state_last_changed_at": "2021-10-01T00:00:00Z",
"demand_product": "accelerate"
}
]
GET /creatives
This endpoint allows you to fetch creative details.
- cURL
- Python
curl --user 'API_KEY:API_SECRET' \
--url https://data.liftoff.io/api/v1/creatives
requests.get(
"https://data.liftoff.io/api/v1/creatives",
auth=("API_KEY", "API_SECRET"),
)
Response Format:
Name | Type | Description |
---|---|---|
id | string | ID of the ad creative. |
name | string (nullable) | Name of the ad creative. |
preview_url | string (nullable) | URL to preview the creative. |
full_html_preview_url | string (nullable) | URL of the full HTML preview. |
width | int (nullable) | Width of the creative (pixels). |
height | int (nullable) | Height of the creative (pixels). |
creative_type | string | How the creative is rendered: "native", "html" or "vast". |
created_at | timestamp (string) | Timestamp in UTC (e.g. "2021-10-01T00:00:00Z"). |
state | string | State of the creative: "enabled", "paused", or "deleted". |
video_duration | int (nullable) | Duration of the creative video in seconds. |
video_url | string (nullable) | CDN URL of the MP4 video creative. |
Sample Response:
[
{
"id": "zzz123",
"name": "320x480_sample_html",
"width": 320,
"height": 480,
"preview_url": null,
"creative_type": "html",
"created_at": "2021-10-01T00:00:00Z",
"state:": "enabled",
"video_duration": 16,
"video_url": "https://cdn.liftoff.io/customers/1234abcd/videos/originals/1234.mp4"
}
]
GET /customer
This endpoint allows you to fetch customer details.
- cURL
- Python
curl --user 'API_KEY:API_SECRET' \
--url https://data.liftoff.io/api/v1/customer
requests.get(
"https://data.liftoff.io/api/v1/customer",
auth=("API_KEY", "API_SECRET"),
)
Response Format:
Name | Type | Description |
---|---|---|
id | string | ID of the company. |
company | string | Name of the company. |
timezone | string | Customer timezone used for billing. Use this value on reporting requests to better align your pulls with Liftoff's dashboards, scheduled reports, and invoices. |
Sample Response:
[
{
"id": "xyz123",
"company": "Liftoff",
"timezone": "US/Pacific"
}
]
GET /events
This endpoint allows you to fetch event details.
- cURL
- Python
curl --user 'API_KEY:API_SECRET' \
--url https://data.liftoff.io/api/v1/events
requests.get(
"https://data.liftoff.io/api/v1/events",
auth=("API_KEY", "API_SECRET"),
)
Response Format:
Name | Type | Description |
---|---|---|
id | string | ID of the in-app event. |
app_id | string | ID of the advertising app. |
name | string | Event name. |
Sample Response:
[
{
"id": "xyz123",
"app_id": "abc123",
"name": "Tutorial"
}
]
Sample Workflow
The following is a sample Python script for requesting and downloading reports.
import requests
import time
api_key = "API_KEY"
api_secret = "API_SECRET"
# Submit request to generate a new report.
report_response = requests.post(
"https://data.liftoff.io/api/v1/reports",
json={
"start_time": "2022-01-01",
"end_time": "2022-02-01",
},
auth=(api_key, api_secret),
headers={"Content-Type": "application/json"},
)
report_id = report_response.json()["id"]
print(f"Report requested. ID: {report_id}")
# Poll for report status until in a finished state.
state = ""
while state not in {"completed", "failed", "cancelled"}:
print("Waiting 30 seconds to check status...")
time.sleep(30)
status_response = requests.get(
f"https://data.liftoff.io/api/v1/reports/{report_id}/status",
auth=(api_key, api_secret),
)
state = status_response.json()["state"]
print(f"Current state: {state}")
# Download the completed report.
if state == "completed":
data_response = requests.get(
f"https://data.liftoff.io/api/v1/reports/{report_id}/data",
auth=(api_key, api_secret),
)
data_response.raise_for_status()
# Handle your raw report data here.
with open("report_response.csv", "w") as f:
f.write(data_response.text)
print("Report downloaded successfully!")