Skip to content

Analytics API Examples

This guide shows practical ways to call the Coldtivate analytics API from partner dashboards and integrations. It assumes you already have an analytics API token with the scopes needed for the endpoints you call.

For token creation, scopes, revocation, and security rules, see Analytics API Authentication.

Setup

Use the production base URL unless Coldtivate gives you a staging or partner-specific URL during onboarding.

export COLDTIVATE_API_BASE_URL="https://api.coldtivate.org/api/v1"
export COLDTIVATE_API_TOKEN="ctv_live_example_token"

Examples use anonymised IDs:

Value Meaning
123 Example cooling unit ID
45 Example location ID
2026-01-01 to 2026-01-31 Example reporting window

Do not put API tokens in browser-side JavaScript, mobile apps, query parameters, or logs. Use these JavaScript examples from a trusted backend service or server-rendered integration.

First Request

Request weekly user analytics for January 2026.

curl "$COLDTIVATE_API_BASE_URL/analytics/users?start_date=2026-01-01&end_date=2026-01-31&period=week&page=1&page_size=100" \
  -H "Authorization: Bearer $COLDTIVATE_API_TOKEN"

Successful analytics responses use this shape:

{
  "data": [],
  "meta": {
    "company_id": 42,
    "filters": {
      "start_date": "2026-01-01",
      "end_date": "2026-01-31",
      "period": "week"
    },
    "pagination": {
      "page": 1,
      "page_size": 100,
      "total_count": 0,
      "total_pages": 0,
      "has_next": false,
      "has_previous": false
    }
  }
}

Users Dashboard

Use the users endpoint to build registered user, active user, signup, and per-cooling-unit user widgets. Requires the users scope.

curl

curl "$COLDTIVATE_API_BASE_URL/analytics/users?start_date=2026-01-01&end_date=2026-01-31&period=week&page=1&page_size=100" \
  -H "Authorization: Bearer $COLDTIVATE_API_TOKEN"

Python

import os
import requests

BASE_URL = os.environ.get("COLDTIVATE_API_BASE_URL", "https://api.coldtivate.org/api/v1")
TOKEN = os.environ["COLDTIVATE_API_TOKEN"]

response = requests.get(
    f"{BASE_URL}/analytics/users",
    headers={"Authorization": f"Bearer {TOKEN}"},
    params={
        "start_date": "2026-01-01",
        "end_date": "2026-01-31",
        "period": "week",
        "page": 1,
        "page_size": 100,
    },
    timeout=30,
)
response.raise_for_status()

payload = response.json()
print(payload["data"])
print(payload["meta"]["pagination"])

JavaScript

const baseUrl = process.env.COLDTIVATE_API_BASE_URL || "https://api.coldtivate.org/api/v1";
const token = process.env.COLDTIVATE_API_TOKEN;

const params = new URLSearchParams({
  start_date: "2026-01-01",
  end_date: "2026-01-31",
  period: "week",
  page: "1",
  page_size: "100",
});

const response = await fetch(`${baseUrl}/analytics/users?${params}`, {
  headers: {
    Authorization: `Bearer ${token}`,
  },
});

if (!response.ok) {
  throw new Error(`Users analytics request failed: ${response.status}`);
}

const payload = await response.json();
console.log(payload.data);
console.log(payload.meta.pagination);

Utilization Dashboard

Use the utilization endpoint to build cooling unit occupancy, check-in, check-out, crate, and kg stored widgets. Requires the utilization scope.

curl

curl "$COLDTIVATE_API_BASE_URL/analytics/utilization?start_date=2026-01-01&end_date=2026-01-31&cooling_unit_id=123&period=day&page=1&page_size=100" \
  -H "Authorization: Bearer $COLDTIVATE_API_TOKEN"

Python

import os
import requests

BASE_URL = os.environ.get("COLDTIVATE_API_BASE_URL", "https://api.coldtivate.org/api/v1")
TOKEN = os.environ["COLDTIVATE_API_TOKEN"]

response = requests.get(
    f"{BASE_URL}/analytics/utilization",
    headers={"Authorization": f"Bearer {TOKEN}"},
    params={
        "start_date": "2026-01-01",
        "end_date": "2026-01-31",
        "cooling_unit_id": 123,
        "period": "day",
        "page": 1,
        "page_size": 100,
    },
    timeout=30,
)
response.raise_for_status()

for unit in response.json()["data"]:
    print(unit["cooling_unit_id"], unit["utilization_rate"])

JavaScript

const baseUrl = process.env.COLDTIVATE_API_BASE_URL || "https://api.coldtivate.org/api/v1";
const token = process.env.COLDTIVATE_API_TOKEN;

