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

# Get Data Object by Primary Key

> Retrieve a single data object by the value of its schema's primary key field

The Get Data Object by Primary Key endpoint returns one record from a dataset, looked up by the value of the field the schema marks as its primary key. It is the direct way to fetch a record when you know its business identifier (a customer number, a SKU, an account code) rather than its `_dataObjectId`, and the cheapest way to check whether a record already exists before creating it.

## Prerequisites

* A Pretectum API key (see [API Keys](/api-reference/authentication/api-keys))
* Permission to access data objects in your tenant
* Valid business area ID (see [List Business Areas](/api-reference/business-areas/list))
* Valid schema ID (see [List Schemas](/api-reference/schemas/list))
* Valid dataset ID (see [List Datasets](/api-reference/datasets/list))
* A schema with a primary key field. The [Get Schema](/api-reference/schemas/get) response marks it with `isPrimaryKey: true`.

## Authentication

Include your API key in the `Authorization` header.

```bash theme={null}
Authorization: pre_your_api_key
```

## Request

### Path Parameters

<ParamField path="businessAreaId" type="string" required>
  The unique identifier of the business area. You can obtain this from the [List Business Areas](/api-reference/business-areas/list) endpoint.
</ParamField>

<ParamField path="schemaId" type="string" required>
  The unique identifier of the schema. You can obtain this from the [List Schemas](/api-reference/schemas/list) endpoint.
</ParamField>

<ParamField path="datasetId" type="string" required>
  The unique identifier of the dataset to look in. Primary key values are unique within a dataset, not across datasets. You can obtain this from the [List Datasets](/api-reference/datasets/list) endpoint.
</ParamField>

<ParamField path="pkValue" type="string" required>
  The value of the primary key field, exactly as stored. Matching is case-sensitive. URL-encode the value if it contains spaces or reserved characters (`CUST 001` becomes `CUST%20001`).
</ParamField>

### Headers

<ParamField header="Authorization" type="string" required initialValue="pre_your_api_key">
  Your Pretectum API key. Create one in the Pretectum app under **Configuration → API Keys**.
</ParamField>

<ParamField header="Accept" type="string" default="application/json">
  The response content type. Currently only `application/json` is supported.
</ParamField>

### Example Requests

