> ## Documentation Index
> Fetch the complete documentation index at: https://docs.qobra.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Quickstart Guide

> Extract your reporting data in 5 minutes

## Overview

This quickstart will guide you through your first data extraction with Qobra's V2 API. By the end, you'll have extracted reporting data from your account.

**What you'll learn:**

* How to discover available data structures
* How to inspect field schemas
* How to extract data with pagination

**Time required:** \~5 minutes.

<Info>
  You'll need a Qobra API key. If you don't have one, [generate it in your Qobra
  settings](https://app.qobra.co/parameters/api).
</Info>

## The 3-Step Workflow

<Steps>
  <Step title="Discover Data Structures">
    Find which tables are available in your Qobra account (statements reporting or records reporting)
  </Step>

  <Step title="Inspect Field Schema">
    Get the structure of each table: field names, types, and metadata
  </Step>

  <Step title="Extract Data">
    Pull the actual data with efficient pagination
  </Step>
</Steps>

***

## Step 1: Discover Your Data Structures

**What's a data structure?**

Think of it as a table in Qobra — it could be your statements reporting or records reporting.

**Endpoint:** `GET /v2/data-structures`

<CodeGroup>
  ```bash cURL theme={null}
  curl --request GET \
    --url https://api.qobra.co/v2/data-structures \
    --header 'X-API-Key: YOUR_API_KEY'
  ```

  ```python Python theme={null}
  import requests

  url = "https://api.qobra.co/v2/data-structures"
  headers = {"X-API-Key": "YOUR_API_KEY"}

  response = requests.get(url, headers=headers)
  data_structures = response.json()["data"]

  print(f"Found {len(data_structures)} data structures")
  for ds in data_structures:
      print(f"- {ds['name']} ({ds['type']}): {ds['total_records']} records")
  ```

  ```javascript JavaScript theme={null}
  const axios = require("axios");

  const url = "https://api.qobra.co/v2/data-structures";
  const headers = { "X-API-Key": "YOUR_API_KEY" };

  const response = await axios.get(url, { headers });
  const dataStructures = response.data.data;

  console.log(`Found ${dataStructures.length} data structures`);
  dataStructures.forEach((ds) => {
    console.log(`- ${ds.name} (${ds.type}): ${ds.total_records} records`);
  });
  ```
</CodeGroup>

### Example Response

```json theme={null}
{
  "data": [
    {
      "id": "507f1f77bcf86cd799439011",
      "name": "Commission Statements",
      "type": "statement_reporting",
      "total_records": 15420,
      "links": {
        "fields": "https://api.qobra.co/v2/data-structures/507f1f77bcf86cd799439011/fields",
        "records": "https://api.qobra.co/v2/reporting/507f1f77bcf86cd799439011/statements"
      }
    },
    {
      "id": "507f1f77bcf86cd799439012",
      "name": "Sales Opportunities",
      "type": "record_reporting",
      "total_records": 8934,
      "links": {
        "fields": "https://api.qobra.co/v2/data-structures/507f1f77bcf86cd799439012/fields",
        "records": "https://api.qobra.co/v2/reporting/507f1f77bcf86cd799439012/records"
      }
    }
  ]
}
```

<Tip>
  Each data structure includes `links` that point to the next endpoints you'll need. Use them to navigate the API naturally.
</Tip>

***

## Step 2: Inspect the Field Schema

Now that you know what data structures exist, inspect their fields to understand what data you can extract.

**Endpoint:** `GET /v2/data-structures/{table_id}/fields`

<CodeGroup>
  ```bash cURL theme={null}
  curl --request GET \
    --url https://api.qobra.co/v2/data-structures/507f1f77bcf86cd799439011/fields \
    --header 'X-API-Key: YOUR_API_KEY'
  ```

  ```python Python theme={null}
  import requests

  table_id = "507f1f77bcf86cd799439011"  # From step 1
  url = f"https://api.qobra.co/v2/data-structures/{table_id}/fields"
  headers = {"X-API-Key": "YOUR_API_KEY"}

  response = requests.get(url, headers=headers)
  result = response.json()

  print(f"Schema hash: {result['schema_hash']}")
  print(f"Found {result['count']}:\n")

  for field in result["fields"][:5]:  # Show first 5
      format_info = f" ({field['format']})" if field.get('format') else ""
      print(f"- {field['name']}: {field['type']}{format_info}")
  ```

  ```javascript JavaScript theme={null}
  const table_id = "507f1f77bcf86cd799439011"; // From step 1
  const url = `https://api.qobra.co/v2/data-structures/${table_id}/fields`;
  const headers = { "X-API-Key": "YOUR_API_KEY" };

  const response = await axios.get(url, { headers });
  const result = response.data;

  console.log(`Schema hash: ${result.schema_hash}`);
  console.log(`Found ${result.count} fields:\n`);

  result.fields.slice(0, 5).forEach((field) => { // Show first 5
    const formatInfo = field.format ? ` (${field.format})` : "";
    console.log(`- ${field.name}: ${field.type}${formatInfo}`);
  });
  ```
</CodeGroup>

### Example Response

```json theme={null}
{
  "schema_hash": "a3f5c89b2e1d4f7e9c8b6a3d5e7f9c1b",
  "count": 5,
  "fields": [
    {
      "api_key": "standard.id",
      "name": "ID",
      "type": "string",
      "format": null,
      "origin": "standard",
      "created_at": "2024-01-15T10:30:00Z",
      "currencies": null,
      "enum_values": null,
      "properties": null
    },
    {
      "api_key": "standard.total_commission",
      "name": "Total Commission",
      "type": "object",
      "format": "currency",
      "origin": "standard",
      "created_at": "2024-01-15T10:30:00Z",
      "currencies": ["USD", "EUR", "GBP"],
      "enum_values": null,
      "properties": {
        "value": {
          "type": "number",
          "required": true
        },
        "currency": {
          "type": "string",
          "required": true
        }
      }
    },
    {
      "api_key": "standard.date",
      "name": "Date",
      "type": "date",
      "format": "date",
      "origin": "standard",
      "created_at": "2024-01-15",
      "currencies": null,
      "enum_values": null,
      "properties": null
    },
    {
      "api_key": "standard.status",
      "name": "Status",
      "type": "string",
      "format": "enum",
      "origin": "standard",
      "created_at": "2024-01-15T10:30:00Z",
      "enum_values": ["paid", "pending", "validated"],
      "currencies": null,
      "properties": null
    },
    {
      "api_key": "custom.bonus_attainment",
      "name": "Bonus Attainment",
      "type": "number",
      "format": "percentage",
      "origin": "custom",
      "created_at": "2024-03-20T14:22:00Z",
      "currencies": null,
      "enum_values": null,
      "properties": null
    }
  ]
}
```

<Note>
  **Understanding API Keys:**

  * `standard.` = Standard metric (built-in fields)
  * `custom.` = Custom metric (your custom fields)
  * `datatable.` = Data table field (from external sources)
</Note>

<Tip>
  Store the `schema_hash`! Compare it on future calls to detect when fields have changed.
</Tip>

***

## Step 3: Extract Your Data

Now extract the actual records. The API uses ID-based pagination for optimal performance with large datasets.

**Endpoint:** `GET /v2/reporting/{table_id}/statements` or `GET /v2/reporting/{table_id}/records`

<CodeGroup>
  ```bash cURL theme={null}
  curl --request GET \
    --url 'https://api.qobra.co/v2/reporting/507f1f77bcf86cd799439011/statements?limit=1000' \
    --header 'X-API-Key: YOUR_API_KEY'
  ```

  ```python Python theme={null}
  import requests

  table_id = "507f1f77bcf86cd799439011" # From step 1
  url = f"https://api.qobra.co/v2/reporting/{table_id}/statements"
  headers = {"X-API-Key": "YOUR_API_KEY"}

  all_records = []
  params = {"limit": 1000}

  while True:
      response = requests.get(url, headers=headers, params=params)
      result = response.json()

      all_records.extend(result["data"])
      print(f"Fetched {len(result['data'])} records (total: {len(all_records)})")

      if not result["meta"]["has_more"]:
          break

      # Use next_url for the next page
      url = result["meta"]["next_url"]
      params = {}

  print(f"\n✓ Extraction complete: {len(all_records)} records")
  ```

  ```javascript JavaScript theme={null}
  const table_id = "507f1f77bcf86cd799439011"; // From step 1
  let url = `https://api.qobra.co/v2/reporting/${table_id}/statements`;
  const headers = { "X-API-Key": "YOUR_API_KEY" };

  let allRecords = [];
  let params = { limit: 1000 };

  while (true) {
    const response = await axios.get(url, { headers, params });
    const result = response.data;

    allRecords.push(...result.data);
    console.log(
      `Fetched ${result.data.length} records (total: ${allRecords.length})`
    );

    if (!result.meta.has_more) break;

    // Use next_url for the next page
    url = result.meta.next_url;
    params = {};
  }

  console.log(`\n✓ Extraction complete: ${allRecords.length} records`);
  ```
</CodeGroup>

### Example Response

```json theme={null}
{
  "data": [
    {
      "standard.id": "65a1b2c3d4e5f6g7h8i9j0k1",
      "standard.total_commission": {
        "value": 4250.0,
        "currency": "USD"
      },
      "standard.date": "2024-01-01",
      "standard.status": "paid",
      "custom.bonus_attainment": 0.87,
    },
    // ... 999 more records
  ],
  "meta": {
    "next_start_id": "65a1b2c3d4e5f6g7h8i9j999",
    "has_more": true,
    "next_url": "https://api.qobra.co/v2/reporting/507f1f77bcf86cd799439011/statements?start_id=65a1b2c3d4e5f6g7h8i9j999&limit=1000"
  }
}
```

<Warning>
  **Always use `next_url` for pagination** when dealing with large datasets.
</Warning>

***

## Troubleshooting

<AccordionGroup>
  <Accordion title="401 Unauthorized">
    **Problem:** Your API key is invalid or missing.

    **Solution:**

    * Verify you're passing the key in the `X-API-Key` header (not as a query parameter)
    * Regenerate your API key in [Qobra settings](https://app.qobra.co/parameters/api)
  </Accordion>

  <Accordion title="404 Not Found on table_id">
    **Problem:** The data structure ID doesn't exist.

    **Solution:**

    * Call `/v2/data-structures` first to get valid IDs
    * Don't hardcode IDs—they may change between environments
  </Accordion>

  <Accordion title="Response is empty (data: [])">
    **Problem:** The table exists but has no records yet.

    **Solution:**

    * Check `total_records` in the data structure response
    * Ensure data has been synced into Qobra
  </Accordion>

  <Accordion title="Pagination seems stuck">
    **Problem:** You're reusing the same `start_id` or not following `next_url`.

    **Solution:**

    * Always update `next_url` from `meta.next_url` after each request
  </Accordion>
</AccordionGroup>

***

## Next Steps

<Card title="Explore All Endpoints" icon="list" href="/api_reference/v2/endpoints/discovery/list_data_structures">
  Complete endpoint reference with all parameters
</Card>

<Check>
  **You're ready!** You now know the V2 API workflow. Dive deeper into the sections above or start building your integration.
</Check>
