Push Measurement API
Audience: Admin, Developer
Page Type: How-To + Reference
Plan Feature: kpi_push_measurement (must be enabled for your tenant)
Overview
The Push Measurement API allows external systems to send KPI measurements directly to COS via HTTP requests authenticated with API keys. Unlike the Pull model (where COS fetches data from your APIs), the Push model lets your CI/CD pipelines, cron jobs, or applications send measurements whenever they're ready.
How It Works
External System COS
───────────────── ─────────────
Your App / CI/CD ──POST──► /integrations/push/measurement
(with x-api-key) │
├─ Validate API key
├─ Verify KPI ownership
└─ Write measurement
Prerequisites
- Plan Feature:
kpi_push_measurementmust be enabled - API Key: At least one active API key for your tenant
- Target ID (
kpiId): The ID of a KPI in the KPI register (Goals & Tracking → KPI Tracking, KPI detail → Integration tab) or the ID of an OKR key result - Target type (
kpiType):KPIorOKR_KEY_RESULT
periodStart, periodEnd and periodicity were removed; sending them returns 400 MEASUREMENT_PERIOD_FIELDS_REMOVED. For the KPI target measurementDate (YYYY-MM-DD) is required; there is no "current month" default. An existing record on the same date is updated. The OKR_KEY_RESULT target updates the key result's actual value and takes no date.
The kpiType contract has been simplified. Only two values are accepted:
| Type | Target |
|---|---|
KPI | A KPI in the KPI register (/kpis). Because reports use the same KPI, the measurement appears in every report that uses it. |
OKR_KEY_RESULT | An OKR key result |
The former family values (THEME_KPI, BSC_KPI, ASIS_KPI, SWOT_KPI, PESTLE_KPI, KPI_DEFINITION, PRODUCT_SCORECARD_KPI, etc.) now return 400; there is no compatibility layer. Former per-report KPI IDs are no longer valid. Update your integrations to use the KPI register ID and kpiType: "KPI".
KPIs of every report type (including Porter and BCG) can receive pushes through the KPI register.
Managing API Keys
Create an API Key
Navigate to Settings → API Keys in the COS sidebar, or go to /platform/api-keys.
- Click New API Key
- Enter a descriptive name (e.g., "CI/CD Pipeline", "Monitoring System")
- Optionally set an expiration date
- Click Create
- Copy the key immediately — it will not be shown again
API keys are only displayed once at creation time. Store the key securely (e.g., in a secrets manager or environment variable). If you lose the key, you must create a new one.
Revoke an API Key
If a key is compromised or no longer needed, click the delete button on the key row and confirm. Revoked keys cannot be reactivated.
API key limit
The number of keys is subject to the effective max_push_api_keys entitlement. A preset name alone does not specify a fixed number. If the limit is reached, revoke an unused key or review the effective limit with your administrator. Revocation blocks subsequent calls using that key.
Sending Measurements
Replace https://your-api-host.example with your deployment's configured API base URL. Do not append /api to the web application origin automatically; include that prefix only if it is part of your API configuration. The Push API section in the KPI detail fills in the correct API URL, the KPI ID and the kpiType. The key supplies tenant context, so the request does not need an extra tenant or report ID.
Single Measurement
curl -X POST https://your-api-host.example/integrations/push/measurement \
-H "x-api-key: cos_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"kpiId": "kpi-id-here",
"kpiType": "KPI",
"measurementDate": "2026-09-30",
"actual": 85.5,
"target": 100
}'
Success response: {"success": true, "kpiType": "KPI", "kpiId": "...", "measurementDate": "2026-09-30", "operation": "CREATED", "recordId": "..."}. An existing record on the same date is updated and operation: "UPDATED" is returned.
Batch Measurements (up to 50)
curl -X POST https://your-api-host.example/integrations/push/measurements/batch \
-H "x-api-key: cos_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"measurements": [
{
"kpiId": "kpi-id-1",
"kpiType": "KPI",
"measurementDate": "2026-09-30",
"actual": 85.5,
"target": 100
},
{
"kpiId": "key-result-id-2",
"kpiType": "OKR_KEY_RESULT",
"actual": 42
}
]
}'
Request Fields
| Field | Type | Required | Description |
|---|---|---|---|
kpiId | string | ✅ | KPI register ID or key result ID; not a library item ID |
kpiType | string | ✅ | KPI or OKR_KEY_RESULT |
measurementDate | string | ✅ (KPI) | Measurement date, YYYY-MM-DD (no time zone). A KPI has one record per date; sending the same date again updates it. Not sent for OKR_KEY_RESULT (400 FIELD_NOT_SUPPORTED_FOR_KEY_RESULT). |
actual | number | ✅ | The actual value. Must be a finite JSON number; a string such as "85.5" returns 400. For a key result it updates the "Actual" field; progress is calculated as for manual entry. |
target | number | ❌ | Target value; if omitted, null, or blank, inherits the target of the same KPI's closest measurement dated before measurementDate. Explicit 0 is preserved. Defaults to 0 without a usable previous target. |
forecast | number | ❌ | Forecast value (KPI only). For a key result: 400 FIELD_NOT_SUPPORTED_FOR_KEY_RESULT. |
statusNote | string | ❌ | Status note |
idempotencyKey | string | ❌ | KPI only; required for a categorized push. Reuse a key only for an identical request. |
configurationVersion, categoryValues | ❌ | Categorized KPI only. With "Sum of categories" the server computes the total and every active category must be sent (400 CATEGORY_VALUES_INCOMPLETE); with "Manually entered total" the actual total is required (400 MANUAL_TOTAL_REQUIRED). |
A key result body accepts only {kpiType, kpiId, actual, target?, statusNote?}; the response carries operation: "UPDATED" and the current progress. In an approved OKR plan a target different from the stored value returns 403 OKR_LOCKED; the actual value stays writable.
Code Examples
Python
import requests
API_KEY = "cos_your_api_key_here"
BASE_URL = "https://your-api-host.example"
response = requests.post(
f"{BASE_URL}/integrations/push/measurement",
headers={
"x-api-key": API_KEY,
"Content-Type": "application/json",
},
json={
"kpiId": "kpi-id-here",
"kpiType": "KPI",
"measurementDate": "2026-09-30",
"actual": 85.5,
"target": 100,
},
)
print(response.status_code, response.json())
JavaScript / Node.js
const API_KEY = 'cos_your_api_key_here';
const BASE_URL = 'https://your-api-host.example';
const response = await fetch(`${BASE_URL}/integrations/push/measurement`, {
method: 'POST',
headers: {
'x-api-key': API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
kpiId: 'kpi-id-here',
kpiType: 'KPI',
measurementDate: '2026-09-30',
actual: 85.5,
target: 100,
}),
});
const data = await response.json();
console.log(data);
Authentication
All Push API requests must include the x-api-key header with a valid, active API key.
The endpoint accepts external HTTP clients, but anonymous requests and a login JWT alone cannot push measurements. A permitted user signs in to COS, opens Settings → API Keys, creates a key, and gives that key to the external system. The key fixes the tenant context: it cannot write another tenant's KPI. The tenant's kpi_push_measurement plan feature must be enabled.
Target inheritance uses the exact kpiType and kpiId target, never a shared library item or another KPI. The previous measurement is the closest one dated before the sent measurementDate. The screen prefills this target and permits editing. See the request fields and target defaults.
Error Responses
Single request (/integrations/push/measurement): a rejected request returns an HTTP error and the body carries a machine-readable errorCode. Clients that check res.ok never treat a rejected measurement as accepted.
| Status | errorCode | Meaning |
|---|---|---|
401 | — | Missing or invalid API key |
403 | — / FORBIDDEN | Key revoked or expired, tenant inactive or plan feature disabled; (when the switch is on) the target has no active binding; a target change in an approved OKR plan (OKR_LOCKED) or an inactive unit |
400 | INVALID | Invalid body, a string instead of a number, or unsupported kpiType (including the former family values) |
400 | MEASUREMENT_PERIOD_FIELDS_REMOVED | periodStart, periodEnd or periodicity was sent |
400 | FIELD_NOT_SUPPORTED_FOR_KEY_RESULT | measurementDate, forecast or categoryValues sent for a key result |
400 | CATEGORY_VALUES_INCOMPLETE / MANUAL_TOTAL_REQUIRED | Categorized push with a missing category value / missing manual total |
404 | NOT_FOUND | KPI or key result not found or not in the key's tenant |
409 | KPI_RETIRED | The KPI is retired; measurements are not accepted |
409 | CONFLICT | Idempotency key or breakdown version conflict |
422 | CYCLE_LOCKED | The key result's OKR cycle is closed (CLOSED/COMPLETED) |
429 | — | Rate limit exceeded |
Batch request (/integrations/push/measurements/batch, up to 50): every item is processed separately. The response is 200 with total, succeeded, failed and per-item results. A failed item has success: false, an errorCode (INVALID, NOT_FOUND, KPI_RETIRED, CYCLE_LOCKED, CONFLICT, FORBIDDEN, ERROR) and a message; the other items are still written.
{
"total": 2, "succeeded": 1, "failed": 1,
"results": [
{ "success": true, "kpiType": "KPI", "kpiId": "kpi-id-1", "measurementDate": "2026-09-30", "operation": "CREATED", "recordId": "..." },
{ "success": false, "kpiType": "OKR_KEY_RESULT", "kpiId": "key-result-id-2", "errorCode": "CYCLE_LOCKED", "error": "..." }
]
}
Rules
- Push is not affected by closed reports or a closed planning period that use the KPI; an active KPI can always be written.
- A retired KPI cannot be written (
KPI_RETIRED). - A key result in a closed OKR cycle cannot be written (
CYCLE_LOCKED). - Every write appears in the KPI's History tab with the
Push APIchannel and the API key ID. - Binding requirement: By default a push is accepted for any active KPI in the key's tenant, even without an integration binding. If the installation administrator enables
PUSH_REQUIRE_ACTIVE_BINDING, a push is accepted only when the target KPI or key result has an active binding; otherwise403 FORBIDDENis returned. The default of this switch is under product review; ask your administrator for your installation's value.
Security Notes
- API keys are hashed (SHA-256) before storage — COS never stores raw keys
- Keys are identified by a 8-character prefix (
cos_xxxx) for management lastUsedAtis updated on each successful request- Set expiration dates for rotating keys on a schedule
- Revoke keys immediately if compromised
In-App Push Snippet
In the Integration tab of a KPI in KPI Tracking, or in the KPI detail drawer of a report, the Push API section (visible when kpi_push_measurement is enabled) provides ready-to-use code snippets with the KPI ID and kpiType: "KPI" pre-filled. Just replace YOUR_API_KEY with your actual key.
Push vs Pull Comparison
| Aspect | Push API | Pull Integration |
|---|---|---|
| Direction | External → COS | COS → External |
| Auth | API Key (x-api-key) | JWT + configured endpoint auth |
| Schedule | On-demand (caller decides) | Cron-based (COS scheduler) |
| Setup | Create API key, send HTTP POST | Configure endpoint + binding |
| Plan Feature | kpi_push_measurement | kpi_api_integration |
| Best For | CI/CD, event-driven, webhooks | Periodic polling of external APIs |