Overview
When using the CircleCI API to generate and retrieve usage reports, you may encounter a situation where the state field indicates completed, but the download_urls field remains null. This article explains the expected behavior of the Usage API, what to do when download URLs are not immediately available, and how to address potential errors during the process.
Prerequisites
You must have a CircleCI account with the appropriate permissions to access the organization's data.
Ensure you have generated a valid CircleCI API token for authentication.
Instructions
To create a usage export job, use the following curl command:
curl --request POST \
--url "https://circleci.com/api/v2/organizations/<ORG_ID>/usage_export_job" \
--header 'Circle-Token: <YOUR_CIRCLECI_TOKEN>' \
--header 'content-type: application/json' \
--data '{"start":"<START_DATE>","end":"<END_DATE>"}'There are 4 keys bits of information that you will need for the command above
<ORG_ID>- This can be found in 'Organization settings' via the Overview' tab<YOUR_CIRCLECI_TOKEN>- You have this saved somewhere safe. Don't worry if you don't, a new one can be created by going into the 'Personal API Tokens' section of your user settings.<START_DATE>- The date that you want the report to start from. It should be in the format YYYY-MM-DD and cannot be older than 90 days.<END_DATE>- The date that you want the report to end at. It should be in the format YYYY-MM-DD and cannot be older than 90 days.Note: The report window cannot be longer than 32 days.
The POST curl request should return something that looks similar to this:
{"usage_export_job_id":"<JOB_ID>","state":"created","start":"2025-11-01T00:00:00Z","end":"2025-11-17T16:00:00Z","download_urls":null}The <JOB_ID> from the output above will need to be used to retrieve and download the reports via a download URL. To check the status of the export job and retrieve the download URL, use the following curl command:
curl --request GET \ --url "https://circleci.com/api/v2/organizations/<ORG_ID>/usage_export_job/<JOB_ID>" \ --header 'Circle-Token: <YOUR_CIRCLECI_TOKEN>'
Solution
The state field changing to completed signifies that your request has been processed. However, the download_urls field may not be immediately populated. This delay occurs because the system needs time to prepare the data. If no data is available for the specified date range, the URL may still be provided later.
If you receive a null response for download_urls, continue to poll the GET request periodically until the field is populated. In case of an "Internal server error" message, it indicates a temporary issue with the CircleCI service. If this error persists, consider reaching out for support.
Downloading the reports
The GET command will produce either a single download URL or multiple download URLs which will be found in download_urls. The number of URLs will depend on the amount of data returned and the number of days queried.
Each URL will be encoded with unicode, meaning that things like '/' will be written as %2F and '&' will be written as \u0026. Unfortunately we don't have an out of the box way to decode these, however the following command can be used to decode the URL:
echo '<DOWNLOAD_URL>' \
| python3 -c '
import sys
import urllib.parsedata = sys.stdin.read()
data = data.encode().decode("unicode_escape")
print(urllib.parse.unquote(data))
'Alternative solution
An alternative way to generate a usage report is to use our 'Usage Export Script' which can be found in our public repo on GitHub which can be found here.
This script essentially does the both of the commands above in one command and if multiple download urls are created, it will combine them into one CSV file.
Additional Resources
Remember, the availability of the download URL can vary, and it's normal to experience a delay. Keep polling the API at reasonable intervals until the data is ready. If you encounter consistent issues or errors, reviewing the API documentation or seeking further support may be necessary.