This page describes how to set up the Jam API.
The Jam API allows you to pull all training-related data from Jam so you can use it for your custom reporting in your business intelligence (BI) tool, LMS, or other systems.
Note: For LMS integration, you can also use the xAPI standard as an alternative.
Create an API Key for your OrganizationAPI Documentation - data-export EndpointExample Use CasesExample 1: Get all sessions played by all usersExample 2: Get the most Active User by Number of Sessions
Create an API Key for your Organization
The first step is to create an API key for your organization.
To create an API key, you need to be an “Owner” of your organization in Jam.
As an Owner, you can go to the following page to create an API key:

API Documentation - data-export Endpoint
Once you have created an API key, you can pull data from the
data-export endpoint of the Jam API. You find documentation of what you can get from this endpoint here:
The most relevant entities for your reporting are:
- Users:
user,teams
- Training content:
missions,tracks
- Training activity:
sprints,track-assignments,mission-assignmentscreated by managers to drive engagement- the resulting
sessionsplayed by learners.
Sessions are defined as a single role-play round (audio interaction + AI feedback) played by one user of a specific mission. The session object includes the most important performance-related information:
- the overall session
score0–100 — the main performance metric in Jam
- breakdown per “task” in the scorecard — underlying
itemScore0 to 6, mapped to status “solved”, “partly solved”, or “not yet solved” for the user
Example Use Cases
Here are two Python examples illustrating of the kind of queries you can write with the data available from the
data-export endpoint. Example 1: Get all sessions played by all users
What we do here
In this Python example, we use the Jam
data-export endpoint to get all role-play sessions played by all users to track each users activity. For this we
- Start from the
sessionsobject
- Extract the
score(0-100) for the session and whether it was successfullycompleted
- Add user names from the
usersobject
- Add mission titles from the
missionsobject
- Export the result as a csv that can be shared with stakeholders to do computations on
This example is designed for Google Colab - an environment which makes it easy for you to run code and work with the results. For this to work, you need to store your API key in the “Secrets” section of the Colab notebook. You refer to this stored API key in the Configuration.
Python Code
import pandas as pd import requests from google.colab import userdata from typing import Any # --- Configuration --- API_KEY: str = userdata.get('JAM-API') BASE_URL: str = "https://api.wejam.ai/api/v1/data-exports" HEADERS: dict[str, str] = {"accept": "application/json", "X-API-KEY": API_KEY} def fetch_paginated(endpoint: str) -> pd.DataFrame: """Fetches all pages for a given endpoint and returns a DataFrame.""" page: int = 1 results: list[dict[str, Any]] = [] while True: url: str = f"{BASE_URL}/{endpoint}?page={page}&limit=100" response: requests.Response = requests.get(url, headers=HEADERS, timeout=10) response.raise_for_status() data: dict[str, Any] = response.json() results.extend(data["data"]) if not data["meta"].get("hasNext"): break page += 1 return pd.DataFrame(results) def process_data(df_sessions: pd.DataFrame, df_users: pd.DataFrame, df_missions: pd.DataFrame) -> pd.DataFrame: """Handles all data cleaning, merging, and restructuring logic (DRY).""" # 1. Clean Sessions: Extract score early to keep logic localized # Using .get safely and ensuring integer type for the 'Score' column df_sessions["score"] = df_sessions["analysis"].apply( lambda x: x.get("score") if isinstance(x, dict) else 0 ).fillna(0).astype(int) # 2. Enrich Sessions with User Data # We merge only the columns we need (id, names) to keep the DataFrame lean df_enriched = df_sessions.merge( df_users[["id", "firstName", "lastName"]], left_on="learnerUserId", right_on="id", how="left" ) # 3. Enrich with Mission Titles # We use suffixes to avoid name collisions if 'id' or other meta-cols exist df_enriched = df_enriched.merge( df_missions[["id", "title"]], left_on="missionId", right_on="id", how="left", suffixes=('', '_mission') ) # 4. Final Transformation: Create derived columns and select output # Concatenating name strings into a single column as requested df_enriched["UserName"] = df_enriched["firstName"].fillna("") + " " + df_enriched["lastName"].fillna("") # Selecting and renaming in one step to finalize the schema output = df_enriched[[ "UserName", "title", "createdAt", "score", "completed" ]].rename(columns={ "title": "MissionTitle", "createdAt": "SessionDate", "score": "Score", "completed": "Completed" }) return output.sort_values("SessionDate", ascending=False) def main() -> None: # Step 1: I/O - Fetch raw data print("Fetching data from API...") df_u = fetch_paginated("users") df_s = fetch_paginated("sessions") df_m = fetch_paginated("missions") # Step 2: Logic - Process and transform df_final = process_data(df_s, df_u, df_m) # Step 3: I/O - Export results to csv df_final.to_csv("sessions_with_users.csv", index=False) print(f"\nSuccess. Processed {len(df_final)} sessions.") display(df_final.head(20)) if __name__ == "__main__": main()
Sample output
index | UserName | MissionTitle | SessionDate | Score | Completed |
0 | Emily Carter | Full Sales Call: “How did you get my number?” | 2026-06-03T13:43:48.354Z | 0 | false |
1 | Emily Carter | Warm Outreach: Following Up with Existing Leads | 2026-06-03T13:34:23.313Z | 83 | true |
2 | Emily Carter | Building Rapport: The Passionate Restaurant Owner | 2026-06-03T10:08:53.948Z | 87 | true |
3 | Sophia Bennett | Negotiating a Price Adjustment | 2026-06-03T09:49:56.301Z | 100 | true |
4 | Sophia Bennett | Handling Customer Objections | 2026-06-03T09:43:03.122Z | 94 | true |
5 | Sophia Bennett | Qualifying a Private Seller | 2026-06-03T09:38:16.523Z | 88 | true |
6 | Sophia Bennett | Warm Outreach: Following Up with Existing Leads | 2026-06-03T09:32:55.634Z | 80 | true |
7 | Sophia Bennett | Cold Outreach: First Call with Broker Leads | 2026-06-03T09:26:15.737Z | 92 | true |
8 | Oliver Brooks | Discovery Call: The Customer Who Wants to Explore Options | 2026-06-03T08:26:32.375Z | 88 | true |
9 | Oliver Brooks | Next Steps: “That Could Be Interesting” | 2026-06-03T08:22:21.249Z | 78 | true |
Example 2: Get the most Active User by Number of Sessions
What we do here
The goal here is to get the most active user (in terms of number of sessions played).
For this we create the following file
most_active_user.py.For this we
- Start from the
sessionstable
- Count the number of role-play sessions played by each user.
- Add the users information from the
userstable
# /// script # dependencies = [ # "requests<3", # "pandas", # ] # /// from typing import Any import pandas as pd import requests API_KEY: str = "YOUR_JAM_API_KEY" BASE_URL: str = "https://api.wejam.ai/api/v1/data-exports" HEADERS: dict[str, str] = {"accept": "application/json", "X-API-KEY": API_KEY} def fetch_paginated(endpoint: str) -> list[dict[str, Any]]: page: int = 1 results: list[dict[str, Any]] = [] while True: url: str = f"{BASE_URL}/{endpoint}?page={page}&limit=100" response: requests.Response = requests.get(url, headers=HEADERS, timeout=10) response.raise_for_status() data: dict[str, Any] = response.json() results.extend(data["data"]) if not data["meta"].get("hasNext"): break page += 1 return results def main() -> None: users_data: list[dict[str, Any]] = fetch_paginated("users") sessions_data: list[dict[str, Any]] = fetch_paginated("sessions") # Convert to DataFrames df_sessions: pd.DataFrame = pd.DataFrame(sessions_data) df_users: pd.DataFrame = pd.DataFrame(users_data) # Count sessions per user session_counts: pd.DataFrame = ( df_sessions["learnerUserId"].value_counts().rename_axis("userId").reset_index(name="sessionCount") ) # Join with users df_users["userId"] = df_users["id"] df_merged: pd.DataFrame = session_counts.merge(df_users, on="userId", how="left") # Get most active user most_active_user: pd.Series = df_merged.sort_values(by="sessionCount", ascending=False).iloc[0] print("Most Active User:") print(f"Name: {most_active_user['firstName']} {most_active_user['lastName']}") print(f"Email: {most_active_user['email']}") print(f"Sessions: {most_active_user['sessionCount']}") if __name__ == "__main__": main()
Sample output
We execute the script using
uv (https://docs.astral.sh/uv/)uv run most_active_user.py
