GHG Audit Export

Table of contents

  • Overview

  • Setup

    • Authentication Token

  • Trigger Audit Export

    • Output Description

  • Get Audit Export Status

    • Output Description

  • Download Audit Export

  • Related Links

Overview

This notebook demonstrates how to use the GHG Audit Export APIs to retrieve a full audit record of API activity for your organization for a given date range.

The export is generated asynchronously. The workflow is:

  1. Trigger — submit an export request with a date range using POST /audit.

  2. Poll — check the status of the request using GET /audit/status until the status is COMPLETED or FAILED.

  3. Download — retrieve the ZIP archive using GET /audit/download once the status is COMPLETED.

All endpoints require an admin bearer token. If the authenticated user is not an admin, the service returns 403 Forbidden.

Setup

Ensure that Python 3+ is installed on your system.

Note: To run this notebook, add your credentials to ../../../auth/secrets.ini and ../../../auth/config.ini.

Example secrets.ini format:

[EAPI]
api.pat_token = <Your Emissions API PAT token>
api.tenant_id = <Your Emissions API Tenant Id>
[ ]:
# Install the prerequisite Python packages
%pip install pandas configparser IPython requests
[ ]:
import pandas as pd
import configparser
import requests
import json
import time
from IPython.display import display as display_summary

Authentication Token

Run the following code snippet to generate a Bearer Token using your PAT token. These admin endpoints require an authenticated admin user.

[ ]:
config = configparser.RawConfigParser()
config.read(['../../../auth/secrets.ini','../../../auth/config.ini'])

EAPI_PAT_TOKEN      = config.get('EAPI', 'api.pat_token')
EAPI_TENANT_ID      = config.get('EAPI', 'api.tenant_id')

EAPI_AUTH_ENDPOINT  = config.get('EAPI', 'api.auth_endpoint')
EAPI_BASE_URL       = config.get('EAPI', 'api.base_url')

EAPI_AUTH_CLIENT_ID = 'saascore-' + EAPI_TENANT_ID
EAPI_CLIENT_ID      = 'ghgemissions-' + EAPI_TENANT_ID

auth_request_headers: dict = {}
auth_request_headers["X-IBM-Client-Id"] = EAPI_AUTH_CLIENT_ID
auth_request_headers["X-IBM-Envizi-Pat"] = EAPI_PAT_TOKEN

verify = True

auth_url = f"{EAPI_AUTH_ENDPOINT}"

response = requests.post(url = auth_url,
                        headers = auth_request_headers,
                        verify  = verify
                       )
if response.status_code == 200:
    jwt_token = response.text
    print("Authentication Success")
else:
    print("Authentication Failed")
    print(response.text)
Authentication Success

Trigger Audit Export

Use POST /audit to queue an audit export job for a given date range.

The request body accepts the following parameters:

  • fromDate (required): Start date of the audit period (inclusive), in YYYY-MM-DD format.

  • toDate (required): End date of the audit period (inclusive), in YYYY-MM-DD format.

  • apiName (optional): Filter the export to a specific API endpoint — partial match on endpoint path.

The endpoint returns:

  • 202 Accepted for a fresh request, or a retry after a previous failure.

  • 409 Conflict if an identical request is already in progress or has already completed.

[ ]:
AUDIT_ENDPOINT = f"{EAPI_BASE_URL}/audit"

payload = {
    'fromDate': '2025-01-01',
    'toDate':   '2025-03-31'
    # Optional: uncomment to filter by a specific API endpoint
    # 'apiName': '/location'
}

request_headers: dict = {}
request_headers['Content-Type'] = 'application/json'
request_headers['x-ibm-client-id'] = EAPI_CLIENT_ID
request_headers['Authorization'] = 'Bearer ' + jwt_token

response = requests.post(
    AUDIT_ENDPOINT,
    headers=request_headers,
    data=json.dumps(payload)
)

print(f'Status Code: {response.status_code}')

if response.status_code == 202:
    response_json = response.json()
    request_id = response_json.get('requestId')
    print(f'requestId: {request_id}')
    display_summary(pd.json_normalize(response_json))
elif response.status_code == 409:
    print('Conflict: an identical request is already in progress or completed.')
    print(response.text)
else:
    print(response.text)
Status Code: 202
requestId: tenant-uuid/20250101/20250331

Output Description

requestId - Unique identifier for the export request. Pass this to the status and download endpoints.

