Fetch Statements Reporting
curl --request GET \
--url https://api.qobra.co/v2/reporting/{table_id}/statements \
--header 'X-API-Key: <api-key>'import requests
url = "https://api.qobra.co/v2/reporting/{table_id}/statements"
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/reporting/{table_id}/statements', 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/reporting/{table_id}/statements",
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/reporting/{table_id}/statements"
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/reporting/{table_id}/statements")
.header("X-API-Key", "<api-key>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.qobra.co/v2/reporting/{table_id}/statements")
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_body{
"data": [
{
"standard.id": "65a1b2c3d4e5f6g7h8i9j0k1",
"standard.user": {
"id": "507f191e810c19729de860ea",
"email": "sarah.johnson@company.com"
},
"standard.date": "2024-01-01",
"standard.total_commission": {
"value": 8500,
"currency": "USD"
},
"standard.status": "paid",
"custom.quota_attainment": 0.92,
"custom.tier": "Accelerator"
},
{
"standard.id": "65a1b2c3d4e5f6g7h8i9j0k2",
"standard.user": {
"id": "507f191e810c19729de860eb",
"email": "michael.chen@company.com"
},
"standard.date": "2024-01-01",
"standard.total_commission": {
"value": 12750,
"currency": "USD"
},
"standard.status": "validated",
"custom.quota_attainment": 1.15,
"custom.tier": "Overachievement"
}
],
"meta": {
"next_start_id": "65a1b2c3d4e5f6g7h8i9j999",
"has_more": true,
"next_url": "https://api.qobra.co/v2/reporting/507f1f77bcf86cd799439011/statements?start_id=65a1b2c3d4e5f6g7h8i9j999&limit=1000"
}
}Reporting
Fetch Statements Reporting
Extracts commission statement records from a statement reporting data structure. Statements represent the final calculated commissions—what each person earned in each period. Use this endpoint for payroll integration, commission reports, earnings history analysis, and compliance audit trails.
GET
/
v2
/
reporting
/
{table_id}
/
statements
Fetch Statements Reporting
curl --request GET \
--url https://api.qobra.co/v2/reporting/{table_id}/statements \
--header 'X-API-Key: <api-key>'import requests
url = "https://api.qobra.co/v2/reporting/{table_id}/statements"
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/reporting/{table_id}/statements', 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/reporting/{table_id}/statements",
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/reporting/{table_id}/statements"
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/reporting/{table_id}/statements")
.header("X-API-Key", "<api-key>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.qobra.co/v2/reporting/{table_id}/statements")
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_body{
"data": [
{
"standard.id": "65a1b2c3d4e5f6g7h8i9j0k1",
"standard.user": {
"id": "507f191e810c19729de860ea",
"email": "sarah.johnson@company.com"
},
"standard.date": "2024-01-01",
"standard.total_commission": {
"value": 8500,
"currency": "USD"
},
"standard.status": "paid",
"custom.quota_attainment": 0.92,
"custom.tier": "Accelerator"
},
{
"standard.id": "65a1b2c3d4e5f6g7h8i9j0k2",
"standard.user": {
"id": "507f191e810c19729de860eb",
"email": "michael.chen@company.com"
},
"standard.date": "2024-01-01",
"standard.total_commission": {
"value": 12750,
"currency": "USD"
},
"standard.status": "validated",
"custom.quota_attainment": 1.15,
"custom.tier": "Overachievement"
}
],
"meta": {
"next_start_id": "65a1b2c3d4e5f6g7h8i9j999",
"has_more": true,
"next_url": "https://api.qobra.co/v2/reporting/507f1f77bcf86cd799439011/statements?start_id=65a1b2c3d4e5f6g7h8i9j999&limit=1000"
}
}Overview
This endpoint extracts commission statement records from a statement reporting data structure. Statements represent the final calculated commissions — what each person earned in each period. Use this endpoint for:- Payroll integration (sending commissions to payroll systems)
- Commission reports and dashboards
- Earnings history analysis
- Compliance and audit trails
Statements = Results. This endpoint gives you the output of Qobra’s
commission calculations, not the underlying deals/activities.
Only reporting tables from your company’s live environment are reachable.
A
table_id that belongs to a sandbox environment returns 404 Not Found,
even if the id was copied from the Qobra app while viewing a sandbox.Pagination
This endpoint uses ID-based pagination for consistent performance with large datasets.Basic Pagination Pattern
url = f"https://api.qobra.co/v2/reporting/{table_id}/statements"
params = {"limit": 2000}
while True:
response = requests.get(url, headers=headers, params=params)
result = response.json()
# Process records
for record in result["data"]:
process(record)
# Check if more pages exist
if not result["meta"]["has_more"]:
break
# Use next_url for next page
url = result["meta"]["next_url"]
Use
next_url for simplicity: The API returns a pre-constructed
next_url in the response meta—just follow it instead of manually building
the next request.Filtering by modification date
Uselast_modified_after to implement incremental sync (only fetch new/updated records).
Incremental sync pattern
from datetime import datetime, timezone
def incremental_sync(table_id: str, api_key: str):
# Load last sync time from your database
last_sync = get_last_sync_time() # e.g., 2024-01-14T00:00:00Z
url = f"https://api.qobra.co/v2/reporting/{table_id}/statements"
headers = {"X-API-Key": api_key}
params = {
"limit": 2000,
"last_modified_after": last_sync.isoformat()
}
new_records = []
while True:
response = requests.get(url, headers=headers, params=params)
result = response.json()
new_records.extend(result["data"])
if not result["meta"]["has_more"]:
break
url = result["meta"]["next_url"]
# Save current time as last sync
save_last_sync_time(datetime.now(timezone.utc))
return new_records
# Usage: Daily incremental sync
new_statements = incremental_sync(table_id, api_key)
print(f"Found {len(new_statements)} new/updated statements since last sync")
- ✅ 10-100x faster than full extracts
- ✅ Reduces API load
- ✅ Keeps your data warehouse up-to-date with minimal overhead
Selecting fields
The endpoint returns every field of the table by default. Passfields to receive only
the ones you need:
?limit=2000&fields=datatable.amount,datatable.close_date,datatable.stage
params = {
"limit": 2000,
"fields": "datatable.amount,datatable.close_date,datatable.stage",
}
- The values are
api_keys, as listed byGET /v2/data-structures/{table_id}/fields, separated by commas. The keys above are examples: read yours from that endpoint. standard.idandstandard.source_idare always returned, whether you list them or not.- A record keeps the table’s order of fields, not the order of your list. Spaces around a key and duplicate keys are ignored.
next_urlkeeps your selection. If you build the next URL yourself fromnext_start_id, passfieldsagain.- Without
fields, nothing changes: every field is returned.
Why select fields
A page holds one value per field and per record, so fewer fields make faster and lighter pages. On a table of 186 fields, keeping 50 divides the server time of a page by about three and its compressed size by about four. For a full extraction of a wide table, list only the fields your warehouse uses.Unknown keys
A key the fields endpoint does not list returns400, naming every unknown
key:
{
"count": 1,
"errors": [
{
"error": "ValidationError",
"resource": "fields",
"description": "Unknown fields: datatable.nope. A field is named by an api_key that /v2/data-structures/<id>/fields lists."
}
]
}
fields, read the fields endpoint again and update your
list. A list left empty (fields=) returns 400 too.
How many fields fit in the URL
The server accepts request lines of up to 4,094 bytes. The rest of the URL (the path,start_id, limit and the date filters) takes about 300 bytes.
That leaves about 3,790 bytes for the value of fields. Count start_id
from your first call: next_url adds it to every next page. Each key costs its
length plus 3 bytes, the size of a comma once encoded (%2C).
| Average key length | Fields that fit |
|---|---|
| 35 characters | About 100 |
| 45 characters | About 80 |
| 60 characters | About 60 |
A URL over the limit is refused before it reaches the API. You receive a
400 Bad Request in text/html, not in the JSON error format above, and its
body contains Request Line is too large. Remove fields from your list, or
drop fields to receive every field.Performance & best practices
Use ID-based pagination
Always use
next_url for large datasets (10K+).url = result["meta"]["next_url"]
Implement incremental sync
Use
last_modified_after for daily/hourly updates.params = {"last_modified_after": last_sync}
Use Limit 2000
Optimal balance between network overhead and response time.
params = {"limit": 2000}
Authorizations
Path Parameters
Query Parameters
Comma-separated api_keys of the fields to return, as listed by /v2/data-structures/{table_id}/fields (e.g. datatable.amount,datatable.close_date). standard.id and standard.source_id are always returned. When omitted, every field is returned. A key the fields endpoint does not list returns 400.
Was this page helpful?