Skip to content
Authors
  • Sem Tadema
    Sem TademaCTO

This guide explains how to create, update, and manage records through the /api/v2/records endpoints.

Records are the actual data instances for your objects (for example candidates, vacancies, companies). These endpoints gives you a complete lifecycle: create/update, search, relation handling, archive/restore, and morphing.

Prerequisites

  • Bearer token or API Token with permissions for the relevant object scopes
  • Object internal name (used as {objectName} in routes)

Endpoint map

Core write and lifecycle endpoints:

  • POST /api/v2/records/{objectName} create a record
  • PUT /api/v2/records/{objectName}/{uuid} update a record
  • POST /api/v2/records/{objectName}/createOrUpdate upsert by unique fields
  • DELETE /api/v2/records/{uuid} archive, anonymize, or hard delete
  • POST /api/v2/records/{uuid}/restore restore archived records
  • POST /api/v2/records/{uuid}/morph move/add visibility across objects

List/search and projection endpoints:

  • POST /api/v2/records/index
  • POST /api/v2/records/search
  • GET /api/v2/records/{uuid}
  • GET /api/v2/records/{uuid}/previews/{name}

Relation endpoints:

  • POST /api/v2/records/relations/{fromUuid}/{relationName}/{toUuid}
  • DELETE /api/v2/records/relations/{fromUuid}/{relationName}/{toUuid}

Step 1: Create a record

curl -X POST "https://your-company.caraer.com/api/v2/records/candidate?parse=true" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "properties": {
      "firstname": "Jane",
      "lastname": "Doe",
      "email": "jane.doe@example.com"
    }
  }'

What happens:

  • The controller builds a Record from your DTO and object name.
  • Property-level validation is applied in record services.
  • You get a CreateResponse with the created record.
  • When setting parse to true the values will be parsed to labels and human readable values. Leave parse out if you want the internal values when working on an integration for example. Parse is by default false.

Step 2: Update a record

curl -X PUT "https://your-company.caraer.com/api/v2/records/candidate/RECORD_UUID?parse=true" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "properties": {
      "application_status": "in_progress"
    }
  }'

The endpoint returns UpdateResponse<Record>.

Step 3: Use create-or-update for idempotent integrations

If your integration receives repeated events, createOrUpdate is usually the safest write pattern:

curl -X POST "https://your-company.caraer.com/api/v2/records/candidate/createOrUpdate" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "values": {
      "email": "jane.doe@example.com",
      "firstname": "Jane",
      "lastname": "Doe"
    }
  }'

If a matching unique record exists, Caraer updates it; otherwise it creates a new one. The matching is based on all the unique properties of a record.

How to submit values by property type and format

When you submit values, the backend validates by property type and uses property format for parsing/formatting behavior. The key is always the property name of the property, and the value shape must match the type.

