KasarKasar Docs
API Reference

Objects

Discovering the workspace data model, in the exact shape records are read and written.

The API is generic over objects, so the data model is not in this documentation: it is in your workspace, and these two endpoints are how you read it. Both require the schema:read right (Read the data model in Settings). See Authentication.

List objects

curl https://platform.kasar.app/api/v1/objects \
  -H "Authorization: Bearer ksr_svc_..."
{
  "data": [
    {
      "name": "opportunities",
      "label": "Opportunity",
      "label_plural": "Opportunities",
      "description": "Sales opportunities: deals in progress, won or lost",
      "field_count": 17
    },
    {
      "name": "companies",
      "label": "Company",
      "label_plural": "Companies",
      "description": "Companies and organizations: customers, leads, partners",
      "field_count": 21
    }
  ]
}
KeyMeaning
nameThe object's name, used in every /objects/{object} path
labelSingular label
label_pluralPlural label
descriptionPresent only when the object has one
field_countThe number of keys GET /objects/{object} describes for this integration

The list contains the objects the API serves and the integration can read. Not listed:

  • objects the integration is not granted Read on (Write alone does not list it);
  • list entries: a list is not an object of the API;
  • the platform's internal objects (conversations, AI threads, preferences...).

Describe one object

curl https://platform.kasar.app/api/v1/objects/opportunities \
  -H "Authorization: Bearer ksr_svc_..."

The response describes the machine shape: one entry per key that GET .../records returns, that POST, PUT and PATCH accept, and that webhook deliveries carry. Reading and writing use the same shape, so what you read is what you write.

{
  "data": {
    "name": "opportunities",
    "label": "Opportunity",
    "label_plural": "Opportunities",
    "description": "Sales opportunities: deals in progress, won or lost",
    "fields": [
      { "name": "name", "label": "Name", "type": "TEXT", "required": true, "readonly": false },
      {
        "name": "client_company_id", "label": "Customer", "type": "MORPH_RELATION",
        "required": false, "readonly": false,
        "description": "Client company associated with this opportunity",
        "target": "companies", "example": "<uuid>"
      },
      {
        "name": "amount", "label": "Amount", "type": "CURRENCY",
        "required": false, "readonly": false,
        "description": "Total deal amount in currency",
        "example": { "amount": 0, "currency_code": null }
      },
      {
        "name": "weighted_amount", "label": "Weighted amount", "type": "CALCULATED",
        "required": false, "readonly": true,
        "description": "Weighted amount = amount x probability / 100",
        "number": true
      },
      {
        "name": "type", "label": "Type", "type": "SELECT",
        "required": false, "readonly": false,
        "description": "Type of sales opportunity",
        "options": ["new_business", "upsell", "cross_sell", "renewal", "migration"]
      },
      {
        "name": "pipeline_id", "label": "Pipeline", "type": "UUID",
        "required": false, "readonly": false,
        "description": "Pipeline the record runs in.", "example": "<pipeline uuid>"
      },
      {
        "name": "pipeline_step_id", "label": "Pipeline", "type": "UUID",
        "required": false, "readonly": false,
        "description": "Current pipeline step. Writing it moves the record.", "example": "<step uuid>"
      },
      {
        "name": "status", "label": "status", "type": "STATUS",
        "required": false, "readonly": true,
        "options": ["open", "won", "lost"],
        "description": "Outcome of the current pipeline step, computed: write the step id to change it."
      },
      { "name": "owner_id", "label": "Owner", "type": "USERS", "required": false, "readonly": false },
      { "name": "created_at", "label": "Created at", "type": "DATETIME", "required": false, "readonly": true }
    ]
  }
}

The fields above are illustrative: yours are the ones of your workspace.

Keys of a field entry

KeyAlwaysMeaning
nameyesThe key, exactly as records carry it and writes accept it
labelyesThe field's label
typeyesThe field type (TEXT, SELECT, CURRENCY, MANY_TO_MANY...). UUID for a pipeline's two columns, STATUS for its computed status.
requiredyesThe field is required in the data model
readonlyyestrue: computed or maintained by Kasar; leave it out of write bodies
numbernotrue when the value is a JSON number
optionsnoThe allowed values of a select field, or of a status
targetnoThe object a relation points to; a list of objects for a link that can point to several
examplenoA value in the exact shape it is read and written
descriptionnoThe field's description, or a note on how the key is written

Absent keys mean "no": a field entry without number is not a number, one without options has no fixed list of values.

How each kind of field is shaped

