curl --request GET \
--url https://api.qobra.co/v2/quotas/{quota_id}/values \
--header 'X-API-Key: <api-key>'import requests
url = "https://api.qobra.co/v2/quotas/{quota_id}/values"
headers = {"X-API-Key": "<api-key>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {'X-API-Key': '<api-key>'}};
fetch('https://api.qobra.co/v2/quotas/{quota_id}/values', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.qobra.co/v2/quotas/{quota_id}/values",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"X-API-Key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.qobra.co/v2/quotas/{quota_id}/values"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("X-API-Key", "<api-key>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.qobra.co/v2/quotas/{quota_id}/values")
.header("X-API-Key", "<api-key>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.qobra.co/v2/quotas/{quota_id}/values")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["X-API-Key"] = '<api-key>'
response = http.request(request)
puts response.read_bodyFetch Quota Values
Extracts the values of a quota: one row per user and per period, with the target set for them. Use this endpoint to compare targets with attainment in a BI tool, or to replicate quotas into a data warehouse. The response is not restricted to a visibility perimeter: the values of every user are returned, unlike in the Qobra app.
curl --request GET \
--url https://api.qobra.co/v2/quotas/{quota_id}/values \
--header 'X-API-Key: <api-key>'import requests
url = "https://api.qobra.co/v2/quotas/{quota_id}/values"
headers = {"X-API-Key": "<api-key>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {'X-API-Key': '<api-key>'}};
fetch('https://api.qobra.co/v2/quotas/{quota_id}/values', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.qobra.co/v2/quotas/{quota_id}/values",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"X-API-Key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.qobra.co/v2/quotas/{quota_id}/values"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("X-API-Key", "<api-key>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.qobra.co/v2/quotas/{quota_id}/values")
.header("X-API-Key", "<api-key>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.qobra.co/v2/quotas/{quota_id}/values")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["X-API-Key"] = '<api-key>'
response = http.request(request)
puts response.read_bodyOverview
This endpoint returns the values of a quota — the target assigned to each person, for each period. Use it to put objectives next to results. Use this endpoint for:- BI dashboards comparing targets with attainment
- Replicating quotas into a data warehouse
- Feeding forecast models with committed objectives
quota_id that belongs to a sandbox environment returns 404 Not Found, even
if the id was copied from the Qobra app while viewing that sandbox. Discover
valid ids with
GET /v2/data-structures,
where every quota is listed with "type": "quota".Your first call
curl --request GET \
--url 'https://api.qobra.co/v2/quotas/QUOTA_ID/values?limit=100' \
--header 'X-API-Key: YOUR_API_KEY'
import requests
url = "https://api.qobra.co/v2/quotas/QUOTA_ID/values"
headers = {"X-API-Key": "YOUR_API_KEY"}
response = requests.get(url, headers=headers, params={"limit": 100})
values = response.json()["data"]
const url = "https://api.qobra.co/v2/quotas/QUOTA_ID/values?limit=100";
const response = await fetch(url, {
headers: { "X-API-Key": "YOUR_API_KEY" },
});
const { data } = await response.json();
Example response
{
"data": [
{
"standard.id": "65a1b2c3d4e5f60718293a01",
"standard.user": {
"id": "507f191e810c19729de860ea",
"email": "sarah.johnson@company.com"
},
"standard.period": "2026-Q1",
"standard.value": { "value": 120000.0, "currency": "EUR" },
"standard.source": "local"
},
{
"standard.id": "65a1b2c3d4e5f60718293a02",
"standard.user": {
"id": "507f191e810c19729de860eb",
"email": "michael.chen@company.com"
},
"standard.period": "2026-Q1",
"standard.value": { "value": 95000.0, "currency": "USD" },
"standard.source": "integration"
}
],
"meta": {
"next_start_id": "65a1b2c3d4e5f60718293a02",
"has_more": true,
"next_url": "https://api.qobra.co/v2/quotas/507f1f77bcf86cd799439013/values?start_id=65a1b2c3d4e5f60718293a02&limit=100"
}
}
One row per person and per period
The response is flat: one row for each combination of a user and a period. A quarterly quota covering 40 people over 4 quarters returns up to 160 rows. Only cells that hold a value produce a row, so a person with no target for a period is simply absent rather than returned with a zero. The response is not grouped by user. The Qobra app shows one line per person with their periods side by side — this endpoint does not. Group onstandard.user.id yourself if you need the app’s shape.
Every row carries the same five keys, whatever the quota’s configuration. They
never depend on the column mapping of an integration, so you can hard-code them:
| Field | What it holds |
|---|---|
standard.id | Id of the quota value |
standard.user | { id, email } — email can be null |
standard.period | 2026-03, 2026-Q1, 2026-S1 or 2026 |
standard.value | { value, currency } for an amount, otherwise a number |
standard.source | local (set in Qobra) or integration (synchronized) |
quota_id you
requested — the response does not repeat it.
Periods follow the quota’s frequency
| Frequency | Format | Example |
|---|---|---|
| Monthly | YYYY-MM | 2026-03 |
| Quarterly | YYYY-Qn | 2026-Q1 |
| Semesterly | YYYY-Sn | 2026-S1 |
| Annually | YYYY | 2026 |
standard.period from
GET /v2/data-structures/{quota_id}/fields
rather than guessing it from the string:
{
"api_key": "standard.period",
"type": "string",
"format": "period",
"frequency": "quarterly"
}
2026-Q1 is the first quarter
of your fiscal year, which is not necessarily January to March.Everyone’s values are returned
This endpoint returns the values of every user in the quota, whoever owns the API key.The value depends on the quota type
| Quota type | standard.value | Example |
|---|---|---|
amount | object | { "value": 120000.0, "currency": "EUR" } |
percentage | number | 0.85 |
float | number | 42.0 |
0.85 means 85%. Multiply it yourself
if you display a percentage.0.85 and 42.0 look alike, so a number on its own does not tell you whether
it is a ratio or an absolute value. What distinguishes them is the format of
standard.value on the fields endpoint — currency, percentage or float.
Read it once per quota before loading numbers into a typed column.
An amount carries the currency of its own value, so a quota whose people are
paid in different currencies returns rows with different currencies inside the
same page.
Changing a quota’s type flips standard.value for every existing row without
touching any value. last_modified_after will not surface that; the
schema_hash on the fields endpoint will.
Pagination
Pages are linked: every response carries anext_url that already includes your
limit and any date filters you sent. Follow it until has_more is false.
# First call
curl --request GET \
--url 'https://api.qobra.co/v2/quotas/QUOTA_ID/values?limit=2000' \
--header 'X-API-Key: YOUR_API_KEY'
# Then call meta.next_url as-is, until meta.has_more is false
url = f"https://api.qobra.co/v2/quotas/{quota_id}/values"
params = {"limit": 2000}
while True:
result = requests.get(url, headers=headers, params=params).json()
for value in result["data"]:
process(value)
if not result["meta"]["has_more"]:
break
url = result["meta"]["next_url"]
params = None
limitdefaults to its maximum, 2000. Omitting it gives you the largest page, not a small one. It is validated rather than capped, so?limit=5000returns400instead of 2000 rows.- Rows come in creation order, not by period and not by user.
Filtering by modification date
last_modified_after and last_modified_before restrict the response to values
modified inside a window. Both bounds are inclusive, and they cannot be equal —
passing the same datetime twice returns 400.
params = {
"limit": 2000,
"last_modified_after": "2026-03-01T09:00:00+00:00",
}
What this endpoint does not do
- No filter by user or period. To read part of a quota, paginate through it and filter on your side.
- No writes. Importing quota values is not part of this endpoint.
- No attainment. Targets only — results come from statements.
Archived quotas stay readable
Archiving a quota in Qobra does not hide it from the API. It stays listed byGET /v2/data-structures — with "status": "archived" — and this endpoint
still serves its values. Read status from the discovery response to leave
archived quotas out of a sync.
Errors
| Status | Cause |
|---|---|
400 | Invalid pagination parameter, see below |
401 | Missing or unknown API key |
403 | The public API is not enabled for your company |
404 | Unknown id, a sandbox quota, or the id of a reporting table |
400 is the one you are most likely to meet. Its causes are a limit above
2000, a malformed start_id, and a last_modified_after equal to or later than
last_modified_before.Authorizations
Your Qobra API key. Generate it from Settings > API Keys in Qobra.
Path Parameters
ID of a quota, listed with the type 'quota' by /v2/data-structures
Query Parameters
Start after this quota value ID. This is the only pagination this endpoint supports
Number of quota values per page (1-2000)
1 <= x <= 2000ISO 8601 datetime - Only return values modified at or after this date (for incremental sync)
ISO 8601 datetime - Only return values modified at or before this date
Was this page helpful?