curl --request GET \
--url https://api.qobra.co/v2/reporting/{table_id}/records \
--header 'X-API-Key: <api-key>'import requests
url = "https://api.qobra.co/v2/reporting/{table_id}/records"
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}/records', 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}/records",
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}/records"
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}/records")
.header("X-API-Key", "<api-key>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.qobra.co/v2/reporting/{table_id}/records")
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": "75b2c3d4e5f6g7h8i9j0k2l3",
"datatable.opportunity_name": "Acme Corp - Annual License",
"datatable.account_name": "Acme Corporation",
"datatable.amount": {
"value": 50000,
"currency": "USD"
},
"custom.total_paid": {
"value": 400,
"currency": "USD"
},
"datatable.close_date": "2024-01-15",
"datatable.stage": "Closed Won",
"standard.user": {
"id": "507f191e810c19729de860ea",
"email": "sarah.johnson@company.com"
},
"standard.commission_impact": {
"value": 2500,
"currency": "USD"
},
"standard.date": "2024-01-01"
},
{
"standard.id": "75b2c3d4e5f6g7h8i9j0k2l4",
"datatable.opportunity_name": "TechStart - Professional Plan",
"datatable.account_name": "TechStart Inc",
"datatable.amount": {
"value": 25000,
"currency": "USD"
},
"custom.total_paid": {
"value": 1200,
"currency": "USD"
},
"datatable.close_date": "2024-01-18",
"datatable.stage": "Closed Won",
"standard.user": {
"id": "507f191e810c19729de860eb",
"email": "sarah.johnson@company.com"
},
"standard.commission_impact": {
"value": 1250,
"currency": "USD"
},
"standard.date": "2024-01-18"
}
],
"meta": {
"next_start_id": "75b2c3d4e5f6g7h8i9j999l3",
"has_more": true,
"next_url": "https://api.qobra.co/v2/reporting/507f1f77bcf86cd799439012/records?start_id=75b2c3d4e5f6g7h8i9j999l3&limit=1000"
}
}{
"count": 1,
"errors": [
{
"error": "ValidationError",
"resource": "start_id",
"description": "Can't parse value for param 'start_id' : Value error, Invalid ObjectId"
}
]
}{
"message": "You're trying to access resource you're not authorized to.",
"type": "UnauthorizedError"
}{
"message": "We couldn't find the requested resource",
"resource": "ObjectModel",
"type": "NotFoundError"
}Fetch Records Reporting
Extracts records from record reporting structures. Records represent the underlying data used in commission calculations—CRM deals, activities, and custom metrics. Use this endpoint for sales analytics, pipeline reports, commission audit trails and CRM data extraction. Data table structures are not exposed by v2: use the v1 fetch records endpoint for those.
curl --request GET \
--url https://api.qobra.co/v2/reporting/{table_id}/records \
--header 'X-API-Key: <api-key>'import requests
url = "https://api.qobra.co/v2/reporting/{table_id}/records"
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}/records', 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}/records",
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}/records"
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}/records")
.header("X-API-Key", "<api-key>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.qobra.co/v2/reporting/{table_id}/records")
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": "75b2c3d4e5f6g7h8i9j0k2l3",
"datatable.opportunity_name": "Acme Corp - Annual License",
"datatable.account_name": "Acme Corporation",
"datatable.amount": {
"value": 50000,
"currency": "USD"
},
"custom.total_paid": {
"value": 400,
"currency": "USD"
},
"datatable.close_date": "2024-01-15",
"datatable.stage": "Closed Won",
"standard.user": {
"id": "507f191e810c19729de860ea",
"email": "sarah.johnson@company.com"
},
"standard.commission_impact": {
"value": 2500,
"currency": "USD"
},
"standard.date": "2024-01-01"
},
{
"standard.id": "75b2c3d4e5f6g7h8i9j0k2l4",
"datatable.opportunity_name": "TechStart - Professional Plan",
"datatable.account_name": "TechStart Inc",
"datatable.amount": {
"value": 25000,
"currency": "USD"
},
"custom.total_paid": {
"value": 1200,
"currency": "USD"
},
"datatable.close_date": "2024-01-18",
"datatable.stage": "Closed Won",
"standard.user": {
"id": "507f191e810c19729de860eb",
"email": "sarah.johnson@company.com"
},
"standard.commission_impact": {
"value": 1250,
"currency": "USD"
},
"standard.date": "2024-01-18"
}
],
"meta": {
"next_start_id": "75b2c3d4e5f6g7h8i9j999l3",
"has_more": true,
"next_url": "https://api.qobra.co/v2/reporting/507f1f77bcf86cd799439012/records?start_id=75b2c3d4e5f6g7h8i9j999l3&limit=1000"
}
}{
"count": 1,
"errors": [
{
"error": "ValidationError",
"resource": "start_id",
"description": "Can't parse value for param 'start_id' : Value error, Invalid ObjectId"
}
]
}{
"message": "You're trying to access resource you're not authorized to.",
"type": "UnauthorizedError"
}{
"message": "We couldn't find the requested resource",
"resource": "ObjectModel",
"type": "NotFoundError"
}Overview
This endpoint extracts records from record reporting structures. Records represent the underlying data used in commission calculations — CRM deals, activities, and custom metrics. Use this endpoint for:- Sales analytics and pipeline reports
- Commission audit trails (what deals contributed to commissions)
- CRM data extraction
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}/records"
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"]
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 for incremental sync (only fetch new/updated records).
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}/records"
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: Hourly incremental sync
new_records = incremental_sync(table_id, api_key)
print(f"Found {len(new_records)} new/updated records since last sync")
Performance & best practices
Use ID-based pagination
next_url for large datasets.url = result["meta"]["next_url"]
Incremental sync
last_modified_after for efficiency.params = {"last_modified_after": last_sync}
Use Limit 2000
params = {"limit": 2000}
Authorizations
Your Qobra API key. Generate it from Settings > API Keys in Qobra.
Path Parameters
ID of a record reporting structure (from /v2/data-structures)
Query Parameters
Start after this record ID (for ID-based pagination, recommended)
Number of records per page (1-2000)
1 <= x <= 2000ISO 8601 datetime - Only return records modified after this timestamp (for incremental sync)
ISO 8601 datetime - Only return records modified before this timestamp
Was this page helpful?