const params = new URLSearchParams({
  start_date: "2026-01-01",
  end_date: "2026-01-31",
  cooling_unit_id: "123",
  period: "day",
  page: "1",
  page_size: "100",
});

const response = await fetch(`${baseUrl}/analytics/utilization?${params}`, {
  headers: {
    Authorization: `Bearer ${token}`,
  },
});

if (!response.ok) {
  throw new Error(`Utilization analytics request failed: ${response.status}`);
}

const payload = await response.json();
for (const unit of payload.data) {
  console.log(unit.cooling_unit_id, unit.utilization_rate);
}

Revenue Dashboard

Use the revenue endpoint to build paid revenue, pending payment, payment method, and trend widgets. Requires the revenue scope.

curl

curl "$COLDTIVATE_API_BASE_URL/analytics/revenue?start_date=2026-01-01&end_date=2026-01-31&payment_status=paid&period=month&page=1&page_size=100" \
  -H "Authorization: Bearer $COLDTIVATE_API_TOKEN"

Python

import os
import requests

BASE_URL = os.environ.get("COLDTIVATE_API_BASE_URL", "https://api.coldtivate.org/api/v1")
TOKEN = os.environ["COLDTIVATE_API_TOKEN"]

response = requests.get(
    f"{BASE_URL}/analytics/revenue",
    headers={"Authorization": f"Bearer {TOKEN}"},
    params={
        "start_date": "2026-01-01",
        "end_date": "2026-01-31",
        "payment_status": "paid",
        "period": "month",
        "page": 1,
        "page_size": 100,
    },
    timeout=30,
)
response.raise_for_status()

for unit in response.json()["data"]:
    for total in unit["currency_totals"]:
        print(unit["cooling_unit_id"], total["currency"], total["total_revenue"])

JavaScript

const baseUrl = process.env.COLDTIVATE_API_BASE_URL || "https://api.coldtivate.org/api/v1";
const token = process.env.COLDTIVATE_API_TOKEN;

const params = new URLSearchParams({
  start_date: "2026-01-01",
  end_date: "2026-01-31",
  payment_status: "paid",
  period: "month",
  page: "1",
  page_size: "100",
});

const response = await fetch(`${baseUrl}/analytics/revenue?${params}`, {
  headers: {
    Authorization: `Bearer ${token}`,
  },
});

if (!response.ok) {
  throw new Error(`Revenue analytics request failed: ${response.status}`);
}

const payload = await response.json();
for (const unit of payload.data) {
  for (const total of unit.currency_totals) {
    console.log(unit.cooling_unit_id, total.currency, total.total_revenue);
  }
}

Impact Dashboard

Use the impact endpoint to build food loss prevented, income increase, CO2, and community served widgets. Requires the impact scope.

curl

curl "$COLDTIVATE_API_BASE_URL/analytics/impact?start_date=2026-01-01&end_date=2026-01-31&location_id=45&period=month&page=1&page_size=100" \
  -H "Authorization: Bearer $COLDTIVATE_API_TOKEN"

Python

import os
import requests

BASE_URL = os.environ.get("COLDTIVATE_API_BASE_URL", "https://api.coldtivate.org/api/v1")
TOKEN = os.environ["COLDTIVATE_API_TOKEN"]

response = requests.get(
    f"{BASE_URL}/analytics/impact",
    headers={"Authorization": f"Bearer {TOKEN}"},
    params={
        "start_date": "2026-01-01",
        "end_date": "2026-01-31",
        "location_id": 45,
        "period": "month",
        "page": 1,
        "page_size": 100,
    },
    timeout=30,
)
response.raise_for_status()

for item in response.json()["data"]:
    print(item["cooling_unit_id"], item["food_loss_prevented_kg"], item["income_increase"])

JavaScript

const baseUrl = process.env.COLDTIVATE_API_BASE_URL || "https://api.coldtivate.org/api/v1";
const token = process.env.COLDTIVATE_API_TOKEN;

const params = new URLSearchParams({
  start_date: "2026-01-01",
  end_date: "2026-01-31",
  location_id: "45",
  period: "month",
  page: "1",
  page_size: "100",
});

const response = await fetch(`${baseUrl}/analytics/impact?${params}`, {
  headers: {
    Authorization: `Bearer ${token}`,
  },
});

if (!response.ok) {
  throw new Error(`Impact analytics request failed: ${response.status}`);
}

const payload = await response.json();
for (const item of payload.data) {
  console.log(item.cooling_unit_id, item.food_loss_prevented_kg, item.income_increase);
}

Sensor Data Dashboard

Use the sensor-data endpoint to build temperature and humidity charts for a cooling unit. Requires the sensor_data scope. For long ranges, use aggregation=hourly or aggregation=daily to reduce payload size.

