Athena News API Documentation
FeaturesPricingContact UsCreate AccountSign In
Athena API v3
Athena API v3
  • About Athena News API
  • v3 Change Summary
  • Account Overview
    • Account Overview
    • Dashboard
    • News Search
    • API Key
    • Account Settings
    • Manage Subscription
  • Getting Started
    • API Quickstart
    • Python SDK
    • Intro to Building a Query
    • API Reference
      • News Search
      • Headlines (BETA)
    • Data Model Reference
      • Articles
      • Entities
    • Supported Languages
    • Rate Limits
    • Troubleshooting
Powered by GitBook
On this page
  1. Getting Started

API Quickstart

PreviousManage SubscriptionNextPython SDK

Last updated 1 month ago

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

Using Async Query Endpoint

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

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 for other options).

API Reference
Example search for Tesla stock outlook