custom_attributes (or data) JSONB field. The schema engine enforces typing only for keys you’ve explicitly declared — unknown keys pass through.
The four cases
| Key state | Behavior on write |
|---|---|
| Declared, value present | Validated against data_type, enum_values, reference_object_type |
Declared, value absent, has default_value | Default is applied (create only — PATCH leaves absent keys absent) |
Declared, value absent, is_required: true, no default | 400 attribute_required (create only — PATCH ignores) |
| Undefined key | Stored as-is, no validation |
Declared, then deprecated (DELETE /schema/...) | New writes rejected with 400 attribute_deprecated; existing data stays readable |
POST /schema/:object_type/attributes.
Supported data_type values
data_type | Accepted input |
|---|---|
text | string |
number | finite number (no NaN/Infinity) |
boolean | true / false |
date | string matching YYYY-MM-DD |
datetime | ISO 8601 string parseable by Date.parse |
enum | string ∈ enum_values |
reference | UUID matching a row in reference_object_type (person / company / deal) in the same workspace |
json | anything JSON-serializable |
Defining an attribute
custom_attributes.tier must be one of the three enum values.
Modifying an attribute (PATCH)
You can change display_name, default_value, is_required, and append to enum_values. data_type is frozen after creation (changing it requires a data migration; not in v1). Enum removal isn’t allowed in v1 — only append.
Deprecating an attribute (DELETE)
DELETE is soft. The definition gets deprecated_at set; existing data is left intact; new writes of the key are rejected.
GET /schema/person shows the attribute with "deprecated": true.
Audit trail
Every schema mutation (POST / PATCH / DELETE / POST /custom-objects) appends a row to schema_migrations with the calling api_key_id, the action, and a JSON details blob.