> ## 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.

# Fetch Data Table Records

> Extracts the records of a data table: the rows synchronized from an integration, or created in Qobra, before any commission calculation. Use this endpoint to replicate a data table into a warehouse or a BI tool.

## Overview

This endpoint returns the records of a data table — the rows Qobra
synchronizes from your CRM, database or files, and the ones created in Qobra —
as they stand before any commission calculation.

**Use this endpoint for:**

* Replicating a data table into a data warehouse
* BI dashboards on the data your plans read
* Checking what Qobra imported from a source

> [!NOTE]
>
> **Data tables = inputs.** This endpoint gives you the rows your plans read.
> What each row contributed to commissions comes from
> [record reporting](https://docs.qobra.co/api_reference/v2/endpoints/data/fetch_records.md).

> [!NOTE]
>
> Discover the ids with
> [`GET /v2/data-structures`](https://docs.qobra.co/api_reference/v2/endpoints/discovery/list_data_structures),
> where every data table is listed with `"type": "data_table"`. A data table
> belongs to no environment: unlike a reporting table or a quota, its id works
> whichever environment you copied it from in the Qobra app.

***

## Your first call

```bash cURL theme={null}
curl --request GET \
  --url 'https://api.qobra.co/v2/data-tables/DATA_TABLE_ID/records?limit=100' \
  --header 'X-API-Key: YOUR_API_KEY'
```

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

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

response = requests.get(url, headers=headers, params={"limit": 100})
records = response.json()["data"]
```

```javascript JavaScript theme={null}
const url = "https://api.qobra.co/v2/data-tables/DATA_TABLE_ID/records?limit=100";

const response = await fetch(url, {
  headers: { "X-API-Key": "YOUR_API_KEY" },
});
const { data } = await response.json();
```

### Example response

```json theme={null}
{
  "data": [
    {
      "standard.id": "6a1b2c3d4e5f60718293a4b1",
      "standard.source_id": "0065g00000AbCdEAAV",
      "datatable.name": "Acme Corp - Annual License",
      "datatable.amount": { "value": 50000.0, "currency": "USD" },
      "datatable.close_date": "2026-01-15",
      "datatable.stage": "Closed Won",
      "datatable.owner": {
        "id": "507f191e810c19729de860ea",
        "email": "sarah.johnson@company.com"
      },
      "datatable.account": {
        "id": "0015g00000XyZaBAAV",
        "name": "Acme Corporation"
      },
      "datatable.is_renewal": false
    },
    {
      "standard.id": "6a1b2c3d4e5f60718293a4b2",
      "standard.source_id": "6a1b2c3d4e5f60718293a4b2",
      "datatable.name": "TechStart - Professional Plan",
      "datatable.amount": { "value": 25000.0, "currency": "EUR" },
      "datatable.close_date": "2026-01-18",
      "datatable.stage": "Negotiation",
      "datatable.owner": {
        "id": "507f191e810c19729de860eb",
        "email": "michael.chen@company.com"
      },
      "datatable.account": { "id": null, "name": null },
      "datatable.is_renewal": true
    }
  ],
  "meta": {
    "next_start_id": "6a1b2c3d4e5f60718293a4b2",
    "has_more": true,
    "next_url": "https://api.qobra.co/v2/data-tables/507f1f77bcf86cd799439010/records?start_id=6a1b2c3d4e5f60718293a4b2&limit=100"
  }
}
```

***

## What a record holds

Every record starts with two identifiers, then carries one key per field of the
table:

* `standard.id` — the id of the record in Qobra.
* `standard.source_id` — its id in the source system, such as a Salesforce
  opportunity id. A record created in Qobra carries its own `standard.id` there,
  as the second record above does.
* `datatable.<key>` — one per field. The keys are specific to your table: read
  them from
  [`GET /v2/data-structures/{table_id}/fields`](https://docs.qobra.co/api_reference/v2/endpoints/discovery/get_fields_schema)
  rather than guessing them from the field names.

Each value follows the type of its field:

| Field type | Value in the response |
| - | - |
| Text | A string |
| Number | A number |
| Percentage | A number, as stored: `0.85` means 85% |
| Amount | `{ "value": …, "currency": … }` |
| Date | `YYYY-MM-DD` |
| Yes/No | `true` or `false`, and `false` when left empty |
| User | `{ "id": …, "email": … }`, both `null` when not found |
| Picklist | The name of the option |
| Multi-picklist | A list of option names |
| Link to a record | `{ "id": …, "name": … }`, see below |

A link points at a record of another table. Its `id` is the **source id** of
that record, not its Qobra id, so it matches the `standard.source_id` you read
from the linked table. An empty link reads `{ "id": null, "name": null }`, and a
link whose target Qobra has not resolved yet carries its id with a `null` name.

An amount carries the currency of its own value, so a page can mix currencies
within the same field.

***

## Records created in Qobra are included

A table synchronized from an integration can also hold records created by hand
in Qobra. This endpoint returns both: it reads the whole table, whatever the
origin of each record. Tell them apart with `standard.source_id`, which equals
`standard.id` on a record created in Qobra.

***

## Pagination

Pages are linked: every response carries a `next_url` that already includes
your `limit` and any date filters you sent. Follow it until `has_more` is
`false`.

```python theme={null}
url = f"https://api.qobra.co/v2/data-tables/{data_table_id}/records"
params = {"limit": 2000}

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

    for record in result["data"]:
        process(record)

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

    url = result["meta"]["next_url"]
    params = None
```

Two things worth knowing:

* `limit` defaults to its maximum, **2000**. Omitting it gives you the largest
  page, not a small one. It is validated rather than capped, so `?limit=5000`
  returns `400` instead of 2000 rows.
* Records come in creation order.

***

## Filtering by modification date

`last_modified_after` and `last_modified_before` restrict the response to
records modified inside a window. Both bounds are inclusive, and they cannot be
equal — passing the same datetime twice returns `400`.

```python theme={null}
params = {
    "limit": 2000,
    "last_modified_after": "2026-03-01T09:00:00+00:00",
}
```

Always include the timezone in your timestamp, as in the example above.

> [!WARNING]
>
> **Deleted records are never returned**, so an incremental sync does not see a
> record disappear. Re-walk the whole table periodically to remove from your
> copy the records deleted in Qobra or in the source.

***

## Errors

| Status | Cause |
| - | - |
| `400` | Invalid pagination parameter |
| `401` | Missing or unknown API key |
| `403` | The public API is not enabled for your company |
| `404` | Unknown id, or an id that is not a data table, see below |

A `404` also answers the id of a reporting table or of a quota, which have
endpoints of their own, and the id of a table Qobra generates from your plans'
results. Those tables are not listed by `GET /v2/data-structures` either: their
results come from
[record reporting](https://docs.qobra.co/api_reference/v2/endpoints/data/fetch_records.md) and
[statements](https://docs.qobra.co/api_reference/v2/endpoints/data/fetch_statements.md).

> [!NOTE]
>
> This endpoint is the v2 counterpart of the v1
> [fetch data table records](https://docs.qobra.co/api_reference/v1/endpoints/data_tables/fetch_records_data.md)
> endpoint, which stays maintained. Moving to v2 is optional: see the
> [migration guide](https://docs.qobra.co/api_reference/v2/migration_guide.md).


## OpenAPI

````yaml get /v2/data-tables/{data_table_id}/records
openapi: 3.0.2
info:
  version: 2.0.0
  title: Qobra API v2
  description: >-
    Modern data extraction API for Qobra - Optimized for high-volume data
    extraction with ID-based pagination and flexible schema discovery.
  license:
    name: Proprietary
    url: https://www.qobra.co/terms
servers:
  - url: https://api.qobra.co
    description: Production API
security: []
paths:
  /v2/data-tables/{data_table_id}/records:
    get:
      tags:
        - public_api
        - v2
        - data_tables
      summary: Fetch Data Table Records
      description: >-
        Extracts the records of a data table: the rows synchronized from an
        integration, or created in Qobra, before any commission calculation. Use
        this endpoint to replicate a data table into a warehouse or a BI tool.
      operationId: GET_/v2/data-tables/(data_table_id)/records
      parameters:
        - name: data_table_id
          in: path
          required: true
          schema:
            type: string
            format: ObjectId
          description: >-
            ID of a data table (from /v2/data-structures, where it is listed
            with type data_table)
        - name: start_id
          in: query
          required: false
          schema:
            type: string
            format: ObjectId
          description: Start after this record ID (for ID-based pagination, recommended)
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 2000
            default: 2000
          description: Number of records per page (1-2000)
        - name: last_modified_after
          in: query
          required: false
          schema:
            type: string
            format: date-time
          description: >-
            ISO 8601 datetime - Only return records modified after this
            timestamp (for incremental sync)
        - name: last_modified_before
          in: query
          required: false
          schema:
            type: string
            format: date-time
          description: >-
            ISO 8601 datetime - Only return records modified before this
            timestamp
      responses:
        '200':
          description: >-
            Successfully retrieved the records of the data table, with
            pagination metadata for navigating through large datasets.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DataTableRecordsResponse'
              examples:
                data-table-records-success:
                  $ref: '#/components/examples/DataTableRecordsResponseExample'
        '400':
          description: Bad Request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InvalidErrorResponse'
              examples:
                invalid-error:
                  $ref: '#/components/examples/InvalidErrorResponseExample'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UnauthorizedErrorResponse'
              examples:
                unauthorized-error:
                  $ref: '#/components/examples/UnauthorizedErrorResponseExample'
        '403':
          description: Forbidden - the public API is not enabled for your company
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ForbiddenErrorResponse'
              examples:
                forbidden-error:
                  $ref: '#/components/examples/ForbiddenErrorResponseExample'
        '404':
          description: >-
            Not Found - unknown id, or the id of a reporting table, of a
            commission output table or of a quota
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NotFoundErrorResponse'
              examples:
                not-found-error:
                  $ref: '#/components/examples/NotFoundErrorResponseExample'
      security:
        - api_key: []
components:
  schemas:
    DataTableRecordsResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/DataTableRecordModel'
          description: List of records
        meta:
          $ref: '#/components/schemas/MetaModel'
      required:
        - data
        - meta
    InvalidErrorResponse:
      type: object
      properties:
        count:
          type: number
          description: Number of errors
        errors:
          type: array
          items:
            type: object
            properties:
              error:
                type: string
                description: Error type
              resource:
                type: string
                description: Resource that caused the error
              description:
                type: string
                description: Description of the error
            required:
              - error
              - resource
              - description
      required:
        - count
        - errors
    UnauthorizedErrorResponse:
      type: object
      properties:
        message:
          type: string
          description: Human-readable error message
        type:
          type: string
          enum:
            - UnauthorizedError
          description: Type of the error
      required:
        - message
        - type
    ForbiddenErrorResponse:
      type: object
      properties:
        message:
          type: string
          description: Human-readable error message
        type:
          type: string
          enum:
            - ForbiddenError
          description: Type of the error
        error:
          type: string
          description: Machine-readable error code
      required:
        - message
        - type
        - error
    NotFoundErrorResponse:
      type: object
      properties:
        message:
          type: string
          description: Human-readable error message
        resource:
          type: string
          description: Resource that caused the error
        type:
          type: string
          enum:
            - NotFoundError
          description: Type of the error
      required:
        - message
        - resource
        - type
    DataTableRecordModel:
      type: object
      description: >-
        Record from a data table. Its two identifiers are prefixed with
        'standard.', every field of the table with 'datatable.', keyed as
        /v2/data-structures/{table_id}/fields lists them.
      properties:
        standard.id:
          type: string
          format: ObjectId
          description: Unique record identifier in Qobra
        standard.source_id:
          type: string
          description: >-
            Identifier of the record in its source system. A record created in
            Qobra carries its own standard.id
      required:
        - standard.id
        - standard.source_id
      additionalProperties: true
    MetaModel:
      type: object
      properties:
        next_start_id:
          type: string
          format: ObjectId
          nullable: true
          description: ID to use for the next page (null if no more records)
        has_more:
          type: boolean
          description: Whether more records exist after this page
        next_url:
          type: string
          nullable: true
          description: Pre-built URL for the next page (null if no more records)
      required:
        - has_more
        - next_url
        - next_start_id
  examples:
    DataTableRecordsResponseExample:
      value:
        data:
          - standard.id: 6a1b2c3d4e5f60718293a4b1
            standard.source_id: 0065g00000AbCdEAAV
            datatable.name: Acme Corp - Annual License
            datatable.amount:
              value: 50000
              currency: USD
            datatable.close_date: '2026-01-15'
            datatable.stage: Closed Won
            datatable.owner:
              id: 507f191e810c19729de860ea
              email: sarah.johnson@company.com
            datatable.account:
              id: 0015g00000XyZaBAAV
              name: Acme Corporation
            datatable.is_renewal: false
          - standard.id: 6a1b2c3d4e5f60718293a4b2
            standard.source_id: 6a1b2c3d4e5f60718293a4b2
            datatable.name: TechStart - Professional Plan
            datatable.amount:
              value: 25000
              currency: EUR
            datatable.close_date: '2026-01-18'
            datatable.stage: Negotiation
            datatable.owner:
              id: 507f191e810c19729de860eb
              email: michael.chen@company.com
            datatable.account:
              id: null
              name: null
            datatable.is_renewal: true
        meta:
          next_start_id: 6a1b2c3d4e5f60718293a4b2
          has_more: true
          next_url: >-
            https://api.qobra.co/v2/data-tables/507f1f77bcf86cd799439010/records?start_id=6a1b2c3d4e5f60718293a4b2&limit=100
    InvalidErrorResponseExample:
      value:
        count: 1
        errors:
          - error: ValidationError
            resource: start_id
            description: >-
              Can't parse value for param 'start_id' : Value error, Invalid
              ObjectId
    UnauthorizedErrorResponseExample:
      value:
        message: You're trying to access resource you're not authorized to.
        type: UnauthorizedError
    ForbiddenErrorResponseExample:
      value:
        message: You don't have permission to access this resource.
        type: ForbiddenError
        error: ForbiddenError
    NotFoundErrorResponseExample:
      value:
        message: We couldn't find the requested resource
        resource: ObjectModel
        type: NotFoundError
  securitySchemes:
    api_key:
      type: apiKey
      name: X-API-Key
      in: header
      description: Your Qobra API key. Generate it from Settings > API Keys in Qobra.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.