I found the python package HorizonJPL in the Python Package Index, but it looks like it's limited to activity in 2013. When I go to the linked documentation page https://docs.google.com/document/d/1g9q3ln9LVAATOZ15986HLTCaqcAj_Jd8e_jOGS3YWrE/pub the info is fairly sparse, and the two links there lead to a dead end in Japanese (below).

Is there a way I can access JPL Horizons for tables of data (Like I am doing here) from within a Python script? In other words, I want to build a url query and receive a json of text with state vectors in return using urllib or urllib2.

Update: This is a typical example of what I want to do:

enter image description here

This is what is displayed on the pypi site - there seems to be names/handles but I don't know how to query them.

API Documentation



Planetary Data System: http://pds.nasa.gov/

Jet Propulsion Labs: http://www.jpl.nasa.gov/

HORIZON User Manual: http://ssd.jpl.nasa.gov/?horizons_doc


Matthew Mihok (@mattmattmatt)

Dexter Jagula (@djagula)

Siddarth Kalra (@SiddarthKalra)

Tiago Moreira (@Kamots)

Here is what happens when I try the links in the google doc:

enter image description here

enter image description here

  • $\begingroup$ If this doesn't attract any help in say another week, then maybe a good candidate for migration to stackoverflow at that time. The python tag is critical there. $\endgroup$
    – uhoh
    Jun 4, 2016 at 11:33
  • 1
    $\begingroup$ Have you tried using telnetlib to access the telnet interface? $\endgroup$ Jun 4, 2016 at 20:25
  • $\begingroup$ @2012rcampion OK - I'll look into it today! If I understand correctly, I would have to write a python script that basically emulates a person typing commands and receiving prompts. It's possible someone has done a good job of this and written a nice interface wrapper - my ultimate goal is to see if anyone has made something like that generally available. In the mean time I will definitely try what you have suggested. Thanks!! $\endgroup$
    – uhoh
    Jun 5, 2016 at 2:03
  • 1
    $\begingroup$ If you're not intending to use the script for a long time, you don't have to process the prompts at all. What I did was to get one dataset manually, then have my script basically replay the same session with one configurable parameter. $\endgroup$ Jun 5, 2016 at 4:44
  • $\begingroup$ @2012rcampion OK got it. The manual is helpful, but I'm searching for example sessions, which are easier for me to digest. I've added an example of what I want to do in the question. If I get it working, I'll post it as an answer - if someone else posts a similar example telnet session or a script first, gosh I'll be forced to accept it! :) $\endgroup$
    – uhoh
    Jun 5, 2016 at 8:44

3 Answers 3


callhorizons is depricated now and refers to the python library astroquery which now seems to be the way to go.

astroquery (GitHub, readthedocs) is "an astropy affiliated package that contains a collection of tools to access online Astronomical data. Each web service has its own sub-package.", where making Horizons queries is just one of many options. It's part of the larger Astropy Project which is "a community effort to develop a common core package for Astronomy in Python and foster an ecosystem of interoperable astronomy packages."

I recommend reading documentation page for JPL Horizons queries for astroquery can be found here: https://astroquery.readthedocs.io/en/latest/jplhorizons/jplhorizons.html It lists a few good examples, e.g.:

from astroquery.jplhorizons import Horizons
obj = Horizons(id='Ceres', location='568',
                epochs={'start':'2010-01-01', 'stop':'2010-03-01', 'step':'10d'})
eph = obj.ephemerides()

targetname    datetime_str   datetime_jd ...   GlxLat  RA_3sigma DEC_3sigma
   ---            ---             d      ...    deg      arcsec    arcsec
---------- ----------------- ----------- ... --------- --------- ----------
   1 Ceres 2010-Jan-01 00:00   2455197.5 ... 24.120057       0.0        0.0
   1 Ceres 2010-Jan-11 00:00   2455207.5 ... 20.621496       0.0        0.0
   1 Ceres 2010-Jan-21 00:00   2455217.5 ... 17.229529       0.0        0.0
   1 Ceres 2010-Jan-31 00:00   2455227.5 ...  13.97264       0.0        0.0
   1 Ceres 2010-Feb-10 00:00   2455237.5 ... 10.877201       0.0        0.0
   1 Ceres 2010-Feb-20 00:00   2455247.5 ...  7.976737       0.0        0.0