FieldKeyValue
Text, number, date, select, boolean...the field namethe value; numbers as JSON numbers, date-times in ISO-8601 UTC, empty as null
Relation to one recordthe field name with _id (company_id, client_company_id)the id of the linked record; target names its object
User (owner...)the field name with _id (owner_id)the id of a workspace member
Link to several recordsthe field name (companies, sites)[{ "id": "<uuid>", ...link columns }]; object is added when the link can point to several objects. On write, the list replaces the existing links.
Emailsthe field name (emails)[{ "id": "<uuid>", "email_address": "...", "label": "work", "is_primary": true }]. See Emails and phones on write.
Phonesthe field name (phones)[{ "id": "<uuid>", "phone_number": "...", "label": "mobile", "is_primary": true }]. Same rule as emails.
LinkedInthe field name[{ "id": "<uuid>", "profile_id": "...", "public_identifier": "...", "is_primary": true }]. A record has one profile: writing one replaces it.
Composite: name, currency, address, date rangethe field namean object of its parts: { "first", "last", "full" }, { "amount", "currency_code" }, { "address1", "address2", "city", "region", "postal_code", "country" }, { "start", "end" }
Pipelinetwo keys: the pipeline column (pipeline_id) and the step column (pipeline_step_id), plus statusids; write the step id to move the record. status is open, won or lost, computed from the step, and read-only.
Computed (formula)the field nameits result; number: true when the result is numeric. Read-only.
External lookuptwo keys: the field name, and the field name with _display_display holds the value read from the third party. Read-only.

What is never in the machine shape:

  • the inverse side of a relation (a company's opportunities, a contact's opportunities): read it from the side that carries the link, where it is written and where its webhooks fire;
  • list memberships;
  • hidden and deactivated fields;
  • confidential fields, and the fields computed from them;
  • display-only derivations, such as an address's formatted.

A pipeline's status key is status when the object has a single pipeline and no field of that name, <pipeline field>_status otherwise.

Emails and phones on write

Emails and phones are facts a CRM accumulates, so a write never removes one:

  • On create, the list you send is the record's emails (or phones).
  • On update, the values you send are added to those already on the record. A value already there is not duplicated. An empty list changes nothing.
  • "is_primary": true on a value makes it the primary one, whether it is new or already on the record.
  • A value that already identifies another record (the email of another contact) is refused with 409 DUPLICATE_RECORD.
  • To remove an entry, or change its label or the primary one, use the entry's route: Emails, phones and LinkedIn. Its id is the one the record carries.

Links to several records behave differently: on update, the list you send becomes the full set of links, and a link left out is removed.

Writing with the schema

  • Send only keys listed in fields. Any other key, including a field that exists but that the integration cannot read, is 422 UNKNOWN_FIELD.
  • Leave out id and every key with readonly: true, even unchanged: writing one is 422 READONLY_FIELD.
  • A select value outside options is 422 INVALID_ENUM_VALUE, and the error lists the allowed values in available_values.

See Records for the write semantics and Errors for every code.

Fields chosen per integration

If the integration was given a list of fields for an object, fields contains only those keys and field_count counts only them. With All fields, every key of the object is described, including fields created after the integration, except confidential fields. See Choosing fields.

Confidential fields

A workspace admin can mark a field confidential (Settings, Data model, the field, Sharing). A confidential field stays visible and editable in Kasar, according to each member's role, and never leaves Kasar through a machine channel:

  • It is absent from GET /objects/{object}, from records and from webhook deliveries, as if it did not exist, whatever the integration's field list. A field list cannot name it.
  • A field computed from it is confidential too: a formula that reads it (a weighted amount), a rollup that aggregates it on another object (the total amount of a company's deals).
  • Filtering, sorting or grouping on it answers 400 INVALID_QUERY_FIELD, exactly like an unknown field; full-text search does not read it; writing it answers 422 UNKNOWN_FIELD, like a misspelled key.
  • An updated event whose only change is a confidential field is not delivered.
  • An automation's HTTP request cannot send it.

Marking a field takes effect everywhere within 30 seconds. Deliveries already queued, and bodies already in a destination's delivery log, are no longer served: see Webhooks.

Hidden means absent

An object the integration cannot read, an object the API does not serve, and an object that does not exist all answer the same 404:

{
  "error": {
    "code": "INVALID_OBJECT",
    "message": "Object 'invoices' not found.",
    "suggestion": "List available objects with GET /api/v1/objects."
  }
}

The API never says what exists beyond what the integration can read. Without the schema:read right, both endpoints answer 403 FORBIDDEN_GRANT.

On this page