Skip to main content
People are the canonical CRUD resource — every other object type follows this template (except they have an extra POST /search).

Endpoints

MethodPathPurpose
GET/peopleList, paginated
POST/peopleCreate
GET/people/:idFetch one. ?expand=primary_company inlines the company
PATCH/people/:idUpdate
DELETE/people/:idDelete
POST/people/searchFilter, sort, paginate

Fields

FieldTypeNotes
iduuidServer-assigned
emailstring | nullIndexed (case-insensitive)
first_name, last_namestring | null
primary_company_iduuid | nullReference to a companies row
custom_attributesobjectValidated by the schema engine
created_at, updated_atdatetime

Create

curl -X POST $SALTY_API/people \
  -H "Authorization: Bearer $SALTY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email":"jane@acme.com","first_name":"Jane","custom_attributes":{"tier":"enterprise"}}'

Expand the company

curl "$SALTY_API/people/<id>?expand=primary_company" \
  -H "Authorization: Bearer $SALTY_API_KEY"
The response inlines primary_company using the same snake_case serializer the /companies endpoint uses — no Drizzle field names leak through. POST /people/search accepts a filter body. Operators: equals | not_equals | contains | gt | gte | lt | lte | is_null. Combinators: and | or (top-level only in v1). Custom attribute fields addressed via custom_attributes.<key>.
curl -X POST $SALTY_API/people/search \
  -H "Authorization: Bearer $SALTY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "filter": {
      "and": [
        {"email": {"contains": "@acme.com"}},
        {"custom_attributes.tier": {"equals": "enterprise"}}
      ]
    },
    "sort": [{"created_at": "desc"}],
    "limit": 50
  }'
Returns the same { data, next_cursor } envelope as the list endpoint.