status - Current processing status. On acceptance this will be QUEUED.

message - Human-readable result message.

submittedAt - ISO-8601 timestamp indicating when the request was submitted.

links - Hypermedia links for the status check and download endpoints.

Possible HTTP status codes:

  • 202 - Request accepted — fresh request or retry after a previous failure

  • 400 - Bad request — missing or invalid parameters

  • 403 - Forbidden — admin token required

  • 409 - Conflict — request is already in progress or already completed

  • 500 - Internal server error

Get Audit Export Status

Use GET /audit/status to check the current processing status of an audit export request.

Pass the requestId returned by POST /audit as a query parameter. The endpoint always returns HTTP 200 for a valid requestId; the status field in the response body indicates the actual job outcome.

The tenant extracted from the token must match the tenant embedded in the requestId.

Possible status values:

Status

Description

QUEUED

Request accepted and waiting to be processed

IN_PROGRESS

Report is currently being generated

COMPLETED

Report is ready for download

FAILED

Report generation failed — see message for details

[ ]:
AUDIT_STATUS_ENDPOINT = f"{EAPI_BASE_URL}/audit/status"

request_headers: dict = {}
request_headers['Content-Type'] = 'application/json'
request_headers['x-ibm-client-id'] = EAPI_CLIENT_ID
request_headers['Authorization'] = 'Bearer ' + jwt_token

print('Polling status...')

while True:
    response = requests.get(
        AUDIT_STATUS_ENDPOINT,
        headers=request_headers,
        params={'requestId': request_id}
    )

    if response.status_code != 200:
        print(f'Unexpected status code: {response.status_code}')
        print(response.text)
        break

    status_json = response.json()
    current_status = status_json.get('status')
    print(f'Status: {current_status}')

    if current_status == 'COMPLETED':
        print('Export is ready for download.')
        display_summary(pd.json_normalize(status_json))
        break
    elif current_status == 'FAILED':
        print(f'Export failed: {status_json.get("message")}')
        break
    else:
        # QUEUED or IN_PROGRESS — wait before polling again
        time.sleep(10)
Polling status...
Status: QUEUED
Status: IN_PROGRESS
Status: COMPLETED
Export is ready for download.

Output Description

requestId - The unique identifier of the export request.

status - Current job status: QUEUED, IN_PROGRESS, COMPLETED, or FAILED.

submittedAt - ISO-8601 timestamp when the request was submitted. Present for QUEUED, IN_PROGRESS, and FAILED states.

completedAt - ISO-8601 timestamp when the export completed. Present only when status is COMPLETED.

message - Human-readable detail. Present when status is FAILED — describes why the export could not be generated.

links.download - Download URL. Present only when status is COMPLETED.

Possible HTTP status codes:

  • 200 - Status returned — always HTTP 200 for a valid requestId

  • 400 - Invalid or missing requestId, or requestId belongs to a different tenant

  • 403 - Forbidden — admin token required

Download Audit Export

Use GET /audit/download to download the completed audit export. This endpoint is only available when the export status is COMPLETED.

The response is a ZIP archive (application/octet-stream) containing a single CSV file named audit-export.csv.

The tenant extracted from the token must match the tenant embedded in the requestId.

[ ]:
AUDIT_DOWNLOAD_ENDPOINT = f"{EAPI_BASE_URL}/audit/download"

request_headers: dict = {}
request_headers['x-ibm-client-id'] = EAPI_CLIENT_ID
request_headers['Authorization'] = 'Bearer ' + jwt_token

response = requests.get(
    AUDIT_DOWNLOAD_ENDPOINT,
    headers=request_headers,
    params={'requestId': request_id}
)

print(f'Status Code: {response.status_code}')

if response.status_code == 200:
    output_file = 'audit-export.zip'
    with open(output_file, 'wb') as f:
        f.write(response.content)
    print(f'Export saved to: {output_file}')
elif response.status_code == 409:
    print('Report is still processing or generation failed. Check status before downloading.')
else:
    print(response.text)
Status Code: 200
Export saved to: audit-export.zip

The downloaded ZIP archive contains a single file: audit-export.csv.

Possible HTTP status codes:

  • 200 - ZIP file returned successfully

  • 400 - Invalid requestId for this tenant

  • 403 - Forbidden — admin token required

  • 409 - Report still processing, or generation failed