Objects
An object is either built-in or custom.
Custom objects are always prefixed with
object:. That prefix is part of the
name — you pass it verbatim in the path, URL-encoded:
Listing objects
GET /v1/objects answers “what can this key work with”, not “what exists”.
Two keys on the same tenant can legitimately get different lists.
Schemas
The field descriptor
Types:
text, textarea, number, boolean, date, datetime, email,
phone, url, currency, select.
That list is closed. An internal type we do not publish is reported as text
rather than passed through, so a UI-side change cannot alter your contract.
Built-in and custom fields look identical
Internally this CRM has two different field systems with different column names. You will never see that. Both are mapped to the descriptor above, and the only way to tell them apart is thecustom flag — which is there because you may want
to treat tenant fields differently in your UI, not because they behave
differently.
What a tenant can change
Two things move under you. Both are why you should read the schema rather than assume one. A built-in field can be turned off. A disabled field is absent from the schema. It is not returned asenabled: false — if it is not in fields, do not
send it.
A built-in field can be made mandatory. A field that is optional by default
comes back with "required": true for that tenant. Two tenants can therefore
return different required values for the same field, and neither is wrong.
Custom fields can be added and removed at any time.
Name collisions
If a tenant creates a custom field whose name matches a built-in, the built-in wins and the custom one is not published. Otherwise a customemail of type
text could shadow the real email field and the published type would stop
matching what writes actually accept.
Errors
The 404 is deliberately ambiguous. Distinguishing “no such object” from “not
allowed” would let a key probe for which objects a tenant has configured, which
is the same reason record fetches return 404 instead of 403.
If you get a 404 for an object you expect, check your key’s scopes before
assuming the object is missing.
Suggested integration flow
- Call
GET /v1/objectsonce at setup, and cache it. - Call
GET /v1/objects/{object}/schemafor each object you write to. - Build your field mapping from
name, validating againsttype,requiredandoptions. - Re-fetch the schema periodically, or when a write fails validation — a tenant may have added a mandatory field since you last looked.
Managing fields (provisioning)
settings:configure scope — a key without it gets 403. This
writes the tenant’s real schema, so a field created here appears on the CRM’s
forms immediately.
nameis normalised to lowercase with underscores. Reserved names (id,created_at,company_id, …) and names that collide with an existing field are rejected.labelis stripped of all markup before storage. A label renders on every form in the workspace, so it is never allowed to carry HTML.- A
selectmust supplyoptions; any other type must not.
Type and name are immutable
PATCH can change label, required and options — nothing else. Sending
type or name returns 422, even if the value matches what is already
stored.
The reason is that values already exist. Flipping text to number converts
nothing; it just leaves stored values that no longer match the declared type, so
reads start failing for records nobody touched. If you need a different type,
create a new field.
Retiring a field
There is no delete. Send{"enabled": false} instead:
{"enabled": true} to bring it back.
A hard delete would orphan that data with no way back, which is why the API does
not offer one. Fields on custom objects cannot currently be disabled this way
(there is no such flag on their storage); remove them in the CRM instead.
Related
- Working with records — reading and writing records of these objects
- Authentication and scopes — what your key can see
- Errors — the error envelope and codes
- Rate limits