curl

curl "$COLDTIVATE_API_BASE_URL/sensor-data?cooling_unit_id=123&specification_type=TEMPERATURE,HUMIDITY&start_date=2026-01-01&end_date=2026-01-31&aggregation=hourly&page=1&page_size=100" \
  -H "Authorization: Bearer $COLDTIVATE_API_TOKEN"

Python

import os
import requests

BASE_URL = os.environ.get("COLDTIVATE_API_BASE_URL", "https://api.coldtivate.org/api/v1")
TOKEN = os.environ["COLDTIVATE_API_TOKEN"]

response = requests.get(
    f"{BASE_URL}/sensor-data",
    headers={"Authorization": f"Bearer {TOKEN}"},
    params={
        "cooling_unit_id": 123,
        "specification_type": "TEMPERATURE,HUMIDITY",
        "start_date": "2026-01-01",
        "end_date": "2026-01-31",
        "aggregation": "hourly",
        "page": 1,
        "page_size": 100,
    },
    timeout=30,
)
response.raise_for_status()

for reading in response.json()["data"]:
    print(
        reading["timestamp"],
        reading["specification_type"],
        reading["value"],
        reading["sample_count"],
    )

JavaScript

const baseUrl = process.env.COLDTIVATE_API_BASE_URL || "https://api.coldtivate.org/api/v1";
const token = process.env.COLDTIVATE_API_TOKEN;

const params = new URLSearchParams({
  cooling_unit_id: "123",
  specification_type: "TEMPERATURE,HUMIDITY",
  start_date: "2026-01-01",
  end_date: "2026-01-31",
  aggregation: "hourly",
  page: "1",
  page_size: "100",
});

const response = await fetch(`${baseUrl}/sensor-data?${params}`, {
  headers: {
    Authorization: `Bearer ${token}`,
  },
});

if (!response.ok) {
  throw new Error(`Sensor data request failed: ${response.status}`);
}

const payload = await response.json();
for (const reading of payload.data) {
  console.log(
    reading.timestamp,
    reading.specification_type,
    reading.value,
    reading.sample_count
  );
}

Pagination

Each analytics response includes meta.pagination. Use has_next to keep requesting pages until all records have been read.

Python

import os
import requests

BASE_URL = os.environ.get("COLDTIVATE_API_BASE_URL", "https://api.coldtivate.org/api/v1")
TOKEN = os.environ["COLDTIVATE_API_TOKEN"]


def fetch_all_pages(endpoint, params):
    page = 1
    results = []

    while True:
        response = requests.get(
            f"{BASE_URL}{endpoint}",
            headers={"Authorization": f"Bearer {TOKEN}"},
            params={**params, "page": page, "page_size": 100},
            timeout=30,
        )
        response.raise_for_status()

        payload = response.json()
        results.extend(payload["data"])

        pagination = payload["meta"]["pagination"]
        if not pagination["has_next"]:
            return results

        page += 1


units = fetch_all_pages(
    "/analytics/utilization",
    {
        "start_date": "2026-01-01",
        "end_date": "2026-01-31",
        "period": "day",
    },
)
print(f"Fetched {len(units)} utilization records")

JavaScript

const baseUrl = process.env.COLDTIVATE_API_BASE_URL || "https://api.coldtivate.org/api/v1";
const token = process.env.COLDTIVATE_API_TOKEN;

async function fetchAllPages(endpoint, params) {
  let page = 1;
  const results = [];

  while (true) {
    const pageParams = new URLSearchParams({
      ...params,
      page: String(page),
      page_size: "100",
    });

    const response = await fetch(`${baseUrl}${endpoint}?${pageParams}`, {
      headers: {
        Authorization: `Bearer ${token}`,
      },
    });

    if (!response.ok) {
      throw new Error(`Analytics request failed: ${response.status}`);
    }

    const payload = await response.json();
    results.push(...payload.data);

    if (!payload.meta.pagination.has_next) {
      return results;
    }

    page += 1;
  }
}

const units = await fetchAllPages("/analytics/utilization", {
  start_date: "2026-01-01",
  end_date: "2026-01-31",
  period: "day",
});

console.log(`Fetched ${units.length} utilization records`);

Error Handling

Handle status codes explicitly. Do not retry every error the same way.

Status Meaning Recommended handling
400 Bad Request Invalid query parameter, date range, pagination value, or filter. Fix the request before retrying.
401 Unauthorized Token is missing, invalid, expired, or revoked. Stop retrying with that token and alert the integration owner.
403 Forbidden Token is valid but lacks the required endpoint scope. Request a token with the minimum required scope.
429 Too Many Requests Token exceeded the rate limit. Back off until X-RateLimit-Reset.

