This guide explains how to fetch records from Caraer and how to build filter
payloads that behave exactly as expected.

It is written for external developers integrating with the Records API.

## What you will learn

- Which endpoint to use for record fetching
- What `mainObject` should be
- How filter payloads are structured
- How `show` works (including `*`)
- How `sort` works
- How `AND` and `OR` logic works in filter groups
- How to filter on related records
- How to combine filters with search, sorting, and pagination


## Primary endpoint for fetching records

Use:

- `POST /api/v2/records/index`


With optional query params:

- `parse=true|false`
- `archived=true|false`
- `relatedRecordUuid=RECORD_UUID`


Basic example:


```bash
curl -X POST "https://your-company.caraer.com/api/v2/records/index?parse=true" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "page": 1,
    "limit": 25,
    "mainObject": "candidate"
  }'
```

## `mainObject`: what to use and why it matters

`mainObject` is required context for record fetching.

Use the **object name** of the record type you want to list, for example:

- `candidate`
- `vacancy`
- `contact`


Example:


```json
{
  "mainObject": "candidate",
  "page": 1,
  "limit": 25
}
```

Why it matters:

- It defines which node type is the base of the query.
- It controls how `filter`, `show`, and `sort` are resolved.
- It is also used for permission checks on property access.


If `mainObject` does not match a valid object name in your environment, request
building and/or query execution will fail.

## Filter payload structure

Filtering is sent inside the request body as `filter`.

High-level shape:


```json
{
  "page": 1,
  "limit": 25,
  "mainObject": "candidate",
  "query": null,
  "filter": {
    "groups": [
      {
        "items": [
          {
            "object": null,
            "relation": null,
            "property": "status_candidate",
            "operator": "equals_exactly",
            "value": "new"
          }
        ]
      }
    ]
  }
}
```

### Meaning of each filter item field

- `object`: object context used by the filter item
- `relation`: relation name if the filter traverses a relation (`*` is allowed for any relation in relation-existence checks)
- `property`: property name to filter on; if omitted, item becomes a relation existence check
- `operator`: filter operator name
- `value`: value used by the operator
- `relationIncluded` (optional, default `true`): for relation existence checks (`property=null`), controls include/exclude relation matches


### How `object` + `relation` are interpreted in filters

Important behavior:

- If you are filtering properties on the `mainObject`, you usually leave
`object` as `null`.
- If you set `object` to the same value as `mainObject`, the filter is treated
as filtering through a related node of that same object (self-related records),
not as a plain main-object property shortcut.
- If `object` is set and `relation` is `null`, filtering targets related records
of that object type regardless of relation name.
- If `object` is set and `relation` is also set, filtering targets related
records through that specific relation only.


Practical recommendation: for normal `mainObject` property filtering, keep
`object` and `relation` as `null` in filter items.

## Show items (`show`) and how `*` works

`show` controls which fields are returned for each record.

Basic shape:


```json
{
  "show": [
    { "object": "candidate", "relation": null, "property": "firstname" },
    { "object": "candidate", "relation": null, "property": "email" }
  ]
}
```

### Using `*` in `show`

You can request all readable properties of the main object with:


```json
{
  "show": [{ "object": "candidate", "relation": null, "property": "*" }]
}
```

Important behavior:

- `*` expansion is applied for the **main object wildcard pattern**.
- Backend resolves it to all readable properties for that main object.
- Access rules still apply: only properties the caller can read are returned.


If you need a stable API response contract, explicitly list the properties
instead of relying on `*`.

## Sort items (`sort`)

`sort` controls ordering of result rows.

Basic shape:


```json
{
  "sort": [
    {
      "object": "candidate",
      "relation": null,
      "property": "updatedAt",
      "direction": "DESC"
    }
  ]
}
```

Rules:

- `direction` supports `ASC` and `DESC`.
- You can provide multiple sort items to create secondary/tertiary ordering.
- Use properties that exist and are readable in the requested context.


Example with fallback ordering:


```json
{
  "sort": [
    {
      "object": "candidate",
      "relation": null,
      "property": "status_candidate",
      "direction": "ASC"
    },
    {
      "object": "candidate",
      "relation": null,
      "property": "updatedAt",
      "direction": "DESC"
    }
  ]
}
```

## How filter logic works (`AND` / `OR`)

Filter logic is:

- Items **inside one group** are combined with `AND`
- Groups **inside one filter** are combined with `OR`


So `groups` let you create `(A AND B) OR (C AND D)` patterns.

Example with two groups:


```json
{
  "filter": {
    "groups": [
      {
        "items": [
          {
            "property": "status_candidate",
            "operator": "equals_exactly",
            "value": "new"
          },
          {
            "property": "source",
            "operator": "equals_exactly",
            "value": "linkedin"
          }
        ]
      },
      {
        "items": [
          {
            "property": "status_candidate",
            "operator": "equals_exactly",
            "value": "in_progress"
          },
          {
            "property": "source",
            "operator": "equals_exactly",
            "value": "referral"
          }
        ]
      }
    ]
  }
}
```

This means:

- `(status=new AND source=linkedin) OR (status=in_progress AND source=referral)`


### All operators

