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
}
]
}| Key | Meaning |
|---|---|
name | The object's name, used in every /objects/{object} path |
label | Singular label |
label_plural | Plural label |
description | Present only when the object has one |
field_count | The 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
| Key | Always | Meaning |
|---|---|---|
name | yes | The key, exactly as records carry it and writes accept it |
label | yes | The field's label |
type | yes | The field type (TEXT, SELECT, CURRENCY, MANY_TO_MANY...). UUID for a pipeline's two columns, STATUS for its computed status. |
required | yes | The field is required in the data model |
readonly | yes | true: computed or maintained by Kasar; leave it out of write bodies |
number | no | true when the value is a JSON number |
options | no | The allowed values of a select field, or of a status |
target | no | The object a relation points to; a list of objects for a link that can point to several |
example | no | A value in the exact shape it is read and written |
description | no | The 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
| Field | Key | Value |
|---|---|---|
| Text, number, date, select, boolean... | the field name | the value; numbers as JSON numbers, date-times in ISO-8601 UTC, empty as null |
| Relation to one record | the 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 records | the 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. |
| Emails | the field name (emails) | [{ "id": "<uuid>", "email_address": "...", "label": "work", "is_primary": true }]. See Emails and phones on write. |
| Phones | the field name (phones) | [{ "id": "<uuid>", "phone_number": "...", "label": "mobile", "is_primary": true }]. Same rule as emails. |
| the 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 range | the field name | an object of its parts: { "first", "last", "full" }, { "amount", "currency_code" }, { "address1", "address2", "city", "region", "postal_code", "country" }, { "start", "end" } |
| Pipeline | two keys: the pipeline column (pipeline_id) and the step column (pipeline_step_id), plus status | ids; 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 name | its result; number: true when the result is numeric. Read-only. |
| External lookup | two 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'sopportunities): 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": trueon 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
idis 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, is422 UNKNOWN_FIELD. - Leave out
idand every key withreadonly: true, even unchanged: writing one is422 READONLY_FIELD. - A select value outside
optionsis422 INVALID_ENUM_VALUE, and the error lists the allowed values inavailable_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 answers422 UNKNOWN_FIELD, like a misspelled key. - An
updatedevent 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.