diff --git a/docs/quickstart/analytics/data-api-demo.php b/docs/quickstart/analytics/data-api-demo.php
new file mode 100644
index 0000000..c9cf977
--- /dev/null
+++ b/docs/quickstart/analytics/data-api-demo.php
@@ -0,0 +1,298 @@
+ $consumerKey,
+ 'domain' => 'localhost'
+];
+
+// Request packet
+$requestPacket = [
+ 'limit' => 3
+];
+
+// Make the Data API request
+$dataApi = new DataApi();
+$response = $dataApi->request(
+ $endpoint,
+ $securityPacket,
+ $consumerSecret,
+ $requestPacket,
+ $action
+);
+
+// Get response data
+$statusCode = $response->getStatusCode();
+$responseBody = $response->getBody();
+
+// Extract metadata from the request packet to show what headers were sent
+// The SDK automatically adds metadata to the request packet
+$init = new Init('data', $securityPacket, $consumerSecret, $requestPacket, $action, null, null, $endpoint);
+$generatedRequest = $init->generate();
+$decodedRequest = json_decode($generatedRequest['request'], true);
+$metadata = $decodedRequest['meta'] ?? [];
+
+// Extract the metadata values that are sent as headers
+$consumerHeader = $metadata['consumer'] ?? 'N/A';
+$actionHeader = $metadata['action'] ?? 'N/A';
+$sdkVersion = $metadata['sdk']['version'] ?? 'unknown';
+$sdkLang = $metadata['sdk']['lang'] ?? 'unknown';
+$sdkHeader = isset($metadata['sdk']['lang']) && isset($metadata['sdk']['version'])
+ ? strtoupper($metadata['sdk']['lang']) . ':' . ltrim($metadata['sdk']['version'], 'v')
+ : 'N/A';
+
+// Format response body for display
+$formattedResponse = json_encode(json_decode($responseBody), JSON_PRETTY_PRINT);
+
+?>
+
+
+
+
+
+ Data API Demo - Metadata Headers
+
+
+
+
+ Data API Demo - Metadata Headers
+ This page demonstrates the metadata headers that are automatically included in every Data API request.
+
+
+
+
+
+
+
+
+ | Endpoint |
+ |
+
+
+ | Action |
+ |
+
+
+ | Status Code |
+
+
+
+
+ |
+
+
+
+
+
+
+
+
+
+
+
These headers are added automatically by the SDK and are invisible to customers:
+
+
+
+ | Header Name |
+ Header Value |
+
+
+
+
+ X-Learnosity-Consumer |
+ |
+
+
+ X-Learnosity-Action |
+ |
+
+
+ X-Learnosity-SDK |
+ |
+
+
+
+
+
Metadata Format:
+
+ - Consumer: The consumer key from the security packet
+ - Action: Format is
{method}_{endpoint} (e.g., )
+ - SDK: Format is
{language}:{version} (e.g., )
+
+
+
+
SDK Metadata (in request packet):
+
+ - Version:
+ - Language:
+ - Language Version:
+ - Platform:
+
+
+
+
+
+
+
+
+
+
+
How It Works
+
+ - The SDK automatically extracts the consumer key from the security packet
+ - The SDK builds the action metadata by combining the HTTP method with the endpoint path
+ - The SDK includes version information from the SDK version file
+ - These values are added as HTTP headers (
X-Learnosity-Consumer and X-Learnosity-Action)
+ - The headers are available at the ALB layer for routing decisions
+ - No changes are required to customer code - it's completely transparent!
+
+
+
+
+
diff --git a/docs/quickstart/index.html b/docs/quickstart/index.html
index 3e0f521..30b97e6 100644
--- a/docs/quickstart/index.html
+++ b/docs/quickstart/index.html
@@ -44,6 +44,10 @@ Analytics examples
Paginated data extraction
Example of how to work with large datasets in Data API that return paginated results. Learn more.
+
+
+ Data API metadata headers
+ Demonstrates the metadata headers that are automatically included in every Data API request by the SDK.
diff --git a/src/Request/Init.php b/src/Request/Init.php
index 4174cef..022349d 100644
--- a/src/Request/Init.php
+++ b/src/Request/Init.php
@@ -261,7 +261,8 @@ private function deriveAction(): string
// Remove version information from the path
// (e.g., /v2023.1.lts/itembank/items -> /itembank/items, /v1/sessions -> /sessions, /developer/items -> /items)
- $path = preg_replace('/\/(v\d+(\.\d+)*(\.[a-zA-Z]+)?|latest(-lts)?|developer)/', '', $path);
+ // Supports: v1, v1.85.0, v2023.1.lts, v2025.3.LTS, v2022.3.preview1, latest, latest-lts, developer
+ $path = preg_replace('/\/(v\d+(\.\d+)*(\.[a-zA-Z0-9]+)?|latest(-lts)?|developer)/', '', $path);
// Ensure path starts with /
if (!empty($path) && $path[0] !== '/') {
diff --git a/src/Request/Remote.php b/src/Request/Remote.php
index 731849e..531d62f 100644
--- a/src/Request/Remote.php
+++ b/src/Request/Remote.php
@@ -140,6 +140,15 @@ private function addMetadataHeaders(array $headers, array $post): array
if (isset($meta['action'])) {
$headers[] = 'X-Learnosity-Action: ' . $meta['action'];
}
+
+ // Add SDK header if available (format: language:version, e.g., "PHP:1.1.0")
+ if (isset($meta['sdk']['lang']) && isset($meta['sdk']['version'])) {
+ $lang = strtoupper($meta['sdk']['lang']);
+ $version = $meta['sdk']['version'];
+ // Remove 'v' prefix if present
+ $version = ltrim($version, 'v');
+ $headers[] = 'X-Learnosity-SDK: ' . $lang . ':' . $version;
+ }
} catch (Exception $e) {
// Silently ignore JSON decode errors to avoid breaking requests
}
diff --git a/tests/Request/InitTest.php b/tests/Request/InitTest.php
index c2eedfa..81c380a 100644
--- a/tests/Request/InitTest.php
+++ b/tests/Request/InitTest.php
@@ -307,6 +307,84 @@ public function testDataApiActionMetadata()
'endpoint' => 'https://data.learnosity.com/v2023.1.lts/session_scores',
'action' => null, // should default to 'get'
'expected' => 'get_/session_scores'
+ ],
+ // Test uppercase LTS
+ [
+ 'endpoint' => 'https://data.learnosity.com/v2025.3.LTS/itembank/items',
+ 'action' => 'get',
+ 'expected' => 'get_/itembank/items'
+ ],
+ // Test v2025.2.LTS
+ [
+ 'endpoint' => 'https://data.learnosity.com/v2025.2.LTS/sessions',
+ 'action' => 'get',
+ 'expected' => 'get_/sessions'
+ ],
+ // Test simple v1
+ [
+ 'endpoint' => 'https://data.learnosity.com/v1/items',
+ 'action' => 'get',
+ 'expected' => 'get_/items'
+ ],
+ // Test v1.85.0
+ [
+ 'endpoint' => 'https://data.learnosity.com/v1.85.0/itembank/items',
+ 'action' => 'get',
+ 'expected' => 'get_/itembank/items'
+ ],
+ // Test latest
+ [
+ 'endpoint' => 'https://data.learnosity.com/latest/itembank/items',
+ 'action' => 'get',
+ 'expected' => 'get_/itembank/items'
+ ],
+ // Test latest-lts
+ [
+ 'endpoint' => 'https://data.learnosity.com/latest-lts/sessions',
+ 'action' => 'get',
+ 'expected' => 'get_/sessions'
+ ],
+ // Test developer
+ [
+ 'endpoint' => 'https://data.learnosity.com/developer/items',
+ 'action' => 'get',
+ 'expected' => 'get_/items'
+ ],
+ // Test preview1 (alphanumeric suffix)
+ [
+ 'endpoint' => 'https://data.learnosity.com/v2022.3.preview1/items',
+ 'action' => 'get',
+ 'expected' => 'get_/items'
+ ],
+ // Test preview2
+ [
+ 'endpoint' => 'https://data.learnosity.com/v2022.2.preview2/sessions',
+ 'action' => 'get',
+ 'expected' => 'get_/sessions'
+ ],
+ // Test beta2
+ [
+ 'endpoint' => 'https://data.learnosity.com/v1.2.3.beta2/test',
+ 'action' => 'get',
+ 'expected' => 'get_/test'
+ ],
+ // Test alpha1
+ [
+ 'endpoint' => 'https://data.learnosity.com/v2024.1.alpha1/items',
+ 'action' => 'get',
+ 'expected' => 'get_/items'
+ ],
+ // Test URL with no version
+ [
+ 'endpoint' => 'https://data.learnosity.com/items',
+ 'action' => 'get',
+ 'expected' => 'get_/items'
+ ],
+ // Test false positive: path starting with 'v' but not a version
+ [
+ 'endpoint' => 'https://data.learnosity.com/validate/items',
+ 'action' => 'get',
+ 'expected' => 'get_/validate/items'
]
];
diff --git a/tests/Request/RemoteTest.php b/tests/Request/RemoteTest.php
index a31a626..261b7e7 100644
--- a/tests/Request/RemoteTest.php
+++ b/tests/Request/RemoteTest.php
@@ -48,7 +48,7 @@ public function testMetadataHeadersAddedToDataApiRequests()
// Create Data API request data with metadata
$requestData = [
'security' => '{"consumer_key":"test_consumer","domain":"localhost"}',
- 'request' => '{"meta":{"sdk":{"version":"test"},"consumer":"test_consumer","action":"get_/itembank/items"},"limit":100}',
+ 'request' => '{"meta":{"sdk":{"version":"v1.1.0","lang":"php"},"consumer":"test_consumer","action":"get_/itembank/items"},"limit":100}',
'action' => 'get'
];
@@ -58,6 +58,7 @@ public function testMetadataHeadersAddedToDataApiRequests()
// Verify that metadata headers were added
$consumerHeaderFound = false;
$actionHeaderFound = false;
+ $sdkHeaderFound = false;
foreach ($result as $header) {
if (strpos($header, 'X-Learnosity-Consumer: test_consumer') === 0) {
@@ -66,10 +67,14 @@ public function testMetadataHeadersAddedToDataApiRequests()
if (strpos($header, 'X-Learnosity-Action: get_/itembank/items') === 0) {
$actionHeaderFound = true;
}
+ if (strpos($header, 'X-Learnosity-SDK: PHP:1.1.0') === 0) {
+ $sdkHeaderFound = true;
+ }
}
$this->assertTrue($consumerHeaderFound, 'Consumer header should be added');
$this->assertTrue($actionHeaderFound, 'Action header should be added');
+ $this->assertTrue($sdkHeaderFound, 'SDK header should be added');
}
/**
@@ -92,7 +97,9 @@ public function testMetadataHeadersNotAddedToNonDataApiRequests()
// Verify that no metadata headers were added
$hasMetadataHeaders = false;
foreach ($result as $header) {
- if (strpos($header, 'X-Learnosity-Consumer:') !== false || strpos($header, 'X-Learnosity-Action:') !== false) {
+ if (strpos($header, 'X-Learnosity-Consumer:') !== false
+ || strpos($header, 'X-Learnosity-Action:') !== false
+ || strpos($header, 'X-Learnosity-SDK:') !== false) {
$hasMetadataHeaders = true;
break;
}