| Operator | Typical meaning |
|  --- | --- |
| `equals_exactly` | Exact equality |
| `equals_flexibly` | Case-insensitive equality |
| `not_equals_exactly` | Exact inequality |
| `not_equals_flexibly` | Case-insensitive inequality |
| `bigger_than` | Greater than |
| `smaller_than` | Less than |
| `bigger_than_or_equal_to` | Greater than or equal |
| `smaller_than_or_equal_to` | Less than or equal |
| `contains_exactly` | Contains (case-sensitive) |
| `contains_flexibly` | Contains (case-insensitive) |
| `not_contains_exactly` | Does not contain (case-sensitive) |
| `not_contains_flexibly` | Does not contain (case-insensitive) |
| `starts_with_exactly` | Starts with (case-sensitive) |
| `starts_with_flexibly` | Starts with (case-insensitive) |
| `ends_with_exactly` | Ends with (case-sensitive) |
| `ends_with_flexibly` | Ends with (case-insensitive) |
| `is_null` | Empty / null |
| `is_not_null` | Not empty / not null |
| `any` | Any of provided values |
| `all_flexibly` | Contains all provided values |
| `all_exactly` | Matches all provided values exactly |
| `distance_between` | Distance-based comparison |
| `range` | In between min/max |


### Which operators are usable per property type/format

In default Caraer property configuration, operator availability is primarily
driven by property **type**, and formats of
that type follow the same list.

| Property type | Formats | Supported operators |
|  --- | --- | --- |
| `string` | `email`, `multi-line`, `phone`, `single-line`, `url` | `is_null`, `is_not_null`, `equals_exactly`, `not_equals_exactly`, `contains_flexibly`, `not_contains_flexibly`, `starts_with_flexibly`, `ends_with_flexibly` |
| `number` | `number`, `currency` | `is_null`, `is_not_null`, `equals_exactly`, `not_equals_exactly`, `bigger_than`, `smaller_than`, `bigger_than_or_equal_to`, `smaller_than_or_equal_to`, `range` |
| `date` | `date` | `is_null`, `is_not_null`, `equals_exactly`, `not_equals_exactly`, `bigger_than`, `smaller_than`, `bigger_than_or_equal_to`, `smaller_than_or_equal_to`, `range` |
| `checkbox` | `single-checkbox` | `is_null`, `is_not_null`, `equals_exactly`, `not_equals_exactly` |
| `single-select` | `single-select` | `any`, `is_null`, `is_not_null` |
| `multi-select` | `multi-select` | `any`, `all_flexibly`, `all_exactly`, `is_null`, `is_not_null` |
| `tag` | `tag` | `is_null`, `is_not_null`, `equals_exactly`, `not_equals_exactly`, `any`, `all_flexibly`, `all_exactly` |
| `range` | `number-range`, `currency-range` | `range` |
| `structure` | `structure` | `contains_flexibly`, `is_null`, `is_not_null` |
| `file` | `file` | `is_null`, `is_not_null` |
| `linked-property` | `linked-property` | Based on the property it's linked to |


### Relation-only filtering note

When `property` is `null`, filter items behave as relation existence checks
(`relationIncluded=true/false`). In that case, property-type operator tables do
not apply because the filter is relation-based, not property-value-based.

## Relation filters

You can filter by relation presence even without specifying a property.

Relation existence example (candidate has any relation to vacancy):


```json
{
  "filter": {
    "groups": [
      {
        "items": [
          {
            "object": "vacancy",
            "relation": "*",
            "property": null,
            "relationIncluded": true
          }
        ]
      }
    ]
  }
}
```

Relation non-existence example (candidate has no `applied_for` relation to vacancy):


```json
{
  "filter": {
    "groups": [
      {
        "items": [
          {
            "object": "vacancy",
            "relation": "applied_for",
            "property": null,
            "relationIncluded": false
          }
        ]
      }
    ]
  }
}
```

## `relatedRecordUuid` shortcut (relation-aware filtering)

When you pass `relatedRecordUuid` as a query parameter, Caraer can help shape
relation-aware filtering:

- If no filter is provided, it defaults to records related to that record
- If filter values contain smarten placeholders, they are resolved against that
related record context


Example:


```bash
curl -X POST "https://your-company.caraer.com/api/v2/records/index?relatedRecordUuid=RELATED_UUID" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "page": 1,
    "limit": 20,
    "mainObject": "candidate",
    "filter": { "groups": [] }
  }'
```

## Combining filter + search + sort + pagination

You can combine all of these in one request:


```json
{
  "page": 1,
  "limit": 20,
  "mainObject": "candidate",
  "query": "jane",
  "filter": {
    "groups": [
      {
        "items": [
          {
            "property": "status_candidate",
            "operator": "equals_exactly",
            "value": "in_progress"
          }
        ]
      }
    ]
  },
  "sort": [
    {
      "object": "candidate",
      "relation": null,
      "property": "updatedAt",
      "direction": "DESC"
    }
  ]
}
```

## Practical checklist

- Always set `mainObject` to the object you are fetching.
- Keep `mainObject` aligned with the object name used in `show`/`sort` where possible.
- Use exact property names from your Caraer schema.
- Start with one simple group, then add complexity.
- Validate operator spelling (`equals_exactly`, not `EQUALS`).
- Use explicit `show` fields for stable integrations; use `*` mainly for exploration.
- Use `parse=true` only if you need human-readable values in response.


## Related reading

- [Save records](/blog/2026-03-25-save-records)
- [Submit forms and embed forms on your website](/blog/2026-03-25-submit-forms-and-embed-on-your-website)
- [API introduction](/apis/get-started)