<CodeGroup>
  ```bash cURL theme={null}
  curl -X GET "https://api.pretectum.io/v1/businessareas/20240115103000123a1b2c3d4e5f6789012345678901234/schemas/20240115103000456d1e2f3a4b5c6789012345678901234/datasets/20240925152201042a1b2c3d4e5f6789012345678901234/dataobjects/by-pk/CUST-000123" \
    -H "Authorization: pre_your_api_key" \
    -H "Accept: application/json"
  ```

  ```javascript JavaScript theme={null}
  const apiKey = 'pre_your_api_key';
  const businessAreaId = '20240115103000123a1b2c3d4e5f6789012345678901234';
  const schemaId = '20240115103000456d1e2f3a4b5c6789012345678901234';
  const datasetId = '20240925152201042a1b2c3d4e5f6789012345678901234';

  async function getDataObjectByPrimaryKey(businessAreaId, schemaId, datasetId, pkValue) {
    const response = await fetch(
      `https://api.pretectum.io/v1/businessareas/${businessAreaId}/schemas/${schemaId}/datasets/${datasetId}/dataobjects/by-pk/${encodeURIComponent(pkValue)}`,
      {
        headers: {
          'Authorization': apiKey,
          'Accept': 'application/json'
        }
      }
    );

    if (response.status === 404) {
      return null;
    }
    if (!response.ok) {
      throw new Error(`Lookup failed: ${response.status} ${response.statusText}`);
    }

    return response.json();
  }

  const customer = await getDataObjectByPrimaryKey(businessAreaId, schemaId, datasetId, 'CUST-000123');
  if (customer) {
    console.log(`Found ${customer['First Name']} ${customer['Last Name']} (version ${customer._version})`);
  } else {
    console.log('No customer with that number');
  }
  ```

  ```python Python theme={null}
  import requests
  from urllib.parse import quote

  api_key = 'pre_your_api_key'
  business_area_id = '20240115103000123a1b2c3d4e5f6789012345678901234'
  schema_id = '20240115103000456d1e2f3a4b5c6789012345678901234'
  dataset_id = '20240925152201042a1b2c3d4e5f6789012345678901234'

  def get_data_object_by_primary_key(business_area_id, schema_id, dataset_id, pk_value):
      response = requests.get(
          f'https://api.pretectum.io/v1/businessareas/{business_area_id}/schemas/{schema_id}/datasets/{dataset_id}/dataobjects/by-pk/{quote(pk_value, safe="")}',
          headers={
              'Authorization': api_key,
              'Accept': 'application/json'
          }
      )

      if response.status_code == 404:
          return None

      response.raise_for_status()
      return response.json()

  customer = get_data_object_by_primary_key(business_area_id, schema_id, dataset_id, 'CUST-000123')
  if customer:
      print(f"Found {customer['First Name']} {customer['Last Name']} (version {customer['_version']})")
  else:
      print('No customer with that number')
  ```
</CodeGroup>

## Response

A successful request returns the data object in the same shape as [List Data Objects](/api-reference/dataobjects/list): the schema's fields keyed by their display names, plus the system metadata fields.

<ResponseField name="_dataObjectId" type="string" required>
  The unique identifier of the data object. Use it with [Update Data Object](/api-reference/dataobjects/update) and [Delete Data Object](/api-reference/dataobjects/delete).
</ResponseField>

<ResponseField name="_version" type="integer" required>
  The current version of the data object. Send this value as `_version` when updating the object; the update is rejected if the object has changed since.
</ResponseField>

<ResponseField name="_errors" type="array" required>
  Validation errors recorded on the data object. Empty when every value passed validation.

  <Expandable title="Error object properties">
    <ResponseField name="name" type="string">
      The display name of the field that has the validation error.
    </ResponseField>

    <ResponseField name="errors" type="string">
      Description of the validation error.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="[Field Name]" type="varies">
  The field values, keyed by the schema's field display names. Date, time and datetime values are formatted according to the schema's field configuration. Attachment-type fields (`image`, `document`, `video`, `attachment`) are returned with time-limited download URLs.
</ResponseField>

### Example Response

```json theme={null}
{
  "Customer Number": "CUST-000123",
  "First Name": "John",
  "Last Name": "Smith",
  "Email": "john.smith@example.com",
  "Phone": "+1 555-123-4567",
  "Date of Birth": "01/15/1985",
  "Status": "Active",
  "_dataObjectId": "20240601120000123f1a2b3c4d5e6789012345678901234",
  "_version": 3,
  "_errors": []
}
```

### Not Found

When no record in the dataset has that primary key value, the response is `404 Not Found` with an empty body. The same response is returned when the schema has no primary key field, and for a record that has been deleted: deleting a record releases its primary key value, so the value can be used again by a new record.

```
HTTP/1.1 404 Not Found
```

## Error Responses

| Status Code | Description |
| - | - |
| `401 Unauthorized` | The API key is missing, malformed, unknown, inactive, expired or deleted. Check the key in **Configuration → API Keys**. |
| `403 Forbidden` | Your application client does not have permission to access data objects. Contact your tenant administrator. |
| `404 Not Found` | No record in the dataset has that primary key value, the schema has no primary key field, or the business area, schema or dataset does not exist or is not accessible to you. |
| `500 Internal Server Error` | An unexpected error occurred on the server. Try again later or contact support. |

## How the Lookup Works

Pretectum enforces primary key uniqueness within a dataset: when a record is created or its primary key value is changed, the value is reserved in the dataset's key index, and a second record with the same value is rejected with `DUPLICATE_PRIMARY_KEY`. This endpoint reads that index, so it is a single, exact lookup regardless of how many records the dataset holds.

<Note>
  Leading and trailing whitespace is trimmed from primary key values both when a record is written and when one is looked up, so `" CUST-000123 "` and `"CUST-000123"` refer to the same record. Matching is otherwise exact and case-sensitive, so `cust-000123` does not.
</Note>

## Use Cases

### Upsert: Create or Update by Business Key

Look a record up by its business identifier and create it if missing, or update it with the version you just read:

<CodeGroup>
  ```javascript JavaScript theme={null}
  async function upsertCustomer(businessAreaId, schemaId, datasetId, customer) {
    const existing = await getDataObjectByPrimaryKey(businessAreaId, schemaId, datasetId, customer['Customer Number']);

    if (!existing) {
      return createDataObject(businessAreaId, schemaId, datasetId, customer);
    }

    return updateDataObject(businessAreaId, schemaId, datasetId, existing._dataObjectId, {
      _version: existing._version,
      ...customer
    });
  }
  ```

  ```python Python theme={null}
  def upsert_customer(business_area_id, schema_id, dataset_id, customer):
      existing = get_data_object_by_primary_key(business_area_id, schema_id, dataset_id, customer['Customer Number'])

      if existing is None:
          return create_data_object(business_area_id, schema_id, dataset_id, customer)

      return update_data_object(
          business_area_id,
          schema_id,
          dataset_id,
          existing['_dataObjectId'],
          {'_version': existing['_version'], **customer}
      )
  ```
</CodeGroup>

### Fetching the Current Version Before an Update

An update needs the record's current `_version`. When you know the business key, this endpoint is cheaper than paging through [List Data Objects](/api-reference/dataobjects/list) to find the record:

```javascript theme={null}
const current = await getDataObjectByPrimaryKey(businessAreaId, schemaId, datasetId, 'CUST-000123');
await updateDataObject(businessAreaId, schemaId, datasetId, current._dataObjectId, {
  _version: current._version,
  'Status': 'Inactive'
});
```

## Best Practices

1. **Treat `404` as "does not exist"**: It is the normal answer for a key that has not been used yet, not a failure. Check the ids only if every lookup returns `404`.
2. **URL-encode the value**: Key values with spaces, slashes or other reserved characters must be encoded in the path.
3. **Use the returned `_version` immediately**: Another writer may change the record between your lookup and your update. On `DATA_OBJECT_VERSION_CONFLICT`, look the record up again and retry.
4. **Prefer this endpoint to search for exact lookups**: [Search Data Objects](/api-reference/dataobjects/search) is for finding records by content; this endpoint is for fetching a known record.

## Related Endpoints

<CardGroup cols={2}>
  <Card title="List Data Objects" icon="list" href="/api-reference/dataobjects/list">
    Page through every record in a dataset
  </Card>

  <Card title="Create Data Object" icon="plus" href="/api-reference/dataobjects/create">
    Add a record when the lookup returns 404
  </Card>

  <Card title="Update Data Object" icon="pen" href="/api-reference/dataobjects/update">
    Modify the record using the returned version
  </Card>

  <Card title="Get Schema Details" icon="file-code" href="/api-reference/schemas/get">
    Find which field is the primary key
  </Card>
</CardGroup>
