diff --git a/src/main/java/com/cognite/client/CogniteClient.java b/src/main/java/com/cognite/client/CogniteClient.java
index 0194ba43..e212ba9a 100644
--- a/src/main/java/com/cognite/client/CogniteClient.java
+++ b/src/main/java/com/cognite/client/CogniteClient.java
@@ -563,6 +563,17 @@ public Transformations transformations() {
return Transformations.of(this);
}
+ /**
+ * Returns {@link LimitValues} representing the Cognite Limit Values API endpoints.
+ *
+ * Note: This API is currently in alpha and requires the cdf-version header.
+ *
+ * @return The limit values api object.
+ */
+ public LimitValues limitValues() {
+ return LimitValues.of(this);
+ }
+
/**
* Returns the services layer mirroring the Cognite Data Fusion API.
* @return
diff --git a/src/main/java/com/cognite/client/LimitValues.java b/src/main/java/com/cognite/client/LimitValues.java
new file mode 100644
index 00000000..1207b1f1
--- /dev/null
+++ b/src/main/java/com/cognite/client/LimitValues.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright (c) 2020 Cognite AS
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.cognite.client;
+
+import com.cognite.client.dto.LimitValue;
+import com.cognite.client.servicesV1.ConnectorConstants;
+import com.cognite.client.servicesV1.ResponseBinary;
+import com.cognite.client.servicesV1.parser.LimitValueParser;
+import com.cognite.client.servicesV1.util.JsonUtil;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.auto.value.AutoValue;
+import com.google.common.base.Preconditions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This class represents the Cognite Limit Values API endpoint.
+ *
+ * It provides methods for reading {@link LimitValue} objects.
+ *
+ * Note: This API is currently in alpha and requires the cdf-version header.
+ */
+@AutoValue
+public abstract class LimitValues extends ApiBase {
+
+ private static final String CDF_VERSION_HEADER = "cdf-version";
+ private static final String CDF_VERSION_VALUE = "20230101-alpha";
+ private static final ObjectMapper objectMapper = JsonUtil.getObjectMapperInstance();
+
+ private static Builder builder() {
+ return new AutoValue_LimitValues.Builder();
+ }
+
+ protected static final Logger LOG = LoggerFactory.getLogger(LimitValues.class);
+
+ /**
+ * Constructs a new {@link LimitValues} object using the provided client configuration.
+ *
+ * This method is intended for internal use--SDK clients should always use {@link CogniteClient}
+ * as the entry point to this class.
+ *
+ * @param client The {@link CogniteClient} to use for configuration settings.
+ * @return the limit values api object.
+ */
+ public static LimitValues of(CogniteClient client) {
+ return LimitValues.builder()
+ .setClient(client)
+ .build();
+ }
+
+ /**
+ * Retrieves a specific limit value by its ID.
+ *
+ *
Example:
+ *
+ * {@code
+ * LimitValue limitValue = client.limitValues().retrieve("my-limit-id");
+ * }
+ *
+ *
+ * @param limitId The ID of the limit value to retrieve.
+ * @return The retrieved {@link LimitValue}.
+ * @throws Exception if the retrieval fails.
+ */
+ public LimitValue retrieve(String limitId) throws Exception {
+ Preconditions.checkArgument(limitId != null && !limitId.isBlank(),
+ "limitId cannot be null or blank.");
+
+ String loggingPrefix = "retrieve() - ";
+ LOG.debug(loggingPrefix + "Retrieving limit value with id: {}", limitId);
+
+ URI requestUri = buildUri("limits/values/" + limitId);
+
+ ResponseBinary response = getClient().experimental().cdfHttpRequest(requestUri)
+ .withHeader(CDF_VERSION_HEADER, CDF_VERSION_VALUE)
+ .get();
+
+ if (!response.getResponse().isSuccessful()) {
+ throw new Exception("Failed to retrieve limit value. Response code: "
+ + response.getResponse().code()
+ + ", message: " + response.getResponse().message());
+ }
+
+ String responseBody = response.getResponseBodyBytes().toStringUtf8();
+ return parseLimitValue(responseBody);
+ }
+
+ /**
+ * Returns all {@link LimitValue} objects using pagination.
+ *
+ * Example:
+ *
+ * {@code
+ * List allLimitValues = new ArrayList<>();
+ * client.limitValues()
+ * .list()
+ * .forEachRemaining(allLimitValues::addAll);
+ * }
+ *
+ *
+ * @return An {@link Iterator} to page through the results.
+ */
+ public Iterator> list() throws Exception {
+ return list(Request.create());
+ }
+
+ /**
+ * Returns {@link LimitValue} objects that match the filters set in the {@link Request}.
+ *
+ * The results are paged through / iterated over via an {@link Iterator}--the entire results set is not buffered in
+ * memory, but streamed in "pages" from the Cognite API.
+ *
+ * Example:
+ *
+ * {@code
+ * List limitValues = new ArrayList<>();
+ * client.limitValues()
+ * .list(Request.create()
+ * .withFilterParameter("prefix", Map.of(
+ * "property", List.of("limitId"),
+ * "value", "atlas.")))
+ * .forEachRemaining(limitValues::addAll);
+ * }
+ *
+ *
+ * @param requestParameters The filters to use for retrieving limit values.
+ * @return An {@link Iterator} to page through the results.
+ */
+ public Iterator> list(Request requestParameters) {
+ return new LimitValuesIterator(requestParameters);
+ }
+
+ /**
+ * Builds the URI for the limit values API endpoint.
+ *
+ * @param pathSegment The path segment to append (e.g., "limits/values/list")
+ * @return The complete URI
+ */
+ private URI buildUri(String pathSegment) {
+ String baseUrl = getClient().getBaseUrl();
+ String project = getClient().getProject();
+ return URI.create(String.format("%s/api/v1/projects/%s/%s",
+ baseUrl, project, pathSegment));
+ }
+
+ /**
+ * Parses a JSON string to a LimitValue object.
+ */
+ private LimitValue parseLimitValue(String json) {
+ try {
+ return LimitValueParser.parseLimitValue(json);
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to parse LimitValue from JSON: " + e.getMessage(), e);
+ }
+ }
+
+ /**
+ * Iterator implementation for paginated limit values listing.
+ */
+ private class LimitValuesIterator implements Iterator> {
+ private final Request requestParameters;
+ private String cursor = null;
+ private boolean hasMore = true;
+
+ public LimitValuesIterator(Request requestParameters) {
+ this.requestParameters = requestParameters;
+ }
+
+ @Override
+ public boolean hasNext() {
+ return hasMore;
+ }
+
+ @Override
+ public List next() {
+ try {
+ return fetchNextBatch();
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to fetch next batch of limit values", e);
+ }
+ }
+
+ private List fetchNextBatch() throws Exception {
+ URI requestUri = buildUri("limits/values/list");
+
+ // Build request body with cursor if available
+ Map requestBody = new java.util.HashMap<>(requestParameters.getRequestParameters());
+
+ if (!requestBody.containsKey("limit")) {
+ requestBody.put("limit", ConnectorConstants.DEFAULT_MAX_BATCH_SIZE);
+ }
+
+ if (cursor != null) {
+ requestBody.put("cursor", cursor);
+ }
+
+ Request request = Request.create().withRequestParameters(requestBody);
+
+ ResponseBinary response = getClient().experimental().cdfHttpRequest(requestUri)
+ .withRequestBody(request)
+ .withHeader(CDF_VERSION_HEADER, CDF_VERSION_VALUE)
+ .post();
+
+ if (!response.getResponse().isSuccessful()) {
+ throw new Exception("Failed to list limit values. Response code: "
+ + response.getResponse().code()
+ + ", message: " + response.getResponse().message());
+ }
+
+ String responseBody = response.getResponseBodyBytes().toStringUtf8();
+ JsonNode root = objectMapper.readTree(responseBody);
+
+ // Parse items
+ List results = new ArrayList<>();
+ JsonNode itemsNode = root.path("items");
+ if (itemsNode.isArray()) {
+ for (JsonNode itemNode : itemsNode) {
+ results.add(LimitValueParser.parseLimitValue(itemNode));
+ }
+ }
+
+ // Check for next cursor
+ JsonNode nextCursorNode = root.path("nextCursor");
+ if (nextCursorNode.isTextual() && !nextCursorNode.textValue().isEmpty()) {
+ cursor = nextCursorNode.textValue();
+ hasMore = true;
+ } else {
+ hasMore = false;
+ }
+
+ return results;
+ }
+ }
+
+ @AutoValue.Builder
+ abstract static class Builder extends ApiBase.Builder {
+ abstract LimitValues build();
+ }
+}
+
diff --git a/src/main/java/com/cognite/client/config/ResourceType.java b/src/main/java/com/cognite/client/config/ResourceType.java
index 29e92109..6742a06b 100644
--- a/src/main/java/com/cognite/client/config/ResourceType.java
+++ b/src/main/java/com/cognite/client/config/ResourceType.java
@@ -45,5 +45,6 @@ public enum ResourceType {
TRANSFORMATIONS_JOBS,
TRANSFORMATIONS_JOB_METRICS,
TRANSFORMATIONS_SCHEDULES,
- TRANSFORMATIONS_NOTIFICATIONS;
+ TRANSFORMATIONS_NOTIFICATIONS,
+ LIMIT_VALUE;
}
diff --git a/src/main/java/com/cognite/client/servicesV1/parser/LimitValueParser.java b/src/main/java/com/cognite/client/servicesV1/parser/LimitValueParser.java
new file mode 100644
index 00000000..f488e4e4
--- /dev/null
+++ b/src/main/java/com/cognite/client/servicesV1/parser/LimitValueParser.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2020 Cognite AS
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.cognite.client.servicesV1.parser;
+
+import com.cognite.client.dto.LimitValue;
+import com.cognite.client.servicesV1.util.JsonUtil;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.collect.ImmutableMap;
+
+import java.util.Map;
+
+import static com.cognite.client.servicesV1.ConnectorConstants.MAX_LOG_ELEMENT_LENGTH;
+
+/**
+ * This class contains a set of methods to help parsing limit value objects between Cognite API representations
+ * (json and proto) and typed objects.
+ */
+public class LimitValueParser {
+ static final String logPrefix = "LimitValueParser - ";
+ static final ObjectMapper objectMapper = JsonUtil.getObjectMapperInstance();
+
+ /**
+ * Parses a limit value json string to {@code LimitValue} proto object.
+ *
+ * @param json The JSON string to parse
+ * @return The parsed LimitValue object
+ * @throws Exception if parsing fails
+ */
+ public static LimitValue parseLimitValue(String json) throws Exception {
+ JsonNode root = objectMapper.readTree(json);
+ return parseLimitValue(root);
+ }
+
+ /**
+ * Parses a limit value JsonNode to {@code LimitValue} proto object.
+ *
+ * This overload allows callers that already have a JsonNode to avoid
+ * the overhead of converting the node to a string and re-parsing.
+ *
+ * @param root The JSON node to parse
+ * @return The parsed LimitValue object
+ * @throws Exception if parsing fails
+ */
+ public static LimitValue parseLimitValue(JsonNode root) throws Exception {
+ LimitValue.Builder builder = LimitValue.newBuilder();
+
+ // limitId is required
+ if (root.path("limitId").isTextual()) {
+ builder.setLimitId(root.get("limitId").textValue());
+ } else {
+ throw new Exception(logPrefix + "Unable to parse attribute: limitId. Item excerpt: "
+ + root.toString().substring(0, Math.min(root.toString().length(), MAX_LOG_ELEMENT_LENGTH)));
+ }
+
+ // value is required
+ if (root.path("value").isIntegralNumber()) {
+ builder.setValue(root.get("value").longValue());
+ } else {
+ throw new Exception(logPrefix + "Unable to parse attribute: value. Item excerpt: "
+ + root.toString().substring(0, Math.min(root.toString().length(), MAX_LOG_ELEMENT_LENGTH)));
+ }
+
+ return builder.build();
+ }
+
+ /**
+ * Builds a request item object from {@link LimitValue}.
+ *
+ * @param element The LimitValue to convert
+ * @return A map representing the request body
+ */
+ public static Map toRequestInsertItem(LimitValue element) {
+ return ImmutableMap.builder()
+ .put("limitId", element.getLimitId())
+ .put("value", element.getValue())
+ .build();
+ }
+}
+
diff --git a/src/main/proto/limit_value.proto b/src/main/proto/limit_value.proto
new file mode 100644
index 00000000..0631cedb
--- /dev/null
+++ b/src/main/proto/limit_value.proto
@@ -0,0 +1,16 @@
+syntax = "proto3";
+
+package com.cognite.client.dto;
+
+option java_package = "com.cognite.client.dto";
+option java_multiple_files = true;
+
+// LimitValue represents a limit value from the Cognite Limits API.
+message LimitValue {
+ // The unique identifier for this limit (e.g., "functions.running_calls")
+ string limit_id = 1;
+
+ // The current value of the limit
+ int64 value = 2;
+}
+
diff --git a/src/test/java/com/cognite/client/LimitValuesIntegrationTest.java b/src/test/java/com/cognite/client/LimitValuesIntegrationTest.java
new file mode 100644
index 00000000..3fb0cb75
--- /dev/null
+++ b/src/test/java/com/cognite/client/LimitValuesIntegrationTest.java
@@ -0,0 +1,192 @@
+package com.cognite.client;
+
+import com.cognite.client.dto.LimitValue;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Integration tests for the LimitValues API.
+ *
+ * Note: These tests require a valid CDF project with the Limits API enabled.
+ * Set the following environment variables:
+ * - TEST_PROJECT: Your CDF project name
+ * - TEST_CLIENT_ID: OAuth client ID
+ * - TEST_CLIENT_SECRET: OAuth client secret
+ * - TEST_TENANT_ID: Azure AD tenant ID
+ * - TEST_HOST: CDF API host (e.g., https://api.cognitedata.com)
+ */
+class LimitValuesIntegrationTest {
+ final Logger LOG = LoggerFactory.getLogger(this.getClass());
+
+ @Test
+ @Tag("remoteCDP")
+ void listLimitValues() throws Exception {
+ Instant startInstant = Instant.now();
+ String loggingPrefix = "IntegrationTest - listLimitValues() - ";
+ LOG.info(loggingPrefix + "Start test. Creating Cognite client.");
+
+ CogniteClient client = TestConfigProvider.getCogniteClient();
+ LOG.info(loggingPrefix + "Finished creating the Cognite client. Duration: {}",
+ Duration.between(startInstant, Instant.now()));
+
+ LOG.info(loggingPrefix + "Start listing limit values.");
+ List listResults = new ArrayList<>();
+
+ try {
+ client.limitValues()
+ .list()
+ .forEachRemaining(listResults::addAll);
+
+ LOG.info(loggingPrefix + "Finished listing limit values. Found {} items. Duration: {}",
+ listResults.size(),
+ Duration.between(startInstant, Instant.now()));
+
+ // Log some sample data if available
+ if (!listResults.isEmpty()) {
+ LimitValue sample = listResults.get(0);
+ LOG.info(loggingPrefix + "Sample limit value - limitId: {}", sample.getLimitId());
+ }
+
+ } catch (Exception e) {
+ LOG.error(loggingPrefix + "Error listing limit values: {}", e.getMessage());
+ throw e;
+ }
+
+ // The test passes if no exception is thrown
+ // The actual count depends on your project's data
+ LOG.info(loggingPrefix + "Test completed successfully.");
+ }
+
+ @Test
+ @Tag("remoteCDP")
+ void listLimitValuesWithFilter() throws Exception {
+ Instant startInstant = Instant.now();
+ String loggingPrefix = "IntegrationTest - listLimitValuesWithFilter() - ";
+ LOG.info(loggingPrefix + "Start test. Creating Cognite client.");
+
+ CogniteClient client = TestConfigProvider.getCogniteClient();
+ LOG.info(loggingPrefix + "Finished creating the Cognite client. Duration: {}",
+ Duration.between(startInstant, Instant.now()));
+
+ LOG.info(loggingPrefix + "First, listing limit values to derive a prefix for filtering.");
+ List initialResults = new ArrayList<>();
+ client.limitValues()
+ .list(Request.create().withRootParameter("limit", 10))
+ .forEachRemaining(initialResults::addAll);
+
+ if (initialResults.isEmpty()) {
+ LOG.warn(loggingPrefix + "No limit values found in project. Skipping filter test.");
+ return;
+ }
+
+ String sampleLimitId = initialResults.get(0).getLimitId();
+ String prefix = sampleLimitId.contains(".")
+ ? sampleLimitId.substring(0, sampleLimitId.indexOf('.') + 1)
+ : sampleLimitId.substring(0, Math.min(5, sampleLimitId.length()));
+ LOG.info(loggingPrefix + "Using derived prefix: '{}'", prefix);
+
+ LOG.info(loggingPrefix + "Start listing limit values with prefix filter.");
+ List filteredResults = new ArrayList<>();
+
+ try {
+ Request filterRequest = Request.create()
+ .withFilterParameter("prefix", Map.of(
+ "property", List.of("limitId"),
+ "value", prefix
+ ));
+
+ client.limitValues()
+ .list(filterRequest)
+ .forEachRemaining(filteredResults::addAll);
+
+ LOG.info(loggingPrefix + "Finished listing filtered limit values. Found {} items. Duration: {}",
+ filteredResults.size(),
+ Duration.between(startInstant, Instant.now()));
+
+ // Check all results match the prefix
+ for (LimitValue lv : filteredResults) {
+ assertTrue(lv.getLimitId().startsWith(prefix),
+ "LimitId should start with prefix: " + prefix);
+ }
+
+ } catch (Exception e) {
+ LOG.error(loggingPrefix + "Error listing limit values with filter: {}", e.getMessage());
+ throw e;
+ }
+
+ LOG.info(loggingPrefix + "Test completed successfully.");
+ }
+
+ @Test
+ @Tag("remoteCDP")
+ void retrieveLimitValueById() throws Exception {
+ Instant startInstant = Instant.now();
+ String loggingPrefix = "IntegrationTest - retrieveLimitValueById() - ";
+ LOG.info(loggingPrefix + "Start test. Creating Cognite client.");
+
+ CogniteClient client = TestConfigProvider.getCogniteClient();
+ LOG.info(loggingPrefix + "Finished creating the Cognite client. Duration: {}",
+ Duration.between(startInstant, Instant.now()));
+
+ // First, list to get a valid limitId
+ LOG.info(loggingPrefix + "First, listing limit values to get a valid ID.");
+ List listResults = new ArrayList<>();
+
+ try {
+ client.limitValues()
+ .list(Request.create().withRootParameter("limit", 1))
+ .forEachRemaining(listResults::addAll);
+
+ if (listResults.isEmpty()) {
+ LOG.warn(loggingPrefix + "No limit values found in project. Skipping retrieve test.");
+ return;
+ }
+
+ String limitId = listResults.get(0).getLimitId();
+ LOG.info(loggingPrefix + "Found limit value with ID: {}. Now retrieving by ID.", limitId);
+
+ // Retrieve by ID
+ LimitValue retrieved = client.limitValues().retrieve(limitId);
+
+ assertNotNull(retrieved, "Retrieved limit value should not be null");
+ assertEquals(limitId, retrieved.getLimitId(), "Limit IDs should match");
+
+ LOG.info(loggingPrefix + "Successfully retrieved limit value. Duration: {}",
+ Duration.between(startInstant, Instant.now()));
+
+ } catch (Exception e) {
+ LOG.error(loggingPrefix + "Error in test: {}", e.getMessage());
+ throw e;
+ }
+
+ LOG.info(loggingPrefix + "Test completed successfully.");
+ }
+
+ @Test
+ @Tag("remoteCDP")
+ void retrieveNonExistentLimitValueThrowsException() throws Exception {
+ String loggingPrefix = "IntegrationTest - retrieveNonExistentLimitValueThrowsException() - ";
+ LOG.info(loggingPrefix + "Start test. Creating Cognite client.");
+
+ CogniteClient client = TestConfigProvider.getCogniteClient();
+
+ LOG.info(loggingPrefix + "Attempting to retrieve non-existent limit value.");
+
+ assertThrows(Exception.class, () -> {
+ client.limitValues().retrieve("non-existent-limit-id-12345");
+ }, "Should throw exception for non-existent limit ID");
+
+ LOG.info(loggingPrefix + "Test completed successfully - exception was thrown as expected.");
+ }
+}
+