Common type rules from backend validation:

  • string: send a string
  • number: send a numeric value (for example 120000 or "120000")
  • date: send unix timestamp milliseconds in UTC (preferred). ISO date/date-time inputs are also accepted by date parsing and normalized
  • checkbox: send true/false (or string "true"/"false")
  • single-select: send one option name exactly as configured
  • multi-select: send option names as one semicolon-separated string or as an array of the names
  • tag: send tags as one semicolon-separated string or as an array of the names
  • range: send "min;max" (both numeric)
  • structure: send a valid JSON string (not a raw nested JSON object)
  • file: send a file key string (needs to be under your company files path)
  • linked-property: do not send a value (backend rejects manual values since they're read only and managed on the related records)

Example payload covering multiple types:

{
  "properties": {
    "firstname": "Jane Doe",
    "salary": 4200,
    "start_date": 1742851200000, // "2025-04-24T00:00:00.000Z"
    "available_remote": true,
    "application_status": "in_progress",
    "skills": "java;spring;neo4j", // ['java', 'spring', 'neo4j']
    "labels": "high_priority;backend", // ['high_priority', 'backend']
    "salary_range": "3500;5000",
    "profile_data": "{\"github\":\"janedoe\",\"years_experience\":6}",
    "resume_file": "COMPANY_UUID/files/candidates/jane-doe-cv.pdf"
  }
}

Type vs format (important distinction)

  • Type controls what value shape is valid.
  • Format controls how that value is interpreted or displayed.

Example: a string property may accept multiple string input styles, but the normalized stored value is still validated as a string type value.

Submission checklist before calling create/update

  • Confirm property name keys match the object schema exactly.
  • Confirm select values exist in property options.
  • For multi-select, tag, and range, use semicolon delimiters or arrays with names of the options.
  • For structure, serialize to a JSON string first.
  • Do not send values for linked-property fields.

Step 4: List records for UI and automation

Use /index for generic pagination, with optional archived=true and parse=true.

Note on archived records:
The archived query parameter determines whether to fetch archived (soft-deleted) records or not. By default, only active (non-archived) records are returned (archived=false).

  • Set archived=true to list only records that have been archived.
  • Omitting this parameter (or setting archived=false) will fetch only currently active records.

Example:
To fetch only archived records, use:

curl -X GET "https://your-company.caraer.com/api/v2/records/OBJECT_NAME/index?archived=true" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Step 5: Connect records with relations

Use these endpoints to link two existing records with a named relation. The route is:

  • POST /api/v2/records/relations/{fromUuid}/{relationName}/{toUuid}
  • DELETE /api/v2/records/relations/{fromUuid}/{relationName}/{toUuid}

Create a relation between two records

Example: link candidate FROM_UUID to vacancy TO_UUID using relation applied_for.

curl -X POST "https://your-company.caraer.com/api/v2/records/relations/FROM_UUID/applied_for/TO_UUID?primary=true" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

What happens on create:

  • Backend loads both records and the relation by relationName.
  • If one of them does not exist, the request fails with a not-found error.
  • The relation direction is resolved against relation schema (from/to) in backend logic, so use the relation name exactly as defined.
  • The relation is merged (created if missing, updated if already present).

Delete a relation between two records

This removes the relation edge between those record UUIDs for that relation name.

curl -X DELETE "https://your-company.caraer.com/api/v2/records/relations/FROM_UUID/applied_for/TO_UUID" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

What happens on delete:

  • It checks relation delete access on both involved objects.
  • It deletes matching relation edge(s) for that relation between the two UUIDs.

How primary works for relations

primary is a query parameter on relation creation:

  • primary=false (default): creates/updates the relation without primary selection behavior.
  • primary=true: marks this specific relation edge as the primary one.

Important primary behavior:

  • The created relation gets primary = true.
  • For the same relation type and same target record (toUuid), other incoming relation edges are set to primary = false.
  • Practical result: you can enforce a single "main" linked record for a target record within that relation type.

Example:

curl -X POST "https://your-company.caraer.com/api/v2/records/relations/CANDIDATE_UUID/applied_for/VACANCY_UUID?primary=true" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

primary vs primary_object (do not confuse these)

  • Relation primary is metadata on a relation edge between two records.
  • primary_object is metadata on the record node itself and represents that record's main object context.

They solve different problems and are managed separately.

Step 6: Handle record lifecycle safely

Delete modes:

  • archive (default): soft delete sets the deletedAt property on the record
  • anonymize: removes most data values from the record, preserving only its structure in the database (useful for keeping references to submissions or campaign aggregates while ensuring privacy)
  • delete: Permanently removes the record from the database (hard delete). Once deleted, the record cannot be recovered.
curl -X DELETE "https://your-company.caraer.com/api/v2/records/RECORD_UUID?mode=archive" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Restore:

Note: The restore endpoint (POST /api/v2/records/{uuid}/restore) can only fully restore records that were archived (soft deleted).

While you can technically call restore for records deleted with anonymize, most data values will not be recovered because they were irreversibly anonymized. Only records deleted with the archive (soft delete) mode can be restored with their original data intact.

curl -X POST "https://your-company.caraer.com/api/v2/records/RECORD_UUID/restore" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Step 7: Morph records across objects

Morphing lets one record be visible in other object contexts without duplicating data. See Object morph explained for primary object vs morph objects, extendsTo, and the person → contact/candidate pattern.

curl -X POST "https://your-company.caraer.com/api/v2/records/RECORD_UUID/morph" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "objects": [
      { "name": "candidate" },
      { "name": "lead" }
    ]
  }'

Common implementation notes

  • create, update, and createOrUpdate all support parse=true query parsing for returned values.
  • /search can return either full records or previews depending on request payload.

Important: Build integrations around validated property names and object names from your environment, not hardcoded assumptions from examples.