I also recommend skimming the docs of the HorizonsClass. In particular, I was confused initially that setting target id=399 didn't target Earth, but that was because the class also takes a id-type argument that defaults to smallbody (asteroids and the like), and needed to be changed to majorbody.

It seems very straight forward to use, and is clearly the way to go now.

PS: I will mention I got an error KeyError: 'Obsrv-lon' for my particular query, but the issue is being investigated.


There is one now! I've just run across the python package callhorizons in github and Python Package Index.

There is more information in readthedocis.io which helps explain how to do queries if you are looking for asteroids/minor bodies, for which there are zillions and the names can be people's name, or alphanumerics.

I'll try to test it further and update here. It looks like there is a lot of capability here, but I can't say for sure yet. There is a blog post by an astronomer but it's from January 2016.

It was a one-line install, namely pip install callhorizons. I then typed in the simple demo at the top of the PyPI page and got an answer!

>>> import callhorizons
>>> eros = callhorizons.query('Eros')
>>> eros.set_discreteepochs([2457446.177083, 2457446.182343])
>>> eros.get_ephemerides(568)
>>> print(eros['RA'], eros['DEC'])
(array([ 292.46003,  292.46332]), array([-27.44392, -27.44335]))

Yay python!

The current version is a pure python script, and contains the comment:

CALLHORIZONS - a Python interface to access JPL HORIZONS
ephemerides and orbital elements.

This module provides a convenient python interface to the JPL
HORIZONS system by directly accessing and parsing the HORIZONS
website. Ephemerides can be obtained through get_ephemerides,
orbital elements through get_elements. Function
export2pyephem provides an interface to the PyEphem module.

michael.mommert (at) nau.edu, latest version: v1.0.5, 2017-05-05.
This code is inspired by code created by Alex Hagen.

Currently it only generates the query url for an observer table, not state vectors, but it could certainly be easily customized to get state vectors. Here is a small chunk, additional parameters are added to the url string before it's used.

### construct URL for HORIZONS query
url = "http://ssd.jpl.nasa.gov/horizons_batch.cgi?batch=l" \
      + "&TABLE_TYPE='OBSERVER'" \
      + "&QUANTITIES='" + str(quantities) + "'" \
      + "&CSV_FORMAT='YES'" \
      + "&ANG_FORMAT='DEG'" \
      + "&CAL_FORMAT='BOTH'" \
      + "&SOLAR_ELONG='" + str(solar_elongation[0]) + "," \
      + str(solar_elongation[1]) + "'" \
      + "&CENTER='"+str(observatory_code)+"'"

OK try a spacecraft, also viewed from Mauna Kea. From JPL Horizons with the following setup:

enter image description here

gives (scrolls horizontally):

Date_________JDUT, , ,R.A._(ICRF/J2000.0), DEC_(ICRF/J2000.0),  APmag, S-brt,            delta,     deldot,    S-O-T,/r,    S-T-O,
2457754.500000000,*,m,20 01 24.54,-56 59 28.4,   n.a.,  n.a., 1.7047550022E+10, 18.9796569,  36.6514,/T,   0.2976,
2457784.500000000,*,m,20 03 22.69,-56 58 00.2,   n.a.,  n.a., 1.7080283550E+10,  6.5439542,  40.6419,/L,   0.3218,
2457814.500000000,*,m,20 05 08.36,-57 01 24.2,   n.a.,  n.a., 1.7082950019E+10, -3.4910462,  59.2751,/L,   0.4270,
2457844.500000000,*,m,20 06 16.02,-57 09 10.1,   n.a.,  n.a., 1.7065586218E+10, -8.4792450,  82.4692,/L,   0.4975,
2457874.500000000,*,m,20 06 30.25,-57 19 39.5,   n.a.,  n.a., 1.7043227392E+10, -7.3046878, 106.0860,/L,   0.4870,
2457904.500000000,*,m,20 05 49.21,-57 30 32.2,   n.a.,  n.a., 1.7031649793E+10, -0.4854246, 127.5819,/L,   0.4048,
2457934.500000000,*,m,20 04 24.98,-57 39 21.6,   n.a.,  n.a., 1.7043463848E+10, 10.1577911, 141.9907,/L,   0.3144,

Compare to this script:

import callhorizons
import numpy as np

JDs = [2457754.5 + 30*i for i in range(7)] # 01-Jan-2017 00:00 UTC + n*30 days