Example 400 response:

{
  "error": {
    "code": "invalid_request",
    "message": "period must be one of day, week, or month.",
    "details": {
      "field": "period"
    }
  }
}

Example 401 response:

{
  "detail": "Invalid API token."
}

Example 403 response:

{
  "detail": "API token does not include the required scope."
}

Example 429 response:

{
  "detail": "Request was throttled. Expected available in 42 seconds."
}

Python Error Handling

import time

import requests


def raise_or_backoff(response):
    if response.status_code == 429:
        reset = int(response.headers.get("X-RateLimit-Reset", "0"))
        wait_seconds = max(reset - int(time.time()), 1)
        time.sleep(wait_seconds)
        return "retry"

    if response.status_code == 401:
        raise RuntimeError("Analytics token is missing, invalid, expired, or revoked.")

    if response.status_code == 403:
        raise RuntimeError("Analytics token does not include the required scope.")

    if response.status_code == 400:
        raise RuntimeError(f"Invalid analytics request: {response.text}")

    response.raise_for_status()
    return "ok"

JavaScript Error Handling

function sleep(milliseconds) {
  return new Promise((resolve) => setTimeout(resolve, milliseconds));
}

async function handleAnalyticsError(response) {
  if (response.status === 429) {
    const reset = Number(response.headers.get("X-RateLimit-Reset") || "0");
    const waitSeconds = Math.max(reset - Math.floor(Date.now() / 1000), 1);
    await sleep(waitSeconds * 1000);
    return "retry";
  }

  if (response.status === 401) {
    throw new Error("Analytics token is missing, invalid, expired, or revoked.");
  }

  if (response.status === 403) {
    throw new Error("Analytics token does not include the required scope.");
  }

  if (response.status === 400) {
    const body = await response.text();
    throw new Error(`Invalid analytics request: ${body}`);
  }

  if (!response.ok) {
    throw new Error(`Analytics request failed: ${response.status}`);
  }

  return "ok";
}

Rate Limiting And Backoff

The default analytics API rate limit is 100 requests per minute per token.

Every analytics response includes these headers:

Header Meaning
X-RateLimit-Limit Maximum requests allowed in the current window.
X-RateLimit-Remaining Requests remaining in the current window.
X-RateLimit-Reset Unix timestamp when the current window resets.

If X-RateLimit-Remaining is low, slow down non-urgent dashboard refreshes. If the API returns 429, wait until X-RateLimit-Reset before retrying. Add a small jitter when many workers may retry at the same time.

Python Backoff Request

import random
import time

import requests


def analytics_get(url, headers, params, max_attempts=3):
    for attempt in range(max_attempts):
        response = requests.get(url, headers=headers, params=params, timeout=30)

        if response.status_code != 429:
            response.raise_for_status()
            return response

        reset = int(response.headers.get("X-RateLimit-Reset", "0"))
        wait_seconds = max(reset - int(time.time()), 1)
        jitter = random.uniform(0, 1)
        time.sleep(wait_seconds + jitter)

    raise RuntimeError("Analytics API rate limit did not clear before max_attempts.")

JavaScript Backoff Request

function sleep(milliseconds) {
  return new Promise((resolve) => setTimeout(resolve, milliseconds));
}

async function analyticsGet(url, options = {}, maxAttempts = 3) {
  for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
    const response = await fetch(url, options);

    if (response.status !== 429) {
      if (!response.ok) {
        throw new Error(`Analytics request failed: ${response.status}`);
      }
      return response;
    }

    const reset = Number(response.headers.get("X-RateLimit-Reset") || "0");
    const waitSeconds = Math.max(reset - Math.floor(Date.now() / 1000), 1);
    const jitterMilliseconds = Math.floor(Math.random() * 1000);
    await sleep(waitSeconds * 1000 + jitterMilliseconds);
  }

  throw new Error("Analytics API rate limit did not clear before maxAttempts.");
}

Verification

Before deploying an integration, verify each endpoint with the token and filters that the dashboard will use:

for endpoint in users utilization revenue impact; do
  curl -i "$COLDTIVATE_API_BASE_URL/analytics/$endpoint?start_date=2026-01-01&end_date=2026-01-31&page=1&page_size=10" \
    -H "Authorization: Bearer $COLDTIVATE_API_TOKEN"
done

curl -i "$COLDTIVATE_API_BASE_URL/sensor-data?cooling_unit_id=123&start_date=2026-01-01&end_date=2026-01-31&page=1&page_size=10" \
  -H "Authorization: Bearer $COLDTIVATE_API_TOKEN"

Expected result for a correctly scoped token is 200 OK for each endpoint. If an endpoint returns 403, the token does not include that endpoint's scope.