API Quickstart
Last updated
Last updated
Making your first request with the Athena API is straightforward with this script.
Athena's News API operates asynchronously — meaning that when you are submitting your query, a job is created (along with a unique job id). You'll then have to poll the get-results endpoint with that job id to see if your job is done.
Submit query and get a query id
Every x seconds poll the get-results endpoint to see if the query is finished, passing in the query id from the previous step
If the search is complete, your results will be returned
First define your ATHENA_API_KEY
. The payload
dictionary specifies the query parameters, including a search term ("donald trump"), your api_key, other configuration details to filter the results (for the sake of simplicity we are choosing to omit extra parameters — see API Reference for other options).
The initial request is sent via a POST
call to Athena’s query-async API endpoint. The response contains your query_id, which is how you will retrieve results from your search with the /api/v2/get-results
endpoint.
import time
import requests
# Constants
ATHENA_API_KEY = "YOUR_API_KEY"
QUERY = "Tesla dealership protests"
KEY_PHRASES = "('tesla takedown')"
start_date = "2025-03-01T15:13:52.466Z"
end_date = "2025-03-20T15:13:52.466Z"
QUERY_URL = "https://app.runathena.com/api/v2/query-async"
RESULTS_URL = "https://app.runathena.com/api/v2/get-results"
HEADERS = {"Content-Type": "application/json"}
ARTICLES_PER_PAGE = 25
POLL_INTERVAL = 1 # seconds to wait between polls
def send_initial_query(query: str, api_key: str) -> str:
"""
Sends the initial query to the API and returns the query_id.
"""
payload = {"query": query, "key_phrases":KEY_PHRASES,"api_key": api_key,'toggle_state':'All Articles',"start_date":start_date,"end_date":end_date}
response = requests.post(QUERY_URL, headers=HEADERS, json=payload)
response.raise_for_status() # raise an error for bad responses
data = response.json()
return data.get('query_id')
def poll_for_results(query_id: str, api_key: str, interval: int = POLL_INTERVAL) -> dict:
"""
Polls the API until the query status is not 'PENDING'.
Returns the final result data.
"""
payload = {"query_id": query_id, "api_key": api_key}
response = requests.post(RESULTS_URL, headers=HEADERS, json=payload)
response.raise_for_status()
data = response.json()
# Poll until the status changes from 'PENDING'
while data.get('state') == 'PENDING':
time.sleep(interval)
response = requests.post(RESULTS_URL, headers=HEADERS, json=payload)
response.raise_for_status()
data = response.json()
return data
def fetch_all_articles(query_id: str, total_results: int, api_key: str) -> list:
"""
Fetches and aggregates all articles by paginating through the results.
"""
all_articles = []
page = 1
payload = {"query_id": query_id, "api_key": api_key,'toggle_state':'Encoded Articles'}
# Continue fetching while there are articles remaining.
while (page - 1) * ARTICLES_PER_PAGE < total_results:
payload['page'] = page
response = requests.post(RESULTS_URL, headers=HEADERS, json=payload)
response.raise_for_status()
data = response.json()
articles = data.get('articles', [])
all_articles.extend(articles)
page += 1
return all_articles
def main():
try:
# Step 1: Send the initial query and retrieve the query_id.
query_id = send_initial_query(QUERY, ATHENA_API_KEY)
if not query_id:
print("Failed to retrieve query ID.")
return
# Step 2: Poll the API until the query is processed.
result_data = poll_for_results(query_id, ATHENA_API_KEY)
#print(result_data)
#print(result_data.keys())
# Check if the query was successful before pagination.
if result_data.get('state') != 'SUCCESS':
print("Query did not complete successfully:", result_data)
return
total_results = result_data.get('totalArticles', 0)
if total_results == 0:
print("No results found.")
return
# Step 3: Paginate through the results and collect all articles.
all_articles = fetch_all_articles(query_id, total_results, ATHENA_API_KEY)
print("Total articles fetched:", len(all_articles))
except requests.RequestException as e:
print("An error occurred while communicating with the API:", e)
if __name__ == '__main__':
main()
// Constants
const ATHENA_API_KEY = "YOUR_API_KEY";
const QUERY = "Tesla dealership protests";
const KEY_PHRASES = "('tesla takedown')";
const start_date = "2025-03-01T15:13:52.466Z";
const end_date = "2025-03-20T15:13:52.466Z";
const QUERY_URL = "https://app.runathena.com/api/v2/query-async";
const RESULTS_URL = "https://app.runathena.com/api/v2/get-results";
const HEADERS = { "Content-Type": "application/json" };
const ARTICLES_PER_PAGE = 25;
const POLL_INTERVAL = 1000; // milliseconds
// Helper sleep function
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function sendInitialQuery(query, apiKey) {
const payload = {
query: QUERY,
key_phrases: KEY_PHRASES,
api_key: apiKey,
toggle_state: 'All Articles',
start_date: start_date,
end_date: end_date
};
const response = await fetch(QUERY_URL, {
method: "POST",
headers: HEADERS,
body: JSON.stringify(payload)
});
if (!response.ok) {
throw new Error(`Network error: ${response.statusText}`);
}
const data = await response.json();
return data.query_id;
}
async function pollForResults(queryId, apiKey, interval = POLL_INTERVAL) {
const payload = {
query_id: queryId,
api_key: apiKey
};
let response = await fetch(RESULTS_URL, {
method: "POST",
headers: HEADERS,
body: JSON.stringify(payload)
});
if (!response.ok) {
throw new Error(`Network error: ${response.statusText}`);
}
let data = await response.json();
// Poll until the state is not 'PENDING'
while (data.state === 'PENDING') {
await sleep(interval);
response = await fetch(RESULTS_URL, {
method: "POST",
headers: HEADERS,
body: JSON.stringify(payload)
});
if (!response.ok) {
throw new Error(`Network error: ${response.statusText}`);
}
data = await response.json();
}
return data;
}
async function fetchAllArticles(queryId, totalResults, apiKey) {
let allArticles = [];
let page = 1;
// Continue fetching while there are articles remaining.
while ((page - 1) * ARTICLES_PER_PAGE < totalResults) {
const payload = {
query_id: queryId,
api_key: apiKey,
toggle_state: 'All Articles',
page: page
};
const response = await fetch(RESULTS_URL, {
method: "POST",
headers: HEADERS,
body: JSON.stringify(payload)
});
if (!response.ok) {
throw new Error(`Network error: ${response.statusText}`);
}
const data = await response.json();
const articles = data.articles || [];
allArticles.push(...articles);
page++;
}
return allArticles;
}
async function main() {
try {
// Step 1: Send the initial query and retrieve the query_id.
const queryId = await sendInitialQuery(QUERY, ATHENA_API_KEY);
if (!queryId) {
console.log("Failed to retrieve query ID.");
return;
}
// Step 2: Poll the API until the query is processed.
const resultData = await pollForResults(queryId, ATHENA_API_KEY);
// Check if the query was successful before pagination.
if (resultData.state !== 'SUCCESS') {
console.log("Query did not complete successfully:", resultData);
return;
}
const totalResults = resultData.totalArticles || 0;
if (totalResults === 0) {
console.log("No results found.");
return;
}
// Step 3: Paginate through the results and collect all articles.
const allArticles = await fetchAllArticles(queryId, totalResults, ATHENA_API_KEY);
console.log("Total articles fetched:", allArticles.length);
} catch (error) {
console.error("An error occurred while communicating with the API:", error);
}
}
main();
<?php
// Constants
define('ATHENA_API_KEY', 'YOUR_API_KEY');
define('QUERY', 'Tesla dealership protests');
define('KEY_PHRASES', "('tesla takedown')");
define('START_DATE', '2025-03-01T15:13:52.466Z');
define('END_DATE', '2025-03-20T15:13:52.466Z');
define('QUERY_URL', 'https://app.runathena.com/api/v2/query-async');
define('RESULTS_URL', 'https://app.runathena.com/api/v2/get-results');
define('ARTICLES_PER_PAGE', 25);
define('POLL_INTERVAL', 1); // seconds
/**
* Send the initial query and return the query_id.
*
* @param string $query
* @param string $apiKey
* @return string|null
* @throws Exception if the request fails
*/
function sendInitialQuery($query, $apiKey) {
$payload = [
"query" => $QUERY,
"key_phrases" => $KEY_PHRASES,
"api_key" => $apiKey,
"toggle_state" => "All Articles",
"start_date" => START_DATE,
"end_date" => END_DATE
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, QUERY_URL);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Content-Type: application/json"]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
$response = curl_exec($ch);
if (curl_errno($ch)) {
throw new Exception('Request Error: ' . curl_error($ch));
}
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode < 200 || $httpCode >= 300) {
throw new Exception("HTTP error: $httpCode");
}
$data = json_decode($response, true);
return isset($data['query_id']) ? $data['query_id'] : null;
}
/**
* Polls the API until the query state is no longer 'PENDING'.
*
* @param string $queryId
* @param string $apiKey
* @param int $interval
* @return array
* @throws Exception if the request fails
*/
function pollForResults($queryId, $apiKey, $interval = POLL_INTERVAL) {
$payload = [
"query_id" => $queryId,
"api_key" => $apiKey
];
do {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, RESULTS_URL);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Content-Type: application/json"]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
$response = curl_exec($ch);
if (curl_errno($ch)) {
throw new Exception('Request Error: ' . curl_error($ch));
}
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode < 200 || $httpCode >= 300) {
throw new Exception("HTTP error: $httpCode");
}
$data = json_decode($response, true);
if (isset($data['state']) && $data['state'] === 'PENDING') {
sleep($interval);
} else {
return $data;
}
} while (true);
}
/**
* Fetches all articles by paginating through the results.
*
* @param string $queryId
* @param int $totalResults
* @param string $apiKey
* @return array
* @throws Exception if the request fails
*/
function fetchAllArticles($queryId, $totalResults, $apiKey) {
$allArticles = [];
$page = 1;
while ((($page - 1) * ARTICLES_PER_PAGE) < $totalResults) {
$payload = [
"query_id" => $queryId,
"api_key" => $apiKey,
"toggle_state" => "Encoded Articles",
"page" => $page
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, RESULTS_URL);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Content-Type: application/json"]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
$response = curl_exec($ch);
if (curl_errno($ch)) {
throw new Exception('Request Error: ' . curl_error($ch));
}
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode < 200 || $httpCode >= 300) {
throw new Exception("HTTP error: $httpCode");
}
$data = json_decode($response, true);
$articles = isset($data['articles']) ? $data['articles'] : [];
$allArticles = array_merge($allArticles, $articles);
$page++;
}
return $allArticles;
}
function main() {
try {
// Step 1: Send the initial query and retrieve the query_id.
$queryId = sendInitialQuery(QUERY, ATHENA_API_KEY);
if (!$queryId) {
echo "Failed to retrieve query ID.\n";
return;
}
// Step 2: Poll the API until the query is processed.
$resultData = pollForResults($queryId, ATHENA_API_KEY);
// Check if the query was successful before pagination.
if (!isset($resultData['state']) || $resultData['state'] !== 'SUCCESS') {
echo "Query did not complete successfully:\n";
print_r($resultData);
return;
}
$totalResults = isset($resultData['totalArticles']) ? $resultData['totalArticles'] : 0;
if ($totalResults == 0) {
echo "No results found.\n";
return;
}
// Step 3: Paginate through the results and collect all articles.
$allArticles = fetchAllArticles($queryId, $totalResults, ATHENA_API_KEY);
echo "Total articles fetched: " . count($allArticles) . "\n";
} catch (Exception $e) {
echo "An error occurred while communicating with the API: " . $e->getMessage() . "\n";
}
}
main();
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
public class AthenaQuery {
// Constants
private static final String ATHENA_API_KEY = "YOUR_API_KEY";
private static final String QUERY = "Tesla dealership protests";
private static final String KEY_PHRASES = "('tesla takedown')"
private static final String START_DATE = "2025-03-01T15:13:52.466Z";
private static final String END_DATE = "2025-03-20T15:13:52.466Z";
private static final String QUERY_URL = "https://app.runathena.com/api/v2/query-async";
private static final String RESULTS_URL = "https://app.runathena.com/api/v2/get-results";
private static final int ARTICLES_PER_PAGE = 25;
private static final int POLL_INTERVAL = 1000; // milliseconds
private static final HttpClient httpClient = HttpClient.newHttpClient();
private static final ObjectMapper objectMapper = new ObjectMapper();
// Sends the initial query and returns the query_id
public static String sendInitialQuery(String query, String apiKey) throws Exception {
String payload = String.format(
"{\"query\":\"%s\", \"key_phrases\":\"%s\", \"api_key\":\"%s\", \"toggle_state\":\"All Articles\", \"start_date\":\"%s\", \"end_date\":\"%s\"}",
QUERY, KEY_PHRASES,apiKey, START_DATE, END_DATE
);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(QUERY_URL))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(payload))
.build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
if(response.statusCode() < 200 || response.statusCode() >= 300) {
throw new RuntimeException("HTTP error: " + response.statusCode());
}
JsonNode jsonResponse = objectMapper.readTree(response.body());
return jsonResponse.has("query_id") ? jsonResponse.get("query_id").asText() : null;
}
// Polls the API until the query state is no longer "PENDING"
public static JsonNode pollForResults(String queryId, String apiKey) throws Exception {
String payload = String.format("{\"query_id\":\"%s\", \"api_key\":\"%s\"}", queryId, apiKey);
JsonNode data;
do {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(RESULTS_URL))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(payload))
.build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
if(response.statusCode() < 200 || response.statusCode() >= 300) {
throw new RuntimeException("HTTP error: " + response.statusCode());
}
data = objectMapper.readTree(response.body());
if (data.has("state") && data.get("state").asText().equals("PENDING")) {
Thread.sleep(POLL_INTERVAL);
} else {
break;
}
} while (true);
return data;
}
// Fetches and aggregates all articles by paginating through the results
public static ArrayNode fetchAllArticles(String queryId, int totalResults, String apiKey) throws Exception {
ArrayNode allArticles = objectMapper.createArrayNode();
int page = 1;
while ((page - 1) * ARTICLES_PER_PAGE < totalResults) {
String payload = String.format(
"{\"query_id\":\"%s\", \"api_key\":\"%s\", \"toggle_state\":\"All Articles\", \"page\":%d}",
queryId, apiKey, page
);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(RESULTS_URL))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(payload))
.build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
if(response.statusCode() < 200 || response.statusCode() >= 300) {
throw new RuntimeException("HTTP error: " + response.statusCode());
}
JsonNode data = objectMapper.readTree(response.body());
JsonNode articles = data.get("articles");
if (articles != null && articles.isArray()) {
allArticles.addAll((ArrayNode) articles);
}
page++;
}
return allArticles;
}
public static void main(String[] args) {
try {
// Step 1: Send the initial query and retrieve the query_id.
String queryId = sendInitialQuery(QUERY, ATHENA_API_KEY);
if (queryId == null || queryId.isEmpty()) {
System.out.println("Failed to retrieve query ID.");
return;
}
// Step 2: Poll the API until the query is processed.
JsonNode resultData = pollForResults(queryId, ATHENA_API_KEY);
if (!resultData.has("state") || !resultData.get("state").asText().equals("SUCCESS")) {
System.out.println("Query did not complete successfully: " + resultData.toString());
return;
}
int totalResults = resultData.has("totalArticles") ? resultData.get("totalArticles").asInt() : 0;
if (totalResults == 0) {
System.out.println("No results found.");
return;
}
// Step 3: Paginate through the results and collect all articles.
ArrayNode allArticles = fetchAllArticles(queryId, totalResults, ATHENA_API_KEY);
System.out.println("Total articles fetched: " + allArticles.size());
} catch (Exception e) {
System.out.println("An error occurred while communicating with the API: " + e.getMessage());
}
}
}
require 'net/http'
require 'uri'
require 'json'
# Constants
ATHENA_API_KEY = "YOUR_API_KEY"
QUERY = "Tesla dealership protests"
KEY_PHRASES = "('tesla takedown')"
START_DATE = "2025-03-04T15:13:52.466Z"
END_DATE = "2025-03-05T15:13:52.466Z"
QUERY_URL = "https://app.runathena.com/api/v2/query-async"
RESULTS_URL = "https://app.runathena.com/api/v2/get-results"
ARTICLES_PER_PAGE = 25
POLL_INTERVAL = 1 # seconds
def send_initial_query(query, api_key)
payload = {
query: query,
api_key: api_key,
toggle_state: 'Encoded Articles',
start_date: START_DATE,
end_date: END_DATE
}
uri = URI.parse(QUERY_URL)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true if uri.scheme == 'https'
request = Net::HTTP::Post.new(uri.request_uri, { 'Content-Type' => 'application/json' })
request.body = payload.to_json
response = http.request(request)
if response.code.to_i < 200 || response.code.to_i >= 300
raise "HTTP error: #{response.code}"
end
data = JSON.parse(response.body)
data['query_id']
end
def poll_for_results(query_id, api_key)
payload = { query_id: query_id, api_key: api_key }
uri = URI.parse(RESULTS_URL)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true if uri.scheme == 'https'
loop do
request = Net::HTTP::Post.new(uri.request_uri, { 'Content-Type' => 'application/json' })
request.body = payload.to_json
response = http.request(request)
if response.code.to_i < 200 || response.code.to_i >= 300
raise "HTTP error: #{response.code}"
end
data = JSON.parse(response.body)
if data['state'] == 'PENDING'
sleep(POLL_INTERVAL)
else
return data
end
end
end
def fetch_all_articles(query_id, total_results, api_key)
all_articles = []
page = 1
uri = URI.parse(RESULTS_URL)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true if uri.scheme == 'https'
while (page - 1) * ARTICLES_PER_PAGE < total_results
payload = {
query_id: query_id,
api_key: api_key,
toggle_state: 'Encoded Articles',
page: page
}
request = Net::HTTP::Post.new(uri.request_uri, { 'Content-Type' => 'application/json' })
request.body = payload.to_json
response = http.request(request)
if response.code.to_i < 200 || response.code.to_i >= 300
raise "HTTP error: #{response.code}"
end
data = JSON.parse(response.body)
articles = data['articles'] || []
all_articles.concat(articles)
page += 1
end
all_articles
end
def main
begin
# Step 1: Send the initial query and retrieve the query_id.
query_id = send_initial_query(QUERY, ATHENA_API_KEY)
unless query_id
puts "Failed to retrieve query ID."
return
end
# Step 2: Poll the API until the query is processed.
result_data = poll_for_results(query_id, ATHENA_API_KEY)
unless result_data['state'] == 'SUCCESS'
puts "Query did not complete successfully: #{result_data}"
return
end
total_results = result_data['totalArticles'] || 0
if total_results == 0
puts "No results found."
return
end
# Step 3: Paginate through the results and collect all articles.
all_articles = fetch_all_articles(query_id, total_results, ATHENA_API_KEY)
puts "Total articles fetched: #{all_articles.size}"
rescue StandardError => e
puts "An error occurred while communicating with the API: #{e.message}"
end
end
main