V2  = callhorizons.query(-32, smallbody=False)     # -32 for Voyager 2


V2.get_ephemerides(568)    # 568 for Mauna Kea 

radecs = np.vstack((V2['RA'], V2['DEC']))

for JD, radec in zip(JDs, radecs.T):
    print JD, radec

A spot check shows that these are the same, great! Fyi the very large apparent motion is due to parallax - the Earth shifting its perspective every month. After one year the position actually only changes by a few tenths of a degree.

2457754.5 [ 300.35226  -56.99121]
2457784.5 [ 300.84454  -56.96671]
2457814.5 [ 301.28482  -57.0234 ]
2457844.5 [ 301.56676  -57.1528 ]
2457874.5 [ 301.62605  -57.32765]
2457904.5 [ 301.45504  -57.50895]
2457934.5 [ 301.10407  -57.65601]
  • 2
    $\begingroup$ Seems like a pretty cool library! I would have recommended SpiceyPy which is an interface to SPICE, and SPICE runs JPL Horizons. But then you have to rewrite all the logic. $\endgroup$
    – ChrisR
    Jun 19, 2017 at 19:34
  • $\begingroup$ @ChrisR I'll take a look. Can you consider leaving a short answer mentioning SpiceyPy? Comments are by definition temporary, and even if it might not be a perfect match to "Python API for JPL Horizons?" it does do something similar enough that future readers might benefit from knowing about. Doesn't have to be a long answer, just a link and an explanation that when one wants to access Horizons automatically, one is really just accessing SPICE and so one should know that the Horizons middle-man is not always necessary. $\endgroup$
    – uhoh
    Jun 20, 2017 at 3:56

Choose your input parameters in Horizons, then click this link (link of "show batch-file data" in Horizons page): https://ssd.jpl.nasa.gov/horizons.cgi?show=1#results

You'll get something like this:


COMMAND= '-64'

CENTER= '500@0'



START_TIME= '2018-10-1'

STOP_TIME= '2018-10-30'

STEP_SIZE= '1 d'











Remove lines starting by "!".

Copy the remaining text in your favorite text+hex editor and make these replacements:

0d0a --> & (carriage return to "&") ' --> %27 space --> %20

You will get a single line to append to this partial URL:


The result is in "mixed CSV" format (i.e. CSV with garbage...)

This javascript source will convert Horizons output to a valid URL and will open it:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
  <meta http-equiv="content-type" content="text/html; charset=utf-8">
  <title>NASA Horizons URL generator</title>
  <script src="js/jquery.js"></script>
  $( document ).ready(function() { // After page load is complete...
		$("#btnGo" ).click(function() { // Associate function to the button to monitor clicking.
			commandsText = $('textarea#commands').val(); // Upon clicking, assign textarea content to variable.		  
			commandsText = commandsText.replace(/\r/g,'&'); // Replace carriage returns
			commandsText = commandsText.replace(/\n/g,'&');	 // Replace carriage returns	  
			commandsText = commandsText.replace(/ /g,"%20"); // Replace spaces			  
			commandsText = commandsText.replace(/\'/g,'%27'); // Replace quotes	  
			var fullURL = 	"https://ssd.jpl.nasa.gov/horizons_batch.cgi?batch=1&" + commandsText; //* Prepend URL base
            window.open(fullURL); // Open Url
  <a href="https://ssd.jpl.nasa.gov/horizons.cgi">Source</a>:<br>
    <textarea id="commands" name="commands" rows = "30" cols = "50"></textarea><br>
    Resulting URL:<br>
    <textarea id="URL" name="URL" rows = "30" cols = "50"></textarea><br>
    <button id="btnGo" name="btnGo">Go</button>
  • 1
    $\begingroup$ added Javascript source (you also asked " I want to build a url query and receive a json of text with state vectors in return "). Unfortunately I can't see any JSON output option in Horizons. $\endgroup$
    – jumpjack
    Oct 30, 2018 at 13:29
  • $\begingroup$ oh you are right, I did! That was 2.5 years ago, close to when I started. I have this allergy to curly braces so I'm only able to use Python. I think your answer is helpful and I've up voted, but there are now several Python packages that do this. I wouldn't use a JSON anymore though. Thanks again! $\endgroup$
    – uhoh
    Oct 30, 2018 at 14:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.